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/. */
5
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 "nsBoundingMetrics.h" // for nsBoundingMetrics
17 #include "nsDebug.h" // for NS_ERROR
18 #include "nsDeviceContext.h" // for nsDeviceContext
19 #include "nsAtom.h" // for nsAtom
20 #include "nsMathUtils.h" // for NS_round
21 #include "nsString.h" // for nsString
22 #include "nsStyleConsts.h" // for StyleHyphens::None
23 #include "mozilla/Assertions.h" // for MOZ_ASSERT
24 #include "mozilla/UniquePtr.h" // for UniquePtr
25
26 class gfxUserFontSet;
27 using namespace mozilla;
28
29 namespace {
30
31 class AutoTextRun {
32 public:
33 typedef mozilla::gfx::DrawTarget DrawTarget;
34
AutoTextRun(nsFontMetrics * aMetrics,DrawTarget * aDrawTarget,const char * aString,int32_t aLength)35 AutoTextRun(nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
36 const char* aString, int32_t aLength) {
37 mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
38 reinterpret_cast<const uint8_t*>(aString), aLength, aDrawTarget,
39 aMetrics->AppUnitsPerDevPixel(), ComputeFlags(aMetrics),
40 nsTextFrameUtils::Flags(), nullptr);
41 }
42
AutoTextRun(nsFontMetrics * aMetrics,DrawTarget * aDrawTarget,const char16_t * aString,int32_t aLength)43 AutoTextRun(nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
44 const char16_t* aString, int32_t aLength) {
45 mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
46 aString, aLength, aDrawTarget, aMetrics->AppUnitsPerDevPixel(),
47 ComputeFlags(aMetrics), nsTextFrameUtils::Flags(), nullptr);
48 }
49
get()50 gfxTextRun* get() { return mTextRun.get(); }
operator ->()51 gfxTextRun* operator->() { return mTextRun.get(); }
52
53 private:
ComputeFlags(nsFontMetrics * aMetrics)54 static gfx::ShapedTextFlags ComputeFlags(nsFontMetrics* aMetrics) {
55 gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
56 if (aMetrics->GetTextRunRTL()) {
57 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
58 }
59 if (aMetrics->GetVertical()) {
60 switch (aMetrics->GetTextOrientation()) {
61 case StyleTextOrientation::Mixed:
62 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
63 break;
64 case StyleTextOrientation::Upright:
65 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
66 break;
67 case StyleTextOrientation::Sideways:
68 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
69 break;
70 }
71 }
72 return flags;
73 }
74
75 RefPtr<gfxTextRun> mTextRun;
76 };
77
78 class StubPropertyProvider final : public gfxTextRun::PropertyProvider {
79 public:
GetHyphenationBreaks(gfxTextRun::Range aRange,gfxTextRun::HyphenType * aBreakBefore) const80 void GetHyphenationBreaks(
81 gfxTextRun::Range aRange,
82 gfxTextRun::HyphenType* aBreakBefore) const override {
83 NS_ERROR(
84 "This shouldn't be called because we never call BreakAndMeasureText");
85 }
GetHyphensOption() const86 mozilla::StyleHyphens GetHyphensOption() const override {
87 NS_ERROR(
88 "This shouldn't be called because we never call BreakAndMeasureText");
89 return mozilla::StyleHyphens::None;
90 }
GetHyphenWidth() const91 gfxFloat GetHyphenWidth() const override {
92 NS_ERROR("This shouldn't be called because we never enable hyphens");
93 return 0;
94 }
GetDrawTarget() const95 already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const override {
96 NS_ERROR("This shouldn't be called because we never enable hyphens");
97 return nullptr;
98 }
GetAppUnitsPerDevUnit() const99 uint32_t GetAppUnitsPerDevUnit() const override {
100 NS_ERROR("This shouldn't be called because we never enable hyphens");
101 return 60;
102 }
GetSpacing(gfxTextRun::Range aRange,Spacing * aSpacing) const103 void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const override {
104 NS_ERROR("This shouldn't be called because we never enable spacing");
105 }
106 };
107
108 } // namespace
109
nsFontMetrics(const nsFont & aFont,const Params & aParams,nsDeviceContext * aContext)110 nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams,
111 nsDeviceContext* aContext)
112 : mFont(aFont),
113 mLanguage(aParams.language),
114 mDeviceContext(aContext),
115 mP2A(aContext->AppUnitsPerDevPixel()),
116 mOrientation(aParams.orientation),
117 mTextRunRTL(false),
118 mVertical(false),
119 mTextOrientation(mozilla::StyleTextOrientation::Mixed) {
120 gfxFontStyle style(
121 aFont.style, aFont.weight, aFont.stretch, gfxFloat(aFont.size) / mP2A,
122 aParams.language, aParams.explicitLanguage, aFont.sizeAdjust,
123 aFont.systemFont, mDeviceContext->IsPrinterContext(),
124 aFont.synthesis & NS_FONT_SYNTHESIS_WEIGHT,
125 aFont.synthesis & NS_FONT_SYNTHESIS_STYLE, aFont.languageOverride);
126
127 aFont.AddFontFeaturesToStyle(&style, mOrientation == eVertical);
128 style.featureValueLookup = aParams.featureValueLookup;
129
130 aFont.AddFontVariationsToStyle(&style);
131
132 gfxFloat devToCssSize = gfxFloat(mP2A) / gfxFloat(AppUnitsPerCSSPixel());
133 mFontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(
134 aFont.fontlist, &style, aParams.textPerf, aParams.fontStats,
135 aParams.userFontSet, devToCssSize);
136 }
137
~nsFontMetrics()138 nsFontMetrics::~nsFontMetrics() {
139 // Should not be dropped by stylo
140 MOZ_ASSERT(NS_IsMainThread());
141 if (mDeviceContext) {
142 mDeviceContext->FontMetricsDeleted(this);
143 }
144 }
145
Destroy()146 void nsFontMetrics::Destroy() { mDeviceContext = nullptr; }
147
148 // XXXTODO get rid of this macro
149 #define ROUND_TO_TWIPS(x) (nscoord) floor(((x)*mP2A) + 0.5)
150 #define CEIL_TO_TWIPS(x) (nscoord) ceil((x)*mP2A)
151
GetMetrics(nsFontMetrics * aFontMetrics,nsFontMetrics::FontOrientation aOrientation)152 static const gfxFont::Metrics& GetMetrics(
153 nsFontMetrics* aFontMetrics, nsFontMetrics::FontOrientation aOrientation) {
154 return aFontMetrics->GetThebesFontGroup()->GetFirstValidFont()->GetMetrics(
155 aOrientation);
156 }
157
GetMetrics(nsFontMetrics * aFontMetrics)158 static const gfxFont::Metrics& GetMetrics(nsFontMetrics* aFontMetrics) {
159 return GetMetrics(aFontMetrics, aFontMetrics->Orientation());
160 }
161
XHeight()162 nscoord nsFontMetrics::XHeight() {
163 return ROUND_TO_TWIPS(GetMetrics(this).xHeight);
164 }
165
CapHeight()166 nscoord nsFontMetrics::CapHeight() {
167 return ROUND_TO_TWIPS(GetMetrics(this).capHeight);
168 }
169
SuperscriptOffset()170 nscoord nsFontMetrics::SuperscriptOffset() {
171 return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
172 NS_FONT_SUPERSCRIPT_OFFSET_RATIO);
173 }
174
SubscriptOffset()175 nscoord nsFontMetrics::SubscriptOffset() {
176 return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
177 NS_FONT_SUBSCRIPT_OFFSET_RATIO);
178 }
179
GetStrikeout(nscoord & aOffset,nscoord & aSize)180 void nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) {
181 aOffset = ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset);
182 aSize = ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize);
183 }
184
GetUnderline(nscoord & aOffset,nscoord & aSize)185 void nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) {
186 aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset());
187 aSize = ROUND_TO_TWIPS(GetMetrics(this).underlineSize);
188 }
189
190 // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
191 // text-decoration lines drawable area. See bug 421353.
192 // BE CAREFUL for rounding each values. The logic MUST be same as
193 // nsCSSRendering::GetTextDecorationRectInternal's.
194
ComputeMaxDescent(const gfxFont::Metrics & aMetrics,gfxFontGroup * aFontGroup)195 static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics,
196 gfxFontGroup* aFontGroup) {
197 gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5);
198 gfxFloat size = NS_round(aMetrics.underlineSize);
199 gfxFloat minDescent = offset + size;
200 return floor(std::max(minDescent, aMetrics.maxDescent) + 0.5);
201 }
202
ComputeMaxAscent(const gfxFont::Metrics & aMetrics)203 static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) {
204 return floor(aMetrics.maxAscent + 0.5);
205 }
206
InternalLeading()207 nscoord nsFontMetrics::InternalLeading() {
208 return ROUND_TO_TWIPS(GetMetrics(this).internalLeading);
209 }
210
ExternalLeading()211 nscoord nsFontMetrics::ExternalLeading() {
212 return ROUND_TO_TWIPS(GetMetrics(this).externalLeading);
213 }
214
EmHeight()215 nscoord nsFontMetrics::EmHeight() {
216 return ROUND_TO_TWIPS(GetMetrics(this).emHeight);
217 }
218
EmAscent()219 nscoord nsFontMetrics::EmAscent() {
220 return ROUND_TO_TWIPS(GetMetrics(this).emAscent);
221 }
222
EmDescent()223 nscoord nsFontMetrics::EmDescent() {
224 return ROUND_TO_TWIPS(GetMetrics(this).emDescent);
225 }
226
MaxHeight()227 nscoord nsFontMetrics::MaxHeight() {
228 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
229 CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
230 }
231
MaxAscent()232 nscoord nsFontMetrics::MaxAscent() {
233 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
234 }
235
MaxDescent()236 nscoord nsFontMetrics::MaxDescent() {
237 return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
238 }
239
MaxAdvance()240 nscoord nsFontMetrics::MaxAdvance() {
241 return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance);
242 }
243
AveCharWidth()244 nscoord nsFontMetrics::AveCharWidth() {
245 // Use CEIL instead of ROUND for consistency with GetMaxAdvance
246 return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth);
247 }
248
SpaceWidth()249 nscoord nsFontMetrics::SpaceWidth() {
250 // For vertical text with mixed or sideways orientation, we want the
251 // width of a horizontal space (even if we're using vertical line-spacing
252 // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
253 return CEIL_TO_TWIPS(
254 GetMetrics(this,
255 mVertical && mTextOrientation == StyleTextOrientation::Upright
256 ? eVertical
257 : eHorizontal)
258 .spaceWidth);
259 }
260
GetMaxStringLength()261 int32_t nsFontMetrics::GetMaxStringLength() {
262 const gfxFont::Metrics& m = GetMetrics(this);
263 const double x = 32767.0 / std::max(1.0, m.maxAdvance);
264 int32_t len = (int32_t)floor(x);
265 return std::max(1, len);
266 }
267
GetWidth(const char * aString,uint32_t aLength,DrawTarget * aDrawTarget)268 nscoord nsFontMetrics::GetWidth(const char* aString, uint32_t aLength,
269 DrawTarget* aDrawTarget) {
270 if (aLength == 0) return 0;
271
272 if (aLength == 1 && aString[0] == ' ') return SpaceWidth();
273
274 StubPropertyProvider provider;
275 AutoTextRun textRun(this, aDrawTarget, aString, aLength);
276 if (textRun.get()) {
277 return NSToCoordRound(
278 textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
279 }
280 return 0;
281 }
282
GetWidth(const char16_t * aString,uint32_t aLength,DrawTarget * aDrawTarget)283 nscoord nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength,
284 DrawTarget* aDrawTarget) {
285 if (aLength == 0) return 0;
286
287 if (aLength == 1 && aString[0] == ' ') return SpaceWidth();
288
289 StubPropertyProvider provider;
290 AutoTextRun textRun(this, aDrawTarget, aString, aLength);
291 if (textRun.get()) {
292 return NSToCoordRound(
293 textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
294 }
295 return 0;
296 }
297
298 // Draw a string using this font handle on the surface passed in.
DrawString(const char * aString,uint32_t aLength,nscoord aX,nscoord aY,gfxContext * aContext)299 void nsFontMetrics::DrawString(const char* aString, uint32_t aLength,
300 nscoord aX, nscoord aY, gfxContext* aContext) {
301 if (aLength == 0) return;
302
303 StubPropertyProvider provider;
304 AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength);
305 if (!textRun.get()) {
306 return;
307 }
308 gfx::Point pt(aX, aY);
309 gfxTextRun::Range range(0, aLength);
310 if (mTextRunRTL) {
311 if (mVertical) {
312 pt.y += textRun->GetAdvanceWidth(range, &provider);
313 } else {
314 pt.x += textRun->GetAdvanceWidth(range, &provider);
315 }
316 }
317 gfxTextRun::DrawParams params(aContext);
318 params.provider = &provider;
319 textRun->Draw(range, pt, params);
320 }
321
DrawString(const char16_t * aString,uint32_t aLength,nscoord aX,nscoord aY,gfxContext * aContext,DrawTarget * aTextRunConstructionDrawTarget)322 void nsFontMetrics::DrawString(const char16_t* aString, uint32_t aLength,
323 nscoord aX, nscoord aY, gfxContext* aContext,
324 DrawTarget* aTextRunConstructionDrawTarget) {
325 if (aLength == 0) return;
326
327 StubPropertyProvider provider;
328 AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength);
329 if (!textRun.get()) {
330 return;
331 }
332 gfx::Point pt(aX, aY);
333 gfxTextRun::Range range(0, aLength);
334 if (mTextRunRTL) {
335 if (mVertical) {
336 pt.y += textRun->GetAdvanceWidth(range, &provider);
337 } else {
338 pt.x += textRun->GetAdvanceWidth(range, &provider);
339 }
340 }
341 gfxTextRun::DrawParams params(aContext);
342 params.provider = &provider;
343 textRun->Draw(range, pt, params);
344 }
345
GetTextBoundingMetrics(nsFontMetrics * aMetrics,const char16_t * aString,uint32_t aLength,mozilla::gfx::DrawTarget * aDrawTarget,gfxFont::BoundingBoxType aType)346 static nsBoundingMetrics GetTextBoundingMetrics(
347 nsFontMetrics* aMetrics, const char16_t* aString, uint32_t aLength,
348 mozilla::gfx::DrawTarget* aDrawTarget, gfxFont::BoundingBoxType aType) {
349 if (aLength == 0) return nsBoundingMetrics();
350
351 StubPropertyProvider provider;
352 AutoTextRun textRun(aMetrics, aDrawTarget, aString, aLength);
353 nsBoundingMetrics m;
354 if (textRun.get()) {
355 gfxTextRun::Metrics theMetrics = textRun->MeasureText(
356 gfxTextRun::Range(0, aLength), aType, aDrawTarget, &provider);
357
358 m.leftBearing = NSToCoordFloor(theMetrics.mBoundingBox.X());
359 m.rightBearing = NSToCoordCeil(theMetrics.mBoundingBox.XMost());
360 m.ascent = NSToCoordCeil(-theMetrics.mBoundingBox.Y());
361 m.descent = NSToCoordCeil(theMetrics.mBoundingBox.YMost());
362 m.width = NSToCoordRound(theMetrics.mAdvanceWidth);
363 }
364 return m;
365 }
366
GetBoundingMetrics(const char16_t * aString,uint32_t aLength,DrawTarget * aDrawTarget)367 nsBoundingMetrics nsFontMetrics::GetBoundingMetrics(const char16_t* aString,
368 uint32_t aLength,
369 DrawTarget* aDrawTarget) {
370 return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
371 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS);
372 }
373
GetInkBoundsForVisualOverflow(const char16_t * aString,uint32_t aLength,DrawTarget * aDrawTarget)374 nsBoundingMetrics nsFontMetrics::GetInkBoundsForVisualOverflow(
375 const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) {
376 return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
377 gfxFont::LOOSE_INK_EXTENTS);
378 }
379
GetUserFontSet() const380 gfxUserFontSet* nsFontMetrics::GetUserFontSet() const {
381 return mFontGroup->GetUserFontSet();
382 }
383