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
.synthesisPosition
== StyleFontSynthesis::Auto
,
134 aFont
.languageOverride
);
136 aFont
.AddFontFeaturesToStyle(&style
, mOrientation
== eVertical
);
137 style
.featureValueLookup
= aParams
.featureValueLookup
;
139 aFont
.AddFontVariationsToStyle(&style
);
141 gfxFloat devToCssSize
= gfxFloat(mP2A
) / gfxFloat(AppUnitsPerCSSPixel());
142 mFontGroup
= new gfxFontGroup(
143 mPresContext
, aFont
.family
.families
, &style
, mLanguage
, mExplicitLanguage
,
144 aParams
.textPerf
, aParams
.userFontSet
, devToCssSize
, aFont
.variantEmoji
);
147 nsFontMetrics::~nsFontMetrics() {
148 // Should not be dropped by stylo
149 MOZ_ASSERT(NS_IsMainThread());
151 mPresContext
->FontMetricsDeleted(this);
155 void nsFontMetrics::Destroy() { mPresContext
= nullptr; }
157 // XXXTODO get rid of this macro
158 #define ROUND_TO_TWIPS(x) (nscoord) floor(((x) * mP2A) + 0.5)
159 #define CEIL_TO_TWIPS(x) (nscoord) ceil((x) * mP2A)
161 static const gfxFont::Metrics
& GetMetrics(
162 const nsFontMetrics
* aFontMetrics
,
163 nsFontMetrics::FontOrientation aOrientation
) {
164 RefPtr
<gfxFont
> font
=
165 aFontMetrics
->GetThebesFontGroup()->GetFirstValidFont();
166 return font
->GetMetrics(aOrientation
);
169 static const gfxFont::Metrics
& GetMetrics(const nsFontMetrics
* aFontMetrics
) {
170 return GetMetrics(aFontMetrics
, aFontMetrics
->Orientation());
173 nscoord
nsFontMetrics::XHeight() const {
174 return ROUND_TO_TWIPS(GetMetrics(this).xHeight
);
177 nscoord
nsFontMetrics::CapHeight() const {
178 return ROUND_TO_TWIPS(GetMetrics(this).capHeight
);
181 nscoord
nsFontMetrics::SuperscriptOffset() const {
182 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
183 NS_FONT_SUPERSCRIPT_OFFSET_RATIO
);
186 nscoord
nsFontMetrics::SubscriptOffset() const {
187 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
*
188 NS_FONT_SUBSCRIPT_OFFSET_RATIO
);
191 void nsFontMetrics::GetStrikeout(nscoord
& aOffset
, nscoord
& aSize
) const {
192 aOffset
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset
);
193 aSize
= ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize
);
196 void nsFontMetrics::GetUnderline(nscoord
& aOffset
, nscoord
& aSize
) const {
197 aOffset
= ROUND_TO_TWIPS(mFontGroup
->GetUnderlineOffset());
198 aSize
= ROUND_TO_TWIPS(GetMetrics(this).underlineSize
);
201 // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
202 // text-decoration lines drawable area. See bug 421353.
203 // BE CAREFUL for rounding each values. The logic MUST be same as
204 // nsCSSRendering::GetTextDecorationRectInternal's.
206 static gfxFloat
ComputeMaxDescent(const gfxFont::Metrics
& aMetrics
,
207 gfxFontGroup
* aFontGroup
) {
208 gfxFloat offset
= floor(-aFontGroup
->GetUnderlineOffset() + 0.5);
209 gfxFloat size
= NS_round(aMetrics
.underlineSize
);
210 gfxFloat minDescent
= offset
+ size
;
211 return floor(std::max(minDescent
, aMetrics
.maxDescent
) + 0.5);
214 static gfxFloat
ComputeMaxAscent(const gfxFont::Metrics
& aMetrics
) {
215 return floor(aMetrics
.maxAscent
+ 0.5);
218 nscoord
nsFontMetrics::InternalLeading() const {
219 return ROUND_TO_TWIPS(GetMetrics(this).internalLeading
);
222 nscoord
nsFontMetrics::ExternalLeading() const {
223 return ROUND_TO_TWIPS(GetMetrics(this).externalLeading
);
226 nscoord
nsFontMetrics::EmHeight() const {
227 return ROUND_TO_TWIPS(GetMetrics(this).emHeight
);
230 nscoord
nsFontMetrics::EmAscent() const {
231 return ROUND_TO_TWIPS(GetMetrics(this).emAscent
);
234 nscoord
nsFontMetrics::EmDescent() const {
235 return ROUND_TO_TWIPS(GetMetrics(this).emDescent
);
238 nscoord
nsFontMetrics::MaxHeight() const {
239 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
240 CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
243 nscoord
nsFontMetrics::MaxAscent() const {
244 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
247 nscoord
nsFontMetrics::MaxDescent() const {
248 return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup
));
251 nscoord
nsFontMetrics::MaxAdvance() const {
252 return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance
);
255 nscoord
nsFontMetrics::AveCharWidth() const {
256 // Use CEIL instead of ROUND for consistency with GetMaxAdvance
257 return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth
);
260 nscoord
nsFontMetrics::ZeroOrAveCharWidth() const {
261 return CEIL_TO_TWIPS(GetMetrics(this).ZeroOrAveCharWidth());
264 nscoord
nsFontMetrics::SpaceWidth() const {
265 // For vertical text with mixed or sideways orientation, we want the
266 // width of a horizontal space (even if we're using vertical line-spacing
267 // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
268 return CEIL_TO_TWIPS(
270 mVertical
&& mTextOrientation
== StyleTextOrientation::Upright
276 int32_t nsFontMetrics::GetMaxStringLength() const {
277 const double x
= 32767.0 / std::max(1.0, GetMetrics(this).maxAdvance
);
278 int32_t len
= (int32_t)floor(x
);
279 return std::max(1, len
);
282 nscoord
nsFontMetrics::GetWidth(const char* aString
, uint32_t aLength
,
283 DrawTarget
* aDrawTarget
) const {
287 if (aLength
== 1 && aString
[0] == ' ') {
290 StubPropertyProvider provider
;
291 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
293 return NSToCoordRound(
294 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
299 nscoord
nsFontMetrics::GetWidth(const char16_t
* aString
, uint32_t aLength
,
300 DrawTarget
* aDrawTarget
) const {
304 if (aLength
== 1 && aString
[0] == ' ') {
307 StubPropertyProvider provider
;
308 AutoTextRun
textRun(this, aDrawTarget
, aString
, aLength
);
310 return NSToCoordRound(
311 textRun
->GetAdvanceWidth(gfxTextRun::Range(0, aLength
), &provider
));
316 // Draw a string using this font handle on the surface passed in.
317 void nsFontMetrics::DrawString(const char* aString
, uint32_t aLength
,
318 nscoord aX
, nscoord aY
,
319 gfxContext
* aContext
) const {
323 StubPropertyProvider provider
;
324 AutoTextRun
textRun(this, aContext
->GetDrawTarget(), aString
, aLength
);
325 if (!textRun
.get()) {
328 gfx::Point
pt(aX
, aY
);
329 gfxTextRun::Range
range(0, aLength
);
332 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
334 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
337 mozilla::gfx::PaletteCache paletteCache
;
338 gfxTextRun::DrawParams
params(aContext
, paletteCache
);
339 params
.provider
= &provider
;
340 textRun
->Draw(range
, pt
, params
);
343 void nsFontMetrics::DrawString(
344 const char16_t
* aString
, uint32_t aLength
, nscoord aX
, nscoord aY
,
345 gfxContext
* aContext
, DrawTarget
* aTextRunConstructionDrawTarget
) const {
349 StubPropertyProvider provider
;
350 AutoTextRun
textRun(this, aTextRunConstructionDrawTarget
, aString
, aLength
);
351 if (!textRun
.get()) {
354 gfx::Point
pt(aX
, aY
);
355 gfxTextRun::Range
range(0, aLength
);
358 pt
.y
+= textRun
->GetAdvanceWidth(range
, &provider
);
360 pt
.x
+= textRun
->GetAdvanceWidth(range
, &provider
);
363 mozilla::gfx::PaletteCache paletteCache
;
364 gfxTextRun::DrawParams
params(aContext
, paletteCache
);
365 params
.provider
= &provider
;
366 textRun
->Draw(range
, pt
, params
);
369 static nsBoundingMetrics
GetTextBoundingMetrics(
370 const nsFontMetrics
* aMetrics
, const char16_t
* aString
, uint32_t aLength
,
371 mozilla::gfx::DrawTarget
* aDrawTarget
, gfxFont::BoundingBoxType aType
) {
373 return nsBoundingMetrics();
375 StubPropertyProvider provider
;
376 AutoTextRun
textRun(aMetrics
, aDrawTarget
, aString
, aLength
);
379 gfxTextRun::Metrics theMetrics
= textRun
->MeasureText(
380 gfxTextRun::Range(0, aLength
), aType
, aDrawTarget
, &provider
);
382 m
.leftBearing
= NSToCoordFloor(theMetrics
.mBoundingBox
.X());
383 m
.rightBearing
= NSToCoordCeil(theMetrics
.mBoundingBox
.XMost());
384 m
.ascent
= NSToCoordCeil(-theMetrics
.mBoundingBox
.Y());
385 m
.descent
= NSToCoordCeil(theMetrics
.mBoundingBox
.YMost());
386 m
.width
= NSToCoordRound(theMetrics
.mAdvanceWidth
);
391 nsBoundingMetrics
nsFontMetrics::GetBoundingMetrics(
392 const char16_t
* aString
, uint32_t aLength
, DrawTarget
* aDrawTarget
) const {
393 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
394 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
);
397 nsBoundingMetrics
nsFontMetrics::GetInkBoundsForInkOverflow(
398 const char16_t
* aString
, uint32_t aLength
, DrawTarget
* aDrawTarget
) const {
399 return GetTextBoundingMetrics(this, aString
, aLength
, aDrawTarget
,
400 gfxFont::LOOSE_INK_EXTENTS
);
403 gfxUserFontSet
* nsFontMetrics::GetUserFontSet() const {
404 return mFontGroup
->GetUserFontSet();