michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxGraphiteShaper.h" michael@0: #include "nsString.h" michael@0: #include "gfxContext.h" michael@0: michael@0: #include "graphite2/Font.h" michael@0: #include "graphite2/Segment.h" michael@0: michael@0: #include "harfbuzz/hb.h" michael@0: michael@0: #define FloatToFixed(f) (65536 * (f)) michael@0: #define FixedToFloat(f) ((f) * (1.0 / 65536.0)) michael@0: // Right shifts of negative (signed) integers are undefined, as are overflows michael@0: // when converting unsigned to negative signed integers. michael@0: // (If speed were an issue we could make some 2's complement assumptions.) michael@0: #define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \ michael@0: : -((32767 - (f)) >> 16)) michael@0: michael@0: using namespace mozilla; // for AutoSwap_* types michael@0: michael@0: /* michael@0: * Creation and destruction; on deletion, release any font tables we're holding michael@0: */ michael@0: michael@0: gfxGraphiteShaper::gfxGraphiteShaper(gfxFont *aFont) michael@0: : gfxFontShaper(aFont), michael@0: mGrFace(mFont->GetFontEntry()->GetGrFace()), michael@0: mGrFont(nullptr) michael@0: { michael@0: mCallbackData.mFont = aFont; michael@0: mCallbackData.mShaper = this; michael@0: } michael@0: michael@0: gfxGraphiteShaper::~gfxGraphiteShaper() michael@0: { michael@0: if (mGrFont) { michael@0: gr_font_destroy(mGrFont); michael@0: } michael@0: mFont->GetFontEntry()->ReleaseGrFace(mGrFace); michael@0: } michael@0: michael@0: /*static*/ float michael@0: gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid) michael@0: { michael@0: const CallbackData *cb = michael@0: static_cast(appFontHandle); michael@0: return FixedToFloat(cb->mFont->GetGlyphWidth(cb->mContext, glyphid)); michael@0: } michael@0: michael@0: static inline uint32_t michael@0: MakeGraphiteLangTag(uint32_t aTag) michael@0: { michael@0: uint32_t grLangTag = aTag; michael@0: // replace trailing space-padding with NULs for graphite michael@0: uint32_t mask = 0x000000FF; michael@0: while ((grLangTag & mask) == ' ') { michael@0: grLangTag &= ~mask; michael@0: mask <<= 8; michael@0: } michael@0: return grLangTag; michael@0: } michael@0: michael@0: struct GrFontFeatures { michael@0: gr_face *mFace; michael@0: gr_feature_val *mFeatures; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: AddFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg) michael@0: { michael@0: GrFontFeatures *f = static_cast(aUserArg); michael@0: michael@0: const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag); michael@0: if (fref) { michael@0: gr_fref_set_feature_value(fref, aValue, f->mFeatures); michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool michael@0: gfxGraphiteShaper::ShapeText(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText) michael@0: { michael@0: // some font back-ends require this in order to get proper hinted metrics michael@0: if (!mFont->SetupCairoFont(aContext)) { michael@0: return false; michael@0: } michael@0: michael@0: mCallbackData.mContext = aContext; michael@0: michael@0: if (!mGrFont) { michael@0: if (!mGrFace) { michael@0: return false; michael@0: } michael@0: michael@0: if (mFont->ProvidesGlyphWidths()) { michael@0: gr_font_ops ops = { michael@0: sizeof(gr_font_ops), michael@0: &GrGetAdvance, michael@0: nullptr // vertical text not yet implemented michael@0: }; michael@0: mGrFont = gr_make_font_with_ops(mFont->GetAdjustedSize(), michael@0: &mCallbackData, &ops, mGrFace); michael@0: } else { michael@0: mGrFont = gr_make_font(mFont->GetAdjustedSize(), mGrFace); michael@0: } michael@0: michael@0: if (!mGrFont) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: gfxFontEntry *entry = mFont->GetFontEntry(); michael@0: const gfxFontStyle *style = mFont->GetStyle(); michael@0: uint32_t grLang = 0; michael@0: if (style->languageOverride) { michael@0: grLang = MakeGraphiteLangTag(style->languageOverride); michael@0: } else if (entry->mLanguageOverride) { michael@0: grLang = MakeGraphiteLangTag(entry->mLanguageOverride); michael@0: } else { michael@0: nsAutoCString langString; michael@0: style->language->ToUTF8String(langString); michael@0: grLang = GetGraphiteTagForLang(langString); michael@0: } michael@0: gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang); michael@0: michael@0: nsDataHashtable mergedFeatures; michael@0: michael@0: // if style contains font-specific features michael@0: if (MergeFontFeatures(style, michael@0: mFont->GetFontEntry()->mFeatureSettings, michael@0: aShapedText->DisableLigatures(), michael@0: mFont->GetFontEntry()->FamilyName(), michael@0: mergedFeatures)) michael@0: { michael@0: // enumerate result and insert into Graphite feature list michael@0: GrFontFeatures f = {mGrFace, grFeatures}; michael@0: mergedFeatures.Enumerate(AddFeature, &f); michael@0: } michael@0: michael@0: size_t numChars = gr_count_unicode_characters(gr_utf16, michael@0: aText, aText + aLength, michael@0: nullptr); michael@0: gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures, michael@0: gr_utf16, aText, numChars, michael@0: aShapedText->IsRightToLeft()); michael@0: michael@0: gr_featureval_destroy(grFeatures); michael@0: michael@0: if (!seg) { michael@0: return false; michael@0: } michael@0: michael@0: nsresult rv = SetGlyphsFromSegment(aContext, aShapedText, aOffset, aLength, michael@0: aText, seg); michael@0: michael@0: gr_seg_destroy(seg); michael@0: michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: michael@0: #define SMALL_GLYPH_RUN 256 // avoid heap allocation of per-glyph data arrays michael@0: // for short (typical) runs up to this length michael@0: michael@0: struct Cluster { michael@0: uint32_t baseChar; // in UTF16 code units, not Unicode character indices michael@0: uint32_t baseGlyph; michael@0: uint32_t nChars; // UTF16 code units michael@0: uint32_t nGlyphs; michael@0: Cluster() : baseChar(0), baseGlyph(0), nChars(0), nGlyphs(0) { } michael@0: }; michael@0: michael@0: nsresult michael@0: gfxGraphiteShaper::SetGlyphsFromSegment(gfxContext *aContext, michael@0: gfxShapedText *aShapedText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: const char16_t *aText, michael@0: gr_segment *aSegment) michael@0: { michael@0: int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit(); michael@0: bool rtl = aShapedText->IsRightToLeft(); michael@0: michael@0: uint32_t glyphCount = gr_seg_n_slots(aSegment); michael@0: michael@0: // identify clusters; graphite may have reordered/expanded/ligated glyphs. michael@0: AutoFallibleTArray clusters; michael@0: AutoFallibleTArray gids; michael@0: AutoFallibleTArray xLocs; michael@0: AutoFallibleTArray yLocs; michael@0: michael@0: if (!clusters.SetLength(aLength) || michael@0: !gids.SetLength(glyphCount) || michael@0: !xLocs.SetLength(glyphCount) || michael@0: !yLocs.SetLength(glyphCount)) michael@0: { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // walk through the glyph slots and check which original character michael@0: // each is associated with michael@0: uint32_t gIndex = 0; // glyph slot index michael@0: uint32_t cIndex = 0; // current cluster index michael@0: for (const gr_slot *slot = gr_seg_first_slot(aSegment); michael@0: slot != nullptr; michael@0: slot = gr_slot_next_in_segment(slot), gIndex++) michael@0: { michael@0: uint32_t before = michael@0: gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_before(slot))); michael@0: uint32_t after = michael@0: gr_cinfo_base(gr_seg_cinfo(aSegment, gr_slot_after(slot))); michael@0: gids[gIndex] = gr_slot_gid(slot); michael@0: xLocs[gIndex] = gr_slot_origin_X(slot); michael@0: yLocs[gIndex] = gr_slot_origin_Y(slot); michael@0: michael@0: // if this glyph has a "before" character index that precedes the michael@0: // current cluster's char index, we need to merge preceding michael@0: // clusters until it gets included michael@0: while (before < clusters[cIndex].baseChar && cIndex > 0) { michael@0: clusters[cIndex-1].nChars += clusters[cIndex].nChars; michael@0: clusters[cIndex-1].nGlyphs += clusters[cIndex].nGlyphs; michael@0: --cIndex; michael@0: } michael@0: michael@0: // if there's a gap between the current cluster's base character and michael@0: // this glyph's, extend the cluster to include the intervening chars michael@0: if (gr_slot_can_insert_before(slot) && clusters[cIndex].nChars && michael@0: before >= clusters[cIndex].baseChar + clusters[cIndex].nChars) michael@0: { michael@0: NS_ASSERTION(cIndex < aLength - 1, "cIndex at end of word"); michael@0: Cluster& c = clusters[cIndex + 1]; michael@0: c.baseChar = clusters[cIndex].baseChar + clusters[cIndex].nChars; michael@0: c.nChars = before - c.baseChar; michael@0: c.baseGlyph = gIndex; michael@0: c.nGlyphs = 0; michael@0: ++cIndex; michael@0: } michael@0: michael@0: // increment cluster's glyph count to include current slot michael@0: NS_ASSERTION(cIndex < aLength, "cIndex beyond word length"); michael@0: ++clusters[cIndex].nGlyphs; michael@0: michael@0: // extend cluster if necessary to reach the glyph's "after" index michael@0: if (clusters[cIndex].baseChar + clusters[cIndex].nChars < after + 1) { michael@0: clusters[cIndex].nChars = after + 1 - clusters[cIndex].baseChar; michael@0: } michael@0: } michael@0: michael@0: bool roundX; michael@0: bool roundY; michael@0: aContext->GetRoundOffsetsToPixels(&roundX, &roundY); michael@0: michael@0: gfxShapedText::CompressedGlyph *charGlyphs = michael@0: aShapedText->GetCharacterGlyphs() + aOffset; michael@0: michael@0: // now put glyphs into the textrun, one cluster at a time michael@0: for (uint32_t i = 0; i <= cIndex; ++i) { michael@0: const Cluster& c = clusters[i]; michael@0: michael@0: float adv; // total advance of the cluster michael@0: if (rtl) { michael@0: if (i == 0) { michael@0: adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; michael@0: } else { michael@0: adv = xLocs[clusters[i-1].baseGlyph] - xLocs[c.baseGlyph]; michael@0: } michael@0: } else { michael@0: if (i == cIndex) { michael@0: adv = gr_seg_advance_X(aSegment) - xLocs[c.baseGlyph]; michael@0: } else { michael@0: adv = xLocs[clusters[i+1].baseGlyph] - xLocs[c.baseGlyph]; michael@0: } michael@0: } michael@0: michael@0: // Check for default-ignorable char that didn't get filtered, combined, michael@0: // etc by the shaping process, and skip it. michael@0: uint32_t offs = c.baseChar; michael@0: NS_ASSERTION(offs < aLength, "unexpected offset"); michael@0: if (c.nGlyphs == 1 && c.nChars == 1 && michael@0: aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) { michael@0: continue; michael@0: } michael@0: michael@0: uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits : michael@0: NSToIntRound(adv * dev2appUnits); michael@0: if (c.nGlyphs == 1 && michael@0: gfxShapedText::CompressedGlyph::IsSimpleGlyphID(gids[c.baseGlyph]) && michael@0: gfxShapedText::CompressedGlyph::IsSimpleAdvance(appAdvance) && michael@0: charGlyphs[offs].IsClusterStart() && michael@0: yLocs[c.baseGlyph] == 0) michael@0: { michael@0: charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]); michael@0: } else { michael@0: // not a one-to-one mapping with simple metrics: use DetailedGlyph michael@0: nsAutoTArray details; michael@0: float clusterLoc; michael@0: for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) { michael@0: gfxShapedText::DetailedGlyph* d = details.AppendElement(); michael@0: d->mGlyphID = gids[j]; michael@0: d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits : michael@0: -yLocs[j] * dev2appUnits; michael@0: if (j == c.baseGlyph) { michael@0: d->mXOffset = 0; michael@0: d->mAdvance = appAdvance; michael@0: clusterLoc = xLocs[j]; michael@0: } else { michael@0: float dx = rtl ? (xLocs[j] - clusterLoc) : michael@0: (xLocs[j] - clusterLoc - adv); michael@0: d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits : michael@0: dx * dev2appUnits; michael@0: d->mAdvance = 0; michael@0: } michael@0: } michael@0: gfxShapedText::CompressedGlyph g; michael@0: g.SetComplex(charGlyphs[offs].IsClusterStart(), michael@0: true, details.Length()); michael@0: aShapedText->SetGlyphs(aOffset + offs, g, details.Elements()); michael@0: } michael@0: michael@0: for (uint32_t j = c.baseChar + 1; j < c.baseChar + c.nChars; ++j) { michael@0: NS_ASSERTION(j < aLength, "unexpected offset"); michael@0: gfxShapedText::CompressedGlyph &g = charGlyphs[j]; michael@0: NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); michael@0: g.SetComplex(g.IsClusterStart(), false, 0); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #undef SMALL_GLYPH_RUN michael@0: michael@0: // for language tag validation - include list of tags from the IANA registry michael@0: #include "gfxLanguageTagList.cpp" michael@0: michael@0: nsTHashtable *gfxGraphiteShaper::sLanguageTags; michael@0: michael@0: /*static*/ uint32_t michael@0: gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) michael@0: { michael@0: int len = aLang.Length(); michael@0: if (len < 2) { michael@0: return 0; michael@0: } michael@0: michael@0: // convert primary language subtag to a left-packed, NUL-padded integer michael@0: // for the Graphite API michael@0: uint32_t grLang = 0; michael@0: for (int i = 0; i < 4; ++i) { michael@0: grLang <<= 8; michael@0: if (i < len) { michael@0: uint8_t ch = aLang[i]; michael@0: if (ch == '-') { michael@0: // found end of primary language subtag, truncate here michael@0: len = i; michael@0: continue; michael@0: } michael@0: if (ch < 'a' || ch > 'z') { michael@0: // invalid character in tag, so ignore it completely michael@0: return 0; michael@0: } michael@0: grLang += ch; michael@0: } michael@0: } michael@0: michael@0: // valid tags must have length = 2 or 3 michael@0: if (len < 2 || len > 3) { michael@0: return 0; michael@0: } michael@0: michael@0: if (!sLanguageTags) { michael@0: // store the registered IANA tags in a hash for convenient validation michael@0: sLanguageTags = new nsTHashtable(ArrayLength(sLanguageTagList)); michael@0: for (const uint32_t *tag = sLanguageTagList; *tag != 0; ++tag) { michael@0: sLanguageTags->PutEntry(*tag); michael@0: } michael@0: } michael@0: michael@0: // only accept tags known in the IANA registry michael@0: if (sLanguageTags->GetEntry(grLang)) { michael@0: return grLang; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxGraphiteShaper::Shutdown() michael@0: { michael@0: #ifdef NS_FREE_PERMANENT_DATA michael@0: if (sLanguageTags) { michael@0: sLanguageTags->Clear(); michael@0: delete sLanguageTags; michael@0: sLanguageTags = nullptr; michael@0: } michael@0: #endif michael@0: }