/src/mozilla-central/gfx/thebes/gfxGraphiteShaper.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "gfxGraphiteShaper.h" |
7 | | #include "nsString.h" |
8 | | #include "gfxContext.h" |
9 | | #include "gfxFontConstants.h" |
10 | | #include "gfxTextRun.h" |
11 | | |
12 | | #include "graphite2/Font.h" |
13 | | #include "graphite2/Segment.h" |
14 | | |
15 | | #include "harfbuzz/hb.h" |
16 | | |
17 | | #define FloatToFixed(f) (65536 * (f)) |
18 | 0 | #define FixedToFloat(f) ((f) * (1.0 / 65536.0)) |
19 | | // Right shifts of negative (signed) integers are undefined, as are overflows |
20 | | // when converting unsigned to negative signed integers. |
21 | | // (If speed were an issue we could make some 2's complement assumptions.) |
22 | | #define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \ |
23 | | : -((32767 - (f)) >> 16)) |
24 | | |
25 | | using namespace mozilla; // for AutoSwap_* types |
26 | | |
27 | | /* |
28 | | * Creation and destruction; on deletion, release any font tables we're holding |
29 | | */ |
30 | | |
31 | | gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont) |
32 | | : gfxFontShaper(aFont), |
33 | | mGrFace(mFont->GetFontEntry()->GetGrFace()), |
34 | | mGrFont(nullptr), mFallbackToSmallCaps(false) |
35 | 0 | { |
36 | 0 | mCallbackData.mDrawTarget = nullptr; |
37 | 0 | mCallbackData.mFont = aFont; |
38 | 0 | } |
39 | | |
40 | | gfxGraphiteShaper::~gfxGraphiteShaper() |
41 | 0 | { |
42 | 0 | if (mGrFont) { |
43 | 0 | gr_font_destroy(mGrFont); |
44 | 0 | } |
45 | 0 | mFont->GetFontEntry()->ReleaseGrFace(mGrFace); |
46 | 0 | } |
47 | | |
48 | | /*static*/ float |
49 | | gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid) |
50 | 0 | { |
51 | 0 | const CallbackData *cb = |
52 | 0 | static_cast<const CallbackData*>(appFontHandle); |
53 | 0 | return FixedToFloat(cb->mFont->GetGlyphWidth(*cb->mDrawTarget, glyphid)); |
54 | 0 | } |
55 | | |
56 | | static inline uint32_t |
57 | | MakeGraphiteLangTag(uint32_t aTag) |
58 | 0 | { |
59 | 0 | uint32_t grLangTag = aTag; |
60 | 0 | // replace trailing space-padding with NULs for graphite |
61 | 0 | uint32_t mask = 0x000000FF; |
62 | 0 | while ((grLangTag & mask) == ' ') { |
63 | 0 | grLangTag &= ~mask; |
64 | 0 | mask <<= 8; |
65 | 0 | } |
66 | 0 | return grLangTag; |
67 | 0 | } |
68 | | |
69 | | struct GrFontFeatures { |
70 | | gr_face *mFace; |
71 | | gr_feature_val *mFeatures; |
72 | | }; |
73 | | |
74 | | static void |
75 | | AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg) |
76 | 0 | { |
77 | 0 | GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg); |
78 | 0 |
|
79 | 0 | const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag); |
80 | 0 | if (fref) { |
81 | 0 | gr_fref_set_feature_value(fref, aValue, f->mFeatures); |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | | bool |
86 | | gfxGraphiteShaper::ShapeText(DrawTarget *aDrawTarget, |
87 | | const char16_t *aText, |
88 | | uint32_t aOffset, |
89 | | uint32_t aLength, |
90 | | Script aScript, |
91 | | bool aVertical, |
92 | | RoundingFlags aRounding, |
93 | | gfxShapedText *aShapedText) |
94 | 0 | { |
95 | 0 | // some font back-ends require this in order to get proper hinted metrics |
96 | 0 | if (!mFont->SetupCairoFont(aDrawTarget)) { |
97 | 0 | return false; |
98 | 0 | } |
99 | 0 | |
100 | 0 | mCallbackData.mDrawTarget = aDrawTarget; |
101 | 0 |
|
102 | 0 | const gfxFontStyle *style = mFont->GetStyle(); |
103 | 0 |
|
104 | 0 | if (!mGrFont) { |
105 | 0 | if (!mGrFace) { |
106 | 0 | return false; |
107 | 0 | } |
108 | 0 | |
109 | 0 | if (mFont->ProvidesGlyphWidths()) { |
110 | 0 | gr_font_ops ops = { |
111 | 0 | sizeof(gr_font_ops), |
112 | 0 | &GrGetAdvance, |
113 | 0 | nullptr // vertical text not yet implemented |
114 | 0 | }; |
115 | 0 | mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(), |
116 | 0 | &mCallbackData, &ops, mGrFace); |
117 | 0 | } else { |
118 | 0 | mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace); |
119 | 0 | } |
120 | 0 |
|
121 | 0 | if (!mGrFont) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 0 | |
125 | 0 | // determine whether petite-caps falls back to small-caps |
126 | 0 | if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { |
127 | 0 | switch (style->variantCaps) { |
128 | 0 | case NS_FONT_VARIANT_CAPS_ALLPETITE: |
129 | 0 | case NS_FONT_VARIANT_CAPS_PETITECAPS: |
130 | 0 | bool synLower, synUpper; |
131 | 0 | mFont->SupportsVariantCaps(aScript, style->variantCaps, |
132 | 0 | mFallbackToSmallCaps, synLower, |
133 | 0 | synUpper); |
134 | 0 | break; |
135 | 0 | default: |
136 | 0 | break; |
137 | 0 | } |
138 | 0 | } |
139 | 0 | } |
140 | 0 | |
141 | 0 | gfxFontEntry *entry = mFont->GetFontEntry(); |
142 | 0 | uint32_t grLang = 0; |
143 | 0 | if (style->languageOverride) { |
144 | 0 | grLang = MakeGraphiteLangTag(style->languageOverride); |
145 | 0 | } else if (entry->mLanguageOverride) { |
146 | 0 | grLang = MakeGraphiteLangTag(entry->mLanguageOverride); |
147 | 0 | } else if (style->explicitLanguage) { |
148 | 0 | nsAutoCString langString; |
149 | 0 | style->language->ToUTF8String(langString); |
150 | 0 | grLang = GetGraphiteTagForLang(langString); |
151 | 0 | } |
152 | 0 | gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang); |
153 | 0 |
|
154 | 0 | // insert any merged features into Graphite feature list |
155 | 0 | GrFontFeatures f = {mGrFace, grFeatures}; |
156 | 0 | MergeFontFeatures(style, |
157 | 0 | mFont->GetFontEntry()->mFeatureSettings, |
158 | 0 | aShapedText->DisableLigatures(), |
159 | 0 | mFont->GetFontEntry()->FamilyName(), |
160 | 0 | mFallbackToSmallCaps, |
161 | 0 | AddFeature, |
162 | 0 | &f); |
163 | 0 |
|
164 | 0 | // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing |
165 | 0 | // from the font, so check for that possibility. (Most fonts double-map |
166 | 0 | // the space glyph to both 0x20 and 0xA0, so this won't often be needed; |
167 | 0 | // so we don't copy the text until we know it's required.) |
168 | 0 | nsAutoString transformed; |
169 | 0 | const char16_t NO_BREAK_SPACE = 0x00a0; |
170 | 0 | if (!entry->HasCharacter(NO_BREAK_SPACE)) { |
171 | 0 | nsDependentSubstring src(aText, aLength); |
172 | 0 | if (src.FindChar(NO_BREAK_SPACE) != kNotFound) { |
173 | 0 | transformed = src; |
174 | 0 | transformed.ReplaceChar(NO_BREAK_SPACE, ' '); |
175 | 0 | aText = transformed.BeginReading(); |
176 | 0 | } |
177 | 0 | } |
178 | 0 |
|
179 | 0 | size_t numChars = gr_count_unicode_characters(gr_utf16, |
180 | 0 | aText, aText + aLength, |
181 | 0 | nullptr); |
182 | 0 | gr_bidirtl grBidi = gr_bidirtl(aShapedText->IsRightToLeft() |
183 | 0 | ? (gr_rtl | gr_nobidi) : gr_nobidi); |
184 | 0 | gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures, |
185 | 0 | gr_utf16, aText, numChars, grBidi); |
186 | 0 |
|
187 | 0 | gr_featureval_destroy(grFeatures); |
188 | 0 |
|
189 | 0 | if (!seg) { |
190 | 0 | return false; |
191 | 0 | } |
192 | 0 | |
193 | 0 | nsresult rv = SetGlyphsFromSegment(aShapedText, aOffset, aLength, |
194 | 0 | aText, seg, aRounding); |
195 | 0 |
|
196 | 0 | gr_seg_destroy(seg); |
197 | 0 |
|
198 | 0 | return NS_SUCCEEDED(rv); |
199 | 0 | } |
200 | | |
201 | | #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays |
202 | | // for short (typical) runs up to this length |
203 | | |
204 | | struct Cluster { |
205 | | uint32_t baseChar; // in UTF16 code units, not Unicode character indices |
206 | | uint32_t baseGlyph; |
207 | | uint32_t nChars; // UTF16 code units |
208 | | uint32_t nGlyphs; |
209 | 0 | Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { } |
210 | | }; |
211 | | |
212 | | nsresult |
213 | | gfxGraphiteShaper::SetGlyphsFromSegment(gfxShapedText *aShapedText, |
214 | | uint32_t aOffset, |
215 | | uint32_t aLength, |
216 | | const char16_t *aText, |
217 | | gr_segment *aSegment, |
218 | | RoundingFlags aRounding) |
219 | 0 | { |
220 | 0 | typedef gfxShapedText::CompressedGlyph CompressedGlyph; |
221 | 0 |
|
222 | 0 | int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit(); |
223 | 0 | bool rtl = aShapedText->IsRightToLeft(); |
224 | 0 |
|
225 | 0 | uint32_t glyphCount = gr_seg_n_slots(aSegment); |
226 | 0 |
|
227 | 0 | // identify clusters; graphite may have reordered/expanded/ligated glyphs. |
228 | 0 | AutoTArray<Cluster,SMALL_GLYPH_RUN> clusters; |
229 | 0 | AutoTArray<uint16_t,SMALL_GLYPH_RUN> gids; |
230 | 0 | AutoTArray<float,SMALL_GLYPH_RUN> xLocs; |
231 | 0 | AutoTArray<float,SMALL_GLYPH_RUN> yLocs; |
232 | 0 |
|
233 | 0 | if (!clusters.SetLength(aLength, fallible) || |
234 | 0 | !gids.SetLength(glyphCount, fallible) || |
235 | 0 | !xLocs.SetLength(glyphCount, fallible) || |
236 | 0 | !yLocs.SetLength(glyphCount, fallible)) |
237 | 0 | { |
238 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
239 | 0 | } |
240 | 0 | |
241 | 0 | // walk through the glyph slots and check which original character |
242 | 0 | // each is associated with |
243 | 0 | uint32_t gIndex = 0; // glyph slot index |
244 | 0 | uint32_t cIndex = 0; // current cluster index |
245 | 0 | for (const gr_slot *slot = gr_seg_first_slot(aSegment); |
246 | 0 | slot != nullptr; |
247 | 0 | slot = gr_slot_next_in_segment(slot), gIndex++) |
248 | 0 | { |
249 | 0 | uint32_t before = |
250 | 0 | gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot))); |
251 | 0 | uint32_t after = |
252 | 0 | gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot))); |
253 | 0 | gids[gIndex] = gr_slot_gid(slot); |
254 | 0 | xLocs[gIndex] = gr_slot_origin_X(slot); |
255 | 0 | yLocs[gIndex] = gr_slot_origin_Y(slot); |
256 | 0 |
|
257 | 0 | // if this glyph has a "before" character index that precedes the |
258 | 0 | // current cluster's char index, we need to merge preceding |
259 | 0 | // clusters until it gets included |
260 | 0 | while (before < clusters[cIndex].baseChar && cIndex > 0) { |
261 | 0 | clusters[cIndex-1].nChars += clusters[cIndex].nChars; |
262 | 0 | clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs; |
263 | 0 | --cIndex; |
264 | 0 | } |
265 | 0 |
|
266 | 0 | // if there's a gap between the current cluster's base character and |
267 | 0 | // this glyph's, extend the cluster to include the intervening chars |
268 | 0 | if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars && |
269 | 0 | before >= clusters[cIndex].baseChar + clusters[cIndex].nChars) |
270 | 0 | { |
271 | 0 | NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word"); |
272 | 0 | Cluster& c = clusters[cIndex + 1]; |
273 | 0 | c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars; |
274 | 0 | c.nChars = before - c.baseChar; |
275 | 0 | c.baseGlyph = gIndex; |
276 | 0 | c.nGlyphs = 0; |
277 | 0 | ++cIndex; |
278 | 0 | } |
279 | 0 |
|
280 | 0 | // increment cluster's glyph count to include current slot |
281 | 0 | NS_ASSERTION(cIndex < aLength, "cIndex beyond word length"); |
282 | 0 | ++clusters[cIndex].nGlyphs; |
283 | 0 |
|
284 | 0 | // bump |after| index if it falls in the middle of a surrogate pair |
285 | 0 | if (NS_IS_HIGH_SURROGATE(aText[after]) && after < aLength - 1 && |
286 | 0 | NS_IS_LOW_SURROGATE(aText[after + 1])) { |
287 | 0 | after++; |
288 | 0 | } |
289 | 0 | // extend cluster if necessary to reach the glyph's "after" index |
290 | 0 | if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) { |
291 | 0 | clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar; |
292 | 0 | } |
293 | 0 | } |
294 | 0 |
|
295 | 0 | CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; |
296 | 0 |
|
297 | 0 | bool roundX = bool(aRounding & RoundingFlags::kRoundX); |
298 | 0 | bool roundY = bool(aRounding & RoundingFlags::kRoundY); |
299 | 0 |
|
300 | 0 | // now put glyphs into the textrun, one cluster at a time |
301 | 0 | for (uint32_t i = 0; i <= cIndex; ++i) { |
302 | 0 | const Cluster& c = clusters[i]; |
303 | 0 |
|
304 | 0 | float adv; // total advance of the cluster |
305 | 0 | if (rtl) { |
306 | 0 | if (i == 0) { |
307 | 0 | adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; |
308 | 0 | } else { |
309 | 0 | adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph]; |
310 | 0 | } |
311 | 0 | } else { |
312 | 0 | if (i == cIndex) { |
313 | 0 | adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; |
314 | 0 | } else { |
315 | 0 | adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph]; |
316 | 0 | } |
317 | 0 | } |
318 | 0 |
|
319 | 0 | // Check for default-ignorable char that didn't get filtered, combined, |
320 | 0 | // etc by the shaping process, and skip it. |
321 | 0 | uint32_t offs = c.baseChar; |
322 | 0 | NS_ASSERTION(offs < aLength, "unexpected offset"); |
323 | 0 | if (c.nGlyphs == 1 && c.nChars == 1 && |
324 | 0 | aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) { |
325 | 0 | continue; |
326 | 0 | } |
327 | 0 | |
328 | 0 | uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits |
329 | 0 | : NSToIntRound(adv * dev2appUnits); |
330 | 0 | if (c.nGlyphs == 1 && |
331 | 0 | CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) && |
332 | 0 | CompressedGlyph::IsSimpleAdvance(appAdvance) && |
333 | 0 | charGlyphs[offs].IsClusterStart() && |
334 | 0 | yLocs[c.baseGlyph] == 0) |
335 | 0 | { |
336 | 0 | charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]); |
337 | 0 | } else { |
338 | 0 | // not a one-to-one mapping with simple metrics: use DetailedGlyph |
339 | 0 | AutoTArray<gfxShapedText::DetailedGlyph,8> details; |
340 | 0 | float clusterLoc; |
341 | 0 | for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) { |
342 | 0 | gfxShapedText::DetailedGlyph* d = details.AppendElement(); |
343 | 0 | d->mGlyphID = gids[j]; |
344 | 0 | d->mOffset.y = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits |
345 | 0 | : -yLocs[j] * dev2appUnits; |
346 | 0 | if (j == c.baseGlyph) { |
347 | 0 | d->mAdvance = appAdvance; |
348 | 0 | clusterLoc = xLocs[j]; |
349 | 0 | } else { |
350 | 0 | float dx = rtl ? (xLocs[j] - clusterLoc) : |
351 | 0 | (xLocs[j] - clusterLoc - adv); |
352 | 0 | d->mOffset.x = roundX ? NSToIntRound(dx) * dev2appUnits |
353 | 0 | : dx * dev2appUnits; |
354 | 0 | d->mAdvance = 0; |
355 | 0 | } |
356 | 0 | } |
357 | 0 | bool isClusterStart = charGlyphs[offs].IsClusterStart(); |
358 | 0 | aShapedText->SetGlyphs(aOffset + offs, |
359 | 0 | CompressedGlyph::MakeComplex(isClusterStart, |
360 | 0 | true, |
361 | 0 | details.Length()), |
362 | 0 | details.Elements()); |
363 | 0 | } |
364 | 0 |
|
365 | 0 | for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) { |
366 | 0 | NS_ASSERTION(j < aLength, "unexpected offset"); |
367 | 0 | CompressedGlyph &g = charGlyphs[j]; |
368 | 0 | NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); |
369 | 0 | g.SetComplex(g.IsClusterStart(), false, 0); |
370 | 0 | } |
371 | 0 | } |
372 | 0 |
|
373 | 0 | return NS_OK; |
374 | 0 | } |
375 | | |
376 | | #undef SMALL_GLYPH_RUN |
377 | | |
378 | | // for language tag validation - include list of tags from the IANA registry |
379 | | #include "gfxLanguageTagList.cpp" |
380 | | |
381 | | nsTHashtable<nsUint32HashKey> *gfxGraphiteShaper::sLanguageTags; |
382 | | |
383 | | /*static*/ uint32_t |
384 | | gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) |
385 | 0 | { |
386 | 0 | int len = aLang.Length(); |
387 | 0 | if (len < 2) { |
388 | 0 | return 0; |
389 | 0 | } |
390 | 0 | |
391 | 0 | // convert primary language subtag to a left-packed, NUL-padded integer |
392 | 0 | // for the Graphite API |
393 | 0 | uint32_t grLang = 0; |
394 | 0 | for (int i = 0; i < 4; ++i) { |
395 | 0 | grLang <<= 8; |
396 | 0 | if (i < len) { |
397 | 0 | uint8_t ch = aLang[i]; |
398 | 0 | if (ch == '-') { |
399 | 0 | // found end of primary language subtag, truncate here |
400 | 0 | len = i; |
401 | 0 | continue; |
402 | 0 | } |
403 | 0 | if (ch < 'a' || ch > 'z') { |
404 | 0 | // invalid character in tag, so ignore it completely |
405 | 0 | return 0; |
406 | 0 | } |
407 | 0 | grLang += ch; |
408 | 0 | } |
409 | 0 | } |
410 | 0 |
|
411 | 0 | // valid tags must have length = 2 or 3 |
412 | 0 | if (len < 2 || len > 3) { |
413 | 0 | return 0; |
414 | 0 | } |
415 | 0 | |
416 | 0 | if (!sLanguageTags) { |
417 | 0 | // store the registered IANA tags in a hash for convenient validation |
418 | 0 | sLanguageTags = new nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList)); |
419 | 0 | for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) { |
420 | 0 | sLanguageTags->PutEntry(*tag); |
421 | 0 | } |
422 | 0 | } |
423 | 0 |
|
424 | 0 | // only accept tags known in the IANA registry |
425 | 0 | if (sLanguageTags->GetEntry(grLang)) { |
426 | 0 | return grLang; |
427 | 0 | } |
428 | 0 | |
429 | 0 | return 0; |
430 | 0 | } |
431 | | |
432 | | /*static*/ void |
433 | | gfxGraphiteShaper::Shutdown() |
434 | 0 | { |
435 | | #ifdef NS_FREE_PERMANENT_DATA |
436 | | if (sLanguageTags) { |
437 | | sLanguageTags->Clear(); |
438 | | delete sLanguageTags; |
439 | | sLanguageTags = nullptr; |
440 | | } |
441 | | #endif |
442 | | } |