gfx/thebes/gfxGraphiteShaper.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     6 #include "gfxGraphiteShaper.h"
     7 #include "nsString.h"
     8 #include "gfxContext.h"
    10 #include "graphite2/Font.h"
    11 #include "graphite2/Segment.h"
    13 #include "harfbuzz/hb.h"
    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))
    23 using namespace mozilla; // for AutoSwap_* types
    25 /*
    26  * Creation and destruction; on deletion, release any font tables we're holding
    27  */
    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 }
    38 gfxGraphiteShaper::~gfxGraphiteShaper()
    39 {
    40     if (mGrFont) {
    41         gr_font_destroy(mGrFont);
    42     }
    43     mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
    44 }
    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 }
    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 }
    67 struct GrFontFeatures {
    68     gr_face        *mFace;
    69     gr_feature_val *mFeatures;
    70 };
    72 static PLDHashOperator
    73 AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg)
    74 {
    75     GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg);
    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 }
    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     }
    97     mCallbackData.mContext = aContext;
    99     if (!mGrFont) {
   100         if (!mGrFace) {
   101             return false;
   102         }
   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         }
   116         if (!mGrFont) {
   117             return false;
   118         }
   119     }
   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);
   135     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
   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     }
   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());
   156     gr_featureval_destroy(grFeatures);
   158     if (!seg) {
   159         return false;
   160     }
   162     nsresult rv = SetGlyphsFromSegment(aContext, aShapedText, aOffset, aLength,
   163                                        aText, seg);
   165     gr_seg_destroy(seg);
   167     return NS_SUCCEEDED(rv);
   168 }
   170 #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays
   171                             // for short (typical) runs up to this length
   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 };
   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();
   192     uint32_t glyphCount = gr_seg_n_slots(aSegment);
   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;
   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     }
   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);
   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         }
   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         }
   247         // increment cluster's glyph count to include current slot
   248         NS_ASSERTION(cIndex < aLength, "cIndex beyond word length");
   249         ++clusters[cIndex].nGlyphs;
   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     }
   257     bool roundX;
   258     bool roundY;
   259     aContext->GetRoundOffsetsToPixels(&roundX, &roundY);
   261     gfxShapedText::CompressedGlyph *charGlyphs =
   262         aShapedText->GetCharacterGlyphs() + aOffset;
   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];
   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         }
   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         }
   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         }
   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     }
   336     return NS_OK;
   337 }
   339 #undef SMALL_GLYPH_RUN
   341 // for language tag validation - include list of tags from the IANA registry
   342 #include "gfxLanguageTagList.cpp"
   344 nsTHashtable<nsUint32HashKey> *gfxGraphiteShaper::sLanguageTags;
   346 /*static*/ uint32_t
   347 gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang)
   348 {
   349     int len = aLang.Length();
   350     if (len < 2) {
   351         return 0;
   352     }
   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     }
   374     // valid tags must have length = 2 or 3
   375     if (len < 2 || len > 3) {
   376         return 0;
   377     }
   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     }
   387     // only accept tags known in the IANA registry
   388     if (sLanguageTags->GetEntry(grLang)) {
   389         return grLang;
   390     }
   392     return 0;
   393 }
   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 }

mercurial