1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=4 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTextBoxFrame.h"
11 #include "mozilla/intl/BidiEmbeddingLevel.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/ComputedStyle.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/intl/Segmenter.h"
17 #include "mozilla/layers/RenderRootStateManager.h"
18 #include "mozilla/gfx/2D.h"
19 #include "nsFontMetrics.h"
20 #include "nsReadableUtils.h"
23 #include "nsGkAtoms.h"
24 #include "nsPresContext.h"
25 #include "gfxContext.h"
26 #include "nsIContent.h"
27 #include "nsNameSpaceManager.h"
28 #include "nsBoxLayoutState.h"
31 #include "nsUnicharUtils.h"
32 #include "nsContentUtils.h"
33 #include "nsDisplayList.h"
34 #include "nsCSSRendering.h"
35 #include "nsIReflowCallback.h"
36 #include "nsBoxFrame.h"
37 #include "nsLayoutUtils.h"
38 #include "TextDrawTarget.h"
41 # include "nsAccessibilityService.h"
44 #include "nsBidiUtils.h"
45 #include "nsBidiPresUtils.h"
47 using namespace mozilla
;
48 using namespace mozilla::gfx
;
50 class nsAccessKeyInfo
{
52 int32_t mAccesskeyIndex
;
53 nscoord mBeforeWidth
, mAccessWidth
, mAccessUnderlineSize
, mAccessOffset
;
56 bool nsTextBoxFrame::gAlwaysAppendAccessKey
= false;
57 bool nsTextBoxFrame::gAccessKeyPrefInitialized
= false;
58 bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey
= false;
59 bool nsTextBoxFrame::gInsertSeparatorPrefInitialized
= false;
61 nsIFrame
* NS_NewTextBoxFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
62 return new (aPresShell
) nsTextBoxFrame(aStyle
, aPresShell
->GetPresContext());
65 NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame
)
67 NS_QUERYFRAME_HEAD(nsTextBoxFrame
)
68 NS_QUERYFRAME_ENTRY(nsTextBoxFrame
)
69 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame
)
71 nsresult
nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID
,
77 UpdateAttributes(aAttribute
, aResize
, aRedraw
);
80 PresShell()->FrameNeedsReflow(
81 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
83 nsBoxLayoutState
state(PresContext());
90 nsTextBoxFrame::nsTextBoxFrame(ComputedStyle
* aStyle
,
91 nsPresContext
* aPresContext
)
92 : nsLeafBoxFrame(aStyle
, aPresContext
, kClassID
),
93 mAccessKeyInfo(nullptr),
96 mNeedsReflowCallback(false) {
97 MarkIntrinsicISizesDirty();
100 nsTextBoxFrame::~nsTextBoxFrame() { delete mAccessKeyInfo
; }
102 void nsTextBoxFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
103 nsIFrame
* aPrevInFlow
) {
104 nsLeafBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
108 UpdateAttributes(nullptr, aResize
, aRedraw
); /* update all */
111 bool nsTextBoxFrame::AlwaysAppendAccessKey() {
112 if (!gAccessKeyPrefInitialized
) {
113 gAccessKeyPrefInitialized
= true;
115 const char* prefName
= "intl.menuitems.alwaysappendaccesskeys";
117 Preferences::GetLocalizedString(prefName
, val
);
118 gAlwaysAppendAccessKey
= val
.EqualsLiteral("true");
120 return gAlwaysAppendAccessKey
;
123 bool nsTextBoxFrame::InsertSeparatorBeforeAccessKey() {
124 if (!gInsertSeparatorPrefInitialized
) {
125 gInsertSeparatorPrefInitialized
= true;
127 const char* prefName
= "intl.menuitems.insertseparatorbeforeaccesskeys";
129 Preferences::GetLocalizedString(prefName
, val
);
130 gInsertSeparatorBeforeAccessKey
= val
.EqualsLiteral("true");
132 return gInsertSeparatorBeforeAccessKey
;
135 class nsAsyncAccesskeyUpdate final
: public nsIReflowCallback
{
137 explicit nsAsyncAccesskeyUpdate(nsIFrame
* aFrame
) : mWeakFrame(aFrame
) {}
139 virtual bool ReflowFinished() override
{
140 bool shouldFlush
= false;
141 nsTextBoxFrame
* frame
= static_cast<nsTextBoxFrame
*>(mWeakFrame
.GetFrame());
143 shouldFlush
= frame
->UpdateAccesskey(mWeakFrame
);
149 virtual void ReflowCallbackCanceled() override
{ delete this; }
151 WeakFrame mWeakFrame
;
154 bool nsTextBoxFrame::UpdateAccesskey(WeakFrame
& aWeakThis
) {
155 nsAutoString accesskey
;
156 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
,
159 if (!accesskey
.Equals(mAccessKey
)) {
160 // Need to get clean mTitle.
162 mAccessKey
= accesskey
;
164 PresShell()->FrameNeedsReflow(
165 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
171 void nsTextBoxFrame::UpdateAttributes(nsAtom
* aAttribute
, bool& aResize
,
173 bool doUpdateTitle
= false;
177 if (aAttribute
== nullptr || aAttribute
== nsGkAtoms::crop
) {
178 static dom::Element::AttrValuesArray strings
[] = {
179 nsGkAtoms::left
, nsGkAtoms::start
, nsGkAtoms::center
,
180 nsGkAtoms::right
, nsGkAtoms::end
, nsGkAtoms::none
,
182 CroppingStyle cropType
;
183 switch (mContent
->AsElement()->FindAttrValueIn(
184 kNameSpaceID_None
, nsGkAtoms::crop
, strings
, eCaseMatters
)) {
190 cropType
= CropCenter
;
194 cropType
= CropRight
;
204 if (cropType
!= mCropType
) {
206 mCropType
= cropType
;
210 if (aAttribute
== nullptr || aAttribute
== nsGkAtoms::value
) {
212 doUpdateTitle
= true;
215 if (aAttribute
== nullptr || aAttribute
== nsGkAtoms::accesskey
) {
216 mNeedsReflowCallback
= true;
217 // Ensure that layout is refreshed and reflow callback called.
229 class nsDisplayXULTextBox final
: public nsPaintedDisplayItem
{
231 nsDisplayXULTextBox(nsDisplayListBuilder
* aBuilder
, nsTextBoxFrame
* aFrame
)
232 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
233 MOZ_COUNT_CTOR(nsDisplayXULTextBox
);
235 #ifdef NS_BUILD_REFCNT_LOGGING
236 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayXULTextBox
)
239 virtual void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
240 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
241 bool* aSnap
) const override
;
242 NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX
)
244 virtual nsRect
GetComponentAlphaBounds(
245 nsDisplayListBuilder
* aBuilder
) const override
;
247 void PaintTextToContext(gfxContext
* aCtx
, nsPoint aOffset
,
248 const nscolor
* aColor
);
250 virtual bool CreateWebRenderCommands(
251 mozilla::wr::DisplayListBuilder
& aBuilder
,
252 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
253 const StackingContextHelper
& aSc
,
254 mozilla::layers::RenderRootStateManager
* aManager
,
255 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
258 static void PaintTextShadowCallback(gfxContext
* aCtx
, nsPoint aShadowOffset
,
259 const nscolor
& aShadowColor
, void* aData
) {
260 reinterpret_cast<nsDisplayXULTextBox
*>(aData
)->PaintTextToContext(
261 aCtx
, aShadowOffset
, &aShadowColor
);
264 void nsDisplayXULTextBox::Paint(nsDisplayListBuilder
* aBuilder
,
266 // Paint the text shadow before doing any foreground stuff
268 static_cast<nsTextBoxFrame
*>(mFrame
)->mTextDrawRect
+ ToReferenceFrame();
269 nsLayoutUtils::PaintTextShadow(mFrame
, aCtx
, drawRect
,
270 GetPaintRect(aBuilder
, aCtx
),
271 mFrame
->StyleText()->mColor
.ToColor(),
272 PaintTextShadowCallback
, (void*)this);
274 PaintTextToContext(aCtx
, nsPoint(0, 0), nullptr);
277 void nsDisplayXULTextBox::PaintTextToContext(gfxContext
* aCtx
, nsPoint aOffset
,
278 const nscolor
* aColor
) {
279 static_cast<nsTextBoxFrame
*>(mFrame
)->PaintTitle(
280 *aCtx
, mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame(),
281 ToReferenceFrame() + aOffset
, aColor
);
284 bool nsDisplayXULTextBox::CreateWebRenderCommands(
285 mozilla::wr::DisplayListBuilder
& aBuilder
,
286 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
287 const StackingContextHelper
& aSc
,
288 mozilla::layers::RenderRootStateManager
* aManager
,
289 nsDisplayListBuilder
* aDisplayListBuilder
) {
291 auto bounds
= GetBounds(aDisplayListBuilder
, &snap
);
293 if (bounds
.IsEmpty()) {
297 auto appUnitsPerDevPixel
= Frame()->PresContext()->AppUnitsPerDevPixel();
298 gfx::Point deviceOffset
=
299 LayoutDevicePoint::FromAppUnits(bounds
.TopLeft(), appUnitsPerDevPixel
)
302 RefPtr
<mozilla::layout::TextDrawTarget
> textDrawer
=
303 new mozilla::layout::TextDrawTarget(aBuilder
, aResources
, aSc
, aManager
,
305 if (!textDrawer
->IsValid()) {
308 gfxContext
captureCtx(textDrawer
, deviceOffset
);
310 Paint(aDisplayListBuilder
, &captureCtx
);
311 textDrawer
->TerminateShadows();
313 return textDrawer
->Finish();
316 nsRect
nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder
* aBuilder
,
319 return mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
322 nsRect
nsDisplayXULTextBox::GetComponentAlphaBounds(
323 nsDisplayListBuilder
* aBuilder
) const {
324 return static_cast<nsTextBoxFrame
*>(mFrame
)->GetComponentAlphaBounds() +
328 } // namespace mozilla
330 void nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
331 const nsDisplayListSet
& aLists
) {
332 if (!IsVisibleForPainting()) return;
334 nsLeafBoxFrame::BuildDisplayList(aBuilder
, aLists
);
336 aLists
.Content()->AppendNewToTop
<nsDisplayXULTextBox
>(aBuilder
, this);
339 void nsTextBoxFrame::PaintTitle(gfxContext
& aRenderingContext
,
340 const nsRect
& aDirtyRect
, nsPoint aPt
,
341 const nscolor
* aOverrideColor
) {
342 if (mTitle
.IsEmpty()) return;
344 DrawText(aRenderingContext
, aDirtyRect
, mTextDrawRect
+ aPt
, aOverrideColor
);
347 void nsTextBoxFrame::DrawText(gfxContext
& aRenderingContext
,
348 const nsRect
& aDirtyRect
, const nsRect
& aTextRect
,
349 const nscolor
* aOverrideColor
) {
350 nsPresContext
* presContext
= PresContext();
351 int32_t appUnitsPerDevPixel
= presContext
->AppUnitsPerDevPixel();
352 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
355 nscolor overColor
= 0;
356 nscolor underColor
= 0;
357 nscolor strikeColor
= 0;
358 auto overStyle
= StyleTextDecorationStyle::None
;
359 auto underStyle
= StyleTextDecorationStyle::None
;
360 auto strikeStyle
= StyleTextDecorationStyle::None
;
362 // Begin with no decorations
363 auto decorations
= StyleTextDecorationLine::NONE
;
364 // A mask of all possible line decorations.
365 auto decorMask
= StyleTextDecorationLine::UNDERLINE
|
366 StyleTextDecorationLine::OVERLINE
|
367 StyleTextDecorationLine::LINE_THROUGH
;
369 WritingMode wm
= GetWritingMode();
370 bool vertical
= wm
.IsVertical();
373 do { // find decoration colors
374 ComputedStyle
* context
= f
->Style();
375 if (!context
->HasTextDecorationLines()) {
378 const nsStyleTextReset
* styleText
= context
->StyleTextReset();
380 // a decoration defined here
381 if (decorMask
& styleText
->mTextDecorationLine
) {
383 if (aOverrideColor
) {
384 color
= *aOverrideColor
;
386 color
= styleText
->mTextDecorationColor
.CalcColor(*context
);
388 const auto style
= styleText
->mTextDecorationStyle
;
390 if (StyleTextDecorationLine::UNDERLINE
& decorMask
&
391 styleText
->mTextDecorationLine
) {
394 decorMask
&= ~StyleTextDecorationLine::UNDERLINE
;
395 decorations
|= StyleTextDecorationLine::UNDERLINE
;
397 if (StyleTextDecorationLine::OVERLINE
& decorMask
&
398 styleText
->mTextDecorationLine
) {
401 decorMask
&= ~StyleTextDecorationLine::OVERLINE
;
402 decorations
|= StyleTextDecorationLine::OVERLINE
;
404 if (StyleTextDecorationLine::LINE_THROUGH
& decorMask
&
405 styleText
->mTextDecorationLine
) {
408 decorMask
&= ~StyleTextDecorationLine::LINE_THROUGH
;
409 decorations
|= StyleTextDecorationLine::LINE_THROUGH
;
412 } while (decorMask
&& (f
= nsLayoutUtils::GetParentOrPlaceholderFor(f
)));
414 RefPtr
<nsFontMetrics
> fontMet
=
415 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f
);
416 fontMet
->SetVertical(wm
.IsVertical());
417 fontMet
->SetTextOrientation(StyleVisibility()->mTextOrientation
);
421 nscoord ascent
= fontMet
->MaxAscent();
424 if (wm
.IsVertical()) {
425 baselinePt
.x
= presContext
->RoundAppUnitsToNearestDevPixels(
426 aTextRect
.x
+ (wm
.IsVerticalRL() ? aTextRect
.width
- ascent
: ascent
));
427 baselinePt
.y
= aTextRect
.y
;
429 baselinePt
.x
= aTextRect
.x
;
431 presContext
->RoundAppUnitsToNearestDevPixels(aTextRect
.y
+ ascent
);
434 nsCSSRendering::PaintDecorationLineParams params
;
435 params
.dirtyRect
= ToRect(presContext
->AppUnitsToGfxUnits(aDirtyRect
));
436 params
.pt
= Point(presContext
->AppUnitsToGfxUnits(aTextRect
.x
),
437 presContext
->AppUnitsToGfxUnits(aTextRect
.y
));
438 params
.icoordInFrame
=
439 Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect
.x
));
440 params
.lineSize
= Size(presContext
->AppUnitsToGfxUnits(aTextRect
.width
), 0);
441 params
.ascent
= presContext
->AppUnitsToGfxUnits(ascent
);
442 params
.vertical
= vertical
;
444 // XXX todo: vertical-mode support for decorations not tested yet,
445 // probably won't be positioned correctly
447 // Underlines are drawn before overlines, and both before the text
448 // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
449 // (We don't apply this rule to the access-key underline because we only
450 // find out where that is as a side effect of drawing the text, in the
451 // general case -- see below.)
452 if (decorations
& (StyleTextDecorationLine::OVERLINE
|
453 StyleTextDecorationLine::UNDERLINE
)) {
454 fontMet
->GetUnderline(offset
, size
);
455 params
.lineSize
.height
= presContext
->AppUnitsToGfxUnits(size
);
456 if ((decorations
& StyleTextDecorationLine::UNDERLINE
) &&
457 underStyle
!= StyleTextDecorationStyle::None
) {
458 params
.color
= underColor
;
459 params
.offset
= presContext
->AppUnitsToGfxUnits(offset
);
460 params
.decoration
= StyleTextDecorationLine::UNDERLINE
;
461 params
.style
= underStyle
;
462 nsCSSRendering::PaintDecorationLine(this, *drawTarget
, params
);
464 if ((decorations
& StyleTextDecorationLine::OVERLINE
) &&
465 overStyle
!= StyleTextDecorationStyle::None
) {
466 params
.color
= overColor
;
467 params
.offset
= params
.ascent
;
468 params
.decoration
= StyleTextDecorationLine::OVERLINE
;
469 params
.style
= overStyle
;
470 nsCSSRendering::PaintDecorationLine(this, *drawTarget
, params
);
474 UniquePtr
<gfxContext
> refContext
=
475 PresShell()->CreateReferenceRenderingContext();
476 DrawTarget
* refDrawTarget
= refContext
->GetDrawTarget();
478 CalculateUnderline(refDrawTarget
, *fontMet
);
480 DeviceColor color
= ToDeviceColor(
481 aOverrideColor
? *aOverrideColor
: StyleText()->mColor
.ToColor());
482 ColorPattern
colorPattern(color
);
483 aRenderingContext
.SetDeviceColor(color
);
485 nsresult rv
= NS_ERROR_FAILURE
;
487 if (HasAnyStateBits(NS_FRAME_IS_BIDI
)) {
488 presContext
->SetBidiEnabled();
489 mozilla::intl::BidiEmbeddingLevel level
=
490 nsBidiPresUtils::BidiLevelFromStyle(Style());
491 if (mAccessKeyInfo
&& mAccessKeyInfo
->mAccesskeyIndex
!= kNotFound
) {
492 // We let the RenderText function calculate the mnemonic's
493 // underline position for us.
494 nsBidiPositionResolve posResolve
;
495 posResolve
.logicalIndex
= mAccessKeyInfo
->mAccesskeyIndex
;
496 rv
= nsBidiPresUtils::RenderText(
497 mCroppedTitle
.get(), mCroppedTitle
.Length(), level
, presContext
,
498 aRenderingContext
, refDrawTarget
, *fontMet
, baselinePt
.x
,
499 baselinePt
.y
, &posResolve
, 1);
500 mAccessKeyInfo
->mBeforeWidth
= posResolve
.visualLeftTwips
;
501 mAccessKeyInfo
->mAccessWidth
= posResolve
.visualWidth
;
503 rv
= nsBidiPresUtils::RenderText(
504 mCroppedTitle
.get(), mCroppedTitle
.Length(), level
, presContext
,
505 aRenderingContext
, refDrawTarget
, *fontMet
, baselinePt
.x
,
510 fontMet
->SetTextRunRTL(false);
512 if (mAccessKeyInfo
&& mAccessKeyInfo
->mAccesskeyIndex
!= kNotFound
) {
513 // In the simple (non-BiDi) case, we calculate the mnemonic's
514 // underline position by getting the text metric.
515 // XXX are attribute values always two byte?
516 if (mAccessKeyInfo
->mAccesskeyIndex
> 0)
517 mAccessKeyInfo
->mBeforeWidth
= nsLayoutUtils::AppUnitWidthOfString(
518 mCroppedTitle
.get(), mAccessKeyInfo
->mAccesskeyIndex
, *fontMet
,
521 mAccessKeyInfo
->mBeforeWidth
= 0;
524 fontMet
->DrawString(mCroppedTitle
.get(), mCroppedTitle
.Length(),
525 baselinePt
.x
, baselinePt
.y
, &aRenderingContext
,
529 if (mAccessKeyInfo
&& mAccessKeyInfo
->mAccesskeyIndex
!= kNotFound
) {
530 nsRect
r(aTextRect
.x
+ mAccessKeyInfo
->mBeforeWidth
,
531 aTextRect
.y
+ mAccessKeyInfo
->mAccessOffset
,
532 mAccessKeyInfo
->mAccessWidth
,
533 mAccessKeyInfo
->mAccessUnderlineSize
);
534 Rect devPxRect
= NSRectToSnappedRect(r
, appUnitsPerDevPixel
, *drawTarget
);
535 drawTarget
->FillRect(devPxRect
, colorPattern
);
538 // Strikeout is drawn on top of the text, per
539 // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1.
540 if ((decorations
& StyleTextDecorationLine::LINE_THROUGH
) &&
541 strikeStyle
!= StyleTextDecorationStyle::None
) {
542 fontMet
->GetStrikeout(offset
, size
);
543 params
.color
= strikeColor
;
544 params
.lineSize
.height
= presContext
->AppUnitsToGfxUnits(size
);
545 params
.offset
= presContext
->AppUnitsToGfxUnits(offset
);
546 params
.decoration
= StyleTextDecorationLine::LINE_THROUGH
;
547 params
.style
= strikeStyle
;
548 nsCSSRendering::PaintDecorationLine(this, *drawTarget
, params
);
552 void nsTextBoxFrame::CalculateUnderline(DrawTarget
* aDrawTarget
,
553 nsFontMetrics
& aFontMetrics
) {
554 if (mAccessKeyInfo
&& mAccessKeyInfo
->mAccesskeyIndex
!= kNotFound
) {
555 // Calculate all fields of mAccessKeyInfo which
556 // are the same for both BiDi and non-BiDi frames.
557 const char16_t
* titleString
= mCroppedTitle
.get();
558 aFontMetrics
.SetTextRunRTL(false);
559 mAccessKeyInfo
->mAccessWidth
= nsLayoutUtils::AppUnitWidthOfString(
560 titleString
[mAccessKeyInfo
->mAccesskeyIndex
], aFontMetrics
,
563 nscoord offset
, baseline
;
564 aFontMetrics
.GetUnderline(offset
, mAccessKeyInfo
->mAccessUnderlineSize
);
565 baseline
= aFontMetrics
.MaxAscent();
566 mAccessKeyInfo
->mAccessOffset
= baseline
- offset
;
570 void nsTextBoxFrame::CropStringForWidth(nsAString
& aText
,
571 gfxContext
& aRenderingContext
,
572 nsFontMetrics
& aFontMetrics
,
574 CroppingStyle aCropType
) {
575 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
577 // See if the width is even smaller than the ellipsis
578 // If so, clear the text completely.
579 const nsDependentString
& kEllipsis
= nsContentUtils::GetLocalizedEllipsis();
580 aFontMetrics
.SetTextRunRTL(false);
581 nscoord ellipsisWidth
=
582 nsLayoutUtils::AppUnitWidthOfString(kEllipsis
, aFontMetrics
, drawTarget
);
584 if (ellipsisWidth
> aWidth
) {
588 if (ellipsisWidth
== aWidth
) {
589 aText
.Assign(kEllipsis
);
593 // We will be drawing an ellipsis, thank you very much.
594 // Subtract out the required width of the ellipsis.
595 // This is the total remaining width we have to play with.
596 aWidth
-= ellipsisWidth
;
598 using mozilla::intl::GraphemeClusterBreakIteratorUtf16
;
599 using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16
;
601 // Now we crop. This is quite basic: it will not be really accurate in the
602 // presence of complex scripts with contextual shaping, etc., as it measures
603 // each grapheme cluster in isolation, not in its proper context.
608 const Span
text(aText
);
609 GraphemeClusterBreakIteratorUtf16
iter(text
);
611 nscoord totalWidth
= 0;
613 while (Maybe
<uint32_t> nextPos
= iter
.Next()) {
614 const nscoord charWidth
= nsLayoutUtils::AppUnitWidthOfString(
615 text
.FromTo(pos
, *nextPos
), aFontMetrics
, drawTarget
);
616 if (totalWidth
+ charWidth
> aWidth
) {
620 totalWidth
+= charWidth
;
623 if (pos
< aText
.Length()) {
624 aText
.Replace(pos
, aText
.Length() - pos
, kEllipsis
);
629 const Span
text(aText
);
630 GraphemeClusterBreakReverseIteratorUtf16
iter(text
);
631 uint32_t pos
= text
.Length();
632 nscoord totalWidth
= 0;
634 // nextPos is decreasing since we use a reverse iterator.
635 while (Maybe
<uint32_t> nextPos
= iter
.Next()) {
636 const nscoord charWidth
= nsLayoutUtils::AppUnitWidthOfString(
637 text
.FromTo(*nextPos
, pos
), aFontMetrics
, drawTarget
);
638 if (totalWidth
+ charWidth
> aWidth
) {
643 totalWidth
+= charWidth
;
647 aText
.Replace(0, pos
, kEllipsis
);
652 const Span
text(aText
);
653 nscoord totalWidth
= 0;
654 GraphemeClusterBreakIteratorUtf16
leftIter(text
);
655 GraphemeClusterBreakReverseIteratorUtf16
rightIter(text
);
656 uint32_t leftPos
= 0;
657 uint32_t rightPos
= text
.Length();
659 while (leftPos
< rightPos
) {
660 Maybe
<uint32_t> nextPos
= leftIter
.Next();
661 nscoord charWidth
= nsLayoutUtils::AppUnitWidthOfString(
662 text
.FromTo(leftPos
, *nextPos
), aFontMetrics
, drawTarget
);
663 if (totalWidth
+ charWidth
> aWidth
) {
668 totalWidth
+= charWidth
;
670 if (leftPos
>= rightPos
) {
674 nextPos
= rightIter
.Next();
675 charWidth
= nsLayoutUtils::AppUnitWidthOfString(
676 text
.FromTo(*nextPos
, rightPos
), aFontMetrics
, drawTarget
);
677 if (totalWidth
+ charWidth
> aWidth
) {
682 totalWidth
+= charWidth
;
685 if (leftPos
< rightPos
) {
686 aText
.Replace(leftPos
, rightPos
- leftPos
, kEllipsis
);
692 nscoord
nsTextBoxFrame::CalculateTitleForWidth(gfxContext
& aRenderingContext
,
694 if (mTitle
.IsEmpty()) {
695 mCroppedTitle
.Truncate();
699 RefPtr
<nsFontMetrics
> fm
= nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f
);
701 // See if the text needs to be cropped to fit in the width given.
702 mCroppedTitle
= mTitle
;
703 nscoord width
= nsLayoutUtils::AppUnitWidthOfStringBidi(
704 mCroppedTitle
, this, *fm
, aRenderingContext
);
705 if (width
> aMaxWidth
&& mCropType
!= CropNone
) {
706 CropStringForWidth(mCroppedTitle
, aRenderingContext
, *fm
, aMaxWidth
,
708 width
= nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle
, this, *fm
,
712 if (StyleVisibility()->mDirection
== StyleDirection::Rtl
||
713 HasRTLChars(mCroppedTitle
)) {
714 AddStateBits(NS_FRAME_IS_BIDI
);
720 #define OLD_ELLIPSIS u"..."_ns
722 // the following block is to append the accesskey to mTitle if there is an
723 // accesskey but the mTitle doesn't have the character
724 void nsTextBoxFrame::UpdateAccessTitle() {
726 * Note that if you change appending access key label spec,
727 * you need to maintain same logic in following methods. See bug 324159.
728 * toolkit/components/prompts/src/CommonDialog.jsm (setLabelForNode)
729 * toolkit/content/widgets/text.js (formatAccessKey)
731 uint32_t menuAccessKey
= LookAndFeel::GetMenuAccessKey();
732 if (!menuAccessKey
|| mAccessKey
.IsEmpty()) {
736 if (!AlwaysAppendAccessKey() &&
737 FindInReadable(mAccessKey
, mTitle
, nsCaseInsensitiveStringComparator
))
740 nsAutoString accessKeyLabel
;
741 accessKeyLabel
+= '(';
742 accessKeyLabel
+= mAccessKey
;
743 ToUpperCase(accessKeyLabel
);
744 accessKeyLabel
+= ')';
746 if (mTitle
.IsEmpty()) {
747 mTitle
= accessKeyLabel
;
751 if (StringEndsWith(mTitle
, accessKeyLabel
)) {
752 // Never append another "(X)" if the title already ends with "(X)".
756 const nsDependentString
& kEllipsis
= nsContentUtils::GetLocalizedEllipsis();
757 uint32_t offset
= mTitle
.Length();
758 if (StringEndsWith(mTitle
, kEllipsis
)) {
759 offset
-= kEllipsis
.Length();
760 } else if (StringEndsWith(mTitle
, OLD_ELLIPSIS
)) {
761 // Try to check with our old ellipsis (for old addons)
762 offset
-= OLD_ELLIPSIS
.Length();
765 // our default ellipsis (for non-localized addons) or ':'
766 const char16_t kLastChar
= mTitle
.Last();
767 if (kLastChar
== char16_t(0x2026) || kLastChar
== char16_t(':')) offset
--;
770 if (InsertSeparatorBeforeAccessKey() && offset
> 0 &&
771 !NS_IS_SPACE(mTitle
[offset
- 1])) {
772 mTitle
.Insert(' ', offset
);
776 mTitle
.Insert(accessKeyLabel
, offset
);
779 void nsTextBoxFrame::UpdateAccessIndex() {
780 uint32_t menuAccessKey
= LookAndFeel::GetMenuAccessKey();
781 if (!menuAccessKey
) {
784 if (mAccessKey
.IsEmpty()) {
785 if (mAccessKeyInfo
) {
786 delete mAccessKeyInfo
;
787 mAccessKeyInfo
= nullptr;
791 if (!mAccessKeyInfo
) {
792 mAccessKeyInfo
= new nsAccessKeyInfo();
795 nsAString::const_iterator start
, end
;
797 mCroppedTitle
.BeginReading(start
);
798 mCroppedTitle
.EndReading(end
);
800 // remember the beginning of the string
801 nsAString::const_iterator originalStart
= start
;
804 if (!AlwaysAppendAccessKey()) {
805 // not appending access key - do case-sensitive search
807 found
= FindInReadable(mAccessKey
, start
, end
);
809 // didn't find it - perform a case-insensitive search
810 start
= originalStart
;
811 found
= FindInReadable(mAccessKey
, start
, end
,
812 nsCaseInsensitiveStringComparator
);
815 found
= RFindInReadable(mAccessKey
, start
, end
,
816 nsCaseInsensitiveStringComparator
);
820 mAccessKeyInfo
->mAccesskeyIndex
= Distance(originalStart
, start
);
822 mAccessKeyInfo
->mAccesskeyIndex
= kNotFound
;
825 void nsTextBoxFrame::RecomputeTitle() {
826 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::value
, mTitle
);
828 // This doesn't handle language-specific uppercasing/lowercasing
829 // rules, unlike textruns.
830 StyleTextTransform textTransform
= StyleText()->mTextTransform
;
831 if (textTransform
.case_
== StyleTextTransformCase::Uppercase
) {
833 } else if (textTransform
.case_
== StyleTextTransformCase::Lowercase
) {
836 // We can't handle StyleTextTransformCase::Capitalize because we
837 // have no clue about word boundaries here. We also don't handle
838 // the full-width or full-size-kana transforms.
841 void nsTextBoxFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
842 nsLeafBoxFrame::DidSetComputedStyle(aOldComputedStyle
);
844 if (!aOldComputedStyle
) {
845 // We're just being initialized
849 const nsStyleText
* oldTextStyle
= aOldComputedStyle
->StyleText();
850 if (oldTextStyle
->mTextTransform
!= StyleText()->mTextTransform
) {
857 nsTextBoxFrame::DoXULLayout(nsBoxLayoutState
& aBoxLayoutState
) {
858 if (mNeedsReflowCallback
) {
859 nsIReflowCallback
* cb
= new nsAsyncAccesskeyUpdate(this);
861 PresShell()->PostReflowCallback(cb
);
863 mNeedsReflowCallback
= false;
866 nsresult rv
= nsLeafBoxFrame::DoXULLayout(aBoxLayoutState
);
868 CalcDrawRect(*aBoxLayoutState
.GetRenderingContext());
870 const nsStyleText
* textStyle
= StyleText();
872 nsRect
scrollBounds(nsPoint(0, 0), GetSize());
873 nsRect textRect
= mTextDrawRect
;
875 RefPtr
<nsFontMetrics
> fontMet
=
876 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f
);
877 nsBoundingMetrics metrics
= fontMet
->GetInkBoundsForInkOverflow(
878 mCroppedTitle
.get(), mCroppedTitle
.Length(),
879 aBoxLayoutState
.GetRenderingContext()->GetDrawTarget());
881 WritingMode wm
= GetWritingMode();
882 LogicalRect
tr(wm
, textRect
, GetSize());
884 tr
.IStart(wm
) -= metrics
.leftBearing
;
885 tr
.ISize(wm
) = metrics
.width
;
886 // In DrawText() we always draw with the baseline at MaxAscent() (relative to
888 tr
.BStart(wm
) += fontMet
->MaxAscent() - metrics
.ascent
;
889 tr
.BSize(wm
) = metrics
.ascent
+ metrics
.descent
;
891 textRect
= tr
.GetPhysicalRect(wm
, GetSize());
893 // Our scrollable overflow is our bounds; our ink overflow may
894 // extend beyond that.
896 visualBounds
.UnionRect(scrollBounds
, textRect
);
897 OverflowAreas
overflow(visualBounds
, scrollBounds
);
899 if (textStyle
->HasTextShadow()) {
900 // text-shadow extends our visual but not scrollable bounds
901 nsRect
& vis
= overflow
.InkOverflow();
903 nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect
, this));
905 FinishAndStoreOverflow(overflow
, GetSize());
910 nsRect
nsTextBoxFrame::GetComponentAlphaBounds() const {
911 if (StyleText()->HasTextShadow()) {
912 return InkOverflowRectRelativeToSelf();
914 return mTextDrawRect
;
917 bool nsTextBoxFrame::XULComputesOwnOverflowArea() { return true; }
920 void nsTextBoxFrame::MarkIntrinsicISizesDirty() {
922 nsLeafBoxFrame::MarkIntrinsicISizesDirty();
925 void nsTextBoxFrame::GetTextSize(gfxContext
& aRenderingContext
,
926 const nsString
& aString
, nsSize
& aSize
,
928 RefPtr
<nsFontMetrics
> fontMet
=
929 nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f
);
930 aSize
.height
= fontMet
->MaxHeight();
931 aSize
.width
= nsLayoutUtils::AppUnitWidthOfStringBidi(aString
, this, *fontMet
,
933 aAscent
= fontMet
->MaxAscent();
936 void nsTextBoxFrame::CalcTextSize(nsBoxLayoutState
& aBoxLayoutState
) {
939 gfxContext
* rendContext
= aBoxLayoutState
.GetRenderingContext();
941 GetTextSize(*rendContext
, mTitle
, size
, mAscent
);
942 if (GetWritingMode().IsVertical()) {
943 std::swap(size
.width
, size
.height
);
946 mNeedsRecalc
= false;
951 void nsTextBoxFrame::CalcDrawRect(gfxContext
& aRenderingContext
) {
952 WritingMode wm
= GetWritingMode();
954 LogicalRect
textRect(wm
, LogicalPoint(wm
, 0, 0), GetLogicalSize(wm
));
955 nsMargin borderPadding
;
956 GetXULBorderAndPadding(borderPadding
);
957 textRect
.Deflate(wm
, LogicalMargin(wm
, borderPadding
));
959 // determine (cropped) title and underline position
960 // determine (cropped) title which fits in aRect, and its width
961 // (where "width" is the text measure along its baseline, i.e. actually
962 // a physical height in vertical writing modes)
964 CalculateTitleForWidth(aRenderingContext
, textRect
.ISize(wm
));
967 // Make sure to update the accessible tree in case when cropped title is
969 nsAccessibilityService
* accService
= GetAccService();
971 accService
->UpdateLabelValue(PresShell(), mContent
, mCroppedTitle
);
975 // determine if and at which position to put the underline
978 // make the rect as small as our (cropped) text.
979 nscoord outerISize
= textRect
.ISize(wm
);
980 textRect
.ISize(wm
) = titleWidth
;
982 // Align our text within the overall rect by checking our text-align property.
983 const nsStyleText
* textStyle
= StyleText();
984 if (textStyle
->mTextAlign
== StyleTextAlign::Center
) {
985 textRect
.IStart(wm
) += (outerISize
- textRect
.ISize(wm
)) / 2;
986 } else if (textStyle
->mTextAlign
== StyleTextAlign::End
||
987 (textStyle
->mTextAlign
== StyleTextAlign::Left
&&
989 (textStyle
->mTextAlign
== StyleTextAlign::Right
&&
991 textRect
.IStart(wm
) += (outerISize
- textRect
.ISize(wm
));
994 mTextDrawRect
= textRect
.GetPhysicalRect(wm
, GetSize());
998 * Ok return our dimensions
1000 nsSize
nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState
& aBoxLayoutState
) {
1001 CalcTextSize(aBoxLayoutState
);
1003 nsSize size
= mTextSize
;
1004 DISPLAY_PREF_SIZE(this, size
);
1006 AddXULBorderAndPadding(size
);
1007 bool widthSet
, heightSet
;
1008 nsIFrame::AddXULPrefSize(this, size
, widthSet
, heightSet
);
1014 * Ok return our dimensions
1016 nsSize
nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState
& aBoxLayoutState
) {
1017 CalcTextSize(aBoxLayoutState
);
1019 nsSize size
= mTextSize
;
1020 DISPLAY_MIN_SIZE(this, size
);
1022 // if there is cropping our min width becomes our border and padding
1023 if (mCropType
!= CropNone
&& mCropType
!= CropAuto
) {
1024 if (GetWritingMode().IsVertical()) {
1031 AddXULBorderAndPadding(size
);
1032 bool widthSet
, heightSet
;
1033 nsIFrame::AddXULMinSize(this, size
, widthSet
, heightSet
);
1038 nscoord
nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState
& aBoxLayoutState
) {
1039 CalcTextSize(aBoxLayoutState
);
1041 nscoord ascent
= mAscent
;
1043 nsMargin
m(0, 0, 0, 0);
1044 GetXULBorderAndPadding(m
);
1046 WritingMode wm
= GetWritingMode();
1047 ascent
+= LogicalMargin(wm
, m
).BStart(wm
);
1052 #ifdef DEBUG_FRAME_DUMP
1053 nsresult
nsTextBoxFrame::GetFrameName(nsAString
& aResult
) const {
1054 MakeFrameName(u
"TextBox"_ns
, aResult
);
1055 aResult
+= u
"[value="_ns
+ mTitle
+ u
"]"_ns
;