/src/mozilla-central/gfx/thebes/gfxTextRun.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim: set ts=4 et sw=4 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "gfxTextRun.h" |
8 | | #include "gfxGlyphExtents.h" |
9 | | #include "gfxHarfBuzzShaper.h" |
10 | | #include "gfxPlatformFontList.h" |
11 | | #include "gfxUserFontSet.h" |
12 | | #include "mozilla/gfx/2D.h" |
13 | | #include "mozilla/gfx/PathHelpers.h" |
14 | | #include "mozilla/Sprintf.h" |
15 | | |
16 | | #include "gfxContext.h" |
17 | | #include "gfxFontConstants.h" |
18 | | #include "gfxFontMissingGlyphs.h" |
19 | | #include "gfxScriptItemizer.h" |
20 | | #include "nsUnicodeProperties.h" |
21 | | #include "nsUnicodeRange.h" |
22 | | #include "nsStyleConsts.h" |
23 | | #include "nsStyleUtil.h" |
24 | | #include "mozilla/Likely.h" |
25 | | #include "gfx2DGlue.h" |
26 | | #include "mozilla/gfx/Logging.h" // for gfxCriticalError |
27 | | #include "mozilla/UniquePtr.h" |
28 | | #include "TextDrawTarget.h" |
29 | | |
30 | | #ifdef XP_WIN |
31 | | #include "gfxWindowsPlatform.h" |
32 | | #endif |
33 | | |
34 | | using namespace mozilla; |
35 | | using namespace mozilla::gfx; |
36 | | using namespace mozilla::unicode; |
37 | | using mozilla::services::GetObserverService; |
38 | | |
39 | | static const char16_t kEllipsisChar[] = { 0x2026, 0x0 }; |
40 | | static const char16_t kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 }; |
41 | | |
42 | | #ifdef DEBUG_roc |
43 | | #define DEBUG_TEXT_RUN_STORAGE_METRICS |
44 | | #endif |
45 | | |
46 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
47 | | extern uint32_t gTextRunStorageHighWaterMark; |
48 | | extern uint32_t gTextRunStorage; |
49 | | extern uint32_t gFontCount; |
50 | | extern uint32_t gGlyphExtentsCount; |
51 | | extern uint32_t gGlyphExtentsWidthsTotalSize; |
52 | | extern uint32_t gGlyphExtentsSetupEagerSimple; |
53 | | extern uint32_t gGlyphExtentsSetupEagerTight; |
54 | | extern uint32_t gGlyphExtentsSetupLazyTight; |
55 | | extern uint32_t gGlyphExtentsSetupFallBackToTight; |
56 | | #endif |
57 | | |
58 | | bool |
59 | | gfxTextRun::GlyphRunIterator::NextRun() |
60 | 0 | { |
61 | 0 | uint32_t glyphRunCount; |
62 | 0 | if (mTextRun->mHasGlyphRunArray) { |
63 | 0 | glyphRunCount = mTextRun->mGlyphRunArray.Length(); |
64 | 0 | if (mNextIndex >= glyphRunCount) { |
65 | 0 | return false; |
66 | 0 | } |
67 | 0 | mGlyphRun = &mTextRun->mGlyphRunArray[mNextIndex]; |
68 | 0 | } else { |
69 | 0 | if (mNextIndex > 0 || !mTextRun->mSingleGlyphRun.mFont) { |
70 | 0 | return false; |
71 | 0 | } |
72 | 0 | glyphRunCount = 1; |
73 | 0 | mGlyphRun = &mTextRun->mSingleGlyphRun; |
74 | 0 | } |
75 | 0 |
|
76 | 0 | if (mGlyphRun->mCharacterOffset >= mEndOffset) { |
77 | 0 | return false; |
78 | 0 | } |
79 | 0 | |
80 | 0 | mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); |
81 | 0 | uint32_t last = mNextIndex + 1 < glyphRunCount |
82 | 0 | ? mTextRun->mGlyphRunArray[mNextIndex + 1].mCharacterOffset |
83 | 0 | : mTextRun->GetLength(); |
84 | 0 | mStringEnd = std::min(mEndOffset, last); |
85 | 0 |
|
86 | 0 | ++mNextIndex; |
87 | 0 | return true; |
88 | 0 | } |
89 | | |
90 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
91 | | static void |
92 | | AccountStorageForTextRun(gfxTextRun *aTextRun, int32_t aSign) |
93 | | { |
94 | | // Ignores detailed glyphs... we don't know when those have been constructed |
95 | | // Also ignores gfxSkipChars dynamic storage (which won't be anything |
96 | | // for preformatted text) |
97 | | // Also ignores GlyphRun array, again because it hasn't been constructed |
98 | | // by the time this gets called. If there's only one glyphrun that's stored |
99 | | // directly in the textrun anyway so no additional overhead. |
100 | | uint32_t length = aTextRun->GetLength(); |
101 | | int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph); |
102 | | bytes += sizeof(gfxTextRun); |
103 | | gTextRunStorage += bytes*aSign; |
104 | | gTextRunStorageHighWaterMark = std::max(gTextRunStorageHighWaterMark, gTextRunStorage); |
105 | | } |
106 | | #endif |
107 | | |
108 | | static bool |
109 | | NeedsGlyphExtents(gfxTextRun *aTextRun) |
110 | 0 | { |
111 | 0 | if (aTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) |
112 | 0 | return true; |
113 | 0 | uint32_t numRuns; |
114 | 0 | const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); |
115 | 0 | for (uint32_t i = 0; i < numRuns; ++i) { |
116 | 0 | if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) |
117 | 0 | return true; |
118 | 0 | } |
119 | 0 | return false; |
120 | 0 | } |
121 | | |
122 | | // Helper for textRun creation to preallocate storage for glyph records; |
123 | | // this function returns a pointer to the newly-allocated glyph storage. |
124 | | // Returns nullptr if allocation fails. |
125 | | void * |
126 | | gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) |
127 | 0 | { |
128 | 0 | // Allocate the storage we need, returning nullptr on failure rather than |
129 | 0 | // throwing an exception (because web content can create huge runs). |
130 | 0 | void *storage = malloc(aSize + aLength * sizeof(CompressedGlyph)); |
131 | 0 | if (!storage) { |
132 | 0 | NS_WARNING("failed to allocate storage for text run!"); |
133 | 0 | return nullptr; |
134 | 0 | } |
135 | 0 |
|
136 | 0 | // Initialize the glyph storage (beyond aSize) to zero |
137 | 0 | memset(reinterpret_cast<char*>(storage) + aSize, 0, |
138 | 0 | aLength * sizeof(CompressedGlyph)); |
139 | 0 |
|
140 | 0 | return storage; |
141 | 0 | } |
142 | | |
143 | | already_AddRefed<gfxTextRun> |
144 | | gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, |
145 | | uint32_t aLength, gfxFontGroup *aFontGroup, |
146 | | gfx::ShapedTextFlags aFlags, |
147 | | nsTextFrameUtils::Flags aFlags2) |
148 | 0 | { |
149 | 0 | void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); |
150 | 0 | if (!storage) { |
151 | 0 | return nullptr; |
152 | 0 | } |
153 | 0 | |
154 | 0 | RefPtr<gfxTextRun> result = new (storage) gfxTextRun(aParams, aLength, |
155 | 0 | aFontGroup, |
156 | 0 | aFlags, aFlags2); |
157 | 0 | return result.forget(); |
158 | 0 | } |
159 | | |
160 | | gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, |
161 | | uint32_t aLength, gfxFontGroup *aFontGroup, |
162 | | gfx::ShapedTextFlags aFlags, |
163 | | nsTextFrameUtils::Flags aFlags2) |
164 | | : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit) |
165 | | , mSingleGlyphRun() |
166 | | , mUserData(aParams->mUserData) |
167 | | , mFontGroup(aFontGroup) |
168 | | , mFlags2(aFlags2) |
169 | | , mReleasedFontGroup(false) |
170 | | , mHasGlyphRunArray(false) |
171 | | , mShapingState(eShapingState_Normal) |
172 | 0 | { |
173 | 0 | NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); |
174 | 0 | NS_ADDREF(mFontGroup); |
175 | 0 |
|
176 | 0 | #ifndef RELEASE_OR_BETA |
177 | 0 | gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics(); |
178 | 0 | if (tp) { |
179 | 0 | tp->current.textrunConst++; |
180 | 0 | } |
181 | 0 | #endif |
182 | 0 |
|
183 | 0 | mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); |
184 | 0 |
|
185 | 0 | if (aParams->mSkipChars) { |
186 | 0 | mSkipChars.TakeFrom(aParams->mSkipChars); |
187 | 0 | } |
188 | 0 |
|
189 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
190 | | AccountStorageForTextRun(this, 1); |
191 | | #endif |
192 | |
|
193 | 0 | mSkipDrawing = mFontGroup->ShouldSkipDrawing(); |
194 | 0 | } |
195 | | |
196 | | gfxTextRun::~gfxTextRun() |
197 | 0 | { |
198 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
199 | | AccountStorageForTextRun(this, -1); |
200 | | #endif |
201 | | #ifdef DEBUG |
202 | | // Make it easy to detect a dead text run |
203 | | mFlags = ~gfx::ShapedTextFlags(); |
204 | | mFlags2 = ~nsTextFrameUtils::Flags(); |
205 | | #endif |
206 | |
|
207 | 0 | if (mHasGlyphRunArray) { |
208 | 0 | mGlyphRunArray.~nsTArray<GlyphRun>(); |
209 | 0 | } else { |
210 | 0 | mSingleGlyphRun.mFont = nullptr; |
211 | 0 | } |
212 | 0 |
|
213 | 0 | // The cached ellipsis textrun (if any) in a fontgroup will have already |
214 | 0 | // been told to release its reference to the group, so we mustn't do that |
215 | 0 | // again here. |
216 | 0 | if (!mReleasedFontGroup) { |
217 | 0 | #ifndef RELEASE_OR_BETA |
218 | 0 | gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics(); |
219 | 0 | if (tp) { |
220 | 0 | tp->current.textrunDestr++; |
221 | 0 | } |
222 | 0 | #endif |
223 | 0 | NS_RELEASE(mFontGroup); |
224 | 0 | } |
225 | 0 | } |
226 | | |
227 | | void |
228 | | gfxTextRun::ReleaseFontGroup() |
229 | 0 | { |
230 | 0 | NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); |
231 | 0 | NS_RELEASE(mFontGroup); |
232 | 0 | mReleasedFontGroup = true; |
233 | 0 | } |
234 | | |
235 | | bool |
236 | | gfxTextRun::SetPotentialLineBreaks(Range aRange, const uint8_t* aBreakBefore) |
237 | 0 | { |
238 | 0 | NS_ASSERTION(aRange.end <= GetLength(), "Overflow"); |
239 | 0 |
|
240 | 0 | uint32_t changed = 0; |
241 | 0 | CompressedGlyph* cg = mCharacterGlyphs + aRange.start; |
242 | 0 | const CompressedGlyph* const end = cg + aRange.Length(); |
243 | 0 | while (cg < end) { |
244 | 0 | uint8_t canBreak = *aBreakBefore++; |
245 | 0 | if (canBreak && !cg->IsClusterStart()) { |
246 | 0 | // XXX If we replace the line-breaker with one based more closely |
247 | 0 | // on UAX#14 (e.g. using ICU), this may not be needed any more. |
248 | 0 | // Avoid possible breaks inside a cluster, EXCEPT when the previous |
249 | 0 | // character was a space (compare UAX#14 rules LB9, LB10). |
250 | 0 | if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) { |
251 | 0 | canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | changed |= cg->SetCanBreakBefore(canBreak); |
255 | 0 | ++cg; |
256 | 0 | } |
257 | 0 | return changed != 0; |
258 | 0 | } |
259 | | |
260 | | gfxTextRun::LigatureData |
261 | | gfxTextRun::ComputeLigatureData(Range aPartRange, |
262 | | PropertyProvider *aProvider) const |
263 | 0 | { |
264 | 0 | NS_ASSERTION(aPartRange.start < aPartRange.end, |
265 | 0 | "Computing ligature data for empty range"); |
266 | 0 | NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow"); |
267 | 0 |
|
268 | 0 | LigatureData result; |
269 | 0 | const CompressedGlyph *charGlyphs = mCharacterGlyphs; |
270 | 0 |
|
271 | 0 | uint32_t i; |
272 | 0 | for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) { |
273 | 0 | NS_ASSERTION(i > 0, "Ligature at the start of the run??"); |
274 | 0 | } |
275 | 0 | result.mRange.start = i; |
276 | 0 | for (i = aPartRange.start + 1; |
277 | 0 | i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { |
278 | 0 | } |
279 | 0 | result.mRange.end = i; |
280 | 0 |
|
281 | 0 | int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange); |
282 | 0 | // Count the number of started clusters we have seen |
283 | 0 | uint32_t totalClusterCount = 0; |
284 | 0 | uint32_t partClusterIndex = 0; |
285 | 0 | uint32_t partClusterCount = 0; |
286 | 0 | for (i = result.mRange.start; i < result.mRange.end; ++i) { |
287 | 0 | // Treat the first character of the ligature as the start of a |
288 | 0 | // cluster for our purposes of allocating ligature width to its |
289 | 0 | // characters. |
290 | 0 | if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) { |
291 | 0 | ++totalClusterCount; |
292 | 0 | if (i < aPartRange.start) { |
293 | 0 | ++partClusterIndex; |
294 | 0 | } else if (i < aPartRange.end) { |
295 | 0 | ++partClusterCount; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | 0 | NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); |
300 | 0 | result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); |
301 | 0 | result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); |
302 | 0 |
|
303 | 0 | // Any rounding errors are apportioned to the final part of the ligature, |
304 | 0 | // so that measuring all parts of a ligature and summing them is equal to |
305 | 0 | // the ligature width. |
306 | 0 | if (aPartRange.end == result.mRange.end) { |
307 | 0 | gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); |
308 | 0 | result.mPartWidth += ligatureWidth - allParts; |
309 | 0 | } |
310 | 0 |
|
311 | 0 | if (partClusterCount == 0) { |
312 | 0 | // nothing to draw |
313 | 0 | result.mClipBeforePart = result.mClipAfterPart = true; |
314 | 0 | } else { |
315 | 0 | // Determine whether we should clip before or after this part when |
316 | 0 | // drawing its slice of the ligature. |
317 | 0 | // We need to clip before the part if any cluster is drawn before |
318 | 0 | // this part. |
319 | 0 | result.mClipBeforePart = partClusterIndex > 0; |
320 | 0 | // We need to clip after the part if any cluster is drawn after |
321 | 0 | // this part. |
322 | 0 | result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount; |
323 | 0 | } |
324 | 0 |
|
325 | 0 | if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { |
326 | 0 | gfxFont::Spacing spacing; |
327 | 0 | if (aPartRange.start == result.mRange.start) { |
328 | 0 | aProvider->GetSpacing( |
329 | 0 | Range(aPartRange.start, aPartRange.start + 1), &spacing); |
330 | 0 | result.mPartWidth += spacing.mBefore; |
331 | 0 | } |
332 | 0 | if (aPartRange.end == result.mRange.end) { |
333 | 0 | aProvider->GetSpacing( |
334 | 0 | Range(aPartRange.end - 1, aPartRange.end), &spacing); |
335 | 0 | result.mPartWidth += spacing.mAfter; |
336 | 0 | } |
337 | 0 | } |
338 | 0 |
|
339 | 0 | return result; |
340 | 0 | } |
341 | | |
342 | | gfxFloat |
343 | | gfxTextRun::ComputePartialLigatureWidth(Range aPartRange, |
344 | | PropertyProvider *aProvider) const |
345 | 0 | { |
346 | 0 | if (aPartRange.start >= aPartRange.end) |
347 | 0 | return 0; |
348 | 0 | LigatureData data = ComputeLigatureData(aPartRange, aProvider); |
349 | 0 | return data.mPartWidth; |
350 | 0 | } |
351 | | |
352 | | int32_t |
353 | | gfxTextRun::GetAdvanceForGlyphs(Range aRange) const |
354 | 0 | { |
355 | 0 | int32_t advance = 0; |
356 | 0 | for (auto i = aRange.start; i < aRange.end; ++i) { |
357 | 0 | advance += GetAdvanceForGlyph(i); |
358 | 0 | } |
359 | 0 | return advance; |
360 | 0 | } |
361 | | |
362 | | static void |
363 | | GetAdjustedSpacing(const gfxTextRun *aTextRun, gfxTextRun::Range aRange, |
364 | | gfxTextRun::PropertyProvider *aProvider, |
365 | | gfxTextRun::PropertyProvider::Spacing *aSpacing) |
366 | 0 | { |
367 | 0 | if (aRange.start >= aRange.end) |
368 | 0 | return; |
369 | 0 | |
370 | 0 | aProvider->GetSpacing(aRange, aSpacing); |
371 | 0 |
|
372 | | #ifdef DEBUG |
373 | | // Check to see if we have spacing inside ligatures |
374 | | |
375 | | const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); |
376 | | uint32_t i; |
377 | | |
378 | | for (i = aRange.start; i < aRange.end; ++i) { |
379 | | if (!charGlyphs[i].IsLigatureGroupStart()) { |
380 | | NS_ASSERTION(i == aRange.start || |
381 | | aSpacing[i - aRange.start].mBefore == 0, |
382 | | "Before-spacing inside a ligature!"); |
383 | | NS_ASSERTION(i - 1 <= aRange.start || |
384 | | aSpacing[i - 1 - aRange.start].mAfter == 0, |
385 | | "After-spacing inside a ligature!"); |
386 | | } |
387 | | } |
388 | | #endif |
389 | | } |
390 | | |
391 | | bool |
392 | | gfxTextRun::GetAdjustedSpacingArray(Range aRange, PropertyProvider *aProvider, |
393 | | Range aSpacingRange, |
394 | | nsTArray<PropertyProvider::Spacing>* |
395 | | aSpacing) const |
396 | 0 | { |
397 | 0 | if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) |
398 | 0 | return false; |
399 | 0 | if (!aSpacing->AppendElements(aRange.Length())) |
400 | 0 | return false; |
401 | 0 | auto spacingOffset = aSpacingRange.start - aRange.start; |
402 | 0 | memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset); |
403 | 0 | GetAdjustedSpacing(this, aSpacingRange, aProvider, |
404 | 0 | aSpacing->Elements() + spacingOffset); |
405 | 0 | memset(aSpacing->Elements() + aSpacingRange.end - aRange.start, 0, |
406 | 0 | sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end)); |
407 | 0 | return true; |
408 | 0 | } |
409 | | |
410 | | void |
411 | | gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const |
412 | 0 | { |
413 | 0 | if (aRange->start >= aRange->end) |
414 | 0 | return; |
415 | 0 | |
416 | 0 | const CompressedGlyph *charGlyphs = mCharacterGlyphs; |
417 | 0 |
|
418 | 0 | while (aRange->start < aRange->end && |
419 | 0 | !charGlyphs[aRange->start].IsLigatureGroupStart()) { |
420 | 0 | ++aRange->start; |
421 | 0 | } |
422 | 0 | if (aRange->end < GetLength()) { |
423 | 0 | while (aRange->end > aRange->start && |
424 | 0 | !charGlyphs[aRange->end].IsLigatureGroupStart()) { |
425 | 0 | --aRange->end; |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | void |
431 | | gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt, |
432 | | PropertyProvider* aProvider, Range aSpacingRange, |
433 | | TextRunDrawParams& aParams, |
434 | | gfx::ShapedTextFlags aOrientation) const |
435 | 0 | { |
436 | 0 | AutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
437 | 0 | bool haveSpacing = GetAdjustedSpacingArray(aRange, aProvider, |
438 | 0 | aSpacingRange, &spacingBuffer); |
439 | 0 | aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; |
440 | 0 | aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation); |
441 | 0 | } |
442 | | |
443 | | static void |
444 | | ClipPartialLigature(const gfxTextRun* aTextRun, |
445 | | gfxFloat *aStart, gfxFloat *aEnd, |
446 | | gfxFloat aOrigin, |
447 | | gfxTextRun::LigatureData *aLigature) |
448 | 0 | { |
449 | 0 | if (aLigature->mClipBeforePart) { |
450 | 0 | if (aTextRun->IsRightToLeft()) { |
451 | 0 | *aEnd = std::min(*aEnd, aOrigin); |
452 | 0 | } else { |
453 | 0 | *aStart = std::max(*aStart, aOrigin); |
454 | 0 | } |
455 | 0 | } |
456 | 0 | if (aLigature->mClipAfterPart) { |
457 | 0 | gfxFloat endEdge = |
458 | 0 | aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth; |
459 | 0 | if (aTextRun->IsRightToLeft()) { |
460 | 0 | *aStart = std::max(*aStart, endEdge); |
461 | 0 | } else { |
462 | 0 | *aEnd = std::min(*aEnd, endEdge); |
463 | 0 | } |
464 | 0 | } |
465 | 0 | } |
466 | | |
467 | | void |
468 | | gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange, |
469 | | gfx::Point* aPt, PropertyProvider* aProvider, |
470 | | TextRunDrawParams& aParams, |
471 | | gfx::ShapedTextFlags aOrientation) const |
472 | 0 | { |
473 | 0 | if (aRange.start >= aRange.end) { |
474 | 0 | return; |
475 | 0 | } |
476 | 0 | |
477 | 0 | // Draw partial ligature. We hack this by clipping the ligature. |
478 | 0 | LigatureData data = ComputeLigatureData(aRange, aProvider); |
479 | 0 | gfxRect clipExtents = aParams.context->GetClipExtents(); |
480 | 0 | gfxFloat start, end; |
481 | 0 | if (aParams.isVerticalRun) { |
482 | 0 | start = clipExtents.Y() * mAppUnitsPerDevUnit; |
483 | 0 | end = clipExtents.YMost() * mAppUnitsPerDevUnit; |
484 | 0 | ClipPartialLigature(this, &start, &end, aPt->y, &data); |
485 | 0 | } else { |
486 | 0 | start = clipExtents.X() * mAppUnitsPerDevUnit; |
487 | 0 | end = clipExtents.XMost() * mAppUnitsPerDevUnit; |
488 | 0 | ClipPartialLigature(this, &start, &end, aPt->x, &data); |
489 | 0 | } |
490 | 0 |
|
491 | 0 | { |
492 | 0 | // use division here to ensure that when the rect is aligned on multiples |
493 | 0 | // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. |
494 | 0 | // Also, make sure we snap the rectangle to device pixels. |
495 | 0 | Rect clipRect = aParams.isVerticalRun ? |
496 | 0 | Rect(clipExtents.X(), start / mAppUnitsPerDevUnit, |
497 | 0 | clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit) : |
498 | 0 | Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(), |
499 | 0 | (end - start) / mAppUnitsPerDevUnit, clipExtents.Height()); |
500 | 0 | MaybeSnapToDevicePixels(clipRect, *aParams.dt, true); |
501 | 0 |
|
502 | 0 | aParams.context->Clip(clipRect); |
503 | 0 | } |
504 | 0 |
|
505 | 0 | gfx::Point pt; |
506 | 0 | if (aParams.isVerticalRun) { |
507 | 0 | pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance); |
508 | 0 | } else { |
509 | 0 | pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); |
510 | 0 | } |
511 | 0 |
|
512 | 0 | DrawGlyphs(aFont, data.mRange, &pt, |
513 | 0 | aProvider, aRange, aParams, aOrientation); |
514 | 0 | aParams.context->PopClip(); |
515 | 0 |
|
516 | 0 | if (aParams.isVerticalRun) { |
517 | 0 | aPt->y += aParams.direction * data.mPartWidth; |
518 | 0 | } else { |
519 | 0 | aPt->x += aParams.direction * data.mPartWidth; |
520 | 0 | } |
521 | 0 | } |
522 | | |
523 | | // Returns true if a glyph run is using a font with synthetic bolding enabled, |
524 | | // or a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to |
525 | | // check whether the text run needs to be explicitly composited in order to |
526 | | // support opacity. |
527 | | static bool |
528 | | HasSyntheticBoldOrColor(const gfxTextRun *aRun, gfxTextRun::Range aRange) |
529 | 0 | { |
530 | 0 | gfxTextRun::GlyphRunIterator iter(aRun, aRange); |
531 | 0 | while (iter.NextRun()) { |
532 | 0 | gfxFont *font = iter.GetGlyphRun()->mFont; |
533 | 0 | if (font) { |
534 | 0 | if (font->IsSyntheticBold()) { |
535 | 0 | return true; |
536 | 0 | } |
537 | 0 | gfxFontEntry* fe = font->GetFontEntry(); |
538 | 0 | if (fe->TryGetSVGData(font) || fe->TryGetColorGlyphs()) { |
539 | 0 | return true; |
540 | 0 | } |
541 | | #if defined(XP_MACOSX) // sbix fonts only supported via Core Text |
542 | | if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { |
543 | | return true; |
544 | | } |
545 | | #endif |
546 | | } |
547 | 0 | } |
548 | 0 | return false; |
549 | 0 | } |
550 | | |
551 | | // helper class for double-buffering drawing with non-opaque color |
552 | | struct MOZ_STACK_CLASS BufferAlphaColor { |
553 | | explicit BufferAlphaColor(gfxContext *aContext) |
554 | | : mContext(aContext) |
555 | 0 | { |
556 | 0 |
|
557 | 0 | } |
558 | | |
559 | 0 | ~BufferAlphaColor() {} |
560 | | |
561 | | void PushSolidColor(const gfxRect& aBounds, const Color& aAlphaColor, uint32_t appsPerDevUnit) |
562 | 0 | { |
563 | 0 | mContext->Save(); |
564 | 0 | mContext->NewPath(); |
565 | 0 | mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, |
566 | 0 | aBounds.Y() / appsPerDevUnit, |
567 | 0 | aBounds.Width() / appsPerDevUnit, |
568 | 0 | aBounds.Height() / appsPerDevUnit), true); |
569 | 0 | mContext->Clip(); |
570 | 0 | mContext->SetColor(Color(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); |
571 | 0 | mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a); |
572 | 0 | } |
573 | | |
574 | | void PopAlpha() |
575 | 0 | { |
576 | 0 | // pop the text, using the color alpha as the opacity |
577 | 0 | mContext->PopGroupAndBlend(); |
578 | 0 | mContext->Restore(); |
579 | 0 | } |
580 | | |
581 | | gfxContext *mContext; |
582 | | }; |
583 | | |
584 | | void |
585 | | gfxTextRun::Draw(Range aRange, gfx::Point aPt, const DrawParams& aParams) const |
586 | 0 | { |
587 | 0 | NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); |
588 | 0 | NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || |
589 | 0 | !(aParams.drawMode & DrawMode::GLYPH_PATH), |
590 | 0 | "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); |
591 | 0 | NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks, |
592 | 0 | "callback must not be specified unless using GLYPH_PATH"); |
593 | 0 |
|
594 | 0 | bool skipDrawing = mSkipDrawing; |
595 | 0 | if (aParams.drawMode & DrawMode::GLYPH_FILL) { |
596 | 0 | Color currentColor; |
597 | 0 | if (aParams.context->GetDeviceColor(currentColor) && |
598 | 0 | currentColor.a == 0 && !aParams.context->GetTextDrawer()) { |
599 | 0 | skipDrawing = true; |
600 | 0 | } |
601 | 0 | } |
602 | 0 |
|
603 | 0 | gfxFloat direction = GetDirection(); |
604 | 0 |
|
605 | 0 | if (skipDrawing) { |
606 | 0 | // We don't need to draw anything; |
607 | 0 | // but if the caller wants advance width, we need to compute it here |
608 | 0 | if (aParams.advanceWidth) { |
609 | 0 | gfxTextRun::Metrics metrics = MeasureText( |
610 | 0 | aRange, gfxFont::LOOSE_INK_EXTENTS, |
611 | 0 | aParams.context->GetDrawTarget(), aParams.provider); |
612 | 0 | *aParams.advanceWidth = metrics.mAdvanceWidth * direction; |
613 | 0 | } |
614 | 0 |
|
615 | 0 | // return without drawing |
616 | 0 | return; |
617 | 0 | } |
618 | 0 |
|
619 | 0 | // synthetic bolding draws glyphs twice ==> colors with opacity won't draw |
620 | 0 | // correctly unless first drawn without alpha |
621 | 0 | BufferAlphaColor syntheticBoldBuffer(aParams.context); |
622 | 0 | Color currentColor; |
623 | 0 | bool needToRestore = false; |
624 | 0 |
|
625 | 0 | if (aParams.drawMode & DrawMode::GLYPH_FILL && |
626 | 0 | aParams.context->HasNonOpaqueNonTransparentColor(currentColor) && |
627 | 0 | HasSyntheticBoldOrColor(this, aRange) && |
628 | 0 | !aParams.context->GetTextDrawer()) { |
629 | 0 |
|
630 | 0 | needToRestore = true; |
631 | 0 | // Measure text; use the bounding box to determine the area we need |
632 | 0 | // to buffer. |
633 | 0 | gfxTextRun::Metrics metrics = MeasureText( |
634 | 0 | aRange, gfxFont::LOOSE_INK_EXTENTS, |
635 | 0 | aParams.context->GetDrawTarget(), aParams.provider); |
636 | 0 | if (IsRightToLeft()) { |
637 | 0 | metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x - metrics.mAdvanceWidth, |
638 | 0 | aPt.y)); |
639 | 0 | } else { |
640 | 0 | metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y)); |
641 | 0 | } |
642 | 0 | syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, |
643 | 0 | GetAppUnitsPerDevUnit()); |
644 | 0 | } |
645 | 0 |
|
646 | 0 | // Set up parameters that will be constant across all glyph runs we need |
647 | 0 | // to draw, regardless of the font used. |
648 | 0 | TextRunDrawParams params; |
649 | 0 | params.context = aParams.context; |
650 | 0 | params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); |
651 | 0 | params.isVerticalRun = IsVertical(); |
652 | 0 | params.isRTL = IsRightToLeft(); |
653 | 0 | params.direction = direction; |
654 | 0 | params.strokeOpts = aParams.strokeOpts; |
655 | 0 | params.textStrokeColor = aParams.textStrokeColor; |
656 | 0 | params.textStrokePattern = aParams.textStrokePattern; |
657 | 0 | params.drawOpts = aParams.drawOpts; |
658 | 0 | params.drawMode = aParams.drawMode; |
659 | 0 | params.callbacks = aParams.callbacks; |
660 | 0 | params.runContextPaint = aParams.contextPaint; |
661 | 0 | params.paintSVGGlyphs = !aParams.callbacks || |
662 | 0 | aParams.callbacks->mShouldPaintSVGGlyphs; |
663 | 0 | params.dt = aParams.context->GetDrawTarget(); |
664 | 0 |
|
665 | 0 | GlyphRunIterator iter(this, aRange); |
666 | 0 | gfxFloat advance = 0.0; |
667 | 0 |
|
668 | 0 | while (iter.NextRun()) { |
669 | 0 | gfxFont *font = iter.GetGlyphRun()->mFont; |
670 | 0 | uint32_t start = iter.GetStringStart(); |
671 | 0 | uint32_t end = iter.GetStringEnd(); |
672 | 0 | Range ligatureRange(start, end); |
673 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
674 | 0 |
|
675 | 0 | bool drawPartial = (aParams.drawMode & DrawMode::GLYPH_FILL) || |
676 | 0 | (aParams.drawMode == DrawMode::GLYPH_PATH && |
677 | 0 | aParams.callbacks); |
678 | 0 | gfx::Point origPt = aPt; |
679 | 0 |
|
680 | 0 | if (drawPartial) { |
681 | 0 | DrawPartialLigature(font, Range(start, ligatureRange.start), |
682 | 0 | &aPt, aParams.provider, params, |
683 | 0 | iter.GetGlyphRun()->mOrientation); |
684 | 0 | } |
685 | 0 |
|
686 | 0 | DrawGlyphs(font, ligatureRange, &aPt, |
687 | 0 | aParams.provider, ligatureRange, params, |
688 | 0 | iter.GetGlyphRun()->mOrientation); |
689 | 0 |
|
690 | 0 | if (drawPartial) { |
691 | 0 | DrawPartialLigature(font, Range(ligatureRange.end, end), |
692 | 0 | &aPt, aParams.provider, params, |
693 | 0 | iter.GetGlyphRun()->mOrientation); |
694 | 0 | } |
695 | 0 |
|
696 | 0 | if (params.isVerticalRun) { |
697 | 0 | advance += (aPt.y - origPt.y) * params.direction; |
698 | 0 | } else { |
699 | 0 | advance += (aPt.x - origPt.x) * params.direction; |
700 | 0 | } |
701 | 0 | } |
702 | 0 |
|
703 | 0 | // composite result when synthetic bolding used |
704 | 0 | if (needToRestore) { |
705 | 0 | syntheticBoldBuffer.PopAlpha(); |
706 | 0 | } |
707 | 0 |
|
708 | 0 | if (aParams.advanceWidth) { |
709 | 0 | *aParams.advanceWidth = advance; |
710 | 0 | } |
711 | 0 | } |
712 | | |
713 | | // This method is mostly parallel to Draw(). |
714 | | void |
715 | | gfxTextRun::DrawEmphasisMarks(gfxContext *aContext, |
716 | | gfxTextRun* aMark, |
717 | | gfxFloat aMarkAdvance, gfx::Point aPt, |
718 | | Range aRange, PropertyProvider* aProvider) const |
719 | 0 | { |
720 | 0 | MOZ_ASSERT(aRange.end <= GetLength()); |
721 | 0 |
|
722 | 0 | EmphasisMarkDrawParams params; |
723 | 0 | params.context = aContext; |
724 | 0 | params.mark = aMark; |
725 | 0 | params.advance = aMarkAdvance; |
726 | 0 | params.direction = GetDirection(); |
727 | 0 | params.isVertical = IsVertical(); |
728 | 0 |
|
729 | 0 | float& inlineCoord = params.isVertical ? aPt.y : aPt.x; |
730 | 0 | float direction = params.direction; |
731 | 0 |
|
732 | 0 | GlyphRunIterator iter(this, aRange); |
733 | 0 | while (iter.NextRun()) { |
734 | 0 | gfxFont* font = iter.GetGlyphRun()->mFont; |
735 | 0 | uint32_t start = iter.GetStringStart(); |
736 | 0 | uint32_t end = iter.GetStringEnd(); |
737 | 0 | Range ligatureRange(start, end); |
738 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
739 | 0 |
|
740 | 0 | inlineCoord += direction * ComputePartialLigatureWidth( |
741 | 0 | Range(start, ligatureRange.start), aProvider); |
742 | 0 |
|
743 | 0 | AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; |
744 | 0 | bool haveSpacing = GetAdjustedSpacingArray( |
745 | 0 | ligatureRange, aProvider, ligatureRange, &spacingBuffer); |
746 | 0 | params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; |
747 | 0 | font->DrawEmphasisMarks(this, &aPt, ligatureRange.start, |
748 | 0 | ligatureRange.Length(), params); |
749 | 0 |
|
750 | 0 | inlineCoord += direction * ComputePartialLigatureWidth( |
751 | 0 | Range(ligatureRange.end, end), aProvider); |
752 | 0 | } |
753 | 0 | } |
754 | | |
755 | | void |
756 | | gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, Range aRange, |
757 | | gfxFont::BoundingBoxType aBoundingBoxType, |
758 | | DrawTarget* aRefDrawTarget, |
759 | | PropertyProvider *aProvider, |
760 | | Range aSpacingRange, |
761 | | gfx::ShapedTextFlags aOrientation, |
762 | | Metrics *aMetrics) const |
763 | 0 | { |
764 | 0 | AutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
765 | 0 | bool haveSpacing = GetAdjustedSpacingArray(aRange, aProvider, |
766 | 0 | aSpacingRange, &spacingBuffer); |
767 | 0 | Metrics metrics = aFont->Measure(this, aRange.start, aRange.end, |
768 | 0 | aBoundingBoxType, aRefDrawTarget, |
769 | 0 | haveSpacing ? spacingBuffer.Elements() : nullptr, |
770 | 0 | aOrientation); |
771 | 0 | aMetrics->CombineWith(metrics, IsRightToLeft()); |
772 | 0 | } |
773 | | |
774 | | void |
775 | | gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, Range aRange, |
776 | | gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, |
777 | | PropertyProvider *aProvider, gfx::ShapedTextFlags aOrientation, |
778 | | Metrics *aMetrics) const |
779 | 0 | { |
780 | 0 | if (aRange.start >= aRange.end) |
781 | 0 | return; |
782 | 0 | |
783 | 0 | // Measure partial ligature. We hack this by clipping the metrics in the |
784 | 0 | // same way we clip the drawing. |
785 | 0 | LigatureData data = ComputeLigatureData(aRange, aProvider); |
786 | 0 |
|
787 | 0 | // First measure the complete ligature |
788 | 0 | Metrics metrics; |
789 | 0 | AccumulateMetricsForRun(aFont, data.mRange, |
790 | 0 | aBoundingBoxType, aRefDrawTarget, |
791 | 0 | aProvider, aRange, aOrientation, &metrics); |
792 | 0 |
|
793 | 0 | // Clip the bounding box to the ligature part |
794 | 0 | gfxFloat bboxLeft = metrics.mBoundingBox.X(); |
795 | 0 | gfxFloat bboxRight = metrics.mBoundingBox.XMost(); |
796 | 0 | // Where we are going to start "drawing" relative to our left baseline origin |
797 | 0 | gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; |
798 | 0 | ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); |
799 | 0 | metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight); |
800 | 0 |
|
801 | 0 | // mBoundingBox is now relative to the left baseline origin for the entire |
802 | 0 | // ligature. Shift it left. |
803 | 0 | metrics.mBoundingBox.MoveByX(-(IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) : data.mPartAdvance)); |
804 | 0 | metrics.mAdvanceWidth = data.mPartWidth; |
805 | 0 |
|
806 | 0 | aMetrics->CombineWith(metrics, IsRightToLeft()); |
807 | 0 | } |
808 | | |
809 | | gfxTextRun::Metrics |
810 | | gfxTextRun::MeasureText(Range aRange, |
811 | | gfxFont::BoundingBoxType aBoundingBoxType, |
812 | | DrawTarget* aRefDrawTarget, |
813 | | PropertyProvider *aProvider) const |
814 | 0 | { |
815 | 0 | NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); |
816 | 0 |
|
817 | 0 | Metrics accumulatedMetrics; |
818 | 0 | GlyphRunIterator iter(this, aRange); |
819 | 0 | while (iter.NextRun()) { |
820 | 0 | gfxFont *font = iter.GetGlyphRun()->mFont; |
821 | 0 | uint32_t start = iter.GetStringStart(); |
822 | 0 | uint32_t end = iter.GetStringEnd(); |
823 | 0 | Range ligatureRange(start, end); |
824 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
825 | 0 |
|
826 | 0 | AccumulatePartialLigatureMetrics( |
827 | 0 | font, Range(start, ligatureRange.start), |
828 | 0 | aBoundingBoxType, aRefDrawTarget, aProvider, |
829 | 0 | iter.GetGlyphRun()->mOrientation, &accumulatedMetrics); |
830 | 0 |
|
831 | 0 | // XXX This sucks. We have to get glyph extents just so we can detect |
832 | 0 | // glyphs outside the font box, even when aBoundingBoxType is LOOSE, |
833 | 0 | // even though in almost all cases we could get correct results just |
834 | 0 | // by getting some ascent/descent from the font and using our stored |
835 | 0 | // advance widths. |
836 | 0 | AccumulateMetricsForRun(font, |
837 | 0 | ligatureRange, aBoundingBoxType, |
838 | 0 | aRefDrawTarget, aProvider, ligatureRange, |
839 | 0 | iter.GetGlyphRun()->mOrientation, &accumulatedMetrics); |
840 | 0 |
|
841 | 0 | AccumulatePartialLigatureMetrics( |
842 | 0 | font, Range(ligatureRange.end, end), |
843 | 0 | aBoundingBoxType, aRefDrawTarget, aProvider, |
844 | 0 | iter.GetGlyphRun()->mOrientation, &accumulatedMetrics); |
845 | 0 | } |
846 | 0 |
|
847 | 0 | return accumulatedMetrics; |
848 | 0 | } |
849 | | |
850 | 0 | #define MEASUREMENT_BUFFER_SIZE 100 |
851 | | |
852 | | void |
853 | | gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange, |
854 | | nsTArray<HyphenType>& aHyphenBuffer, |
855 | | HyphenationState* aWordState) |
856 | 0 | { |
857 | 0 | MOZ_ASSERT(aRange.end - aStart <= aHyphenBuffer.Length() && |
858 | 0 | aRange.start >= aStart, "Range out of bounds"); |
859 | 0 | MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart, |
860 | 0 | "Unexpected aMostRecentWordBoundary!!"); |
861 | 0 |
|
862 | 0 | uint32_t start = std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary); |
863 | 0 |
|
864 | 0 | for (uint32_t i = start; i < aRange.end; ++i) { |
865 | 0 | if (aHyphenBuffer[i - aStart] == HyphenType::Explicit && |
866 | 0 | !aWordState->hasExplicitHyphen) { |
867 | 0 | aWordState->hasExplicitHyphen = true; |
868 | 0 | } |
869 | 0 | if (!aWordState->hasManualHyphen && |
870 | 0 | (aHyphenBuffer[i - aStart] == HyphenType::Soft || |
871 | 0 | aHyphenBuffer[i - aStart] == HyphenType::Explicit)) { |
872 | 0 | aWordState->hasManualHyphen = true; |
873 | 0 | // This is the first manual hyphen in the current word. We can only |
874 | 0 | // know if the current word has a manual hyphen until now. So, we need |
875 | 0 | // to run a sub loop to update the auto hyphens between the start of |
876 | 0 | // the current word and this manual hyphen. |
877 | 0 | if (aWordState->hasAutoHyphen) { |
878 | 0 | for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) { |
879 | 0 | if (aHyphenBuffer[j - aStart] == HyphenType::AutoWithoutManualInSameWord) { |
880 | 0 | aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord; |
881 | 0 | } |
882 | 0 | } |
883 | 0 | } |
884 | 0 | } |
885 | 0 | if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) { |
886 | 0 | if (!aWordState->hasAutoHyphen) { |
887 | 0 | aWordState->hasAutoHyphen = true; |
888 | 0 | } |
889 | 0 | if (aWordState->hasManualHyphen) { |
890 | 0 | aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord; |
891 | 0 | } |
892 | 0 | } |
893 | 0 |
|
894 | 0 | // If we're at the word boundary, clear/reset couple states. |
895 | 0 | if (mCharacterGlyphs[i].CharIsSpace() || |
896 | 0 | mCharacterGlyphs[i].CharIsTab() || |
897 | 0 | mCharacterGlyphs[i].CharIsNewline() || |
898 | 0 | // Since we will not have a boundary in the end of the string, let's |
899 | 0 | // call the end of the string a special case for word boundary. |
900 | 0 | i == GetLength() - 1) { |
901 | 0 | // We can only get to know whether we should raise/clear an explicit |
902 | 0 | // manual hyphen until we get to the end of a word, because this depends |
903 | 0 | // on whether there exists at least one auto hyphen in the same word. |
904 | 0 | if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) { |
905 | 0 | for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) { |
906 | 0 | if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) { |
907 | 0 | aHyphenBuffer[j - aStart] = HyphenType::None; |
908 | 0 | } |
909 | 0 | } |
910 | 0 | } |
911 | 0 | aWordState->mostRecentBoundary = i; |
912 | 0 | aWordState->hasManualHyphen = false; |
913 | 0 | aWordState->hasAutoHyphen = false; |
914 | 0 | aWordState->hasExplicitHyphen = false; |
915 | 0 | } |
916 | 0 | } |
917 | 0 | } |
918 | | |
919 | | uint32_t |
920 | | gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, |
921 | | bool aLineBreakBefore, gfxFloat aWidth, |
922 | | PropertyProvider *aProvider, |
923 | | SuppressBreak aSuppressBreak, |
924 | | gfxFloat *aTrimWhitespace, |
925 | | bool aWhitespaceCanHang, |
926 | | Metrics *aMetrics, |
927 | | gfxFont::BoundingBoxType aBoundingBoxType, |
928 | | DrawTarget* aRefDrawTarget, |
929 | | bool *aUsedHyphenation, |
930 | | uint32_t *aLastBreak, |
931 | | bool aCanWordWrap, |
932 | | gfxBreakPriority *aBreakPriority) |
933 | 0 | { |
934 | 0 | aMaxLength = std::min(aMaxLength, GetLength() - aStart); |
935 | 0 |
|
936 | 0 | NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); |
937 | 0 |
|
938 | 0 | Range bufferRange(aStart, aStart + |
939 | 0 | std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE)); |
940 | 0 | PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; |
941 | 0 | bool haveSpacing = aProvider && |
942 | 0 | !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING); |
943 | 0 | if (haveSpacing) { |
944 | 0 | GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); |
945 | 0 | } |
946 | 0 | AutoTArray<HyphenType, 4096> hyphenBuffer; |
947 | 0 | HyphenationState wordState; |
948 | 0 | wordState.mostRecentBoundary = aStart; |
949 | 0 | bool haveHyphenation = aProvider && |
950 | 0 | (aProvider->GetHyphensOption() == StyleHyphens::Auto || |
951 | 0 | (aProvider->GetHyphensOption() == StyleHyphens::Manual && |
952 | 0 | !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); |
953 | 0 | if (haveHyphenation) { |
954 | 0 | if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { |
955 | 0 | aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements()); |
956 | 0 | if (aProvider->GetHyphensOption() == StyleHyphens::Auto) { |
957 | 0 | ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, |
958 | 0 | &wordState); |
959 | 0 | } |
960 | 0 | } else { |
961 | 0 | haveHyphenation = false; |
962 | 0 | } |
963 | 0 | } |
964 | 0 |
|
965 | 0 | gfxFloat width = 0; |
966 | 0 | gfxFloat advance = 0; |
967 | 0 | // The number of space characters that can be trimmed or hang at a soft-wrap |
968 | 0 | uint32_t trimmableChars = 0; |
969 | 0 | // The amount of space removed by ignoring trimmableChars |
970 | 0 | gfxFloat trimmableAdvance = 0; |
971 | 0 | int32_t lastBreak = -1; |
972 | 0 | int32_t lastBreakTrimmableChars = -1; |
973 | 0 | gfxFloat lastBreakTrimmableAdvance = -1; |
974 | 0 | // Cache the last candidate break |
975 | 0 | int32_t lastCandidateBreak = -1; |
976 | 0 | int32_t lastCandidateBreakTrimmableChars = -1; |
977 | 0 | gfxFloat lastCandidateBreakTrimmableAdvance = -1; |
978 | 0 | bool lastCandidateBreakUsedHyphenation = false; |
979 | 0 | gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak; |
980 | 0 | bool aborted = false; |
981 | 0 | uint32_t end = aStart + aMaxLength; |
982 | 0 | bool lastBreakUsedHyphenation = false; |
983 | 0 | Range ligatureRange(aStart, end); |
984 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
985 | 0 |
|
986 | 0 | // We may need to move `i` backwards in the following loop, and re-scan |
987 | 0 | // part of the textrun; we'll use `rescanLimit` so we can tell when that |
988 | 0 | // is happening: if `i < rescanLimit` then we're rescanning. |
989 | 0 | uint32_t rescanLimit = aStart; |
990 | 0 | for (uint32_t i = aStart; i < end; ++i) { |
991 | 0 | if (i >= bufferRange.end) { |
992 | 0 | // Fetch more spacing and hyphenation data |
993 | 0 | uint32_t oldHyphenBufferLength = hyphenBuffer.Length(); |
994 | 0 | bufferRange.start = i; |
995 | 0 | bufferRange.end = std::min(aStart + aMaxLength, |
996 | 0 | i + MEASUREMENT_BUFFER_SIZE); |
997 | 0 | // For spacing, we always overwrite the old data with the newly |
998 | 0 | // fetched one. However, for hyphenation, hyphenation data sometimes |
999 | 0 | // depends on the context in every word (if "hyphens: auto" is set). |
1000 | 0 | // To ensure we get enough information between neighboring buffers, |
1001 | 0 | // we grow the hyphenBuffer instead of overwrite it. |
1002 | 0 | // NOTE that this means bufferRange does not correspond to the |
1003 | 0 | // entire hyphenBuffer, but only to the most recently added portion. |
1004 | 0 | // Therefore, we need to add the old length to hyphenBuffer.Elements() |
1005 | 0 | // when getting more data. |
1006 | 0 | if (haveSpacing) { |
1007 | 0 | GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); |
1008 | 0 | } |
1009 | 0 | if (haveHyphenation) { |
1010 | 0 | if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { |
1011 | 0 | aProvider->GetHyphenationBreaks( |
1012 | 0 | bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength); |
1013 | 0 | if (aProvider->GetHyphensOption() == StyleHyphens::Auto) { |
1014 | 0 | uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary; |
1015 | 0 | ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, |
1016 | 0 | &wordState); |
1017 | 0 | // If the buffer boundary is in the middle of a word, |
1018 | 0 | // we need to go back to the start of the current word. |
1019 | 0 | // So, we can correct the wrong candidates that we set |
1020 | 0 | // in the previous runs of the loop. |
1021 | 0 | if (prevMostRecentWordBoundary < oldHyphenBufferLength) { |
1022 | 0 | rescanLimit = i; |
1023 | 0 | i = prevMostRecentWordBoundary - 1; |
1024 | 0 | continue; |
1025 | 0 | } |
1026 | 0 | } |
1027 | 0 | } else { |
1028 | 0 | haveHyphenation = false; |
1029 | 0 | } |
1030 | 0 | } |
1031 | 0 | } |
1032 | 0 |
|
1033 | 0 | // There can't be a word-wrap break opportunity at the beginning of the |
1034 | 0 | // line: if the width is too small for even one character to fit, it |
1035 | 0 | // could be the first and last break opportunity on the line, and that |
1036 | 0 | // would trigger an infinite loop. |
1037 | 0 | if (aSuppressBreak != eSuppressAllBreaks && |
1038 | 0 | (aSuppressBreak != eSuppressInitialBreak || i > aStart)) { |
1039 | 0 | bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; |
1040 | 0 | bool atHyphenationBreak = !atNaturalBreak && haveHyphenation && |
1041 | 0 | hyphenBuffer[i - aStart] != HyphenType::None; |
1042 | 0 | bool atAutoHyphenWithManualHyphenInSameWord = atHyphenationBreak && |
1043 | 0 | hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord; |
1044 | 0 | bool atBreak = atNaturalBreak || atHyphenationBreak; |
1045 | 0 | bool wordWrapping = |
1046 | 0 | aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && |
1047 | 0 | *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; |
1048 | 0 |
|
1049 | 0 | if (atBreak || wordWrapping) { |
1050 | 0 | gfxFloat hyphenatedAdvance = advance; |
1051 | 0 | if (atHyphenationBreak) { |
1052 | 0 | hyphenatedAdvance += aProvider->GetHyphenWidth(); |
1053 | 0 | } |
1054 | 0 |
|
1055 | 0 | if (lastBreak < 0 || |
1056 | 0 | width + hyphenatedAdvance - trimmableAdvance <= aWidth) { |
1057 | 0 | // We can break here. |
1058 | 0 | lastBreak = i; |
1059 | 0 | lastBreakTrimmableChars = trimmableChars; |
1060 | 0 | lastBreakTrimmableAdvance = trimmableAdvance; |
1061 | 0 | lastBreakUsedHyphenation = atHyphenationBreak; |
1062 | 0 | *aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak |
1063 | 0 | : gfxBreakPriority::eWordWrapBreak; |
1064 | 0 | } |
1065 | 0 |
|
1066 | 0 | width += advance; |
1067 | 0 | advance = 0; |
1068 | 0 | if (width - trimmableAdvance > aWidth) { |
1069 | 0 | // No more text fits. Abort |
1070 | 0 | aborted = true; |
1071 | 0 | break; |
1072 | 0 | } |
1073 | 0 | // There are various kinds of break opportunities: |
1074 | 0 | // 1. word wrap break, |
1075 | 0 | // 2. natural break, |
1076 | 0 | // 3. manual hyphenation break, |
1077 | 0 | // 4. auto hyphenation break without any manual hyphenation |
1078 | 0 | // in the same word, |
1079 | 0 | // 5. auto hyphenation break with another manual hyphenation |
1080 | 0 | // in the same word. |
1081 | 0 | // Allow all of them except the last one to be a candidate. |
1082 | 0 | // So, we can ensure that we don't use an automatic |
1083 | 0 | // hyphenation opportunity within a word that contains another |
1084 | 0 | // manual hyphenation, unless it is the only choice. |
1085 | 0 | if (wordWrapping || |
1086 | 0 | !atAutoHyphenWithManualHyphenInSameWord) { |
1087 | 0 | lastCandidateBreak = lastBreak; |
1088 | 0 | lastCandidateBreakTrimmableChars = lastBreakTrimmableChars; |
1089 | 0 | lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance; |
1090 | 0 | lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation; |
1091 | 0 | lastCandidateBreakPriority = *aBreakPriority; |
1092 | 0 | } |
1093 | 0 | } |
1094 | 0 | } |
1095 | 0 |
|
1096 | 0 | // If we're re-scanning part of a word (to re-process potential |
1097 | 0 | // hyphenation types) then we don't want to accumulate widths again |
1098 | 0 | // for the characters that were already added to `advance`. |
1099 | 0 | if (i < rescanLimit) { |
1100 | 0 | continue; |
1101 | 0 | } |
1102 | 0 | |
1103 | 0 | gfxFloat charAdvance; |
1104 | 0 | if (i >= ligatureRange.start && i < ligatureRange.end) { |
1105 | 0 | charAdvance = GetAdvanceForGlyphs(Range(i, i + 1)); |
1106 | 0 | if (haveSpacing) { |
1107 | 0 | PropertyProvider::Spacing *space = |
1108 | 0 | &spacingBuffer[i - bufferRange.start]; |
1109 | 0 | charAdvance += space->mBefore + space->mAfter; |
1110 | 0 | } |
1111 | 0 | } else { |
1112 | 0 | charAdvance = |
1113 | 0 | ComputePartialLigatureWidth(Range(i, i + 1), aProvider); |
1114 | 0 | } |
1115 | 0 |
|
1116 | 0 | advance += charAdvance; |
1117 | 0 | if (aTrimWhitespace || aWhitespaceCanHang) { |
1118 | 0 | if (mCharacterGlyphs[i].CharIsSpace()) { |
1119 | 0 | ++trimmableChars; |
1120 | 0 | trimmableAdvance += charAdvance; |
1121 | 0 | } else { |
1122 | 0 | trimmableAdvance = 0; |
1123 | 0 | trimmableChars = 0; |
1124 | 0 | } |
1125 | 0 | } |
1126 | 0 | } |
1127 | 0 |
|
1128 | 0 | if (!aborted) { |
1129 | 0 | width += advance; |
1130 | 0 | } |
1131 | 0 |
|
1132 | 0 | // There are three possibilities: |
1133 | 0 | // 1) all the text fit (width <= aWidth) |
1134 | 0 | // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0) |
1135 | 0 | // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0) |
1136 | 0 | uint32_t charsFit; |
1137 | 0 | bool usedHyphenation = false; |
1138 | 0 | if (width - trimmableAdvance <= aWidth) { |
1139 | 0 | charsFit = aMaxLength; |
1140 | 0 | } else if (lastBreak >= 0) { |
1141 | 0 | if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) { |
1142 | 0 | lastBreak = lastCandidateBreak; |
1143 | 0 | lastBreakTrimmableChars = lastCandidateBreakTrimmableChars; |
1144 | 0 | lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance; |
1145 | 0 | lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation; |
1146 | 0 | *aBreakPriority = lastCandidateBreakPriority; |
1147 | 0 | } |
1148 | 0 | charsFit = lastBreak - aStart; |
1149 | 0 | trimmableChars = lastBreakTrimmableChars; |
1150 | 0 | trimmableAdvance = lastBreakTrimmableAdvance; |
1151 | 0 | usedHyphenation = lastBreakUsedHyphenation; |
1152 | 0 | } else { |
1153 | 0 | charsFit = aMaxLength; |
1154 | 0 | } |
1155 | 0 |
|
1156 | 0 | if (aMetrics) { |
1157 | 0 | auto fitEnd = aStart + charsFit; |
1158 | 0 | // Initially, measure everything, so that our bounding box includes |
1159 | 0 | // any trimmable or hanging whitespace. |
1160 | 0 | *aMetrics = MeasureText(Range(aStart, fitEnd), |
1161 | 0 | aBoundingBoxType, aRefDrawTarget, |
1162 | 0 | aProvider); |
1163 | 0 | if (aTrimWhitespace || aWhitespaceCanHang) { |
1164 | 0 | // Measure trailing whitespace that is to be trimmed/hung. |
1165 | 0 | Metrics trimOrHangMetrics = |
1166 | 0 | MeasureText(Range(fitEnd - trimmableChars, fitEnd), |
1167 | 0 | aBoundingBoxType, aRefDrawTarget, |
1168 | 0 | aProvider); |
1169 | 0 | if (aTrimWhitespace) { |
1170 | 0 | aMetrics->mAdvanceWidth -= trimOrHangMetrics.mAdvanceWidth; |
1171 | 0 | } else if (aMetrics->mAdvanceWidth > aWidth) { |
1172 | 0 | // Restrict width of hanging whitespace so it doesn't overflow. |
1173 | 0 | aMetrics->mAdvanceWidth = |
1174 | 0 | std::max(aWidth, aMetrics->mAdvanceWidth - |
1175 | 0 | trimOrHangMetrics.mAdvanceWidth); |
1176 | 0 | } |
1177 | 0 | } |
1178 | 0 | } |
1179 | 0 | if (aTrimWhitespace) { |
1180 | 0 | *aTrimWhitespace = trimmableAdvance; |
1181 | 0 | } |
1182 | 0 | if (aUsedHyphenation) { |
1183 | 0 | *aUsedHyphenation = usedHyphenation; |
1184 | 0 | } |
1185 | 0 | if (aLastBreak && charsFit == aMaxLength) { |
1186 | 0 | if (lastBreak < 0) { |
1187 | 0 | *aLastBreak = UINT32_MAX; |
1188 | 0 | } else { |
1189 | 0 | *aLastBreak = lastBreak - aStart; |
1190 | 0 | } |
1191 | 0 | } |
1192 | 0 |
|
1193 | 0 | return charsFit; |
1194 | 0 | } |
1195 | | |
1196 | | gfxFloat |
1197 | | gfxTextRun::GetAdvanceWidth(Range aRange, PropertyProvider *aProvider, |
1198 | | PropertyProvider::Spacing* aSpacing) const |
1199 | 0 | { |
1200 | 0 | NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); |
1201 | 0 |
|
1202 | 0 | Range ligatureRange = aRange; |
1203 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
1204 | 0 |
|
1205 | 0 | gfxFloat result = |
1206 | 0 | ComputePartialLigatureWidth(Range(aRange.start, ligatureRange.start), |
1207 | 0 | aProvider) + |
1208 | 0 | ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end), |
1209 | 0 | aProvider); |
1210 | 0 |
|
1211 | 0 | if (aSpacing) { |
1212 | 0 | aSpacing->mBefore = aSpacing->mAfter = 0; |
1213 | 0 | } |
1214 | 0 |
|
1215 | 0 | // Account for all remaining spacing here. This is more efficient than |
1216 | 0 | // processing it along with the glyphs. |
1217 | 0 | if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { |
1218 | 0 | uint32_t i; |
1219 | 0 | AutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
1220 | 0 | if (spacingBuffer.AppendElements(aRange.Length())) { |
1221 | 0 | GetAdjustedSpacing(this, ligatureRange, aProvider, |
1222 | 0 | spacingBuffer.Elements()); |
1223 | 0 | for (i = 0; i < ligatureRange.Length(); ++i) { |
1224 | 0 | PropertyProvider::Spacing *space = &spacingBuffer[i]; |
1225 | 0 | result += space->mBefore + space->mAfter; |
1226 | 0 | } |
1227 | 0 | if (aSpacing) { |
1228 | 0 | aSpacing->mBefore = spacingBuffer[0].mBefore; |
1229 | 0 | aSpacing->mAfter = spacingBuffer.LastElement().mAfter; |
1230 | 0 | } |
1231 | 0 | } |
1232 | 0 | } |
1233 | 0 |
|
1234 | 0 | return result + GetAdvanceForGlyphs(ligatureRange); |
1235 | 0 | } |
1236 | | |
1237 | | gfxFloat |
1238 | | gfxTextRun::GetMinAdvanceWidth(Range aRange) |
1239 | 0 | { |
1240 | 0 | MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range"); |
1241 | 0 |
|
1242 | 0 | Range ligatureRange = aRange; |
1243 | 0 | ShrinkToLigatureBoundaries(&ligatureRange); |
1244 | 0 |
|
1245 | 0 | gfxFloat result = std::max( |
1246 | 0 | ComputePartialLigatureWidth(Range(aRange.start, ligatureRange.start), |
1247 | 0 | nullptr), |
1248 | 0 | ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end), |
1249 | 0 | nullptr)); |
1250 | 0 |
|
1251 | 0 | // XXX Do we need to take spacing into account? When each grapheme cluster |
1252 | 0 | // takes its own line, we shouldn't be adding spacings around them. |
1253 | 0 | gfxFloat clusterAdvance = 0; |
1254 | 0 | for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) { |
1255 | 0 | clusterAdvance += GetAdvanceForGlyph(i); |
1256 | 0 | if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) { |
1257 | 0 | result = std::max(result, clusterAdvance); |
1258 | 0 | clusterAdvance = 0; |
1259 | 0 | } |
1260 | 0 | } |
1261 | 0 |
|
1262 | 0 | return result; |
1263 | 0 | } |
1264 | | |
1265 | | bool |
1266 | | gfxTextRun::SetLineBreaks(Range aRange, |
1267 | | bool aLineBreakBefore, bool aLineBreakAfter, |
1268 | | gfxFloat *aAdvanceWidthDelta) |
1269 | 0 | { |
1270 | 0 | // Do nothing because our shaping does not currently take linebreaks into |
1271 | 0 | // account. There is no change in advance width. |
1272 | 0 | if (aAdvanceWidthDelta) { |
1273 | 0 | *aAdvanceWidthDelta = 0; |
1274 | 0 | } |
1275 | 0 | return false; |
1276 | 0 | } |
1277 | | |
1278 | | uint32_t |
1279 | | gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) const |
1280 | 0 | { |
1281 | 0 | NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); |
1282 | 0 | NS_ASSERTION(GetLength() == 0 || |
1283 | 0 | (!mHasGlyphRunArray && mSingleGlyphRun.mFont) || |
1284 | 0 | (mHasGlyphRunArray && mGlyphRunArray.Length() > 0), |
1285 | 0 | "non-empty text but no glyph runs present!"); |
1286 | 0 | if (!mHasGlyphRunArray) { |
1287 | 0 | return 0; |
1288 | 0 | } |
1289 | 0 | if (aOffset == GetLength()) { |
1290 | 0 | return mGlyphRunArray.Length(); |
1291 | 0 | } |
1292 | 0 | uint32_t start = 0; |
1293 | 0 | uint32_t end = mGlyphRunArray.Length(); |
1294 | 0 | while (end - start > 1) { |
1295 | 0 | uint32_t mid = (start + end)/2; |
1296 | 0 | if (mGlyphRunArray[mid].mCharacterOffset <= aOffset) { |
1297 | 0 | start = mid; |
1298 | 0 | } else { |
1299 | 0 | end = mid; |
1300 | 0 | } |
1301 | 0 | } |
1302 | 0 | NS_ASSERTION(mGlyphRunArray[start].mCharacterOffset <= aOffset, |
1303 | 0 | "Hmm, something went wrong, aOffset should have been found"); |
1304 | 0 | return start; |
1305 | 0 | } |
1306 | | |
1307 | | nsresult |
1308 | | gfxTextRun::AddGlyphRun(gfxFont *aFont, gfxTextRange::MatchType aMatchType, |
1309 | | uint32_t aUTF16Offset, bool aForceNewRun, |
1310 | | gfx::ShapedTextFlags aOrientation) |
1311 | 0 | { |
1312 | 0 | NS_ASSERTION(aFont, "adding glyph run for null font!"); |
1313 | 0 | NS_ASSERTION(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, |
1314 | 0 | "mixed orientation should have been resolved"); |
1315 | 0 | if (!aFont) { |
1316 | 0 | return NS_OK; |
1317 | 0 | } |
1318 | 0 | if (!mHasGlyphRunArray) { |
1319 | 0 | // We don't currently have an array. |
1320 | 0 | if (!mSingleGlyphRun.mFont) { |
1321 | 0 | // This is the first glyph run: just store it directly. |
1322 | 0 | mSingleGlyphRun.mFont = aFont; |
1323 | 0 | mSingleGlyphRun.mMatchType = aMatchType; |
1324 | 0 | mSingleGlyphRun.mOrientation = aOrientation; |
1325 | 0 | mSingleGlyphRun.mCharacterOffset = aUTF16Offset; |
1326 | 0 | return NS_OK; |
1327 | 0 | } |
1328 | 0 | } |
1329 | 0 | uint32_t numGlyphRuns = mHasGlyphRunArray ? mGlyphRunArray.Length() : 1; |
1330 | 0 | if (!aForceNewRun && numGlyphRuns > 0) { |
1331 | 0 | GlyphRun* lastGlyphRun = |
1332 | 0 | mHasGlyphRunArray ? &mGlyphRunArray[numGlyphRuns - 1] |
1333 | 0 | : &mSingleGlyphRun; |
1334 | 0 |
|
1335 | 0 | NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset, |
1336 | 0 | "Glyph runs out of order (and run not forced)"); |
1337 | 0 |
|
1338 | 0 | // Don't append a run if the font is already the one we want |
1339 | 0 | if (lastGlyphRun->mFont == aFont && |
1340 | 0 | lastGlyphRun->mMatchType == aMatchType && |
1341 | 0 | lastGlyphRun->mOrientation == aOrientation) |
1342 | 0 | { |
1343 | 0 | return NS_OK; |
1344 | 0 | } |
1345 | 0 | |
1346 | 0 | // If the offset has not changed, avoid leaving a zero-length run |
1347 | 0 | // by overwriting the last entry instead of appending... |
1348 | 0 | if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { |
1349 | 0 |
|
1350 | 0 | // ...except that if the run before the last entry had the same |
1351 | 0 | // font as the new one wants, merge with it instead of creating |
1352 | 0 | // adjacent runs with the same font |
1353 | 0 | if (numGlyphRuns > 1 && |
1354 | 0 | mGlyphRunArray[numGlyphRuns - 2].mFont == aFont && |
1355 | 0 | mGlyphRunArray[numGlyphRuns - 2].mMatchType == aMatchType && |
1356 | 0 | mGlyphRunArray[numGlyphRuns - 2].mOrientation == aOrientation) |
1357 | 0 | { |
1358 | 0 | mGlyphRunArray.TruncateLength(numGlyphRuns - 1); |
1359 | 0 | if (mGlyphRunArray.Length() == 1) { |
1360 | 0 | ConvertFromGlyphRunArray(); |
1361 | 0 | } |
1362 | 0 | return NS_OK; |
1363 | 0 | } |
1364 | 0 |
|
1365 | 0 | lastGlyphRun->mFont = aFont; |
1366 | 0 | lastGlyphRun->mMatchType = aMatchType; |
1367 | 0 | lastGlyphRun->mOrientation = aOrientation; |
1368 | 0 | return NS_OK; |
1369 | 0 | } |
1370 | 0 | } |
1371 | 0 |
|
1372 | 0 | NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, |
1373 | 0 | "First run doesn't cover the first character (and run not forced)?"); |
1374 | 0 |
|
1375 | 0 | if (!mHasGlyphRunArray) { |
1376 | 0 | ConvertToGlyphRunArray(); |
1377 | 0 | } |
1378 | 0 |
|
1379 | 0 | GlyphRun* glyphRun = mGlyphRunArray.AppendElement(); |
1380 | 0 | if (!glyphRun) { |
1381 | 0 | if (mGlyphRunArray.Length() == 1) { |
1382 | 0 | ConvertFromGlyphRunArray(); |
1383 | 0 | } |
1384 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1385 | 0 | } |
1386 | 0 | glyphRun->mFont = aFont; |
1387 | 0 | glyphRun->mCharacterOffset = aUTF16Offset; |
1388 | 0 | glyphRun->mMatchType = aMatchType; |
1389 | 0 | glyphRun->mOrientation = aOrientation; |
1390 | 0 |
|
1391 | 0 | return NS_OK; |
1392 | 0 | } |
1393 | | |
1394 | | void |
1395 | | gfxTextRun::SortGlyphRuns() |
1396 | 0 | { |
1397 | 0 | if (!mHasGlyphRunArray) { |
1398 | 0 | return; |
1399 | 0 | } |
1400 | 0 | |
1401 | 0 | // We should never have an empty or one-element array here; if there's only |
1402 | 0 | // one glyphrun, it should be stored directly in the textrun without using |
1403 | 0 | // an array at all. |
1404 | 0 | MOZ_ASSERT(mGlyphRunArray.Length() > 1); |
1405 | 0 |
|
1406 | 0 | AutoTArray<GlyphRun,16> runs(std::move(mGlyphRunArray)); |
1407 | 0 | GlyphRunOffsetComparator comp; |
1408 | 0 | runs.Sort(comp); |
1409 | 0 |
|
1410 | 0 | // Now copy back, coalescing adjacent glyph runs that have the same font |
1411 | 0 | mGlyphRunArray.Clear(); |
1412 | 0 | gfxFont* prevFont = nullptr; |
1413 | 0 | gfx::ShapedTextFlags prevOrient = gfx::ShapedTextFlags(); |
1414 | 0 | DebugOnly<uint32_t> prevOffset = 0; |
1415 | 0 | for (auto& run : runs) { |
1416 | 0 | // a GlyphRun with the same font and orientation as the previous can |
1417 | 0 | // just be skipped; the last GlyphRun will cover its character range. |
1418 | 0 | MOZ_ASSERT(run.mFont != nullptr); |
1419 | 0 | if (prevFont == nullptr || |
1420 | 0 | run.mFont != prevFont || run.mOrientation != prevOrient) { |
1421 | 0 | // If two fonts have the same character offset, Sort() will have |
1422 | 0 | // randomized the order. |
1423 | 0 | MOZ_ASSERT(prevFont == nullptr || |
1424 | 0 | run.mCharacterOffset != prevOffset, |
1425 | 0 | "Two fonts for the same run, glyph indices unreliable"); |
1426 | 0 | prevFont = run.mFont; |
1427 | 0 | prevOrient = run.mOrientation; |
1428 | | #ifdef DEBUG |
1429 | | prevOffset = run.mCharacterOffset; |
1430 | | #endif |
1431 | 0 | if (!mGlyphRunArray.AppendElement(std::move(run))) { |
1432 | 0 | NS_WARNING("Failed to append glyph run!"); |
1433 | 0 | } |
1434 | 0 | } |
1435 | 0 | } |
1436 | 0 |
|
1437 | 0 | MOZ_ASSERT(mGlyphRunArray.Length() > 0); |
1438 | 0 | if (mGlyphRunArray.Length() == 1) { |
1439 | 0 | ConvertFromGlyphRunArray(); |
1440 | 0 | } |
1441 | 0 | } |
1442 | | |
1443 | | // Note that SanitizeGlyphRuns scans all glyph runs in the textrun; |
1444 | | // therefore we only call it once, at the end of textrun construction, |
1445 | | // NOT incrementally as each glyph run is added (bug 680402). |
1446 | | void |
1447 | | gfxTextRun::SanitizeGlyphRuns() |
1448 | 0 | { |
1449 | 0 | if (!mHasGlyphRunArray) { |
1450 | 0 | return; |
1451 | 0 | } |
1452 | 0 | |
1453 | 0 | MOZ_ASSERT(mGlyphRunArray.Length() > 1); |
1454 | 0 |
|
1455 | 0 | // If any glyph run starts with ligature-continuation characters, we need to advance it |
1456 | 0 | // to the first "real" character to avoid drawing partial ligature glyphs from wrong font |
1457 | 0 | // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes |
1458 | 0 | // it appear as if a ligature has been formed) |
1459 | 0 | int32_t i, lastRunIndex = mGlyphRunArray.Length() - 1; |
1460 | 0 | const CompressedGlyph *charGlyphs = mCharacterGlyphs; |
1461 | 0 | for (i = lastRunIndex; i >= 0; --i) { |
1462 | 0 | GlyphRun& run = mGlyphRunArray[i]; |
1463 | 0 | while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && |
1464 | 0 | run.mCharacterOffset < GetLength()) { |
1465 | 0 | run.mCharacterOffset++; |
1466 | 0 | } |
1467 | 0 | // if the run has become empty, eliminate it |
1468 | 0 | if ((i < lastRunIndex && |
1469 | 0 | run.mCharacterOffset >= mGlyphRunArray[i+1].mCharacterOffset) || |
1470 | 0 | (i == lastRunIndex && run.mCharacterOffset == GetLength())) { |
1471 | 0 | mGlyphRunArray.RemoveElementAt(i); |
1472 | 0 | --lastRunIndex; |
1473 | 0 | } |
1474 | 0 | } |
1475 | 0 |
|
1476 | 0 | MOZ_ASSERT(mGlyphRunArray.Length() > 0); |
1477 | 0 | if (mGlyphRunArray.Length() == 1) { |
1478 | 0 | ConvertFromGlyphRunArray(); |
1479 | 0 | } |
1480 | 0 | } |
1481 | | |
1482 | | uint32_t |
1483 | | gfxTextRun::CountMissingGlyphs() const |
1484 | 0 | { |
1485 | 0 | uint32_t i; |
1486 | 0 | uint32_t count = 0; |
1487 | 0 | for (i = 0; i < GetLength(); ++i) { |
1488 | 0 | if (mCharacterGlyphs[i].IsMissing()) { |
1489 | 0 | ++count; |
1490 | 0 | } |
1491 | 0 | } |
1492 | 0 | return count; |
1493 | 0 | } |
1494 | | |
1495 | | void |
1496 | | gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset) |
1497 | 0 | { |
1498 | 0 | uint32_t wordLen = aShapedWord->GetLength(); |
1499 | 0 | NS_ASSERTION(aOffset + wordLen <= GetLength(), |
1500 | 0 | "word overruns end of textrun!"); |
1501 | 0 |
|
1502 | 0 | CompressedGlyph *charGlyphs = GetCharacterGlyphs(); |
1503 | 0 | const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs(); |
1504 | 0 | if (aShapedWord->HasDetailedGlyphs()) { |
1505 | 0 | for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) { |
1506 | 0 | const CompressedGlyph& g = wordGlyphs[i]; |
1507 | 0 | if (g.IsSimpleGlyph()) { |
1508 | 0 | charGlyphs[aOffset] = g; |
1509 | 0 | } else { |
1510 | 0 | const DetailedGlyph *details = |
1511 | 0 | g.GetGlyphCount() > 0 ? |
1512 | 0 | aShapedWord->GetDetailedGlyphs(i) : nullptr; |
1513 | 0 | SetGlyphs(aOffset, g, details); |
1514 | 0 | } |
1515 | 0 | } |
1516 | 0 | } else { |
1517 | 0 | memcpy(charGlyphs + aOffset, wordGlyphs, |
1518 | 0 | wordLen * sizeof(CompressedGlyph)); |
1519 | 0 | } |
1520 | 0 | } |
1521 | | |
1522 | | void |
1523 | | gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, Range aRange, uint32_t aDest) |
1524 | 0 | { |
1525 | 0 | NS_ASSERTION(aRange.end <= aSource->GetLength(), |
1526 | 0 | "Source substring out of range"); |
1527 | 0 | NS_ASSERTION(aDest + aRange.Length() <= GetLength(), |
1528 | 0 | "Destination substring out of range"); |
1529 | 0 |
|
1530 | 0 | if (aSource->mSkipDrawing) { |
1531 | 0 | mSkipDrawing = true; |
1532 | 0 | } |
1533 | 0 |
|
1534 | 0 | // Copy base glyph data, and DetailedGlyph data where present |
1535 | 0 | const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aRange.start; |
1536 | 0 | CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; |
1537 | 0 | for (uint32_t i = 0; i < aRange.Length(); ++i) { |
1538 | 0 | CompressedGlyph g = srcGlyphs[i]; |
1539 | 0 | g.SetCanBreakBefore(!g.IsClusterStart() ? |
1540 | 0 | CompressedGlyph::FLAG_BREAK_TYPE_NONE : |
1541 | 0 | dstGlyphs[i].CanBreakBefore()); |
1542 | 0 | if (!g.IsSimpleGlyph()) { |
1543 | 0 | uint32_t count = g.GetGlyphCount(); |
1544 | 0 | if (count > 0) { |
1545 | 0 | DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); |
1546 | 0 | if (dst) { |
1547 | 0 | DetailedGlyph *src = |
1548 | 0 | aSource->GetDetailedGlyphs(i + aRange.start); |
1549 | 0 | if (src) { |
1550 | 0 | ::memcpy(dst, src, count * sizeof(DetailedGlyph)); |
1551 | 0 | } else { |
1552 | 0 | g.SetMissing(0); |
1553 | 0 | } |
1554 | 0 | } else { |
1555 | 0 | g.SetMissing(0); |
1556 | 0 | } |
1557 | 0 | } |
1558 | 0 | } |
1559 | 0 | dstGlyphs[i] = g; |
1560 | 0 | } |
1561 | 0 |
|
1562 | 0 | // Copy glyph runs |
1563 | 0 | GlyphRunIterator iter(aSource, aRange); |
1564 | | #ifdef DEBUG |
1565 | | const GlyphRun *prevRun = nullptr; |
1566 | | #endif |
1567 | 0 | while (iter.NextRun()) { |
1568 | 0 | gfxFont *font = iter.GetGlyphRun()->mFont; |
1569 | 0 | NS_ASSERTION(!prevRun || prevRun->mFont != iter.GetGlyphRun()->mFont || |
1570 | 0 | prevRun->mMatchType != iter.GetGlyphRun()->mMatchType || |
1571 | 0 | prevRun->mOrientation != iter.GetGlyphRun()->mOrientation, |
1572 | 0 | "Glyphruns not coalesced?"); |
1573 | | #ifdef DEBUG |
1574 | | prevRun = iter.GetGlyphRun(); |
1575 | | uint32_t end = iter.GetStringEnd(); |
1576 | | #endif |
1577 | | uint32_t start = iter.GetStringStart(); |
1578 | 0 |
|
1579 | 0 | // These used to be NS_ASSERTION()s, but WARNING is more appropriate. |
1580 | 0 | // Although it's unusual (and not desirable), it's possible for us to assign |
1581 | 0 | // different fonts to a base character and a following diacritic. |
1582 | 0 | // Example on OSX 10.5/10.6 with default fonts installed: |
1583 | 0 | // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> |
1584 | 0 | // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; |
1585 | 0 | // This means the rendering of the cluster will probably not be very good, |
1586 | 0 | // but it's the best we can do for now if the specified font only covered the |
1587 | 0 | // initial base character and not its applied marks. |
1588 | 0 | NS_WARNING_ASSERTION( |
1589 | 0 | aSource->IsClusterStart(start), |
1590 | 0 | "Started font run in the middle of a cluster"); |
1591 | 0 | NS_WARNING_ASSERTION( |
1592 | 0 | end == aSource->GetLength() || aSource->IsClusterStart(end), |
1593 | 0 | "Ended font run in the middle of a cluster"); |
1594 | 0 |
|
1595 | 0 | nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, |
1596 | 0 | start - aRange.start + aDest, false, |
1597 | 0 | iter.GetGlyphRun()->mOrientation); |
1598 | 0 | if (NS_FAILED(rv)) |
1599 | 0 | return; |
1600 | 0 | } |
1601 | 0 | } |
1602 | | |
1603 | | void |
1604 | | gfxTextRun::ClearGlyphsAndCharacters() |
1605 | 0 | { |
1606 | 0 | ResetGlyphRuns(); |
1607 | 0 | memset(reinterpret_cast<char*>(mCharacterGlyphs), 0, |
1608 | 0 | mLength * sizeof(CompressedGlyph)); |
1609 | 0 | mDetailedGlyphs = nullptr; |
1610 | 0 | } |
1611 | | |
1612 | | void |
1613 | | gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, |
1614 | | uint32_t aCharIndex, |
1615 | | gfx::ShapedTextFlags aOrientation) |
1616 | 0 | { |
1617 | 0 | if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) { |
1618 | 0 | return; |
1619 | 0 | } |
1620 | 0 | |
1621 | 0 | aFont->InitWordCache(); |
1622 | 0 | static const uint8_t space = ' '; |
1623 | 0 | gfx::ShapedTextFlags |
1624 | 0 | flags = gfx::ShapedTextFlags::TEXT_IS_8BIT | |
1625 | 0 | aOrientation; |
1626 | 0 | bool vertical = |
1627 | 0 | !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT); |
1628 | 0 | gfxFontShaper::RoundingFlags roundingFlags = |
1629 | 0 | aFont->GetRoundOffsetsToPixels(aDrawTarget); |
1630 | 0 | gfxShapedWord* sw = aFont->GetShapedWord(aDrawTarget, |
1631 | 0 | &space, 1, |
1632 | 0 | gfxShapedWord::HashMix(0, ' '), |
1633 | 0 | Script::LATIN, |
1634 | 0 | vertical, |
1635 | 0 | mAppUnitsPerDevUnit, |
1636 | 0 | flags, |
1637 | 0 | roundingFlags, |
1638 | 0 | nullptr); |
1639 | 0 | if (sw) { |
1640 | 0 | AddGlyphRun(aFont, gfxTextRange::MatchType::kFontGroup, aCharIndex, |
1641 | 0 | false, aOrientation); |
1642 | 0 | CopyGlyphDataFrom(sw, aCharIndex); |
1643 | 0 | } |
1644 | 0 | } |
1645 | | |
1646 | | bool |
1647 | | gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, |
1648 | | char16_t aSpaceChar, |
1649 | | gfx::ShapedTextFlags aOrientation) |
1650 | 0 | { |
1651 | 0 | uint32_t spaceGlyph = aFont->GetSpaceGlyph(); |
1652 | 0 | if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { |
1653 | 0 | return false; |
1654 | 0 | } |
1655 | 0 | |
1656 | 0 | gfxFont::Orientation fontOrientation = |
1657 | 0 | (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) ? |
1658 | 0 | gfxFont::eVertical : gfxFont::eHorizontal; |
1659 | 0 | uint32_t spaceWidthAppUnits = |
1660 | 0 | NS_lroundf(aFont->GetMetrics(fontOrientation).spaceWidth * |
1661 | 0 | mAppUnitsPerDevUnit); |
1662 | 0 | if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { |
1663 | 0 | return false; |
1664 | 0 | } |
1665 | 0 | |
1666 | 0 | AddGlyphRun(aFont, gfxTextRange::MatchType::kFontGroup, aCharIndex, false, |
1667 | 0 | aOrientation); |
1668 | 0 | CompressedGlyph g = |
1669 | 0 | CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph); |
1670 | 0 | if (aSpaceChar == ' ') { |
1671 | 0 | g.SetIsSpace(); |
1672 | 0 | } |
1673 | 0 | GetCharacterGlyphs()[aCharIndex] = g; |
1674 | 0 | return true; |
1675 | 0 | } |
1676 | | |
1677 | | void |
1678 | | gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) |
1679 | 0 | { |
1680 | 0 | bool needsGlyphExtents = NeedsGlyphExtents(this); |
1681 | 0 | if (!needsGlyphExtents && !mDetailedGlyphs) |
1682 | 0 | return; |
1683 | 0 | |
1684 | 0 | uint32_t runCount; |
1685 | 0 | const GlyphRun* glyphRuns = GetGlyphRuns(&runCount); |
1686 | 0 | CompressedGlyph *charGlyphs = mCharacterGlyphs; |
1687 | 0 | for (uint32_t i = 0; i < runCount; ++i) { |
1688 | 0 | const GlyphRun& run = glyphRuns[i]; |
1689 | 0 | gfxFont *font = run.mFont; |
1690 | 0 | if (MOZ_UNLIKELY(font->GetStyle()->size == 0) || |
1691 | 0 | MOZ_UNLIKELY(font->GetStyle()->sizeAdjust == 0.0f)) { |
1692 | 0 | continue; |
1693 | 0 | } |
1694 | 0 | |
1695 | 0 | uint32_t start = run.mCharacterOffset; |
1696 | 0 | uint32_t end = i + 1 < runCount ? |
1697 | 0 | glyphRuns[i + 1].mCharacterOffset : GetLength(); |
1698 | 0 | uint32_t j; |
1699 | 0 | gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); |
1700 | 0 |
|
1701 | 0 | for (j = start; j < end; ++j) { |
1702 | 0 | const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[j]; |
1703 | 0 | if (glyphData->IsSimpleGlyph()) { |
1704 | 0 | // If we're in speed mode, don't set up glyph extents here; we'll |
1705 | 0 | // just return "optimistic" glyph bounds later |
1706 | 0 | if (needsGlyphExtents) { |
1707 | 0 | uint32_t glyphIndex = glyphData->GetSimpleGlyph(); |
1708 | 0 | if (!extents->IsGlyphKnown(glyphIndex)) { |
1709 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
1710 | | ++gGlyphExtentsSetupEagerSimple; |
1711 | | #endif |
1712 | | font->SetupGlyphExtents(aRefDrawTarget, |
1713 | 0 | glyphIndex, false, extents); |
1714 | 0 | } |
1715 | 0 | } |
1716 | 0 | } else if (!glyphData->IsMissing()) { |
1717 | 0 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
1718 | 0 | if (glyphCount == 0) { |
1719 | 0 | continue; |
1720 | 0 | } |
1721 | 0 | const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j); |
1722 | 0 | if (!details) { |
1723 | 0 | continue; |
1724 | 0 | } |
1725 | 0 | for (uint32_t k = 0; k < glyphCount; ++k, ++details) { |
1726 | 0 | uint32_t glyphIndex = details->mGlyphID; |
1727 | 0 | if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) { |
1728 | | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
1729 | | ++gGlyphExtentsSetupEagerTight; |
1730 | | #endif |
1731 | | font->SetupGlyphExtents(aRefDrawTarget, |
1732 | 0 | glyphIndex, true, extents); |
1733 | 0 | } |
1734 | 0 | } |
1735 | 0 | } |
1736 | 0 | } |
1737 | 0 | } |
1738 | 0 | } |
1739 | | |
1740 | | |
1741 | | size_t |
1742 | | gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) |
1743 | 0 | { |
1744 | 0 | // The second arg is how much gfxTextRun::AllocateStorage would have |
1745 | 0 | // allocated. |
1746 | 0 | size_t total = mHasGlyphRunArray |
1747 | 0 | ? mGlyphRunArray.ShallowSizeOfExcludingThis(aMallocSizeOf) |
1748 | 0 | : 0; |
1749 | 0 |
|
1750 | 0 | if (mDetailedGlyphs) { |
1751 | 0 | total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); |
1752 | 0 | } |
1753 | 0 |
|
1754 | 0 | return total; |
1755 | 0 | } |
1756 | | |
1757 | | size_t |
1758 | | gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) |
1759 | 0 | { |
1760 | 0 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
1761 | 0 | } |
1762 | | |
1763 | | |
1764 | | #ifdef DEBUG |
1765 | | void |
1766 | | gfxTextRun::Dump(FILE* aOutput) { |
1767 | | if (!aOutput) { |
1768 | | aOutput = stdout; |
1769 | | } |
1770 | | |
1771 | | fputc('[', aOutput); |
1772 | | uint32_t numGlyphRuns; |
1773 | | const GlyphRun* glyphRuns = GetGlyphRuns(&numGlyphRuns); |
1774 | | for (uint32_t i = 0; i < numGlyphRuns; ++i) { |
1775 | | if (i > 0) { |
1776 | | fputc(',', aOutput); |
1777 | | } |
1778 | | gfxFont* font = glyphRuns[i].mFont; |
1779 | | const gfxFontStyle* style = font->GetStyle(); |
1780 | | nsAutoString styleString; |
1781 | | nsStyleUtil::AppendFontSlantStyle(style->style, styleString); |
1782 | | nsAutoCString lang; |
1783 | | style->language->ToUTF8String(lang); |
1784 | | fprintf(aOutput, "%d: %s %f/%g/%s/%s", glyphRuns[i].mCharacterOffset, |
1785 | | font->GetName().get(), style->size, |
1786 | | style->weight.ToFloat(), |
1787 | | NS_ConvertUTF16toUTF8(styleString).get(), |
1788 | | lang.get()); |
1789 | | } |
1790 | | fputc(']', aOutput); |
1791 | | } |
1792 | | #endif |
1793 | | |
1794 | | gfxFontGroup::gfxFontGroup(const FontFamilyList& aFontFamilyList, |
1795 | | const gfxFontStyle *aStyle, |
1796 | | gfxTextPerfMetrics* aTextPerf, |
1797 | | gfxUserFontSet *aUserFontSet, |
1798 | | gfxFloat aDevToCssSize) |
1799 | | : mFamilyList(aFontFamilyList) |
1800 | | , mStyle(*aStyle) |
1801 | | , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) |
1802 | | , mHyphenWidth(-1) |
1803 | | , mDevToCssSize(aDevToCssSize) |
1804 | | , mUserFontSet(aUserFontSet) |
1805 | | , mTextPerf(aTextPerf) |
1806 | | , mLastPrefLang(eFontPrefLang_Western) |
1807 | | , mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aStyle->language)) |
1808 | | , mLastPrefFirstFont(false) |
1809 | | , mSkipDrawing(false) |
1810 | 0 | { |
1811 | 0 | // We don't use SetUserFontSet() here, as we want to unconditionally call |
1812 | 0 | // BuildFontList() rather than only do UpdateUserFonts() if it changed. |
1813 | 0 | mCurrGeneration = GetGeneration(); |
1814 | 0 | BuildFontList(); |
1815 | 0 | } |
1816 | | |
1817 | | gfxFontGroup::~gfxFontGroup() |
1818 | 0 | { |
1819 | 0 | // Should not be dropped by stylo |
1820 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1821 | 0 | } |
1822 | | |
1823 | | void |
1824 | | gfxFontGroup::BuildFontList() |
1825 | 0 | { |
1826 | 0 | // initialize fonts in the font family list |
1827 | 0 | AutoTArray<FamilyAndGeneric,10> fonts; |
1828 | 0 | gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); |
1829 | 0 |
|
1830 | 0 | // lookup fonts in the fontlist |
1831 | 0 | for (const FontFamilyName& name : mFamilyList.GetFontlist()->mNames) { |
1832 | 0 | if (name.IsNamed()) { |
1833 | 0 | AddPlatformFont(nsAtomCString(name.mName), fonts); |
1834 | 0 | } else { |
1835 | 0 | pfl->AddGenericFonts(name.mType, mStyle.language, fonts); |
1836 | 0 | if (mTextPerf) { |
1837 | 0 | mTextPerf->current.genericLookups++; |
1838 | 0 | } |
1839 | 0 | } |
1840 | 0 | } |
1841 | 0 |
|
1842 | 0 | // if necessary, append default generic onto the end |
1843 | 0 | if (mFamilyList.GetDefaultFontType() != eFamily_none && |
1844 | 0 | !mFamilyList.HasDefaultGeneric()) { |
1845 | 0 | pfl->AddGenericFonts(mFamilyList.GetDefaultFontType(), |
1846 | 0 | mStyle.language, fonts); |
1847 | 0 | if (mTextPerf) { |
1848 | 0 | mTextPerf->current.genericLookups++; |
1849 | 0 | } |
1850 | 0 | } |
1851 | 0 |
|
1852 | 0 | // build the fontlist from the specified families |
1853 | 0 | for (const auto& f : fonts) { |
1854 | 0 | AddFamilyToFontList(f.mFamily, f.mGeneric); |
1855 | 0 | } |
1856 | 0 | } |
1857 | | |
1858 | | void |
1859 | | gfxFontGroup::AddPlatformFont(const nsACString& aName, |
1860 | | nsTArray<FamilyAndGeneric>& aFamilyList) |
1861 | 0 | { |
1862 | 0 | // First, look up in the user font set... |
1863 | 0 | // If the fontSet matches the family, we must not look for a platform |
1864 | 0 | // font of the same name, even if we fail to actually get a fontEntry |
1865 | 0 | // here; we'll fall back to the next name in the CSS font-family list. |
1866 | 0 | if (mUserFontSet) { |
1867 | 0 | // Add userfonts to the fontlist whether already loaded |
1868 | 0 | // or not. Loading is initiated during font matching. |
1869 | 0 | gfxFontFamily* family = mUserFontSet->LookupFamily(aName); |
1870 | 0 | if (family) { |
1871 | 0 | aFamilyList.AppendElement(family); |
1872 | 0 | return; |
1873 | 0 | } |
1874 | 0 | } |
1875 | 0 | |
1876 | 0 | // Not known in the user font set ==> check system fonts |
1877 | 0 | gfxPlatformFontList::PlatformFontList() |
1878 | 0 | ->FindAndAddFamilies(aName, &aFamilyList, |
1879 | 0 | gfxPlatformFontList::FindFamiliesFlags(0), |
1880 | 0 | &mStyle, mDevToCssSize); |
1881 | 0 | } |
1882 | | |
1883 | | void |
1884 | | gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily, |
1885 | | FontFamilyType aGeneric) |
1886 | 0 | { |
1887 | 0 | NS_ASSERTION(aFamily, "trying to add a null font family to fontlist"); |
1888 | 0 | AutoTArray<gfxFontEntry*,4> fontEntryList; |
1889 | 0 | aFamily->FindAllFontsForStyle(mStyle, fontEntryList); |
1890 | 0 | // add these to the fontlist |
1891 | 0 | for (gfxFontEntry* fe : fontEntryList) { |
1892 | 0 | if (!HasFont(fe)) { |
1893 | 0 | FamilyFace ff(aFamily, fe, aGeneric); |
1894 | 0 | if (fe->mIsUserFontContainer) { |
1895 | 0 | ff.CheckState(mSkipDrawing); |
1896 | 0 | } |
1897 | 0 | mFonts.AppendElement(ff); |
1898 | 0 | } |
1899 | 0 | } |
1900 | 0 | // for a family marked as "check fallback faces", only mark the last |
1901 | 0 | // entry so that fallbacks for a family are only checked once |
1902 | 0 | if (aFamily->CheckForFallbackFaces() && |
1903 | 0 | !fontEntryList.IsEmpty() && !mFonts.IsEmpty()) { |
1904 | 0 | mFonts.LastElement().SetCheckForFallbackFaces(); |
1905 | 0 | } |
1906 | 0 | } |
1907 | | |
1908 | | bool |
1909 | | gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) |
1910 | 0 | { |
1911 | 0 | uint32_t count = mFonts.Length(); |
1912 | 0 | for (uint32_t i = 0; i < count; ++i) { |
1913 | 0 | if (mFonts[i].FontEntry() == aFontEntry) { |
1914 | 0 | return true; |
1915 | 0 | } |
1916 | 0 | } |
1917 | 0 | return false; |
1918 | 0 | } |
1919 | | |
1920 | | gfxFont* |
1921 | | gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh) |
1922 | 0 | { |
1923 | 0 | if (uint32_t(i) >= mFonts.Length()) { |
1924 | 0 | return nullptr; |
1925 | 0 | } |
1926 | 0 | |
1927 | 0 | FamilyFace& ff = mFonts[i]; |
1928 | 0 | if (ff.IsInvalid() || ff.IsLoading()) { |
1929 | 0 | return nullptr; |
1930 | 0 | } |
1931 | 0 | |
1932 | 0 | gfxFont* font = ff.Font(); |
1933 | 0 | if (!font) { |
1934 | 0 | gfxFontEntry* fe = mFonts[i].FontEntry(); |
1935 | 0 | gfxCharacterMap* unicodeRangeMap = nullptr; |
1936 | 0 | if (fe->mIsUserFontContainer) { |
1937 | 0 | gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); |
1938 | 0 | if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && |
1939 | 0 | ufe->CharacterInUnicodeRange(aCh) && !mSkipDrawing && |
1940 | 0 | !FontLoadingForFamily(ff.Family(), aCh)) { |
1941 | 0 | ufe->Load(); |
1942 | 0 | ff.CheckState(mSkipDrawing); |
1943 | 0 | } |
1944 | 0 | fe = ufe->GetPlatformFontEntry(); |
1945 | 0 | if (!fe) { |
1946 | 0 | return nullptr; |
1947 | 0 | } |
1948 | 0 | unicodeRangeMap = ufe->GetUnicodeRangeMap(); |
1949 | 0 | } |
1950 | 0 | font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap); |
1951 | 0 | if (!font || !font->Valid()) { |
1952 | 0 | ff.SetInvalid(); |
1953 | 0 | // We can't just |delete font| here, in case there are other |
1954 | 0 | // references to the object FindOrMakeFont returned. |
1955 | 0 | RefPtr<gfxFont> ref(font); |
1956 | 0 | return nullptr; |
1957 | 0 | } |
1958 | 0 | mFonts[i].SetFont(font); |
1959 | 0 | } |
1960 | 0 | return font; |
1961 | 0 | } |
1962 | | |
1963 | | void |
1964 | | gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) |
1965 | 0 | { |
1966 | 0 | gfxFontEntry* fe = FontEntry(); |
1967 | 0 | if (fe->mIsUserFontContainer) { |
1968 | 0 | gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); |
1969 | 0 | gfxUserFontEntry::UserFontLoadState state = ufe->LoadState(); |
1970 | 0 | switch (state) { |
1971 | 0 | case gfxUserFontEntry::STATUS_LOAD_PENDING: |
1972 | 0 | case gfxUserFontEntry::STATUS_LOADING: |
1973 | 0 | SetLoading(true); |
1974 | 0 | break; |
1975 | 0 | case gfxUserFontEntry::STATUS_FAILED: |
1976 | 0 | SetInvalid(); |
1977 | 0 | // fall-thru to the default case |
1978 | 0 | MOZ_FALLTHROUGH; |
1979 | 0 | default: |
1980 | 0 | SetLoading(false); |
1981 | 0 | } |
1982 | 0 | if (ufe->WaitForUserFont()) { |
1983 | 0 | aSkipDrawing = true; |
1984 | 0 | } |
1985 | 0 | } |
1986 | 0 | } |
1987 | | |
1988 | | bool |
1989 | | gfxFontGroup::FamilyFace::EqualsUserFont(const gfxUserFontEntry* aUserFont) const |
1990 | 0 | { |
1991 | 0 | gfxFontEntry* fe = FontEntry(); |
1992 | 0 | // if there's a font, the entry is the underlying platform font |
1993 | 0 | if (mFontCreated) { |
1994 | 0 | gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry(); |
1995 | 0 | if (pfe == fe) { |
1996 | 0 | return true; |
1997 | 0 | } |
1998 | 0 | } else if (fe == aUserFont) { |
1999 | 0 | return true; |
2000 | 0 | } |
2001 | 0 | return false; |
2002 | 0 | } |
2003 | | |
2004 | | bool |
2005 | | gfxFontGroup::FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const |
2006 | 0 | { |
2007 | 0 | uint32_t count = mFonts.Length(); |
2008 | 0 | for (uint32_t i = 0; i < count; ++i) { |
2009 | 0 | const FamilyFace& ff = mFonts[i]; |
2010 | 0 | if (ff.IsLoading() && ff.Family() == aFamily) { |
2011 | 0 | const gfxUserFontEntry* ufe = |
2012 | 0 | static_cast<gfxUserFontEntry*>(ff.FontEntry()); |
2013 | 0 | if (ufe->CharacterInUnicodeRange(aCh)) { |
2014 | 0 | return true; |
2015 | 0 | } |
2016 | 0 | } |
2017 | 0 | } |
2018 | 0 | return false; |
2019 | 0 | } |
2020 | | |
2021 | | gfxFont* |
2022 | | gfxFontGroup::GetDefaultFont() |
2023 | 0 | { |
2024 | 0 | if (mDefaultFont) { |
2025 | 0 | return mDefaultFont.get(); |
2026 | 0 | } |
2027 | 0 | |
2028 | 0 | gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); |
2029 | 0 | gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle); |
2030 | 0 | NS_ASSERTION(defaultFamily, |
2031 | 0 | "invalid default font returned by GetDefaultFont"); |
2032 | 0 |
|
2033 | 0 | if (defaultFamily) { |
2034 | 0 | gfxFontEntry *fe = |
2035 | 0 | defaultFamily->FindFontForStyle(mStyle, true); |
2036 | 0 | if (fe) { |
2037 | 0 | mDefaultFont = fe->FindOrMakeFont(&mStyle); |
2038 | 0 | } |
2039 | 0 | } |
2040 | 0 |
|
2041 | 0 | uint32_t numInits, loaderState; |
2042 | 0 | pfl->GetFontlistInitInfo(numInits, loaderState); |
2043 | 0 | NS_ASSERTION(numInits != 0, |
2044 | 0 | "must initialize system fontlist before getting default font!"); |
2045 | 0 |
|
2046 | 0 | uint32_t numFonts = 0; |
2047 | 0 | if (!mDefaultFont) { |
2048 | 0 | // Try for a "font of last resort...." |
2049 | 0 | // Because an empty font list would be Really Bad for later code |
2050 | 0 | // that assumes it will be able to get valid metrics for layout, |
2051 | 0 | // just look for the first usable font and put in the list. |
2052 | 0 | // (see bug 554544) |
2053 | 0 | AutoTArray<RefPtr<gfxFontFamily>,200> familyList; |
2054 | 0 | pfl->GetFontFamilyList(familyList); |
2055 | 0 | numFonts = familyList.Length(); |
2056 | 0 | for (uint32_t i = 0; i < numFonts; ++i) { |
2057 | 0 | gfxFontEntry *fe = |
2058 | 0 | familyList[i]->FindFontForStyle(mStyle, true); |
2059 | 0 | if (fe) { |
2060 | 0 | mDefaultFont = fe->FindOrMakeFont(&mStyle); |
2061 | 0 | if (mDefaultFont) { |
2062 | 0 | break; |
2063 | 0 | } |
2064 | 0 | } |
2065 | 0 | } |
2066 | 0 | } |
2067 | 0 |
|
2068 | 0 | if (!mDefaultFont) { |
2069 | 0 | // an empty font list at this point is fatal; we're not going to |
2070 | 0 | // be able to do even the most basic layout operations |
2071 | 0 |
|
2072 | 0 | // annotate crash report with fontlist info |
2073 | 0 | nsAutoCString fontInitInfo; |
2074 | 0 | fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d", |
2075 | 0 | numInits, numFonts, loaderState); |
2076 | | #ifdef XP_WIN |
2077 | | bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled(); |
2078 | | double upTime = (double) GetTickCount(); |
2079 | | fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec", |
2080 | | dwriteEnabled ? "directwrite" : "gdi", upTime/1000); |
2081 | | #endif |
2082 | 0 | gfxCriticalError() << fontInitInfo.get(); |
2083 | 0 |
|
2084 | 0 | char msg[256]; // CHECK buffer length if revising message below |
2085 | 0 | nsAutoCString familiesString; |
2086 | 0 | mFamilyList.ToString(familiesString); |
2087 | 0 | SprintfLiteral(msg, "unable to find a usable font (%.220s)", |
2088 | 0 | familiesString.get()); |
2089 | 0 | MOZ_CRASH_UNSAFE_OOL(msg); |
2090 | 0 | } |
2091 | 0 |
|
2092 | 0 | return mDefaultFont.get(); |
2093 | 0 | } |
2094 | | |
2095 | | gfxFont* |
2096 | | gfxFontGroup::GetFirstValidFont(uint32_t aCh, FontFamilyType* aGeneric) |
2097 | 0 | { |
2098 | 0 | uint32_t count = mFonts.Length(); |
2099 | 0 | for (uint32_t i = 0; i < count; ++i) { |
2100 | 0 | FamilyFace& ff = mFonts[i]; |
2101 | 0 | if (ff.IsInvalid()) { |
2102 | 0 | continue; |
2103 | 0 | } |
2104 | 0 | |
2105 | 0 | // already have a font? |
2106 | 0 | gfxFont* font = ff.Font(); |
2107 | 0 | if (font) { |
2108 | 0 | if (aGeneric) { |
2109 | 0 | *aGeneric = ff.Generic(); |
2110 | 0 | } |
2111 | 0 | return font; |
2112 | 0 | } |
2113 | 0 |
|
2114 | 0 | // Need to build a font, loading userfont if not loaded. In |
2115 | 0 | // cases where unicode range might apply, use the character |
2116 | 0 | // provided. |
2117 | 0 | if (ff.IsUserFontContainer()) { |
2118 | 0 | gfxUserFontEntry* ufe = |
2119 | 0 | static_cast<gfxUserFontEntry*>(mFonts[i].FontEntry()); |
2120 | 0 | bool inRange = ufe->CharacterInUnicodeRange(aCh); |
2121 | 0 | if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && |
2122 | 0 | inRange && !mSkipDrawing && |
2123 | 0 | !FontLoadingForFamily(ff.Family(), aCh)) { |
2124 | 0 | ufe->Load(); |
2125 | 0 | ff.CheckState(mSkipDrawing); |
2126 | 0 | } |
2127 | 0 | if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || |
2128 | 0 | !inRange) { |
2129 | 0 | continue; |
2130 | 0 | } |
2131 | 0 | } |
2132 | 0 | |
2133 | 0 | font = GetFontAt(i, aCh); |
2134 | 0 | if (font) { |
2135 | 0 | if (aGeneric) { |
2136 | 0 | *aGeneric = mFonts[i].Generic(); |
2137 | 0 | } |
2138 | 0 | return font; |
2139 | 0 | } |
2140 | 0 | } |
2141 | 0 | if (aGeneric) { |
2142 | 0 | *aGeneric = eFamily_none; |
2143 | 0 | } |
2144 | 0 | return GetDefaultFont(); |
2145 | 0 | } |
2146 | | |
2147 | | gfxFont * |
2148 | | gfxFontGroup::GetFirstMathFont() |
2149 | 0 | { |
2150 | 0 | uint32_t count = mFonts.Length(); |
2151 | 0 | for (uint32_t i = 0; i < count; ++i) { |
2152 | 0 | gfxFont* font = GetFontAt(i); |
2153 | 0 | if (font && font->TryGetMathTable()) { |
2154 | 0 | return font; |
2155 | 0 | } |
2156 | 0 | } |
2157 | 0 | return nullptr; |
2158 | 0 | } |
2159 | | |
2160 | | gfxFontGroup * |
2161 | | gfxFontGroup::Copy(const gfxFontStyle *aStyle) |
2162 | 0 | { |
2163 | 0 | gfxFontGroup *fg = |
2164 | 0 | new gfxFontGroup(mFamilyList, aStyle, mTextPerf, |
2165 | 0 | mUserFontSet, mDevToCssSize); |
2166 | 0 | return fg; |
2167 | 0 | } |
2168 | | |
2169 | | bool |
2170 | | gfxFontGroup::IsInvalidChar(uint8_t ch) |
2171 | 0 | { |
2172 | 0 | return ((ch & 0x7f) < 0x20 || ch == 0x7f); |
2173 | 0 | } |
2174 | | |
2175 | | bool |
2176 | | gfxFontGroup::IsInvalidChar(char16_t ch) |
2177 | 0 | { |
2178 | 0 | // All printable 7-bit ASCII values are OK |
2179 | 0 | if (ch >= ' ' && ch < 0x7f) { |
2180 | 0 | return false; |
2181 | 0 | } |
2182 | 0 | // No point in sending non-printing control chars through font shaping |
2183 | 0 | if (ch <= 0x9f) { |
2184 | 0 | return true; |
2185 | 0 | } |
2186 | 0 | // Word-separating format/bidi control characters are not shaped as part |
2187 | 0 | // of words. |
2188 | 0 | return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && |
2189 | 0 | (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || |
2190 | 0 | ch == 0x2029/*PSEP*/ || ch == 0x2060/*WJ*/)) || |
2191 | 0 | ch == 0xfeff/*ZWNBSP*/ || |
2192 | 0 | IsBidiControl(ch)); |
2193 | 0 | } |
2194 | | |
2195 | | already_AddRefed<gfxTextRun> |
2196 | | gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, |
2197 | | gfx::ShapedTextFlags aFlags, |
2198 | | nsTextFrameUtils::Flags aFlags2) |
2199 | 0 | { |
2200 | 0 | aFlags |= ShapedTextFlags::TEXT_IS_8BIT; |
2201 | 0 | return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2); |
2202 | 0 | } |
2203 | | |
2204 | | already_AddRefed<gfxTextRun> |
2205 | | gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, |
2206 | | gfx::ShapedTextFlags aFlags, |
2207 | | nsTextFrameUtils::Flags aFlags2) |
2208 | 0 | { |
2209 | 0 | aFlags |= ShapedTextFlags::TEXT_IS_8BIT; |
2210 | 0 |
|
2211 | 0 | RefPtr<gfxTextRun> textRun = |
2212 | 0 | gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2); |
2213 | 0 | if (!textRun) { |
2214 | 0 | return nullptr; |
2215 | 0 | } |
2216 | 0 | |
2217 | 0 | gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; |
2218 | 0 | if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { |
2219 | 0 | orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
2220 | 0 | } |
2221 | 0 |
|
2222 | 0 | gfxFont *font = GetFirstValidFont(); |
2223 | 0 | if (MOZ_UNLIKELY(GetStyle()->size == 0) || |
2224 | 0 | MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) { |
2225 | 0 | // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle |
2226 | 0 | // them, and always create at least size 1 fonts, i.e. they still |
2227 | 0 | // render something for size 0 fonts. |
2228 | 0 | textRun->AddGlyphRun(font, gfxTextRange::MatchType::kFontGroup, 0, |
2229 | 0 | false, orientation); |
2230 | 0 | } |
2231 | 0 | else { |
2232 | 0 | if (font->GetSpaceGlyph()) { |
2233 | 0 | // Normally, the font has a cached space glyph, so we can avoid |
2234 | 0 | // the cost of calling FindFontForChar. |
2235 | 0 | textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation); |
2236 | 0 | } else { |
2237 | 0 | // In case the primary font doesn't have <space> (bug 970891), |
2238 | 0 | // find one that does. |
2239 | 0 | gfxTextRange::MatchType matchType; |
2240 | 0 | gfxFont* spaceFont = |
2241 | 0 | FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, |
2242 | 0 | &matchType); |
2243 | 0 | if (spaceFont) { |
2244 | 0 | textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, |
2245 | 0 | orientation); |
2246 | 0 | } |
2247 | 0 | } |
2248 | 0 | } |
2249 | 0 |
|
2250 | 0 | // Note that the gfxGlyphExtents glyph bounds storage for the font will |
2251 | 0 | // always contain an entry for the font's space glyph, so we don't have |
2252 | 0 | // to call FetchGlyphExtents here. |
2253 | 0 | return textRun.forget(); |
2254 | 0 | } |
2255 | | |
2256 | | already_AddRefed<gfxTextRun> |
2257 | | gfxFontGroup::MakeBlankTextRun(uint32_t aLength, |
2258 | | const Parameters *aParams, |
2259 | | gfx::ShapedTextFlags aFlags, |
2260 | | nsTextFrameUtils::Flags aFlags2) |
2261 | 0 | { |
2262 | 0 | RefPtr<gfxTextRun> textRun = |
2263 | 0 | gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); |
2264 | 0 | if (!textRun) { |
2265 | 0 | return nullptr; |
2266 | 0 | } |
2267 | 0 | |
2268 | 0 | gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; |
2269 | 0 | if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { |
2270 | 0 | orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; |
2271 | 0 | } |
2272 | 0 | textRun->AddGlyphRun(GetFirstValidFont(), |
2273 | 0 | gfxTextRange::MatchType::kFontGroup, 0, false, |
2274 | 0 | orientation); |
2275 | 0 | return textRun.forget(); |
2276 | 0 | } |
2277 | | |
2278 | | already_AddRefed<gfxTextRun> |
2279 | | gfxFontGroup::MakeHyphenTextRun(DrawTarget* aDrawTarget, |
2280 | | uint32_t aAppUnitsPerDevUnit) |
2281 | 0 | { |
2282 | 0 | // only use U+2010 if it is supported by the first font in the group; |
2283 | 0 | // it's better to use ASCII '-' from the primary font than to fall back to |
2284 | 0 | // U+2010 from some other, possibly poorly-matching face |
2285 | 0 | static const char16_t hyphen = 0x2010; |
2286 | 0 | gfxFont *font = GetFirstValidFont(uint32_t(hyphen)); |
2287 | 0 | if (font->HasCharacter(hyphen)) { |
2288 | 0 | return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit, |
2289 | 0 | ShapedTextFlags(), |
2290 | 0 | nsTextFrameUtils::Flags(), nullptr); |
2291 | 0 | } |
2292 | 0 | |
2293 | 0 | static const uint8_t dash = '-'; |
2294 | 0 | return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, |
2295 | 0 | ShapedTextFlags(), |
2296 | 0 | nsTextFrameUtils::Flags(), nullptr); |
2297 | 0 | } |
2298 | | |
2299 | | gfxFloat |
2300 | | gfxFontGroup::GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider) |
2301 | 0 | { |
2302 | 0 | if (mHyphenWidth < 0) { |
2303 | 0 | RefPtr<DrawTarget> dt(aProvider->GetDrawTarget()); |
2304 | 0 | if (dt) { |
2305 | 0 | RefPtr<gfxTextRun> |
2306 | 0 | hyphRun(MakeHyphenTextRun(dt, |
2307 | 0 | aProvider->GetAppUnitsPerDevUnit())); |
2308 | 0 | mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0; |
2309 | 0 | } |
2310 | 0 | } |
2311 | 0 | return mHyphenWidth; |
2312 | 0 | } |
2313 | | |
2314 | | already_AddRefed<gfxTextRun> |
2315 | | gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength, |
2316 | | const Parameters *aParams, |
2317 | | gfx::ShapedTextFlags aFlags, |
2318 | | nsTextFrameUtils::Flags aFlags2, |
2319 | | gfxMissingFontRecorder *aMFR) |
2320 | 0 | { |
2321 | 0 | if (aLength == 0) { |
2322 | 0 | return MakeEmptyTextRun(aParams, aFlags, aFlags2); |
2323 | 0 | } |
2324 | 0 | if (aLength == 1 && aString[0] == ' ') { |
2325 | 0 | return MakeSpaceTextRun(aParams, aFlags, aFlags2); |
2326 | 0 | } |
2327 | 0 | |
2328 | 0 | aFlags |= ShapedTextFlags::TEXT_IS_8BIT; |
2329 | 0 |
|
2330 | 0 | if (MOZ_UNLIKELY(GetStyle()->size == 0) || |
2331 | 0 | MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) { |
2332 | 0 | // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle |
2333 | 0 | // them, and always create at least size 1 fonts, i.e. they still |
2334 | 0 | // render something for size 0 fonts. |
2335 | 0 | return MakeBlankTextRun(aLength, aParams, aFlags, aFlags2); |
2336 | 0 | } |
2337 | 0 | |
2338 | 0 | RefPtr<gfxTextRun> textRun = gfxTextRun::Create(aParams, aLength, this, |
2339 | 0 | aFlags, aFlags2); |
2340 | 0 | if (!textRun) { |
2341 | 0 | return nullptr; |
2342 | 0 | } |
2343 | 0 | |
2344 | 0 | InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); |
2345 | 0 |
|
2346 | 0 | textRun->FetchGlyphExtents(aParams->mDrawTarget); |
2347 | 0 |
|
2348 | 0 | return textRun.forget(); |
2349 | 0 | } |
2350 | | |
2351 | | already_AddRefed<gfxTextRun> |
2352 | | gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength, |
2353 | | const Parameters *aParams, |
2354 | | gfx::ShapedTextFlags aFlags, |
2355 | | nsTextFrameUtils::Flags aFlags2, |
2356 | | gfxMissingFontRecorder *aMFR) |
2357 | 0 | { |
2358 | 0 | if (aLength == 0) { |
2359 | 0 | return MakeEmptyTextRun(aParams, aFlags, aFlags2); |
2360 | 0 | } |
2361 | 0 | if (aLength == 1 && aString[0] == ' ') { |
2362 | 0 | return MakeSpaceTextRun(aParams, aFlags, aFlags2); |
2363 | 0 | } |
2364 | 0 | if (MOZ_UNLIKELY(GetStyle()->size == 0) || |
2365 | 0 | MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0f)) { |
2366 | 0 | return MakeBlankTextRun(aLength, aParams, aFlags, aFlags2); |
2367 | 0 | } |
2368 | 0 | |
2369 | 0 | RefPtr<gfxTextRun> textRun = gfxTextRun::Create(aParams, aLength, this, |
2370 | 0 | aFlags, aFlags2); |
2371 | 0 | if (!textRun) { |
2372 | 0 | return nullptr; |
2373 | 0 | } |
2374 | 0 | |
2375 | 0 | InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); |
2376 | 0 |
|
2377 | 0 | textRun->FetchGlyphExtents(aParams->mDrawTarget); |
2378 | 0 |
|
2379 | 0 | return textRun.forget(); |
2380 | 0 | } |
2381 | | |
2382 | | template<typename T> |
2383 | | void |
2384 | | gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, |
2385 | | gfxTextRun *aTextRun, |
2386 | | const T *aString, |
2387 | | uint32_t aLength, |
2388 | | gfxMissingFontRecorder *aMFR) |
2389 | 0 | { |
2390 | 0 | NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); |
2391 | 0 |
|
2392 | 0 | // we need to do numeral processing even on 8-bit text, |
2393 | 0 | // in case we're converting Western to Hindi/Arabic digits |
2394 | 0 | int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); |
2395 | 0 | UniquePtr<char16_t[]> transformedString; |
2396 | 0 | if (numOption != IBMBIDI_NUMERAL_NOMINAL) { |
2397 | 0 | // scan the string for numerals that may need to be transformed; |
2398 | 0 | // if we find any, we'll make a local copy here and use that for |
2399 | 0 | // font matching and glyph generation/shaping |
2400 | 0 | bool prevIsArabic = |
2401 | 0 | !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR); |
2402 | 0 | for (uint32_t i = 0; i < aLength; ++i) { |
2403 | 0 | char16_t origCh = aString[i]; |
2404 | 0 | char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); |
2405 | 0 | if (newCh != origCh) { |
2406 | 0 | if (!transformedString) { |
2407 | 0 | transformedString = MakeUnique<char16_t[]>(aLength); |
2408 | 0 | if (sizeof(T) == sizeof(char16_t)) { |
2409 | 0 | memcpy(transformedString.get(), aString, i * sizeof(char16_t)); |
2410 | 0 | } else { |
2411 | 0 | for (uint32_t j = 0; j < i; ++j) { |
2412 | 0 | transformedString[j] = aString[j]; |
2413 | 0 | } |
2414 | 0 | } |
2415 | 0 | } |
2416 | 0 | } |
2417 | 0 | if (transformedString) { |
2418 | 0 | transformedString[i] = newCh; |
2419 | 0 | } |
2420 | 0 | prevIsArabic = IS_ARABIC_CHAR(newCh); |
2421 | 0 | } |
2422 | 0 | } |
2423 | 0 |
|
2424 | 0 | LogModule* log = mStyle.systemFont |
2425 | 0 | ? gfxPlatform::GetLog(eGfxLog_textrunui) |
2426 | 0 | : gfxPlatform::GetLog(eGfxLog_textrun); |
2427 | 0 |
|
2428 | 0 | // variant fallback handling may end up passing through this twice |
2429 | 0 | bool redo; |
2430 | 0 | do { |
2431 | 0 | redo = false; |
2432 | 0 |
|
2433 | 0 | if (sizeof(T) == sizeof(uint8_t) && !transformedString) { |
2434 | 0 |
|
2435 | 0 | if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { |
2436 | 0 | nsAutoCString lang; |
2437 | 0 | mStyle.language->ToUTF8String(lang); |
2438 | 0 | nsAutoCString families; |
2439 | 0 | mFamilyList.ToString(families); |
2440 | 0 | nsAutoCString str((const char*)aString, aLength); |
2441 | 0 | nsAutoString styleString; |
2442 | 0 | nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString); |
2443 | 0 | MOZ_LOG(log, LogLevel::Warning,\ |
2444 | 0 | ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " |
2445 | 0 | "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte " |
2446 | 0 | "TEXTRUN [%s] ENDTEXTRUN\n", |
2447 | 0 | (mStyle.systemFont ? "textrunui" : "textrun"), |
2448 | 0 | families.get(), |
2449 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_serif ? |
2450 | 0 | "serif" : |
2451 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? |
2452 | 0 | "sans-serif" : "none")), |
2453 | 0 | lang.get(), static_cast<int>(Script::LATIN), aLength, |
2454 | 0 | mStyle.weight.ToFloat(), |
2455 | 0 | mStyle.stretch.Percentage(), |
2456 | 0 | NS_ConvertUTF16toUTF8(styleString).get(), |
2457 | 0 | mStyle.size, |
2458 | 0 | sizeof(T), |
2459 | 0 | str.get())); |
2460 | 0 | } |
2461 | 0 |
|
2462 | 0 | // the text is still purely 8-bit; bypass the script-run itemizer |
2463 | 0 | // and treat it as a single Latin run |
2464 | 0 | InitScriptRun(aDrawTarget, aTextRun, aString, |
2465 | 0 | 0, aLength, Script::LATIN, aMFR); |
2466 | 0 | } else { |
2467 | 0 | const char16_t *textPtr; |
2468 | 0 | if (transformedString) { |
2469 | 0 | textPtr = transformedString.get(); |
2470 | 0 | } else { |
2471 | 0 | // typecast to avoid compilation error for the 8-bit version, |
2472 | 0 | // even though this is dead code in that case |
2473 | 0 | textPtr = reinterpret_cast<const char16_t*>(aString); |
2474 | 0 | } |
2475 | 0 |
|
2476 | 0 | // split into script runs so that script can potentially influence |
2477 | 0 | // the font matching process below |
2478 | 0 | gfxScriptItemizer scriptRuns(textPtr, aLength); |
2479 | 0 |
|
2480 | 0 | uint32_t runStart = 0, runLimit = aLength; |
2481 | 0 | Script runScript = Script::LATIN; |
2482 | 0 | while (scriptRuns.Next(runStart, runLimit, runScript)) { |
2483 | 0 |
|
2484 | 0 | if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { |
2485 | 0 | nsAutoCString lang; |
2486 | 0 | mStyle.language->ToUTF8String(lang); |
2487 | 0 | nsAutoCString families; |
2488 | 0 | mFamilyList.ToString(families); |
2489 | 0 | nsAutoString styleString; |
2490 | 0 | nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString); |
2491 | 0 | uint32_t runLen = runLimit - runStart; |
2492 | 0 | MOZ_LOG(log, LogLevel::Warning,\ |
2493 | 0 | ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " |
2494 | 0 | "len %d weight: %g stretch: %g%% style: %s size: %6.2f " |
2495 | 0 | "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n", |
2496 | 0 | (mStyle.systemFont ? "textrunui" : "textrun"), |
2497 | 0 | families.get(), |
2498 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_serif ? |
2499 | 0 | "serif" : |
2500 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? |
2501 | 0 | "sans-serif" : "none")), |
2502 | 0 | lang.get(), static_cast<int>(runScript), runLen, |
2503 | 0 | mStyle.weight.ToFloat(), |
2504 | 0 | mStyle.stretch.Percentage(), |
2505 | 0 | NS_ConvertUTF16toUTF8(styleString).get(), |
2506 | 0 | mStyle.size, |
2507 | 0 | sizeof(T), |
2508 | 0 | NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); |
2509 | 0 | } |
2510 | 0 |
|
2511 | 0 | InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, |
2512 | 0 | runStart, runLimit - runStart, runScript, aMFR); |
2513 | 0 | } |
2514 | 0 | } |
2515 | 0 |
|
2516 | 0 | // if shaping was aborted due to lack of feature support, clear out |
2517 | 0 | // glyph runs and redo shaping with fallback forced on |
2518 | 0 | if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) { |
2519 | 0 | redo = true; |
2520 | 0 | aTextRun->SetShapingState( |
2521 | 0 | gfxTextRun::eShapingState_ForceFallbackFeature); |
2522 | 0 | aTextRun->ClearGlyphsAndCharacters(); |
2523 | 0 | } |
2524 | 0 |
|
2525 | 0 | } while (redo); |
2526 | 0 |
|
2527 | 0 | if (sizeof(T) == sizeof(char16_t) && aLength > 0) { |
2528 | 0 | gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs(); |
2529 | 0 | if (!glyph->IsSimpleGlyph()) { |
2530 | 0 | glyph->SetClusterStart(true); |
2531 | 0 | } |
2532 | 0 | } |
2533 | 0 |
|
2534 | 0 | // It's possible for CoreText to omit glyph runs if it decides they contain |
2535 | 0 | // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we |
2536 | 0 | // need to eliminate them from the glyph run array to avoid drawing "partial |
2537 | 0 | // ligatures" with the wrong font. |
2538 | 0 | // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because |
2539 | 0 | // it will iterate back over all glyphruns in the textrun, which leads to |
2540 | 0 | // pathologically-bad perf in the case where a textrun contains many script |
2541 | 0 | // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs |
2542 | 0 | // every time a new script subrun is processed. |
2543 | 0 | aTextRun->SanitizeGlyphRuns(); |
2544 | 0 |
|
2545 | 0 | aTextRun->SortGlyphRuns(); |
2546 | 0 | } Unexecuted instantiation: void gfxFontGroup::InitTextRun<unsigned char>(mozilla::gfx::DrawTarget*, gfxTextRun*, unsigned char const*, unsigned int, gfxMissingFontRecorder*) Unexecuted instantiation: void gfxFontGroup::InitTextRun<char16_t>(mozilla::gfx::DrawTarget*, gfxTextRun*, char16_t const*, unsigned int, gfxMissingFontRecorder*) |
2547 | | |
2548 | | static inline bool |
2549 | | IsPUA(uint32_t aUSV) |
2550 | 0 | { |
2551 | 0 | // We could look up the General Category of the codepoint here, |
2552 | 0 | // but it's simpler to check PUA codepoint ranges. |
2553 | 0 | return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000); |
2554 | 0 | } |
2555 | | |
2556 | | template<typename T> |
2557 | | void |
2558 | | gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, |
2559 | | gfxTextRun *aTextRun, |
2560 | | const T *aString, // text for this script run, |
2561 | | // not the entire textrun |
2562 | | uint32_t aOffset, // position of the script run |
2563 | | // within the textrun |
2564 | | uint32_t aLength, // length of the script run |
2565 | | Script aRunScript, |
2566 | | gfxMissingFontRecorder *aMFR) |
2567 | 0 | { |
2568 | 0 | NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run"); |
2569 | 0 | NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted, |
2570 | 0 | "don't call InitScriptRun with aborted shaping state"); |
2571 | 0 |
|
2572 | 0 | // confirm the load state of userfonts in the list |
2573 | 0 | if (mUserFontSet && |
2574 | 0 | mCurrGeneration != mUserFontSet->GetGeneration()) { |
2575 | 0 | UpdateUserFonts(); |
2576 | 0 | } |
2577 | 0 |
|
2578 | 0 | gfxFont *mainFont = GetFirstValidFont(); |
2579 | 0 |
|
2580 | 0 | ShapedTextFlags orientation = |
2581 | 0 | aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK; |
2582 | 0 |
|
2583 | 0 | if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && |
2584 | 0 | (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) { |
2585 | 0 | // Mongolian and Phags-pa text should ignore text-orientation and |
2586 | 0 | // always render in its "native" vertical mode, implemented by fonts |
2587 | 0 | // as sideways-right (i.e as if shaped horizontally, and then the |
2588 | 0 | // entire line is rotated to render vertically). Therefore, we ignore |
2589 | 0 | // the aOrientation value from the textrun's flags, and make all |
2590 | 0 | // vertical Mongolian/Phags-pa use sideways-right. |
2591 | 0 | orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
2592 | 0 | } |
2593 | 0 |
|
2594 | 0 | uint32_t runStart = 0; |
2595 | 0 | AutoTArray<gfxTextRange,3> fontRanges; |
2596 | 0 | ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation); |
2597 | 0 | uint32_t numRanges = fontRanges.Length(); |
2598 | 0 | bool missingChars = false; |
2599 | 0 |
|
2600 | 0 | for (uint32_t r = 0; r < numRanges; r++) { |
2601 | 0 | const gfxTextRange& range = fontRanges[r]; |
2602 | 0 | uint32_t matchedLength = range.Length(); |
2603 | 0 | gfxFont *matchedFont = range.font; |
2604 | 0 | // create the glyph run for this range |
2605 | 0 | if (matchedFont && mStyle.noFallbackVariantFeatures) { |
2606 | 0 | // common case - just do glyph layout and record the |
2607 | 0 | // resulting positioned glyphs |
2608 | 0 | aTextRun->AddGlyphRun(matchedFont, range.matchType, |
2609 | 0 | aOffset + runStart, (matchedLength > 0), |
2610 | 0 | range.orientation); |
2611 | 0 | if (!matchedFont->SplitAndInitTextRun(aDrawTarget, aTextRun, |
2612 | 0 | aString + runStart, |
2613 | 0 | aOffset + runStart, |
2614 | 0 | matchedLength, |
2615 | 0 | aRunScript, |
2616 | 0 | range.orientation)) { |
2617 | 0 | // glyph layout failed! treat as missing glyphs |
2618 | 0 | matchedFont = nullptr; |
2619 | 0 | } |
2620 | 0 | } else if (matchedFont) { |
2621 | 0 | // shape with some variant feature that requires fallback handling |
2622 | 0 | bool petiteToSmallCaps = false; |
2623 | 0 | bool syntheticLower = false; |
2624 | 0 | bool syntheticUpper = false; |
2625 | 0 |
|
2626 | 0 | if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && |
2627 | 0 | (aTextRun->GetShapingState() == |
2628 | 0 | gfxTextRun::eShapingState_ForceFallbackFeature || |
2629 | 0 | !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, |
2630 | 0 | aString, aLength, |
2631 | 0 | aRunScript))) |
2632 | 0 | { |
2633 | 0 | // fallback for subscript/superscript variant glyphs |
2634 | 0 |
|
2635 | 0 | // if the feature was already used, abort and force |
2636 | 0 | // fallback across the entire textrun |
2637 | 0 | gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); |
2638 | 0 |
|
2639 | 0 | if (ss == gfxTextRun::eShapingState_Normal) { |
2640 | 0 | aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFallback); |
2641 | 0 | } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) { |
2642 | 0 | aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); |
2643 | 0 | return; |
2644 | 0 | } |
2645 | 0 | |
2646 | 0 | RefPtr<gfxFont> subSuperFont = |
2647 | 0 | matchedFont->GetSubSuperscriptFont(aTextRun->GetAppUnitsPerDevUnit()); |
2648 | 0 | aTextRun->AddGlyphRun(subSuperFont, range.matchType, |
2649 | 0 | aOffset + runStart, (matchedLength > 0), |
2650 | 0 | range.orientation); |
2651 | 0 | if (!subSuperFont->SplitAndInitTextRun(aDrawTarget, aTextRun, |
2652 | 0 | aString + runStart, |
2653 | 0 | aOffset + runStart, |
2654 | 0 | matchedLength, |
2655 | 0 | aRunScript, |
2656 | 0 | range.orientation)) { |
2657 | 0 | // glyph layout failed! treat as missing glyphs |
2658 | 0 | matchedFont = nullptr; |
2659 | 0 | } |
2660 | 0 | } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && |
2661 | 0 | !matchedFont->SupportsVariantCaps(aRunScript, |
2662 | 0 | mStyle.variantCaps, |
2663 | 0 | petiteToSmallCaps, |
2664 | 0 | syntheticLower, |
2665 | 0 | syntheticUpper)) |
2666 | 0 | { |
2667 | 0 | // fallback for small-caps variant glyphs |
2668 | 0 | if (!matchedFont->InitFakeSmallCapsRun(aDrawTarget, aTextRun, |
2669 | 0 | aString + runStart, |
2670 | 0 | aOffset + runStart, |
2671 | 0 | matchedLength, |
2672 | 0 | range.matchType, |
2673 | 0 | range.orientation, |
2674 | 0 | aRunScript, |
2675 | 0 | syntheticLower, |
2676 | 0 | syntheticUpper)) { |
2677 | 0 | matchedFont = nullptr; |
2678 | 0 | } |
2679 | 0 | } else { |
2680 | 0 | // shape normally with variant feature enabled |
2681 | 0 | gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); |
2682 | 0 |
|
2683 | 0 | // adjust the shaping state if necessary |
2684 | 0 | if (ss == gfxTextRun::eShapingState_Normal) { |
2685 | 0 | aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFeature); |
2686 | 0 | } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) { |
2687 | 0 | // already have shaping results using fallback, need to redo |
2688 | 0 | aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); |
2689 | 0 | return; |
2690 | 0 | } |
2691 | 0 | |
2692 | 0 | // do glyph layout and record the resulting positioned glyphs |
2693 | 0 | aTextRun->AddGlyphRun(matchedFont, range.matchType, |
2694 | 0 | aOffset + runStart, (matchedLength > 0), |
2695 | 0 | range.orientation); |
2696 | 0 | if (!matchedFont->SplitAndInitTextRun(aDrawTarget, aTextRun, |
2697 | 0 | aString + runStart, |
2698 | 0 | aOffset + runStart, |
2699 | 0 | matchedLength, |
2700 | 0 | aRunScript, |
2701 | 0 | range.orientation)) { |
2702 | 0 | // glyph layout failed! treat as missing glyphs |
2703 | 0 | matchedFont = nullptr; |
2704 | 0 | } |
2705 | 0 | } |
2706 | 0 | } else { |
2707 | 0 | aTextRun->AddGlyphRun(mainFont, |
2708 | 0 | gfxTextRange::MatchType::kFontGroup, |
2709 | 0 | aOffset + runStart, (matchedLength > 0), |
2710 | 0 | range.orientation); |
2711 | 0 | } |
2712 | 0 |
|
2713 | 0 | if (!matchedFont) { |
2714 | 0 | // We need to set cluster boundaries (and mark spaces) so that |
2715 | 0 | // surrogate pairs, combining characters, etc behave properly, |
2716 | 0 | // even if we don't have glyphs for them |
2717 | 0 | aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart, |
2718 | 0 | matchedLength); |
2719 | 0 |
|
2720 | 0 | // various "missing" characters may need special handling, |
2721 | 0 | // so we check for them here |
2722 | 0 | uint32_t runLimit = runStart + matchedLength; |
2723 | 0 | for (uint32_t index = runStart; index < runLimit; index++) { |
2724 | 0 | T ch = aString[index]; |
2725 | 0 |
|
2726 | 0 | // tab and newline are not to be displayed as hexboxes, |
2727 | 0 | // but do need to be recorded in the textrun |
2728 | 0 | if (ch == '\n') { |
2729 | 0 | aTextRun->SetIsNewline(aOffset + index); |
2730 | 0 | continue; |
2731 | 0 | } |
2732 | 0 | if (ch == '\t') { |
2733 | 0 | aTextRun->SetIsTab(aOffset + index); |
2734 | 0 | continue; |
2735 | 0 | } |
2736 | 0 | |
2737 | 0 | // for 16-bit textruns only, check for surrogate pairs and |
2738 | 0 | // special Unicode spaces; omit these checks in 8-bit runs |
2739 | 0 | if (sizeof(T) == sizeof(char16_t)) { |
2740 | 0 | if (NS_IS_HIGH_SURROGATE(ch) && |
2741 | 0 | index + 1 < aLength && |
2742 | 0 | NS_IS_LOW_SURROGATE(aString[index + 1])) |
2743 | 0 | { |
2744 | 0 | uint32_t usv = |
2745 | 0 | SURROGATE_TO_UCS4(ch, aString[index + 1]); |
2746 | 0 | aTextRun->SetMissingGlyph(aOffset + index, |
2747 | 0 | usv, |
2748 | 0 | mainFont); |
2749 | 0 | index++; |
2750 | 0 | if (!mSkipDrawing && !IsPUA(usv)) { |
2751 | 0 | missingChars = true; |
2752 | 0 | } |
2753 | 0 | continue; |
2754 | 0 | } |
2755 | 0 |
|
2756 | 0 | // check if this is a known Unicode whitespace character that |
2757 | 0 | // we can render using the space glyph with a custom width |
2758 | 0 | gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); |
2759 | 0 | if (wid >= 0.0) { |
2760 | 0 | nscoord advance = |
2761 | 0 | aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); |
2762 | 0 | if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { |
2763 | 0 | aTextRun->GetCharacterGlyphs()[aOffset + index]. |
2764 | 0 | SetSimpleGlyph(advance, |
2765 | 0 | mainFont->GetSpaceGlyph()); |
2766 | 0 | } else { |
2767 | 0 | gfxTextRun::DetailedGlyph detailedGlyph; |
2768 | 0 | detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); |
2769 | 0 | detailedGlyph.mAdvance = advance; |
2770 | 0 | CompressedGlyph g = |
2771 | 0 | CompressedGlyph::MakeComplex(true, true, 1); |
2772 | 0 | aTextRun->SetGlyphs(aOffset + index, |
2773 | 0 | g, &detailedGlyph); |
2774 | 0 | } |
2775 | 0 | continue; |
2776 | 0 | } |
2777 | 0 | } |
2778 | 0 |
|
2779 | 0 | if (IsInvalidChar(ch)) { |
2780 | 0 | // invalid chars are left as zero-width/invisible |
2781 | 0 | continue; |
2782 | 0 | } |
2783 | 0 | |
2784 | 0 | // record char code so we can draw a box with the Unicode value |
2785 | 0 | aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont); |
2786 | 0 | if (!mSkipDrawing && !IsPUA(ch)) { |
2787 | 0 | missingChars = true; |
2788 | 0 | } |
2789 | 0 | } |
2790 | 0 | } |
2791 | 0 |
|
2792 | 0 | runStart += matchedLength; |
2793 | 0 | } |
2794 | 0 |
|
2795 | 0 | if (aMFR && missingChars) { |
2796 | 0 | aMFR->RecordScript(aRunScript); |
2797 | 0 | } |
2798 | 0 | } Unexecuted instantiation: void gfxFontGroup::InitScriptRun<unsigned char>(mozilla::gfx::DrawTarget*, gfxTextRun*, unsigned char const*, unsigned int, unsigned int, mozilla::unicode::Script, gfxMissingFontRecorder*) Unexecuted instantiation: void gfxFontGroup::InitScriptRun<char16_t>(mozilla::gfx::DrawTarget*, gfxTextRun*, char16_t const*, unsigned int, unsigned int, mozilla::unicode::Script, gfxMissingFontRecorder*) |
2799 | | |
2800 | | gfxTextRun * |
2801 | | gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, |
2802 | | gfx::ShapedTextFlags aFlags, |
2803 | | LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) |
2804 | 0 | { |
2805 | 0 | MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK), |
2806 | 0 | "flags here should only be used to specify orientation"); |
2807 | 0 | if (mCachedEllipsisTextRun && |
2808 | 0 | (mCachedEllipsisTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags && |
2809 | 0 | mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { |
2810 | 0 | return mCachedEllipsisTextRun.get(); |
2811 | 0 | } |
2812 | 0 | |
2813 | 0 | // Use a Unicode ellipsis if the font supports it, |
2814 | 0 | // otherwise use three ASCII periods as fallback. |
2815 | 0 | gfxFont* firstFont = GetFirstValidFont(uint32_t(kEllipsisChar[0])); |
2816 | 0 | nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0]) |
2817 | 0 | ? nsDependentString(kEllipsisChar, |
2818 | 0 | ArrayLength(kEllipsisChar) - 1) |
2819 | 0 | : nsDependentString(kASCIIPeriodsChar, |
2820 | 0 | ArrayLength(kASCIIPeriodsChar) - 1); |
2821 | 0 |
|
2822 | 0 | RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget(); |
2823 | 0 | Parameters params = { |
2824 | 0 | refDT, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel |
2825 | 0 | }; |
2826 | 0 | mCachedEllipsisTextRun = |
2827 | 0 | MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, |
2828 | 0 | aFlags, nsTextFrameUtils::Flags(), nullptr); |
2829 | 0 | if (!mCachedEllipsisTextRun) { |
2830 | 0 | return nullptr; |
2831 | 0 | } |
2832 | 0 | // don't let the presence of a cached ellipsis textrun prolong the |
2833 | 0 | // fontgroup's life |
2834 | 0 | mCachedEllipsisTextRun->ReleaseFontGroup(); |
2835 | 0 | return mCachedEllipsisTextRun.get(); |
2836 | 0 | } |
2837 | | |
2838 | | gfxFont* |
2839 | | gfxFontGroup::FindFallbackFaceForChar(gfxFontFamily* aFamily, uint32_t aCh) |
2840 | 0 | { |
2841 | 0 | GlobalFontMatch data(aCh, mStyle); |
2842 | 0 | aFamily->SearchAllFontsForChar(&data); |
2843 | 0 | gfxFontEntry* fe = data.mBestMatch; |
2844 | 0 | if (!fe) { |
2845 | 0 | return nullptr; |
2846 | 0 | } |
2847 | 0 | return fe->FindOrMakeFont(&mStyle); |
2848 | 0 | } |
2849 | | |
2850 | | gfxFloat |
2851 | | gfxFontGroup::GetUnderlineOffset() |
2852 | 0 | { |
2853 | 0 | if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) { |
2854 | 0 | // if the fontlist contains a bad underline font, make the underline |
2855 | 0 | // offset the min of the first valid font and bad font underline offsets |
2856 | 0 | uint32_t len = mFonts.Length(); |
2857 | 0 | for (uint32_t i = 0; i < len; i++) { |
2858 | 0 | FamilyFace& ff = mFonts[i]; |
2859 | 0 | if (!ff.IsUserFontContainer() && |
2860 | 0 | !ff.FontEntry()->IsUserFont() && |
2861 | 0 | ff.Family() && |
2862 | 0 | ff.Family()->IsBadUnderlineFamily()) { |
2863 | 0 | gfxFont* font = GetFontAt(i); |
2864 | 0 | if (!font) { |
2865 | 0 | continue; |
2866 | 0 | } |
2867 | 0 | gfxFloat bad = font->GetMetrics(gfxFont::eHorizontal). |
2868 | 0 | underlineOffset; |
2869 | 0 | gfxFloat first = |
2870 | 0 | GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal). |
2871 | 0 | underlineOffset; |
2872 | 0 | mUnderlineOffset = std::min(first, bad); |
2873 | 0 | return mUnderlineOffset; |
2874 | 0 | } |
2875 | 0 | } |
2876 | 0 |
|
2877 | 0 | // no bad underline fonts, use the first valid font's metric |
2878 | 0 | mUnderlineOffset = GetFirstValidFont()-> |
2879 | 0 | GetMetrics(gfxFont::eHorizontal).underlineOffset; |
2880 | 0 | } |
2881 | 0 |
|
2882 | 0 | return mUnderlineOffset; |
2883 | 0 | } |
2884 | | |
2885 | 0 | #define NARROW_NO_BREAK_SPACE 0x202fu |
2886 | | |
2887 | | gfxFont* |
2888 | | gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh, |
2889 | | Script aRunScript, gfxFont *aPrevMatchedFont, |
2890 | | gfxTextRange::MatchType* aMatchType) |
2891 | 0 | { |
2892 | 0 | // If the char is a cluster extender, we want to use the same font as the |
2893 | 0 | // preceding character if possible. This is preferable to using the font |
2894 | 0 | // group because it avoids breaks in shaping within a cluster. |
2895 | 0 | if (aPrevMatchedFont && IsClusterExtender(aCh) && |
2896 | 0 | aPrevMatchedFont->HasCharacter(aCh)) { |
2897 | 0 | return aPrevMatchedFont; |
2898 | 0 | } |
2899 | 0 | |
2900 | 0 | // Special cases for NNBSP (as used in Mongolian): |
2901 | 0 | if (aCh == NARROW_NO_BREAK_SPACE) { |
2902 | 0 | // If there is no preceding character, try the font that we'd use |
2903 | 0 | // for the next char (unless it's just another NNBSP; we don't try |
2904 | 0 | // to look ahead through a whole run of them). |
2905 | 0 | if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) { |
2906 | 0 | gfxFont* nextFont = |
2907 | 0 | FindFontForChar(aNextCh, 0, 0, aRunScript, aPrevMatchedFont, |
2908 | 0 | aMatchType); |
2909 | 0 | if (nextFont && nextFont->HasCharacter(aCh)) { |
2910 | 0 | return nextFont; |
2911 | 0 | } |
2912 | 0 | } |
2913 | 0 | // Otherwise, treat NNBSP like a cluster extender (as above) and try |
2914 | 0 | // to continue the preceding font run. |
2915 | 0 | if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { |
2916 | 0 | return aPrevMatchedFont; |
2917 | 0 | } |
2918 | 0 | } |
2919 | 0 | |
2920 | 0 | // To optimize common cases, try the first font in the font-group |
2921 | 0 | // before going into the more detailed checks below |
2922 | 0 | uint32_t nextIndex = 0; |
2923 | 0 | bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); |
2924 | 0 | bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); |
2925 | 0 | bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); |
2926 | 0 |
|
2927 | 0 | if (!isJoinControl && !wasJoinCauser && !isVarSelector) { |
2928 | 0 | gfxFont* firstFont = GetFontAt(0, aCh); |
2929 | 0 | if (firstFont) { |
2930 | 0 | if (firstFont->HasCharacter(aCh)) { |
2931 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
2932 | 0 | gfxTextRange::MatchType(mFonts[0].Generic()); |
2933 | 0 | return firstFont; |
2934 | 0 | } |
2935 | 0 | |
2936 | 0 | gfxFont* font = nullptr; |
2937 | 0 | if (mFonts[0].CheckForFallbackFaces()) { |
2938 | 0 | font = FindFallbackFaceForChar(mFonts[0].Family(), aCh); |
2939 | 0 | } else if (!firstFont->GetFontEntry()->IsUserFont()) { |
2940 | 0 | // For platform fonts (but not userfonts), we may need to do |
2941 | 0 | // fallback within the family to handle cases where some faces |
2942 | 0 | // such as Italic or Black have reduced character sets compared |
2943 | 0 | // to the family's Regular face. |
2944 | 0 | font = FindFallbackFaceForChar(mFonts[0].Family(), aCh); |
2945 | 0 | } |
2946 | 0 | if (font) { |
2947 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
2948 | 0 | gfxTextRange::MatchType(mFonts[0].Generic()); |
2949 | 0 | return font; |
2950 | 0 | } |
2951 | 0 | } |
2952 | 0 | |
2953 | 0 | // we don't need to check the first font again below |
2954 | 0 | ++nextIndex; |
2955 | 0 | } |
2956 | 0 |
|
2957 | 0 | if (aPrevMatchedFont) { |
2958 | 0 | // Don't switch fonts for control characters, regardless of |
2959 | 0 | // whether they are present in the current font, as they won't |
2960 | 0 | // actually be rendered (see bug 716229) |
2961 | 0 | if (isJoinControl || |
2962 | 0 | GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { |
2963 | 0 | return aPrevMatchedFont; |
2964 | 0 | } |
2965 | 0 | |
2966 | 0 | // if previous character was a join-causer (ZWJ), |
2967 | 0 | // use the same font as the previous range if we can |
2968 | 0 | if (wasJoinCauser) { |
2969 | 0 | if (aPrevMatchedFont->HasCharacter(aCh)) { |
2970 | 0 | return aPrevMatchedFont; |
2971 | 0 | } |
2972 | 0 | } |
2973 | 0 | } |
2974 | 0 | |
2975 | 0 | // if this character is a variation selector, |
2976 | 0 | // use the previous font regardless of whether it supports VS or not. |
2977 | 0 | // otherwise the text run will be divided. |
2978 | 0 | if (isVarSelector) { |
2979 | 0 | if (aPrevMatchedFont) { |
2980 | 0 | return aPrevMatchedFont; |
2981 | 0 | } |
2982 | 0 | // VS alone. it's meaningless to search different fonts |
2983 | 0 | return nullptr; |
2984 | 0 | } |
2985 | 0 | |
2986 | 0 | // 1. check remaining fonts in the font group |
2987 | 0 | uint32_t fontListLength = mFonts.Length(); |
2988 | 0 | for (uint32_t i = nextIndex; i < fontListLength; i++) { |
2989 | 0 | FamilyFace& ff = mFonts[i]; |
2990 | 0 | if (ff.IsInvalid() || ff.IsLoading()) { |
2991 | 0 | continue; |
2992 | 0 | } |
2993 | 0 | |
2994 | 0 | // if available, use already made gfxFont and check for character |
2995 | 0 | gfxFont* font = ff.Font(); |
2996 | 0 | if (font) { |
2997 | 0 | if (font->HasCharacter(aCh)) { |
2998 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
2999 | 0 | gfxTextRange::MatchType(ff.Generic()); |
3000 | 0 | return font; |
3001 | 0 | } |
3002 | 0 | continue; |
3003 | 0 | } |
3004 | 0 | |
3005 | 0 | // don't have a gfxFont yet, test before building |
3006 | 0 | gfxFontEntry *fe = ff.FontEntry(); |
3007 | 0 | if (fe->mIsUserFontContainer) { |
3008 | 0 | // for userfonts, need to test both the unicode range map and |
3009 | 0 | // the cmap of the platform font entry |
3010 | 0 | gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); |
3011 | 0 |
|
3012 | 0 | // never match a character outside the defined unicode range |
3013 | 0 | if (!ufe->CharacterInUnicodeRange(aCh)) { |
3014 | 0 | continue; |
3015 | 0 | } |
3016 | 0 | |
3017 | 0 | // load if not already loaded but only if no other font in similar |
3018 | 0 | // range within family is loading |
3019 | 0 | if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && |
3020 | 0 | !mSkipDrawing && !FontLoadingForFamily(ff.Family(), aCh)) { |
3021 | 0 | ufe->Load(); |
3022 | 0 | ff.CheckState(mSkipDrawing); |
3023 | 0 | } |
3024 | 0 | gfxFontEntry* pfe = ufe->GetPlatformFontEntry(); |
3025 | 0 | if (pfe && pfe->HasCharacter(aCh)) { |
3026 | 0 | font = GetFontAt(i, aCh); |
3027 | 0 | if (font) { |
3028 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
3029 | 0 | gfxTextRange::MatchType(mFonts[i].Generic()); |
3030 | 0 | return font; |
3031 | 0 | } |
3032 | 0 | } |
3033 | 0 | } else if (fe->HasCharacter(aCh)) { |
3034 | 0 | // for normal platform fonts, after checking the cmap |
3035 | 0 | // build the font via GetFontAt |
3036 | 0 | font = GetFontAt(i, aCh); |
3037 | 0 | if (font) { |
3038 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
3039 | 0 | gfxTextRange::MatchType(mFonts[i].Generic()); |
3040 | 0 | return font; |
3041 | 0 | } |
3042 | 0 | } |
3043 | 0 | |
3044 | 0 | // check other family faces if needed |
3045 | 0 | if (ff.CheckForFallbackFaces()) { |
3046 | 0 | NS_ASSERTION(i == 0 ? true : |
3047 | 0 | !mFonts[i-1].CheckForFallbackFaces() || |
3048 | 0 | !mFonts[i-1].Family()->Name().Equals(ff.Family()->Name()), |
3049 | 0 | "should only do fallback once per font family"); |
3050 | 0 | font = FindFallbackFaceForChar(ff.Family(), aCh); |
3051 | 0 | if (font) { |
3052 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
3053 | 0 | gfxTextRange::MatchType(ff.Generic()); |
3054 | 0 | return font; |
3055 | 0 | } |
3056 | 0 | } else { |
3057 | 0 | // For platform fonts, but not user fonts, consider intra-family |
3058 | 0 | // fallback to handle styles with reduced character sets (see |
3059 | 0 | // also above). |
3060 | 0 | fe = ff.FontEntry(); |
3061 | 0 | if (!fe->mIsUserFontContainer && !fe->IsUserFont()) { |
3062 | 0 | font = FindFallbackFaceForChar(ff.Family(), aCh); |
3063 | 0 | if (font) { |
3064 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup | |
3065 | 0 | gfxTextRange::MatchType(ff.Generic()); |
3066 | 0 | return font; |
3067 | 0 | } |
3068 | 0 | } |
3069 | 0 | } |
3070 | 0 | } |
3071 | 0 |
|
3072 | 0 | if (fontListLength == 0) { |
3073 | 0 | gfxFont* defaultFont = GetDefaultFont(); |
3074 | 0 | if (defaultFont->HasCharacter(aCh)) { |
3075 | 0 | *aMatchType = gfxTextRange::MatchType::kFontGroup; |
3076 | 0 | return defaultFont; |
3077 | 0 | } |
3078 | 0 | } |
3079 | 0 | |
3080 | 0 | // if character is in Private Use Area, don't do matching against pref or system fonts |
3081 | 0 | if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) |
3082 | 0 | return nullptr; |
3083 | 0 | |
3084 | 0 | // 2. search pref fonts |
3085 | 0 | gfxFont* font = WhichPrefFontSupportsChar(aCh, aNextCh); |
3086 | 0 | if (font) { |
3087 | 0 | *aMatchType = gfxTextRange::MatchType::kPrefsFallback; |
3088 | 0 | return font; |
3089 | 0 | } |
3090 | 0 | |
3091 | 0 | // 3. use fallback fonts |
3092 | 0 | // -- before searching for something else check the font used for the previous character |
3093 | 0 | if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { |
3094 | 0 | *aMatchType = gfxTextRange::MatchType::kSystemFallback; |
3095 | 0 | return aPrevMatchedFont; |
3096 | 0 | } |
3097 | 0 | |
3098 | 0 | // for known "space" characters, don't do a full system-fallback search; |
3099 | 0 | // we'll synthesize appropriate-width spaces instead of missing-glyph boxes |
3100 | 0 | if (GetGeneralCategory(aCh) == |
3101 | 0 | HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && |
3102 | 0 | GetFirstValidFont()->SynthesizeSpaceWidth(aCh) >= 0.0) |
3103 | 0 | { |
3104 | 0 | return nullptr; |
3105 | 0 | } |
3106 | 0 | |
3107 | 0 | // -- otherwise look for other stuff |
3108 | 0 | *aMatchType = gfxTextRange::MatchType::kSystemFallback; |
3109 | 0 | return WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript); |
3110 | 0 | } |
3111 | | |
3112 | | template<typename T> |
3113 | | void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges, |
3114 | | const T *aString, uint32_t aLength, |
3115 | | Script aRunScript, |
3116 | | gfx::ShapedTextFlags aOrientation) |
3117 | 0 | { |
3118 | 0 | NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); |
3119 | 0 | NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); |
3120 | 0 |
|
3121 | 0 | uint32_t prevCh = 0; |
3122 | 0 | uint32_t nextCh = aString[0]; |
3123 | 0 | if (sizeof(T) == sizeof(char16_t)) { |
3124 | 0 | if (aLength > 1 && NS_IS_HIGH_SURROGATE(nextCh) && |
3125 | 0 | NS_IS_LOW_SURROGATE(aString[1])) { |
3126 | 0 | nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]); |
3127 | 0 | } |
3128 | 0 | } |
3129 | 0 | int32_t lastRangeIndex = -1; |
3130 | 0 |
|
3131 | 0 | // initialize prevFont to the group's primary font, so that this will be |
3132 | 0 | // used for string-initial control chars, etc rather than risk hitting font |
3133 | 0 | // fallback for these (bug 716229) |
3134 | 0 | FontFamilyType generic = eFamily_none; |
3135 | 0 | gfxFont *prevFont = GetFirstValidFont(' ', &generic); |
3136 | 0 |
|
3137 | 0 | // if we use the initial value of prevFont, we treat this as a match from |
3138 | 0 | // the font group; fixes bug 978313 |
3139 | 0 | gfxTextRange::MatchType matchType = gfxTextRange::MatchType::kFontGroup | |
3140 | 0 | gfxTextRange::MatchType(generic); |
3141 | 0 |
|
3142 | 0 | for (uint32_t i = 0; i < aLength; i++) { |
3143 | 0 |
|
3144 | 0 | const uint32_t origI = i; // save off in case we increase for surrogate |
3145 | 0 |
|
3146 | 0 | // set up current ch |
3147 | 0 | uint32_t ch = nextCh; |
3148 | 0 |
|
3149 | 0 | // Get next char (if any) so that FindFontForChar can look ahead |
3150 | 0 | // for a possible variation selector. |
3151 | 0 |
|
3152 | 0 | if (sizeof(T) == sizeof(char16_t)) { |
3153 | 0 | // In 16-bit case only, check for surrogate pairs. |
3154 | 0 | if (ch > 0xffffu) { |
3155 | 0 | i++; |
3156 | 0 | } |
3157 | 0 | if (i < aLength - 1) { |
3158 | 0 | nextCh = aString[i + 1]; |
3159 | 0 | if ((i + 2 < aLength) && NS_IS_HIGH_SURROGATE(nextCh) && |
3160 | 0 | NS_IS_LOW_SURROGATE(aString[i + 2])) { |
3161 | 0 | nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]); |
3162 | 0 | } |
3163 | 0 | } else { |
3164 | 0 | nextCh = 0; |
3165 | 0 | } |
3166 | 0 | } else { |
3167 | 0 | // 8-bit case is trivial. |
3168 | 0 | nextCh = i < aLength - 1 ? aString[i + 1] : 0; |
3169 | 0 | } |
3170 | 0 |
|
3171 | 0 | if (ch == 0xa0) { |
3172 | 0 | ch = ' '; |
3173 | 0 | } |
3174 | 0 |
|
3175 | 0 | gfxFont* font; |
3176 | 0 |
|
3177 | 0 | // Find the font for this char; but try to avoid calling the expensive |
3178 | 0 | // FindFontForChar method for the most common case, where the first |
3179 | 0 | // font in the list supports the current char, and it is not one of |
3180 | 0 | // the special cases where FindFontForChar will attempt to propagate |
3181 | 0 | // the font selected for an adjacent character. |
3182 | 0 | if ((font = GetFontAt(0, ch)) != nullptr |
3183 | 0 | && font->HasCharacter(ch) |
3184 | 0 | && (sizeof(T) == sizeof(uint8_t) |
3185 | 0 | || (!IsClusterExtender(ch) |
3186 | 0 | && ch != NARROW_NO_BREAK_SPACE |
3187 | 0 | && !gfxFontUtils::IsJoinControl(ch) |
3188 | 0 | && !gfxFontUtils::IsJoinCauser(prevCh) |
3189 | 0 | && !gfxFontUtils::IsVarSelector(ch)))) { |
3190 | 0 | matchType = gfxTextRange::MatchType::kFontGroup | |
3191 | 0 | gfxTextRange::MatchType(mFonts[0].Generic()); |
3192 | 0 | } else { |
3193 | 0 | font = FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, |
3194 | 0 | &matchType); |
3195 | 0 | } |
3196 | 0 |
|
3197 | 0 | #ifndef RELEASE_OR_BETA |
3198 | 0 | if (MOZ_UNLIKELY(mTextPerf)) { |
3199 | 0 | if (matchType & gfxTextRange::MatchType::kPrefsFallback) { |
3200 | 0 | mTextPerf->current.fallbackPrefs++; |
3201 | 0 | } else if (matchType & gfxTextRange::MatchType::kSystemFallback) { |
3202 | 0 | mTextPerf->current.fallbackSystem++; |
3203 | 0 | } |
3204 | 0 | } |
3205 | 0 | #endif |
3206 | 0 |
|
3207 | 0 | prevCh = ch; |
3208 | 0 |
|
3209 | 0 | ShapedTextFlags orient = aOrientation; |
3210 | 0 | if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { |
3211 | 0 | // For CSS text-orientation:mixed, we need to resolve orientation |
3212 | 0 | // on a per-character basis using the UTR50 orientation property. |
3213 | 0 | switch (GetVerticalOrientation(ch)) { |
3214 | 0 | case VERTICAL_ORIENTATION_U: |
3215 | 0 | case VERTICAL_ORIENTATION_Tu: |
3216 | 0 | orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; |
3217 | 0 | break; |
3218 | 0 | case VERTICAL_ORIENTATION_Tr: { |
3219 | 0 | // We check for a vertical presentation form first as that's |
3220 | 0 | // likely to be cheaper than inspecting lookups to see if the |
3221 | 0 | // 'vert' feature is going to handle this character, and if the |
3222 | 0 | // presentation form is available then it will be used as |
3223 | 0 | // fallback if needed, so it's OK if the feature is missing. |
3224 | 0 | uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch); |
3225 | 0 | orient = (!font || |
3226 | 0 | (v && font->HasCharacter(v)) || |
3227 | 0 | font->FeatureWillHandleChar(aRunScript, |
3228 | 0 | HB_TAG('v','e','r','t'), |
3229 | 0 | ch)) |
3230 | 0 | ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT |
3231 | 0 | : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
3232 | 0 | break; |
3233 | 0 | } |
3234 | 0 | case VERTICAL_ORIENTATION_R: |
3235 | 0 | orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; |
3236 | 0 | break; |
3237 | 0 | } |
3238 | 0 | } |
3239 | 0 | |
3240 | 0 | if (lastRangeIndex == -1) { |
3241 | 0 | // first char ==> make a new range |
3242 | 0 | aRanges.AppendElement(gfxTextRange(0, 1, font, matchType, orient)); |
3243 | 0 | lastRangeIndex++; |
3244 | 0 | prevFont = font; |
3245 | 0 | } else { |
3246 | 0 | // if font or orientation has changed, make a new range... |
3247 | 0 | // unless ch is a variation selector (bug 1248248) |
3248 | 0 | gfxTextRange& prevRange = aRanges[lastRangeIndex]; |
3249 | 0 | if (prevRange.font != font || |
3250 | 0 | (prevRange.orientation != orient && !IsClusterExtender(ch))) { |
3251 | 0 | // close out the previous range |
3252 | 0 | prevRange.end = origI; |
3253 | 0 | aRanges.AppendElement(gfxTextRange(origI, i + 1, |
3254 | 0 | font, matchType, orient)); |
3255 | 0 | lastRangeIndex++; |
3256 | 0 |
|
3257 | 0 | // update prevFont for the next match, *unless* we switched |
3258 | 0 | // fonts on a ZWJ, in which case propagating the changed font |
3259 | 0 | // is probably not a good idea (see bug 619511) |
3260 | 0 | if (sizeof(T) == sizeof(uint8_t) || |
3261 | 0 | !gfxFontUtils::IsJoinCauser(ch)) |
3262 | 0 | { |
3263 | 0 | prevFont = font; |
3264 | 0 | } |
3265 | 0 | } else { |
3266 | 0 | prevRange.matchType |= matchType; |
3267 | 0 | } |
3268 | 0 | } |
3269 | 0 | } |
3270 | 0 |
|
3271 | 0 | aRanges[lastRangeIndex].end = aLength; |
3272 | 0 |
|
3273 | 0 | #ifndef RELEASE_OR_BETA |
3274 | 0 | LogModule* log = mStyle.systemFont |
3275 | 0 | ? gfxPlatform::GetLog(eGfxLog_textrunui) |
3276 | 0 | : gfxPlatform::GetLog(eGfxLog_textrun); |
3277 | 0 |
|
3278 | 0 | if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { |
3279 | 0 | nsAutoCString lang; |
3280 | 0 | mStyle.language->ToUTF8String(lang); |
3281 | 0 | nsAutoCString families; |
3282 | 0 | mFamilyList.ToString(families); |
3283 | 0 |
|
3284 | 0 | // collect the font matched for each range |
3285 | 0 | nsAutoCString fontMatches; |
3286 | 0 | for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) { |
3287 | 0 | const gfxTextRange& r = aRanges[i]; |
3288 | 0 | nsAutoCString matchTypes; |
3289 | 0 | if (r.matchType & gfxTextRange::MatchType::kFontGroup) { |
3290 | 0 | matchTypes.AppendLiteral("list"); |
3291 | 0 | } |
3292 | 0 | if (r.matchType & gfxTextRange::MatchType::kPrefsFallback) { |
3293 | 0 | if (!matchTypes.IsEmpty()) { |
3294 | 0 | matchTypes.AppendLiteral(","); |
3295 | 0 | } |
3296 | 0 | matchTypes.AppendLiteral("prefs"); |
3297 | 0 | } |
3298 | 0 | if (r.matchType & gfxTextRange::MatchType::kPrefsFallback) { |
3299 | 0 | if (!matchTypes.IsEmpty()) { |
3300 | 0 | matchTypes.AppendLiteral(","); |
3301 | 0 | } |
3302 | 0 | matchTypes.AppendLiteral("sys"); |
3303 | 0 | } |
3304 | 0 | fontMatches.AppendPrintf(" [%u:%u] %.200s (%s)", r.start, r.end, |
3305 | 0 | (r.font.get() ? |
3306 | 0 | r.font->GetName().get() : "<null>"), |
3307 | 0 | matchTypes.get()); |
3308 | 0 | } |
3309 | 0 | MOZ_LOG(log, LogLevel::Debug,\ |
3310 | 0 | ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d" |
3311 | 0 | "%s\n", |
3312 | 0 | (mStyle.systemFont ? "textrunui" : "textrun"), |
3313 | 0 | families.get(), |
3314 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_serif ? |
3315 | 0 | "serif" : |
3316 | 0 | (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? |
3317 | 0 | "sans-serif" : "none")), |
3318 | 0 | lang.get(), static_cast<int>(aRunScript), |
3319 | 0 | fontMatches.get())); |
3320 | 0 | } |
3321 | 0 | #endif |
3322 | 0 | } Unexecuted instantiation: void gfxFontGroup::ComputeRanges<unsigned char>(nsTArray<gfxTextRange>&, unsigned char const*, unsigned int, mozilla::unicode::Script, mozilla::gfx::ShapedTextFlags) Unexecuted instantiation: void gfxFontGroup::ComputeRanges<char16_t>(nsTArray<gfxTextRange>&, char16_t const*, unsigned int, mozilla::unicode::Script, mozilla::gfx::ShapedTextFlags) |
3323 | | |
3324 | | gfxUserFontSet* |
3325 | | gfxFontGroup::GetUserFontSet() |
3326 | 0 | { |
3327 | 0 | return mUserFontSet; |
3328 | 0 | } |
3329 | | |
3330 | | void |
3331 | | gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) |
3332 | 0 | { |
3333 | 0 | if (aUserFontSet == mUserFontSet) { |
3334 | 0 | return; |
3335 | 0 | } |
3336 | 0 | mUserFontSet = aUserFontSet; |
3337 | 0 | mCurrGeneration = GetGeneration() - 1; |
3338 | 0 | UpdateUserFonts(); |
3339 | 0 | } |
3340 | | |
3341 | | uint64_t |
3342 | | gfxFontGroup::GetGeneration() |
3343 | 0 | { |
3344 | 0 | if (!mUserFontSet) |
3345 | 0 | return 0; |
3346 | 0 | return mUserFontSet->GetGeneration(); |
3347 | 0 | } |
3348 | | |
3349 | | uint64_t |
3350 | | gfxFontGroup::GetRebuildGeneration() |
3351 | 0 | { |
3352 | 0 | if (!mUserFontSet) |
3353 | 0 | return 0; |
3354 | 0 | return mUserFontSet->GetRebuildGeneration(); |
3355 | 0 | } |
3356 | | |
3357 | | void |
3358 | | gfxFontGroup::UpdateUserFonts() |
3359 | 0 | { |
3360 | 0 | if (mCurrGeneration < GetRebuildGeneration()) { |
3361 | 0 | // fonts in userfont set changed, need to redo the fontlist |
3362 | 0 | mFonts.Clear(); |
3363 | 0 | ClearCachedData(); |
3364 | 0 | BuildFontList(); |
3365 | 0 | mCurrGeneration = GetGeneration(); |
3366 | 0 | } else if (mCurrGeneration != GetGeneration()) { |
3367 | 0 | // load state change occurred, verify load state and validity of fonts |
3368 | 0 | ClearCachedData(); |
3369 | 0 |
|
3370 | 0 | uint32_t len = mFonts.Length(); |
3371 | 0 | for (uint32_t i = 0; i < len; i++) { |
3372 | 0 | FamilyFace& ff = mFonts[i]; |
3373 | 0 | if (ff.Font() || !ff.IsUserFontContainer()) { |
3374 | 0 | continue; |
3375 | 0 | } |
3376 | 0 | ff.CheckState(mSkipDrawing); |
3377 | 0 | } |
3378 | 0 |
|
3379 | 0 | mCurrGeneration = GetGeneration(); |
3380 | 0 | } |
3381 | 0 | } |
3382 | | |
3383 | | bool |
3384 | | gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) |
3385 | 0 | { |
3386 | 0 | UpdateUserFonts(); |
3387 | 0 | // search through the fonts list for a specific user font |
3388 | 0 | uint32_t len = mFonts.Length(); |
3389 | 0 | for (uint32_t i = 0; i < len; i++) { |
3390 | 0 | FamilyFace& ff = mFonts[i]; |
3391 | 0 | if (ff.EqualsUserFont(aUserFont)) { |
3392 | 0 | return true; |
3393 | 0 | } |
3394 | 0 | } |
3395 | 0 | return false; |
3396 | 0 | } |
3397 | | |
3398 | | gfxFont* |
3399 | | gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh, uint32_t aNextCh) |
3400 | 0 | { |
3401 | 0 | eFontPrefLang charLang; |
3402 | 0 | gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); |
3403 | 0 |
|
3404 | 0 | EmojiPresentation emoji = GetEmojiPresentation(aCh); |
3405 | 0 | if ((emoji != EmojiPresentation::TextOnly && |
3406 | 0 | (aNextCh == kVariationSelector16 || |
3407 | 0 | (emoji == EmojiPresentation::EmojiDefault && |
3408 | 0 | aNextCh != kVariationSelector15)))) { |
3409 | 0 | charLang = eFontPrefLang_Emoji; |
3410 | 0 | } else { |
3411 | 0 | // get the pref font list if it hasn't been set up already |
3412 | 0 | uint32_t unicodeRange = FindCharUnicodeRange(aCh); |
3413 | 0 | charLang = pfl->GetFontPrefLangFor(unicodeRange); |
3414 | 0 | } |
3415 | 0 |
|
3416 | 0 | // if the last pref font was the first family in the pref list, no need to recheck through a list of families |
3417 | 0 | if (mLastPrefFont && charLang == mLastPrefLang && |
3418 | 0 | mLastPrefFirstFont && mLastPrefFont->HasCharacter(aCh)) { |
3419 | 0 | return mLastPrefFont; |
3420 | 0 | } |
3421 | 0 | |
3422 | 0 | // based on char lang and page lang, set up list of pref lang fonts to check |
3423 | 0 | eFontPrefLang prefLangs[kMaxLenPrefLangList]; |
3424 | 0 | uint32_t i, numLangs = 0; |
3425 | 0 |
|
3426 | 0 | pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); |
3427 | 0 |
|
3428 | 0 | for (i = 0; i < numLangs; i++) { |
3429 | 0 | eFontPrefLang currentLang = prefLangs[i]; |
3430 | 0 | mozilla::FontFamilyType defaultGeneric = |
3431 | 0 | pfl->GetDefaultGeneric(currentLang); |
3432 | 0 | nsTArray<RefPtr<gfxFontFamily>>* families = |
3433 | 0 | pfl->GetPrefFontsLangGroup(defaultGeneric, currentLang); |
3434 | 0 | NS_ASSERTION(families, "no pref font families found"); |
3435 | 0 |
|
3436 | 0 | // find the first pref font that includes the character |
3437 | 0 | uint32_t j, numPrefs; |
3438 | 0 | numPrefs = families->Length(); |
3439 | 0 | for (j = 0; j < numPrefs; j++) { |
3440 | 0 | // look up the appropriate face |
3441 | 0 | gfxFontFamily *family = (*families)[j]; |
3442 | 0 | if (!family) { |
3443 | 0 | continue; |
3444 | 0 | } |
3445 | 0 | |
3446 | 0 | // if a pref font is used, it's likely to be used again in the same text run. |
3447 | 0 | // the style doesn't change so the face lookup can be cached rather than calling |
3448 | 0 | // FindOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent |
3449 | 0 | // pref font lookups |
3450 | 0 | if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { |
3451 | 0 | return mLastPrefFont; |
3452 | 0 | } |
3453 | 0 | |
3454 | 0 | gfxFontEntry *fe = family->FindFontForStyle(mStyle); |
3455 | 0 | if (!fe) { |
3456 | 0 | continue; |
3457 | 0 | } |
3458 | 0 | |
3459 | 0 | // if ch in cmap, create and return a gfxFont |
3460 | 0 | if (fe->HasCharacter(aCh)) { |
3461 | 0 | gfxFont* prefFont = fe->FindOrMakeFont(&mStyle); |
3462 | 0 | if (!prefFont) { |
3463 | 0 | continue; |
3464 | 0 | } |
3465 | 0 | mLastPrefFamily = family; |
3466 | 0 | mLastPrefFont = prefFont; |
3467 | 0 | mLastPrefLang = charLang; |
3468 | 0 | mLastPrefFirstFont = (i == 0 && j == 0); |
3469 | 0 | return prefFont; |
3470 | 0 | } |
3471 | 0 |
|
3472 | 0 | // If the char was not available, see if we can fall back to an |
3473 | 0 | // alternative face in the same family. |
3474 | 0 | gfxFont* prefFont = FindFallbackFaceForChar(family, aCh); |
3475 | 0 | if (prefFont) { |
3476 | 0 | mLastPrefFamily = family; |
3477 | 0 | mLastPrefFont = prefFont; |
3478 | 0 | mLastPrefLang = charLang; |
3479 | 0 | mLastPrefFirstFont = (i == 0 && j == 0); |
3480 | 0 | return prefFont; |
3481 | 0 | } |
3482 | 0 | } |
3483 | 0 | } |
3484 | 0 |
|
3485 | 0 | return nullptr; |
3486 | 0 | } |
3487 | | |
3488 | | gfxFont* |
3489 | | gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, uint32_t aNextCh, |
3490 | | Script aRunScript) |
3491 | 0 | { |
3492 | 0 | gfxFontEntry *fe = |
3493 | 0 | gfxPlatformFontList::PlatformFontList()-> |
3494 | 0 | SystemFindFontForChar(aCh, aNextCh, aRunScript, &mStyle); |
3495 | 0 | if (fe) { |
3496 | 0 | return fe->FindOrMakeFont(&mStyle); |
3497 | 0 | } |
3498 | 0 | |
3499 | 0 | return nullptr; |
3500 | 0 | } |
3501 | | |
3502 | | void |
3503 | | gfxMissingFontRecorder::Flush() |
3504 | 0 | { |
3505 | 0 | static bool mNotifiedFontsInitialized = false; |
3506 | 0 | static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords]; |
3507 | 0 | if (!mNotifiedFontsInitialized) { |
3508 | 0 | memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts)); |
3509 | 0 | mNotifiedFontsInitialized = true; |
3510 | 0 | } |
3511 | 0 |
|
3512 | 0 | nsAutoString fontNeeded; |
3513 | 0 | for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) { |
3514 | 0 | mMissingFonts[i] &= ~mNotifiedFonts[i]; |
3515 | 0 | if (!mMissingFonts[i]) { |
3516 | 0 | continue; |
3517 | 0 | } |
3518 | 0 | for (uint32_t j = 0; j < 32; ++j) { |
3519 | 0 | if (!(mMissingFonts[i] & (1 << j))) { |
3520 | 0 | continue; |
3521 | 0 | } |
3522 | 0 | mNotifiedFonts[i] |= (1 << j); |
3523 | 0 | if (!fontNeeded.IsEmpty()) { |
3524 | 0 | fontNeeded.Append(char16_t(',')); |
3525 | 0 | } |
3526 | 0 | uint32_t sc = i * 32 + j; |
3527 | 0 | MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES), |
3528 | 0 | "how did we set the bit for an invalid script code?"); |
3529 | 0 | uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc)); |
3530 | 0 | fontNeeded.Append(char16_t(tag >> 24)); |
3531 | 0 | fontNeeded.Append(char16_t((tag >> 16) & 0xff)); |
3532 | 0 | fontNeeded.Append(char16_t((tag >> 8) & 0xff)); |
3533 | 0 | fontNeeded.Append(char16_t(tag & 0xff)); |
3534 | 0 | } |
3535 | 0 | mMissingFonts[i] = 0; |
3536 | 0 | } |
3537 | 0 | if (!fontNeeded.IsEmpty()) { |
3538 | 0 | nsCOMPtr<nsIObserverService> service = GetObserverService(); |
3539 | 0 | service->NotifyObservers(nullptr, "font-needed", fontNeeded.get()); |
3540 | 0 | } |
3541 | 0 | } |