gfx/thebes/gfxGraphiteShaper.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/gfx/thebes/gfxGraphiteShaper.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,405 @@
     1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "gfxGraphiteShaper.h"
    1.10 +#include "nsString.h"
    1.11 +#include "gfxContext.h"
    1.12 +
    1.13 +#include "graphite2/Font.h"
    1.14 +#include "graphite2/Segment.h"
    1.15 +
    1.16 +#include "harfbuzz/hb.h"
    1.17 +
    1.18 +#define FloatToFixed(f) (65536 * (f))
    1.19 +#define FixedToFloat(f) ((f) * (1.0 / 65536.0))
    1.20 +// Right shifts of negative (signed) integers are undefined, as are overflows
    1.21 +// when converting unsigned to negative signed integers.
    1.22 +// (If speed were an issue we could make some 2's complement assumptions.)
    1.23 +#define FixedToIntRound(f) ((f) > 0 ?  ((32768 + (f)) >> 16) \
    1.24 +                                    : -((32767 - (f)) >> 16))
    1.25 +
    1.26 +using namespace mozilla; // for AutoSwap_* types
    1.27 +
    1.28 +/*
    1.29 + * Creation and destruction; on deletion, release any font tables we're holding
    1.30 + */
    1.31 +
    1.32 +gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont)
    1.33 +    : gfxFontShaper(aFont),
    1.34 +      mGrFace(mFont->GetFontEntry()->GetGrFace()),
    1.35 +      mGrFont(nullptr)
    1.36 +{
    1.37 +    mCallbackData.mFont = aFont;
    1.38 +    mCallbackData.mShaper = this;
    1.39 +}
    1.40 +
    1.41 +gfxGraphiteShaper::~gfxGraphiteShaper()
    1.42 +{
    1.43 +    if (mGrFont) {
    1.44 +        gr_font_destroy(mGrFont);
    1.45 +    }
    1.46 +    mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
    1.47 +}
    1.48 +
    1.49 +/*static*/ float
    1.50 +gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid)
    1.51 +{
    1.52 +    const CallbackData *cb =
    1.53 +        static_cast<const CallbackData*>(appFontHandle);
    1.54 +    return FixedToFloat(cb->mFont->GetGlyphWidth(cb->mContext, glyphid));
    1.55 +}
    1.56 +
    1.57 +static inline uint32_t
    1.58 +MakeGraphiteLangTag(uint32_t aTag)
    1.59 +{
    1.60 +    uint32_t grLangTag = aTag;
    1.61 +    // replace trailing space-padding with NULs for graphite
    1.62 +    uint32_t mask = 0x000000FF;
    1.63 +    while ((grLangTag & mask) == ' ') {
    1.64 +        grLangTag &= ~mask;
    1.65 +        mask <<= 8;
    1.66 +    }
    1.67 +    return grLangTag;
    1.68 +}
    1.69 +
    1.70 +struct GrFontFeatures {
    1.71 +    gr_face        *mFace;
    1.72 +    gr_feature_val *mFeatures;
    1.73 +};
    1.74 +
    1.75 +static PLDHashOperator
    1.76 +AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg)
    1.77 +{
    1.78 +    GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg);
    1.79 +
    1.80 +    const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag);
    1.81 +    if (fref) {
    1.82 +        gr_fref_set_feature_value(fref, aValue, f->mFeatures);
    1.83 +    }
    1.84 +    return PL_DHASH_NEXT;
    1.85 +}
    1.86 +
    1.87 +bool
    1.88 +gfxGraphiteShaper::ShapeText(gfxContext      *aContext,
    1.89 +                             const char16_t *aText,
    1.90 +                             uint32_t         aOffset,
    1.91 +                             uint32_t         aLength,
    1.92 +                             int32_t          aScript,
    1.93 +                             gfxShapedText   *aShapedText)
    1.94 +{
    1.95 +    // some font back-ends require this in order to get proper hinted metrics
    1.96 +    if (!mFont->SetupCairoFont(aContext)) {
    1.97 +        return false;
    1.98 +    }
    1.99 +
   1.100 +    mCallbackData.mContext = aContext;
   1.101 +
   1.102 +    if (!mGrFont) {
   1.103 +        if (!mGrFace) {
   1.104 +            return false;
   1.105 +        }
   1.106 +
   1.107 +        if (mFont->ProvidesGlyphWidths()) {
   1.108 +            gr_font_ops ops = {
   1.109 +                sizeof(gr_font_ops),
   1.110 +                &GrGetAdvance,
   1.111 +                nullptr // vertical text not yet implemented
   1.112 +            };
   1.113 +            mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(),
   1.114 +                                            &mCallbackData, &ops, mGrFace);
   1.115 +        } else {
   1.116 +            mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace);
   1.117 +        }
   1.118 +
   1.119 +        if (!mGrFont) {
   1.120 +            return false;
   1.121 +        }
   1.122 +    }
   1.123 +
   1.124 +    gfxFontEntry *entry = mFont->GetFontEntry();
   1.125 +    const gfxFontStyle *style = mFont->GetStyle();
   1.126 +    uint32_t grLang = 0;
   1.127 +    if (style->languageOverride) {
   1.128 +        grLang = MakeGraphiteLangTag(style->languageOverride);
   1.129 +    } else if (entry->mLanguageOverride) {
   1.130 +        grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
   1.131 +    } else {
   1.132 +        nsAutoCString langString;
   1.133 +        style->language->ToUTF8String(langString);
   1.134 +        grLang = GetGraphiteTagForLang(langString);
   1.135 +    }
   1.136 +    gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
   1.137 +
   1.138 +    nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
   1.139 +
   1.140 +    // if style contains font-specific features
   1.141 +    if (MergeFontFeatures(style,
   1.142 +                          mFont->GetFontEntry()->mFeatureSettings,
   1.143 +                          aShapedText->DisableLigatures(),
   1.144 +                          mFont->GetFontEntry()->FamilyName(),
   1.145 +                          mergedFeatures))
   1.146 +    {
   1.147 +        // enumerate result and insert into Graphite feature list
   1.148 +        GrFontFeatures f = {mGrFace, grFeatures};
   1.149 +        mergedFeatures.Enumerate(AddFeature, &f);
   1.150 +    }
   1.151 +
   1.152 +    size_t numChars = gr_count_unicode_characters(gr_utf16,
   1.153 +                                                  aText, aText + aLength,
   1.154 +                                                  nullptr);
   1.155 +    gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
   1.156 +                                  gr_utf16, aText, numChars,
   1.157 +                                  aShapedText->IsRightToLeft());
   1.158 +
   1.159 +    gr_featureval_destroy(grFeatures);
   1.160 +
   1.161 +    if (!seg) {
   1.162 +        return false;
   1.163 +    }
   1.164 +
   1.165 +    nsresult rv = SetGlyphsFromSegment(aContext, aShapedText, aOffset, aLength,
   1.166 +                                       aText, seg);
   1.167 +
   1.168 +    gr_seg_destroy(seg);
   1.169 +
   1.170 +    return NS_SUCCEEDED(rv);
   1.171 +}
   1.172 +
   1.173 +#define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
   1.174 +                            // for short (typical) runs up to this length
   1.175 +
   1.176 +struct Cluster {
   1.177 +    uint32_t baseChar; // in UTF16 code units, not Unicode character indices
   1.178 +    uint32_t baseGlyph;
   1.179 +    uint32_t nChars; // UTF16 code units
   1.180 +    uint32_t nGlyphs;
   1.181 +    Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { }
   1.182 +};
   1.183 +
   1.184 +nsresult
   1.185 +gfxGraphiteShaper::SetGlyphsFromSegment(gfxContext      *aContext,
   1.186 +                                        gfxShapedText   *aShapedText,
   1.187 +                                        uint32_t         aOffset,
   1.188 +                                        uint32_t         aLength,
   1.189 +                                        const char16_t *aText,
   1.190 +                                        gr_segment      *aSegment)
   1.191 +{
   1.192 +    int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit();
   1.193 +    bool rtl = aShapedText->IsRightToLeft();
   1.194 +
   1.195 +    uint32_t glyphCount = gr_seg_n_slots(aSegment);
   1.196 +
   1.197 +    // identify clusters; graphite may have reordered/expanded/ligated glyphs.
   1.198 +    AutoFallibleTArray<Cluster,SMALL_GLYPH_RUN> clusters;
   1.199 +    AutoFallibleTArray<uint16_t,SMALL_GLYPH_RUN> gids;
   1.200 +    AutoFallibleTArray<float,SMALL_GLYPH_RUN> xLocs;
   1.201 +    AutoFallibleTArray<float,SMALL_GLYPH_RUN> yLocs;
   1.202 +
   1.203 +    if (!clusters.SetLength(aLength) ||
   1.204 +        !gids.SetLength(glyphCount) ||
   1.205 +        !xLocs.SetLength(glyphCount) ||
   1.206 +        !yLocs.SetLength(glyphCount))
   1.207 +    {
   1.208 +        return NS_ERROR_OUT_OF_MEMORY;
   1.209 +    }
   1.210 +
   1.211 +    // walk through the glyph slots and check which original character
   1.212 +    // each is associated with
   1.213 +    uint32_t gIndex = 0; // glyph slot index
   1.214 +    uint32_t cIndex = 0; // current cluster index
   1.215 +    for (const gr_slot *slot = gr_seg_first_slot(aSegment);
   1.216 +         slot != nullptr;
   1.217 +         slot = gr_slot_next_in_segment(slot), gIndex++)
   1.218 +    {
   1.219 +        uint32_t before =
   1.220 +            gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot)));
   1.221 +        uint32_t after =
   1.222 +            gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot)));
   1.223 +        gids[gIndex] = gr_slot_gid(slot);
   1.224 +        xLocs[gIndex] = gr_slot_origin_X(slot);
   1.225 +        yLocs[gIndex] = gr_slot_origin_Y(slot);
   1.226 +
   1.227 +        // if this glyph has a "before" character index that precedes the
   1.228 +        // current cluster's char index, we need to merge preceding
   1.229 +        // clusters until it gets included
   1.230 +        while (before < clusters[cIndex].baseChar && cIndex > 0) {
   1.231 +            clusters[cIndex-1].nChars += clusters[cIndex].nChars;
   1.232 +            clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs;
   1.233 +            --cIndex;
   1.234 +        }
   1.235 +
   1.236 +        // if there's a gap between the current cluster's base character and
   1.237 +        // this glyph's, extend the cluster to include the intervening chars
   1.238 +        if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars &&
   1.239 +            before >= clusters[cIndex].baseChar + clusters[cIndex].nChars)
   1.240 +        {
   1.241 +            NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word");
   1.242 +            Cluster& c = clusters[cIndex + 1];
   1.243 +            c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars;
   1.244 +            c.nChars = before - c.baseChar;
   1.245 +            c.baseGlyph = gIndex;
   1.246 +            c.nGlyphs = 0;
   1.247 +            ++cIndex;
   1.248 +        }
   1.249 +
   1.250 +        // increment cluster's glyph count to include current slot
   1.251 +        NS_ASSERTION(cIndex < aLength, "cIndex beyond word length");
   1.252 +        ++clusters[cIndex].nGlyphs;
   1.253 +
   1.254 +        // extend cluster if necessary to reach the glyph's "after" index
   1.255 +        if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) {
   1.256 +            clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar;
   1.257 +        }
   1.258 +    }
   1.259 +
   1.260 +    bool roundX;
   1.261 +    bool roundY;
   1.262 +    aContext->GetRoundOffsetsToPixels(&roundX, &roundY);
   1.263 +
   1.264 +    gfxShapedText::CompressedGlyph *charGlyphs =
   1.265 +        aShapedText->GetCharacterGlyphs() + aOffset;
   1.266 +
   1.267 +    // now put glyphs into the textrun, one cluster at a time
   1.268 +    for (uint32_t i = 0; i <= cIndex; ++i) {
   1.269 +        const Cluster& c = clusters[i];
   1.270 +
   1.271 +        float adv; // total advance of the cluster
   1.272 +        if (rtl) {
   1.273 +            if (i == 0) {
   1.274 +                adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
   1.275 +            } else {
   1.276 +                adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph];
   1.277 +            }
   1.278 +        } else {
   1.279 +            if (i == cIndex) {
   1.280 +                adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph];
   1.281 +            } else {
   1.282 +                adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph];
   1.283 +            }
   1.284 +        }
   1.285 +
   1.286 +        // Check for default-ignorable char that didn't get filtered, combined,
   1.287 +        // etc by the shaping process, and skip it.
   1.288 +        uint32_t offs = c.baseChar;
   1.289 +        NS_ASSERTION(offs < aLength, "unexpected offset");
   1.290 +        if (c.nGlyphs == 1 && c.nChars == 1 &&
   1.291 +            aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) {
   1.292 +            continue;
   1.293 +        }
   1.294 +
   1.295 +        uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits :
   1.296 +                                       NSToIntRound(adv * dev2appUnits);
   1.297 +        if (c.nGlyphs == 1 &&
   1.298 +            gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) &&
   1.299 +            gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance) &&
   1.300 +            charGlyphs[offs].IsClusterStart() &&
   1.301 +            yLocs[c.baseGlyph] == 0)
   1.302 +        {
   1.303 +            charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]);
   1.304 +        } else {
   1.305 +            // not a one-to-one mapping with simple metrics: use DetailedGlyph
   1.306 +            nsAutoTArray<gfxShapedText::DetailedGlyph,8> details;
   1.307 +            float clusterLoc;
   1.308 +            for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
   1.309 +                gfxShapedText::DetailedGlyph* d = details.AppendElement();
   1.310 +                d->mGlyphID = gids[j];
   1.311 +                d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits :
   1.312 +                              -yLocs[j] * dev2appUnits;
   1.313 +                if (j == c.baseGlyph) {
   1.314 +                    d->mXOffset = 0;
   1.315 +                    d->mAdvance = appAdvance;
   1.316 +                    clusterLoc = xLocs[j];
   1.317 +                } else {
   1.318 +                    float dx = rtl ? (xLocs[j] - clusterLoc) :
   1.319 +                                     (xLocs[j] - clusterLoc - adv);
   1.320 +                    d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits :
   1.321 +                                           dx * dev2appUnits;
   1.322 +                    d->mAdvance = 0;
   1.323 +                }
   1.324 +            }
   1.325 +            gfxShapedText::CompressedGlyph g;
   1.326 +            g.SetComplex(charGlyphs[offs].IsClusterStart(),
   1.327 +                         true, details.Length());
   1.328 +            aShapedText->SetGlyphs(aOffset + offs, g, details.Elements());
   1.329 +        }
   1.330 +
   1.331 +        for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) {
   1.332 +            NS_ASSERTION(j < aLength, "unexpected offset");
   1.333 +            gfxShapedText::CompressedGlyph &g = charGlyphs[j];
   1.334 +            NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
   1.335 +            g.SetComplex(g.IsClusterStart(), false, 0);
   1.336 +        }
   1.337 +    }
   1.338 +
   1.339 +    return NS_OK;
   1.340 +}
   1.341 +
   1.342 +#undef SMALL_GLYPH_RUN
   1.343 +
   1.344 +// for language tag validation - include list of tags from the IANA registry
   1.345 +#include "gfxLanguageTagList.cpp"
   1.346 +
   1.347 +nsTHashtable<nsUint32HashKey> *gfxGraphiteShaper::sLanguageTags;
   1.348 +
   1.349 +/*static*/ uint32_t
   1.350 +gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang)
   1.351 +{
   1.352 +    int len = aLang.Length();
   1.353 +    if (len < 2) {
   1.354 +        return 0;
   1.355 +    }
   1.356 +
   1.357 +    // convert primary language subtag to a left-packed, NUL-padded integer
   1.358 +    // for the Graphite API
   1.359 +    uint32_t grLang = 0;
   1.360 +    for (int i = 0; i < 4; ++i) {
   1.361 +        grLang <<= 8;
   1.362 +        if (i < len) {
   1.363 +            uint8_t ch = aLang[i];
   1.364 +            if (ch == '-') {
   1.365 +                // found end of primary language subtag, truncate here
   1.366 +                len = i;
   1.367 +                continue;
   1.368 +            }
   1.369 +            if (ch < 'a' || ch > 'z') {
   1.370 +                // invalid character in tag, so ignore it completely
   1.371 +                return 0;
   1.372 +            }
   1.373 +            grLang += ch;
   1.374 +        }
   1.375 +    }
   1.376 +
   1.377 +    // valid tags must have length = 2 or 3
   1.378 +    if (len < 2 || len > 3) {
   1.379 +        return 0;
   1.380 +    }
   1.381 +
   1.382 +    if (!sLanguageTags) {
   1.383 +        // store the registered IANA tags in a hash for convenient validation
   1.384 +        sLanguageTags = new nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList));
   1.385 +        for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) {
   1.386 +            sLanguageTags->PutEntry(*tag);
   1.387 +        }
   1.388 +    }
   1.389 +
   1.390 +    // only accept tags known in the IANA registry
   1.391 +    if (sLanguageTags->GetEntry(grLang)) {
   1.392 +        return grLang;
   1.393 +    }
   1.394 +
   1.395 +    return 0;
   1.396 +}
   1.397 +
   1.398 +/*static*/ void
   1.399 +gfxGraphiteShaper::Shutdown()
   1.400 +{
   1.401 +#ifdef NS_FREE_PERMANENT_DATA
   1.402 +    if (sLanguageTags) {
   1.403 +        sLanguageTags->Clear();
   1.404 +        delete sLanguageTags;
   1.405 +        sLanguageTags = nullptr;
   1.406 +    }
   1.407 +#endif
   1.408 +}

mercurial