Coverage Report

Created: 2018-09-25 14:53

/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
}