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