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