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