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(const nsFontMetrics
* aMetrics
, DrawTarget
* aDrawTarget
,
37 const char* aString
, uint32_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(const nsFontMetrics
* aMetrics
, DrawTarget
* aDrawTarget
,
45 const char16_t
* aString
, uint32_t aLength
) {
46 mTextRun
= aMetrics
->GetThebesFontGroup()->MakeTextRun(
47 aString
, aLength
, aDrawTarget
, aMetrics
->AppUnitsPerDevPixel(),
48 ComputeFlags(aMetrics
), nsTextFrameUtils::Flags(), nullptr);
51 gfxTextRun
* get() const { return mTextRun
.get(); }
52 gfxTextRun
* operator->() const { return mTextRun
.get(); }
55 static gfx::ShapedTextFlags
ComputeFlags(const 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 const nsFontMetrics
* aFontMetrics
,
162 nsFontMetrics::FontOrientation aOrientation
) {
163 RefPtr
<gfxFont
> font
=
164 aFontMetrics
->GetThebesFontGroup()->GetFirstValidFont();
165 return font
->GetMetrics(aOrientation
);
168 static const gfxFont::Metrics
& GetMetrics(const nsFontMetrics
* aFontMetrics
) {
169 return GetMetrics(aFontMetrics
, aFontMetrics
->Orientation());
172 nscoord
nsFontMetrics::XHeight() const {
173 return ROUND_TO_TWIPS(GetMetrics(this).xHeight
);
176 nscoord
nsFontMetrics::CapHeight() const {
177 return ROUND_TO_TWIPS(GetMetrics(this).capHeight
);
180 nscoord
nsFontMetrics::SuperscriptOffset() const {
181 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
182 NS_FONT_SUPERSCRIPT_OFFSET_RATIO
);
185 nscoord
nsFontMetrics::SubscriptOffset() const {
186 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
187 NS_FONT_SUBSCRIPT_OFFSET_RATIO
);
190 void nsFontMetrics::GetStrikeout(nscoord
& aOffset
, nscoord
& aSize
) const {
191 aOffset
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset
);
192 aSize
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize
);
195 void nsFontMetrics::GetUnderline(nscoord
& aOffset
, nscoord
& aSize
) const {
196 aOffset
= ROUND_TO_TWIPS(mFontGroup
->GetUnderlineOffset());
197 aSize
= ROUND_TO_TWIPS(GetMetrics(this).underlineSize
);
200 // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
201 // text-decoration lines drawable area. See bug 421353.
202 // BE CAREFUL for rounding each values. The logic MUST be same as
203 // nsCSSRendering::GetTextDecorationRectInternal's.
205 static gfxFloat
ComputeMaxDescent(const gfxFont::Metrics
& aMetrics
,
206 gfxFontGroup
* aFontGroup
) {
207 gfxFloat offset
= floor(-aFontGroup
->GetUnderlineOffset() + 0.5);
208 gfxFloat size
= NS_round(aMetrics
.underlineSize
);
209 gfxFloat minDescent
= offset
+ size
;
210 return floor(std::max(minDescent
, aMetrics
.maxDescent
) + 0.5);
213 static gfxFloat
ComputeMaxAscent(const gfxFont::Metrics
& aMetrics
) {
214 return floor(aMetrics
.maxAscent
+ 0.5);
217 nscoord
nsFontMetrics::InternalLeading() const {
218 return ROUND_TO_TWIPS(GetMetrics(this).internalLeading
);
221 nscoord
nsFontMetrics::ExternalLeading() const {
222 return ROUND_TO_TWIPS(GetMetrics(this).externalLeading
);
225 nscoord
nsFontMetrics::EmHeight() const {
226 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
);
229 nscoord
nsFontMetrics::EmAscent() const {
230 return ROUND_TO_TWIPS(GetMetrics(this).emAscent
);
233 nscoord
nsFontMetrics::EmDescent() const {
234 return ROUND_TO_TWIPS(GetMetrics(this).emDescent
);
237 nscoord
nsFontMetrics::MaxHeight() const {
238 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
239 CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
242 nscoord
nsFontMetrics::MaxAscent() const {
243 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
246 nscoord
nsFontMetrics::MaxDescent() const {
247 return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
250 nscoord
nsFontMetrics::MaxAdvance() const {
251 return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance
);
254 nscoord
nsFontMetrics::AveCharWidth() const {
255 // Use CEIL instead of ROUND for consistency with GetMaxAdvance
256 return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth
);
259 nscoord
nsFontMetrics::ZeroOrAveCharWidth() const {
260 return CEIL_TO_TWIPS(GetMetrics(this).ZeroOrAveCharWidth());
263 nscoord
nsFontMetrics::SpaceWidth() const {
264 // For vertical text with mixed or sideways orientation, we want the
265 // width of a horizontal space (even if we're using vertical line-spacing
266 // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
267 return CEIL_TO_TWIPS(
269 mVertical
&& mTextOrientation
== StyleTextOrientation::Upright
275 int32_t nsFontMetrics::GetMaxStringLength() const {
276 const double x
= 32767.0 / std::max(1.0, GetMetrics(this).maxAdvance
);
277 int32_t len
= (int32_t)floor(x
);
278 return std::max(1, len
);
281 nscoord
nsFontMetrics::GetWidth(const char* aString
, uint32_t aLength
,
282 DrawTarget
* aDrawTarget
) const {
286 if (aLength
== 1 && aString
[0] == ' ') {
289 StubPropertyProvider provider
;
290 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
292 return NSToCoordRound(
293 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
298 nscoord
nsFontMetrics::GetWidth(const char16_t
* aString
, uint32_t aLength
,
299 DrawTarget
* aDrawTarget
) const {
303 if (aLength
== 1 && aString
[0] == ' ') {
306 StubPropertyProvider provider
;
307 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
309 return NSToCoordRound(
310 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
315 // Draw a string using this font handle on the surface passed in.
316 void nsFontMetrics::DrawString(const char* aString
, uint32_t aLength
,
317 nscoord aX
, nscoord aY
,
318 gfxContext
* aContext
) const {
322 StubPropertyProvider provider
;
323 AutoTextRun
textRun(this, aContext
->GetDrawTarget(), aString
, aLength
);
324 if (!textRun
.get()) {
327 gfx::Point
pt(aX
, aY
);
328 gfxTextRun::Range
range(0, aLength
);
331 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
333 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
336 gfxTextRun::DrawParams
params(aContext
);
337 params
.provider
= &provider
;
338 textRun
->Draw(range
, pt
, params
);
341 void nsFontMetrics::DrawString(
342 const char16_t
* aString
, uint32_t aLength
, nscoord aX
, nscoord aY
,
343 gfxContext
* aContext
, DrawTarget
* aTextRunConstructionDrawTarget
) const {
347 StubPropertyProvider provider
;
348 AutoTextRun
textRun(this, aTextRunConstructionDrawTarget
, aString
, aLength
);
349 if (!textRun
.get()) {
352 gfx::Point
pt(aX
, aY
);
353 gfxTextRun::Range
range(0, aLength
);
356 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
358 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
361 gfxTextRun::DrawParams
params(aContext
);
362 params
.provider
= &provider
;
363 textRun
->Draw(range
, pt
, params
);
366 static nsBoundingMetrics
GetTextBoundingMetrics(
367 const nsFontMetrics
* aMetrics
, const char16_t
* aString
, uint32_t aLength
,
368 mozilla::gfx::DrawTarget
* aDrawTarget
, gfxFont::BoundingBoxType aType
) {
370 return nsBoundingMetrics();
372 StubPropertyProvider provider
;
373 AutoTextRun
textRun(aMetrics
, aDrawTarget
, aString
, aLength
);
376 gfxTextRun::Metrics theMetrics
= textRun
->MeasureText(
377 gfxTextRun::Range(0, aLength
), aType
, aDrawTarget
, &provider
);
379 m
.leftBearing
= NSToCoordFloor(theMetrics
.mBoundingBox
.X());
380 m
.rightBearing
= NSToCoordCeil(theMetrics
.mBoundingBox
.XMost());
381 m
.ascent
= NSToCoordCeil(-theMetrics
.mBoundingBox
.Y());
382 m
.descent
= NSToCoordCeil(theMetrics
.mBoundingBox
.YMost());
383 m
.width
= NSToCoordRound(theMetrics
.mAdvanceWidth
);
388 nsBoundingMetrics
nsFontMetrics::GetBoundingMetrics(
389 const char16_t
* aString
, uint32_t aLength
, DrawTarget
* aDrawTarget
) const {
390 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
391 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
);
394 nsBoundingMetrics
nsFontMetrics::GetInkBoundsForInkOverflow(
395 const char16_t
* aString
, uint32_t aLength
, DrawTarget
* aDrawTarget
) const {
396 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
397 gfxFont::LOOSE_INK_EXTENTS
);
400 gfxUserFontSet
* nsFontMetrics::GetUserFontSet() const {
401 return mFontGroup
->GetUserFontSet();