diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /gfx/thebes/gfxGraphiteShaper.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/thebes/gfxGraphiteShaper.cpp')
-rw-r--r-- | gfx/thebes/gfxGraphiteShaper.cpp | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp new file mode 100644 index 0000000000..aeebf30f28 --- /dev/null +++ b/gfx/thebes/gfxGraphiteShaper.cpp @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxGraphiteShaper.h" +#include "nsString.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" + +#include "graphite2/Font.h" +#include "graphite2/Segment.h" + +#include "harfbuzz/hb.h" + +#define FloatToFixed(f) (65536 * (f)) +#define FixedToFloat(f) ((f) * (1.0 / 65536.0)) +// Right shifts of negative (signed) integers are undefined, as are overflows +// when converting unsigned to negative signed integers. +// (If speed were an issue we could make some 2's complement assumptions.) +#define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \ + : -((32767 - (f)) >> 16)) + +using namespace mozilla; // for AutoSwap_* types + +/* + * Creation and destruction; on deletion, release any font tables we're holding + */ + +gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont) + : gfxFontShaper(aFont), + mGrFace(mFont->GetFontEntry()->GetGrFace()), + mGrFont(nullptr), mFallbackToSmallCaps(false) +{ + mCallbackData.mFont = aFont; +} + +gfxGraphiteShaper::~gfxGraphiteShaper() +{ + if (mGrFont) { + gr_font_destroy(mGrFont); + } + mFont->GetFontEntry()->ReleaseGrFace(mGrFace); +} + +/*static*/ float +gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid) +{ + const CallbackData *cb = + static_cast<const CallbackData*>(appFontHandle); + return FixedToFloat(cb->mFont->GetGlyphWidth(*cb->mDrawTarget, glyphid)); +} + +static inline uint32_t +MakeGraphiteLangTag(uint32_t aTag) +{ + uint32_t grLangTag = aTag; + // replace trailing space-padding with NULs for graphite + uint32_t mask = 0x000000FF; + while ((grLangTag & mask) == ' ') { + grLangTag &= ~mask; + mask <<= 8; + } + return grLangTag; +} + +struct GrFontFeatures { + gr_face *mFace; + gr_feature_val *mFeatures; +}; + +static void +AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg) +{ + GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg); + + const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag); + if (fref) { + gr_fref_set_feature_value(fref, aValue, f->mFeatures); + } +} + +bool +gfxGraphiteShaper::ShapeText(DrawTarget *aDrawTarget, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + Script aScript, + bool aVertical, + gfxShapedText *aShapedText) +{ + // some font back-ends require this in order to get proper hinted metrics + if (!mFont->SetupCairoFont(aDrawTarget)) { + return false; + } + + mCallbackData.mDrawTarget = aDrawTarget; + + const gfxFontStyle *style = mFont->GetStyle(); + + if (!mGrFont) { + if (!mGrFace) { + return false; + } + + if (mFont->ProvidesGlyphWidths()) { + gr_font_ops ops = { + sizeof(gr_font_ops), + &GrGetAdvance, + nullptr // vertical text not yet implemented + }; + mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(), + &mCallbackData, &ops, mGrFace); + } else { + mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace); + } + + if (!mGrFont) { + return false; + } + + // determine whether petite-caps falls back to small-caps + if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) { + switch (style->variantCaps) { + case NS_FONT_VARIANT_CAPS_ALLPETITE: + case NS_FONT_VARIANT_CAPS_PETITECAPS: + bool synLower, synUpper; + mFont->SupportsVariantCaps(aScript, style->variantCaps, + mFallbackToSmallCaps, synLower, + synUpper); + break; + default: + break; + } + } + } + + gfxFontEntry *entry = mFont->GetFontEntry(); + uint32_t grLang = 0; + if (style->languageOverride) { + grLang = MakeGraphiteLangTag(style->languageOverride); + } else if (entry->mLanguageOverride) { + grLang = MakeGraphiteLangTag(entry->mLanguageOverride); + } else if (style->explicitLanguage) { + nsAutoCString langString; + style->language->ToUTF8String(langString); + grLang = GetGraphiteTagForLang(langString); + } + gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang); + + // insert any merged features into Graphite feature list + GrFontFeatures f = {mGrFace, grFeatures}; + MergeFontFeatures(style, + mFont->GetFontEntry()->mFeatureSettings, + aShapedText->DisableLigatures(), + mFont->GetFontEntry()->FamilyName(), + mFallbackToSmallCaps, + AddFeature, + &f); + + // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing + // from the font, so check for that possibility. (Most fonts double-map + // the space glyph to both 0x20 and 0xA0, so this won't often be needed; + // so we don't copy the text until we know it's required.) + nsAutoString transformed; + const char16_t NO_BREAK_SPACE = 0x00a0; + if (!entry->HasCharacter(NO_BREAK_SPACE)) { + nsDependentSubstring src(aText, aLength); + if (src.FindChar(NO_BREAK_SPACE) != kNotFound) { + transformed = src; + transformed.ReplaceChar(NO_BREAK_SPACE, ' '); + aText = transformed.BeginReading(); + } + } + + size_t numChars = gr_count_unicode_characters(gr_utf16, + aText, aText + aLength, + nullptr); + gr_bidirtl grBidi = gr_bidirtl(aShapedText->IsRightToLeft() + ? (gr_rtl | gr_nobidi) : gr_nobidi); + gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures, + gr_utf16, aText, numChars, grBidi); + + gr_featureval_destroy(grFeatures); + + if (!seg) { + return false; + } + + nsresult rv = SetGlyphsFromSegment(aDrawTarget, aShapedText, aOffset, aLength, + aText, seg); + + gr_seg_destroy(seg); + + return NS_SUCCEEDED(rv); +} + +#define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays + // for short (typical) runs up to this length + +struct Cluster { + uint32_t baseChar; // in UTF16 code units, not Unicode character indices + uint32_t baseGlyph; + uint32_t nChars; // UTF16 code units + uint32_t nGlyphs; + Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { } +}; + +nsresult +gfxGraphiteShaper::SetGlyphsFromSegment(DrawTarget *aDrawTarget, + gfxShapedText *aShapedText, + uint32_t aOffset, + uint32_t aLength, + const char16_t *aText, + gr_segment *aSegment) +{ + int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit(); + bool rtl = aShapedText->IsRightToLeft(); + + uint32_t glyphCount = gr_seg_n_slots(aSegment); + + // identify clusters; graphite may have reordered/expanded/ligated glyphs. + AutoTArray<Cluster,SMALL_GLYPH_RUN> clusters; + AutoTArray<uint16_t,SMALL_GLYPH_RUN> gids; + AutoTArray<float,SMALL_GLYPH_RUN> xLocs; + AutoTArray<float,SMALL_GLYPH_RUN> yLocs; + + if (!clusters.SetLength(aLength, fallible) || + !gids.SetLength(glyphCount, fallible) || + !xLocs.SetLength(glyphCount, fallible) || + !yLocs.SetLength(glyphCount, fallible)) + { + return NS_ERROR_OUT_OF_MEMORY; + } + + // walk through the glyph slots and check which original character + // each is associated with + uint32_t gIndex = 0; // glyph slot index + uint32_t cIndex = 0; // current cluster index + for (const gr_slot *slot = gr_seg_first_slot(aSegment); + slot != nullptr; + slot = gr_slot_next_in_segment(slot), gIndex++) + { + uint32_t before = + gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot))); + uint32_t after = + gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot))); + gids[gIndex] = gr_slot_gid(slot); + xLocs[gIndex] = gr_slot_origin_X(slot); + yLocs[gIndex] = gr_slot_origin_Y(slot); + + // if this glyph has a "before" character index that precedes the + // current cluster's char index, we need to merge preceding + // clusters until it gets included + while (before < clusters[cIndex].baseChar && cIndex > 0) { + clusters[cIndex-1].nChars += clusters[cIndex].nChars; + clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs; + --cIndex; + } + + // if there's a gap between the current cluster's base character and + // this glyph's, extend the cluster to include the intervening chars + if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars && + before >= clusters[cIndex].baseChar + clusters[cIndex].nChars) + { + NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word"); + Cluster& c = clusters[cIndex + 1]; + c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars; + c.nChars = before - c.baseChar; + c.baseGlyph = gIndex; + c.nGlyphs = 0; + ++cIndex; + } + + // increment cluster's glyph count to include current slot + NS_ASSERTION(cIndex < aLength, "cIndex beyond word length"); + ++clusters[cIndex].nGlyphs; + + // bump |after| index if it falls in the middle of a surrogate pair + if (NS_IS_HIGH_SURROGATE(aText[after]) && after < aLength - 1 && + NS_IS_LOW_SURROGATE(aText[after + 1])) { + after++; + } + // extend cluster if necessary to reach the glyph's "after" index + if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) { + clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar; + } + } + + bool roundX, roundY; + GetRoundOffsetsToPixels(aDrawTarget, &roundX, &roundY); + + gfxShapedText::CompressedGlyph *charGlyphs = + aShapedText->GetCharacterGlyphs() + aOffset; + + // now put glyphs into the textrun, one cluster at a time + for (uint32_t i = 0; i <= cIndex; ++i) { + const Cluster& c = clusters[i]; + + float adv; // total advance of the cluster + if (rtl) { + if (i == 0) { + adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; + } else { + adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph]; + } + } else { + if (i == cIndex) { + adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; + } else { + adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph]; + } + } + + // Check for default-ignorable char that didn't get filtered, combined, + // etc by the shaping process, and skip it. + uint32_t offs = c.baseChar; + NS_ASSERTION(offs < aLength, "unexpected offset"); + if (c.nGlyphs == 1 && c.nChars == 1 && + aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) { + continue; + } + + uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits : + NSToIntRound(adv * dev2appUnits); + if (c.nGlyphs == 1 && + gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) && + gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance) && + charGlyphs[offs].IsClusterStart() && + yLocs[c.baseGlyph] == 0) + { + charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]); + } else { + // not a one-to-one mapping with simple metrics: use DetailedGlyph + AutoTArray<gfxShapedText::DetailedGlyph,8> details; + float clusterLoc; + for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) { + gfxShapedText::DetailedGlyph* d = details.AppendElement(); + d->mGlyphID = gids[j]; + d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits : + -yLocs[j] * dev2appUnits; + if (j == c.baseGlyph) { + d->mXOffset = 0; + d->mAdvance = appAdvance; + clusterLoc = xLocs[j]; + } else { + float dx = rtl ? (xLocs[j] - clusterLoc) : + (xLocs[j] - clusterLoc - adv); + d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits : + dx * dev2appUnits; + d->mAdvance = 0; + } + } + gfxShapedText::CompressedGlyph g; + g.SetComplex(charGlyphs[offs].IsClusterStart(), + true, details.Length()); + aShapedText->SetGlyphs(aOffset + offs, g, details.Elements()); + } + + for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) { + NS_ASSERTION(j < aLength, "unexpected offset"); + gfxShapedText::CompressedGlyph &g = charGlyphs[j]; + NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); + g.SetComplex(g.IsClusterStart(), false, 0); + } + } + + return NS_OK; +} + +#undef SMALL_GLYPH_RUN + +// for language tag validation - include list of tags from the IANA registry +#include "gfxLanguageTagList.cpp" + +nsTHashtable<nsUint32HashKey> *gfxGraphiteShaper::sLanguageTags; + +/*static*/ uint32_t +gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) +{ + int len = aLang.Length(); + if (len < 2) { + return 0; + } + + // convert primary language subtag to a left-packed, NUL-padded integer + // for the Graphite API + uint32_t grLang = 0; + for (int i = 0; i < 4; ++i) { + grLang <<= 8; + if (i < len) { + uint8_t ch = aLang[i]; + if (ch == '-') { + // found end of primary language subtag, truncate here + len = i; + continue; + } + if (ch < 'a' || ch > 'z') { + // invalid character in tag, so ignore it completely + return 0; + } + grLang += ch; + } + } + + // valid tags must have length = 2 or 3 + if (len < 2 || len > 3) { + return 0; + } + + if (!sLanguageTags) { + // store the registered IANA tags in a hash for convenient validation + sLanguageTags = new nsTHashtable<nsUint32HashKey>(ArrayLength(sLanguageTagList)); + for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) { + sLanguageTags->PutEntry(*tag); + } + } + + // only accept tags known in the IANA registry + if (sLanguageTags->GetEntry(grLang)) { + return grLang; + } + + return 0; +} + +/*static*/ void +gfxGraphiteShaper::Shutdown() +{ +#ifdef NS_FREE_PERMANENT_DATA + if (sLanguageTags) { + sLanguageTags->Clear(); + delete sLanguageTags; + sLanguageTags = nullptr; + } +#endif +} |