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