1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsFontMetrics.h"
7 #include <math.h> // for floor, ceil
8 #include <algorithm> // for max
9 #include "gfxContext.h" // for gfxContext
10 #include "gfxFontConstants.h" // for NS_FONT_SYNTHESIS_*
11 #include "gfxPlatform.h" // for gfxPlatform
12 #include "gfxPoint.h" // for gfxPoint
13 #include "gfxRect.h" // for gfxRect
14 #include "gfxTextRun.h" // for gfxFontGroup
15 #include "gfxTypes.h" // for gfxFloat
16 #include "nsAtom.h" // for nsAtom
17 #include "nsBoundingMetrics.h" // for nsBoundingMetrics
18 #include "nsDebug.h" // for NS_ERROR
19 #include "nsDeviceContext.h" // for nsDeviceContext
20 #include "nsMathUtils.h" // for NS_round
21 #include "nsPresContext.h" // for nsPresContext
22 #include "nsString.h" // for nsString
23 #include "nsStyleConsts.h" // for StyleHyphens::None
24 #include "mozilla/Assertions.h" // for MOZ_ASSERT
25 #include "mozilla/UniquePtr.h" // for UniquePtr
28 using namespace mozilla
;
34 typedef mozilla::gfx::DrawTarget DrawTarget
;
36 AutoTextRun(nsFontMetrics
* aMetrics
, DrawTarget
* aDrawTarget
,
37 const char* aString
, int32_t aLength
) {
38 mTextRun
= aMetrics
->GetThebesFontGroup()->MakeTextRun(
39 reinterpret_cast<const uint8_t*>(aString
), aLength
, aDrawTarget
,
40 aMetrics
->AppUnitsPerDevPixel(), ComputeFlags(aMetrics
),
41 nsTextFrameUtils::Flags(), nullptr);
44 AutoTextRun(nsFontMetrics
* aMetrics
, DrawTarget
* aDrawTarget
,
45 const char16_t
* aString
, int32_t aLength
) {
46 mTextRun
= aMetrics
->GetThebesFontGroup()->MakeTextRun(
47 aString
, aLength
, aDrawTarget
, aMetrics
->AppUnitsPerDevPixel(),
48 ComputeFlags(aMetrics
), nsTextFrameUtils::Flags(), nullptr);
51 gfxTextRun
* get() { return mTextRun
.get(); }
52 gfxTextRun
* operator->() { return mTextRun
.get(); }
55 static gfx::ShapedTextFlags
ComputeFlags(nsFontMetrics
* aMetrics
) {
56 gfx::ShapedTextFlags flags
= gfx::ShapedTextFlags();
57 if (aMetrics
->GetTextRunRTL()) {
58 flags
|= gfx::ShapedTextFlags::TEXT_IS_RTL
;
60 if (aMetrics
->GetVertical()) {
61 switch (aMetrics
->GetTextOrientation()) {
62 case StyleTextOrientation::Mixed
:
63 flags
|= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
;
65 case StyleTextOrientation::Upright
:
66 flags
|= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
68 case StyleTextOrientation::Sideways
:
69 flags
|= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
;
76 RefPtr
<gfxTextRun
> mTextRun
;
79 class StubPropertyProvider final
: public gfxTextRun::PropertyProvider
{
81 void GetHyphenationBreaks(
82 gfxTextRun::Range aRange
,
83 gfxTextRun::HyphenType
* aBreakBefore
) const override
{
85 "This shouldn't be called because we never call BreakAndMeasureText");
87 mozilla::StyleHyphens
GetHyphensOption() const override
{
89 "This shouldn't be called because we never call BreakAndMeasureText");
90 return mozilla::StyleHyphens::None
;
92 gfxFloat
GetHyphenWidth() const override
{
93 NS_ERROR("This shouldn't be called because we never enable hyphens");
96 already_AddRefed
<mozilla::gfx::DrawTarget
> GetDrawTarget() const override
{
97 NS_ERROR("This shouldn't be called because we never enable hyphens");
100 uint32_t GetAppUnitsPerDevUnit() const override
{
101 NS_ERROR("This shouldn't be called because we never enable hyphens");
104 void GetSpacing(gfxTextRun::Range aRange
, Spacing
* aSpacing
) const override
{
105 NS_ERROR("This shouldn't be called because we never enable spacing");
107 gfx::ShapedTextFlags
GetShapedTextFlags() const override
{
108 NS_ERROR("This shouldn't be called because we never enable hyphens");
109 return gfx::ShapedTextFlags();
115 nsFontMetrics::nsFontMetrics(const nsFont
& aFont
, const Params
& aParams
,
116 nsPresContext
* aContext
)
118 mLanguage(aParams
.language
),
119 mPresContext(aContext
),
120 mP2A(aContext
->DeviceContext()->AppUnitsPerDevPixel()),
121 mOrientation(aParams
.orientation
),
122 mExplicitLanguage(aParams
.explicitLanguage
),
125 mTextOrientation(mozilla::StyleTextOrientation::Mixed
) {
126 gfxFontStyle
style(aFont
.style
, aFont
.weight
, aFont
.stretch
,
127 gfxFloat(aFont
.size
.ToAppUnits()) / mP2A
, aFont
.sizeAdjust
,
128 aFont
.family
.is_system_font
,
129 aContext
->DeviceContext()->IsPrinterContext(),
130 aFont
.synthesisWeight
== StyleFontSynthesis::Auto
,
131 aFont
.synthesisStyle
== StyleFontSynthesis::Auto
,
132 aFont
.synthesisSmallCaps
== StyleFontSynthesis::Auto
,
133 aFont
.languageOverride
);
135 aFont
.AddFontFeaturesToStyle(&style
, mOrientation
== eVertical
);
136 style
.featureValueLookup
= aParams
.featureValueLookup
;
138 aFont
.AddFontVariationsToStyle(&style
);
140 gfxFloat devToCssSize
= gfxFloat(mP2A
) / gfxFloat(AppUnitsPerCSSPixel());
141 mFontGroup
= new gfxFontGroup(
142 mPresContext
, aFont
.family
.families
, &style
, mLanguage
, mExplicitLanguage
,
143 aParams
.textPerf
, aParams
.userFontSet
, devToCssSize
, aFont
.variantEmoji
);
146 nsFontMetrics::~nsFontMetrics() {
147 // Should not be dropped by stylo
148 MOZ_ASSERT(NS_IsMainThread());
150 mPresContext
->FontMetricsDeleted(this);
154 void nsFontMetrics::Destroy() { mPresContext
= nullptr; }
156 // XXXTODO get rid of this macro
157 #define ROUND_TO_TWIPS(x) (nscoord) floor(((x)*mP2A) + 0.5)
158 #define CEIL_TO_TWIPS(x) (nscoord) ceil((x)*mP2A)
160 static const gfxFont::Metrics
& GetMetrics(
161 nsFontMetrics
* aFontMetrics
, nsFontMetrics::FontOrientation aOrientation
) {
162 RefPtr
<gfxFont
> font
=
163 aFontMetrics
->GetThebesFontGroup()->GetFirstValidFont();
164 return font
->GetMetrics(aOrientation
);
167 static const gfxFont::Metrics
& GetMetrics(nsFontMetrics
* aFontMetrics
) {
168 return GetMetrics(aFontMetrics
, aFontMetrics
->Orientation());
171 nscoord
nsFontMetrics::XHeight() {
172 return ROUND_TO_TWIPS(GetMetrics(this).xHeight
);
175 nscoord
nsFontMetrics::CapHeight() {
176 return ROUND_TO_TWIPS(GetMetrics(this).capHeight
);
179 nscoord
nsFontMetrics::SuperscriptOffset() {
180 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
181 NS_FONT_SUPERSCRIPT_OFFSET_RATIO
);
184 nscoord
nsFontMetrics::SubscriptOffset() {
185 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
186 NS_FONT_SUBSCRIPT_OFFSET_RATIO
);
189 void nsFontMetrics::GetStrikeout(nscoord
& aOffset
, nscoord
& aSize
) {
190 aOffset
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset
);
191 aSize
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize
);
194 void nsFontMetrics::GetUnderline(nscoord
& aOffset
, nscoord
& aSize
) {
195 aOffset
= ROUND_TO_TWIPS(mFontGroup
->GetUnderlineOffset());
196 aSize
= ROUND_TO_TWIPS(GetMetrics(this).underlineSize
);
199 // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
200 // text-decoration lines drawable area. See bug 421353.
201 // BE CAREFUL for rounding each values. The logic MUST be same as
202 // nsCSSRendering::GetTextDecorationRectInternal's.
204 static gfxFloat
ComputeMaxDescent(const gfxFont::Metrics
& aMetrics
,
205 gfxFontGroup
* aFontGroup
) {
206 gfxFloat offset
= floor(-aFontGroup
->GetUnderlineOffset() + 0.5);
207 gfxFloat size
= NS_round(aMetrics
.underlineSize
);
208 gfxFloat minDescent
= offset
+ size
;
209 return floor(std::max(minDescent
, aMetrics
.maxDescent
) + 0.5);
212 static gfxFloat
ComputeMaxAscent(const gfxFont::Metrics
& aMetrics
) {
213 return floor(aMetrics
.maxAscent
+ 0.5);
216 nscoord
nsFontMetrics::InternalLeading() {
217 return ROUND_TO_TWIPS(GetMetrics(this).internalLeading
);
220 nscoord
nsFontMetrics::ExternalLeading() {
221 return ROUND_TO_TWIPS(GetMetrics(this).externalLeading
);
224 nscoord
nsFontMetrics::EmHeight() {
225 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
);
228 nscoord
nsFontMetrics::EmAscent() {
229 return ROUND_TO_TWIPS(GetMetrics(this).emAscent
);
232 nscoord
nsFontMetrics::EmDescent() {
233 return ROUND_TO_TWIPS(GetMetrics(this).emDescent
);
236 nscoord
nsFontMetrics::MaxHeight() {
237 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
238 CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
241 nscoord
nsFontMetrics::MaxAscent() {
242 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
245 nscoord
nsFontMetrics::MaxDescent() {
246 return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
249 nscoord
nsFontMetrics::MaxAdvance() {
250 return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance
);
253 nscoord
nsFontMetrics::AveCharWidth() {
254 // Use CEIL instead of ROUND for consistency with GetMaxAdvance
255 return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth
);
258 nscoord
nsFontMetrics::SpaceWidth() {
259 // For vertical text with mixed or sideways orientation, we want the
260 // width of a horizontal space (even if we're using vertical line-spacing
261 // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
262 return CEIL_TO_TWIPS(
264 mVertical
&& mTextOrientation
== StyleTextOrientation::Upright
270 int32_t nsFontMetrics::GetMaxStringLength() {
271 const gfxFont::Metrics
& m
= GetMetrics(this);
272 const double x
= 32767.0 / std::max(1.0, m
.maxAdvance
);
273 int32_t len
= (int32_t)floor(x
);
274 return std::max(1, len
);
277 nscoord
nsFontMetrics::GetWidth(const char* aString
, uint32_t aLength
,
278 DrawTarget
* aDrawTarget
) {
279 if (aLength
== 0) return 0;
281 if (aLength
== 1 && aString
[0] == ' ') return SpaceWidth();
283 StubPropertyProvider provider
;
284 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
286 return NSToCoordRound(
287 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
292 nscoord
nsFontMetrics::GetWidth(const char16_t
* aString
, uint32_t aLength
,
293 DrawTarget
* aDrawTarget
) {
294 if (aLength
== 0) return 0;
296 if (aLength
== 1 && aString
[0] == ' ') return SpaceWidth();
298 StubPropertyProvider provider
;
299 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
301 return NSToCoordRound(
302 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
307 // Draw a string using this font handle on the surface passed in.
308 void nsFontMetrics::DrawString(const char* aString
, uint32_t aLength
,
309 nscoord aX
, nscoord aY
, gfxContext
* aContext
) {
310 if (aLength
== 0) return;
312 StubPropertyProvider provider
;
313 AutoTextRun
textRun(this, aContext
->GetDrawTarget(), aString
, aLength
);
314 if (!textRun
.get()) {
317 gfx::Point
pt(aX
, aY
);
318 gfxTextRun::Range
range(0, aLength
);
321 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
323 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
326 gfxTextRun::DrawParams
params(aContext
);
327 params
.provider
= &provider
;
328 textRun
->Draw(range
, pt
, params
);
331 void nsFontMetrics::DrawString(const char16_t
* aString
, uint32_t aLength
,
332 nscoord aX
, nscoord aY
, gfxContext
* aContext
,
333 DrawTarget
* aTextRunConstructionDrawTarget
) {
334 if (aLength
== 0) return;
336 StubPropertyProvider provider
;
337 AutoTextRun
textRun(this, aTextRunConstructionDrawTarget
, aString
, aLength
);
338 if (!textRun
.get()) {
341 gfx::Point
pt(aX
, aY
);
342 gfxTextRun::Range
range(0, aLength
);
345 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
347 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
350 gfxTextRun::DrawParams
params(aContext
);
351 params
.provider
= &provider
;
352 textRun
->Draw(range
, pt
, params
);
355 static nsBoundingMetrics
GetTextBoundingMetrics(
356 nsFontMetrics
* aMetrics
, const char16_t
* aString
, uint32_t aLength
,
357 mozilla::gfx::DrawTarget
* aDrawTarget
, gfxFont::BoundingBoxType aType
) {
358 if (aLength
== 0) return nsBoundingMetrics();
360 StubPropertyProvider provider
;
361 AutoTextRun
textRun(aMetrics
, aDrawTarget
, aString
, aLength
);
364 gfxTextRun::Metrics theMetrics
= textRun
->MeasureText(
365 gfxTextRun::Range(0, aLength
), aType
, aDrawTarget
, &provider
);
367 m
.leftBearing
= NSToCoordFloor(theMetrics
.mBoundingBox
.X());
368 m
.rightBearing
= NSToCoordCeil(theMetrics
.mBoundingBox
.XMost());
369 m
.ascent
= NSToCoordCeil(-theMetrics
.mBoundingBox
.Y());
370 m
.descent
= NSToCoordCeil(theMetrics
.mBoundingBox
.YMost());
371 m
.width
= NSToCoordRound(theMetrics
.mAdvanceWidth
);
376 nsBoundingMetrics
nsFontMetrics::GetBoundingMetrics(const char16_t
* aString
,
378 DrawTarget
* aDrawTarget
) {
379 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
380 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
);
383 nsBoundingMetrics
nsFontMetrics::GetInkBoundsForInkOverflow(
384 const char16_t
* aString
, uint32_t aLength
, DrawTarget
* aDrawTarget
) {
385 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
386 gfxFont::LOOSE_INK_EXTENTS
);
389 gfxUserFontSet
* nsFontMetrics::GetUserFontSet() const {
390 return mFontGroup
->GetUserFontSet();