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