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