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