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 "nsString.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxHarfBuzzShaper.h" michael@0: #include "gfxFontUtils.h" michael@0: #include "nsUnicodeProperties.h" michael@0: #include "nsUnicodeScriptCodes.h" michael@0: #include "nsUnicodeNormalizer.h" michael@0: michael@0: #include "harfbuzz/hb.h" michael@0: #include "harfbuzz/hb-ot.h" michael@0: michael@0: #include 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: using namespace mozilla::unicode; // for Unicode property lookup michael@0: michael@0: /* michael@0: * Creation and destruction; on deletion, release any font tables we're holding michael@0: */ michael@0: michael@0: gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont *aFont) michael@0: : gfxFontShaper(aFont), michael@0: mHBFace(aFont->GetFontEntry()->GetHBFace()), michael@0: mHBFont(nullptr), michael@0: mKernTable(nullptr), michael@0: mHmtxTable(nullptr), michael@0: mNumLongMetrics(0), michael@0: mCmapTable(nullptr), michael@0: mCmapFormat(-1), michael@0: mSubtableOffset(0), michael@0: mUVSTableOffset(0), michael@0: mUseFontGetGlyph(aFont->ProvidesGetGlyph()), michael@0: mUseFontGlyphWidths(false), michael@0: mInitialized(false) michael@0: { michael@0: } michael@0: michael@0: gfxHarfBuzzShaper::~gfxHarfBuzzShaper() michael@0: { michael@0: if (mCmapTable) { michael@0: hb_blob_destroy(mCmapTable); michael@0: } michael@0: if (mHmtxTable) { michael@0: hb_blob_destroy(mHmtxTable); michael@0: } michael@0: if (mKernTable) { michael@0: hb_blob_destroy(mKernTable); michael@0: } michael@0: if (mHBFont) { michael@0: hb_font_destroy(mHBFont); michael@0: } michael@0: if (mHBFace) { michael@0: hb_face_destroy(mHBFace); michael@0: } michael@0: } michael@0: michael@0: #define UNICODE_BMP_LIMIT 0x10000 michael@0: michael@0: hb_codepoint_t michael@0: gfxHarfBuzzShaper::GetGlyph(hb_codepoint_t unicode, michael@0: hb_codepoint_t variation_selector) const michael@0: { michael@0: hb_codepoint_t gid = 0; michael@0: michael@0: if (mUseFontGetGlyph) { michael@0: gid = mFont->GetGlyph(unicode, variation_selector); michael@0: } else { michael@0: // we only instantiate a harfbuzz shaper if there's a cmap available michael@0: NS_ASSERTION(mFont->GetFontEntry()->HasCmapTable(), michael@0: "we cannot be using this font!"); michael@0: michael@0: NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0), michael@0: "cmap data not correctly set up, expect disaster"); michael@0: michael@0: const uint8_t* data = michael@0: (const uint8_t*)hb_blob_get_data(mCmapTable, nullptr); michael@0: michael@0: if (variation_selector) { michael@0: if (mUVSTableOffset) { michael@0: gid = michael@0: gfxFontUtils::MapUVSToGlyphFormat14(data + mUVSTableOffset, michael@0: unicode, michael@0: variation_selector); michael@0: } michael@0: if (!gid) { michael@0: uint32_t compat = michael@0: gfxFontUtils::GetUVSFallback(unicode, variation_selector); michael@0: if (compat) { michael@0: switch (mCmapFormat) { michael@0: case 4: michael@0: if (compat < UNICODE_BMP_LIMIT) { michael@0: gid = gfxFontUtils::MapCharToGlyphFormat4(data + mSubtableOffset, michael@0: compat); michael@0: } michael@0: break; michael@0: case 12: michael@0: gid = gfxFontUtils::MapCharToGlyphFormat12(data + mSubtableOffset, michael@0: compat); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: // If the variation sequence was not supported, return zero here; michael@0: // harfbuzz will call us again for the base character alone michael@0: return gid; michael@0: } michael@0: michael@0: switch (mCmapFormat) { michael@0: case 4: michael@0: gid = unicode < UNICODE_BMP_LIMIT ? michael@0: gfxFontUtils::MapCharToGlyphFormat4(data + mSubtableOffset, michael@0: unicode) : 0; michael@0: break; michael@0: case 12: michael@0: gid = gfxFontUtils::MapCharToGlyphFormat12(data + mSubtableOffset, michael@0: unicode); michael@0: break; michael@0: default: michael@0: NS_WARNING("unsupported cmap format, glyphs will be missing"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!gid) { michael@0: // if there's no glyph for  , just use the space glyph instead michael@0: if (unicode == 0xA0) { michael@0: gid = mFont->GetSpaceGlyph(); michael@0: } michael@0: } michael@0: michael@0: return gid; michael@0: } michael@0: michael@0: static hb_bool_t michael@0: HBGetGlyph(hb_font_t *font, void *font_data, michael@0: hb_codepoint_t unicode, hb_codepoint_t variation_selector, michael@0: hb_codepoint_t *glyph, michael@0: void *user_data) michael@0: { michael@0: const gfxHarfBuzzShaper::FontCallbackData *fcd = michael@0: static_cast(font_data); michael@0: *glyph = fcd->mShaper->GetGlyph(unicode, variation_selector); michael@0: return *glyph != 0; michael@0: } michael@0: michael@0: struct HMetricsHeader { michael@0: AutoSwap_PRUint32 tableVersionNumber; michael@0: AutoSwap_PRInt16 ascender; michael@0: AutoSwap_PRInt16 descender; michael@0: AutoSwap_PRInt16 lineGap; michael@0: AutoSwap_PRUint16 advanceWidthMax; michael@0: AutoSwap_PRInt16 minLeftSideBearing; michael@0: AutoSwap_PRInt16 minRightSideBearing; michael@0: AutoSwap_PRInt16 xMaxExtent; michael@0: AutoSwap_PRInt16 caretSlopeRise; michael@0: AutoSwap_PRInt16 caretSlopeRun; michael@0: AutoSwap_PRInt16 caretOffset; michael@0: AutoSwap_PRInt16 reserved[4]; michael@0: AutoSwap_PRInt16 metricDataFormat; michael@0: AutoSwap_PRUint16 numberOfHMetrics; michael@0: }; michael@0: michael@0: struct HLongMetric { michael@0: AutoSwap_PRUint16 advanceWidth; michael@0: AutoSwap_PRInt16 lsb; michael@0: }; michael@0: michael@0: struct HMetrics { michael@0: HLongMetric metrics[1]; // actually numberOfHMetrics michael@0: // the variable-length metrics[] array is immediately followed by: michael@0: // AutoSwap_PRUint16 leftSideBearing[]; michael@0: }; michael@0: michael@0: hb_position_t michael@0: gfxHarfBuzzShaper::GetGlyphHAdvance(gfxContext *aContext, michael@0: hb_codepoint_t glyph) const michael@0: { michael@0: // font did not implement GetHintedGlyphWidth, so get an unhinted value michael@0: // directly from the font tables michael@0: michael@0: NS_ASSERTION((mNumLongMetrics > 0) && mHmtxTable != nullptr, michael@0: "font is lacking metrics, we shouldn't be here"); michael@0: michael@0: if (glyph >= uint32_t(mNumLongMetrics)) { michael@0: glyph = mNumLongMetrics - 1; michael@0: } michael@0: michael@0: // glyph must be valid now, because we checked during initialization michael@0: // that mNumLongMetrics is > 0, and that the hmtx table is large enough michael@0: // to contain mNumLongMetrics records michael@0: const HMetrics* hmtx = michael@0: reinterpret_cast(hb_blob_get_data(mHmtxTable, nullptr)); michael@0: return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * michael@0: uint16_t(hmtx->metrics[glyph].advanceWidth)); michael@0: } michael@0: michael@0: /* static */ michael@0: hb_position_t michael@0: gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t *font, void *font_data, michael@0: hb_codepoint_t glyph, void *user_data) michael@0: { michael@0: const gfxHarfBuzzShaper::FontCallbackData *fcd = michael@0: static_cast(font_data); michael@0: gfxFont *gfxfont = fcd->mShaper->GetFont(); michael@0: if (gfxfont->ProvidesGlyphWidths()) { michael@0: return gfxfont->GetGlyphWidth(fcd->mContext, glyph); michael@0: } else { michael@0: return fcd->mShaper->GetGlyphHAdvance(fcd->mContext, glyph); michael@0: } michael@0: } michael@0: michael@0: static hb_bool_t michael@0: HBGetContourPoint(hb_font_t *font, void *font_data, michael@0: unsigned int point_index, hb_codepoint_t glyph, michael@0: hb_position_t *x, hb_position_t *y, michael@0: void *user_data) michael@0: { michael@0: /* not yet implemented - no support for used of hinted contour points michael@0: to fine-tune anchor positions in GPOS AnchorFormat2 */ michael@0: return false; michael@0: } michael@0: michael@0: struct KernHeaderFmt0 { michael@0: AutoSwap_PRUint16 nPairs; michael@0: AutoSwap_PRUint16 searchRange; michael@0: AutoSwap_PRUint16 entrySelector; michael@0: AutoSwap_PRUint16 rangeShift; michael@0: }; michael@0: michael@0: struct KernPair { michael@0: AutoSwap_PRUint16 left; michael@0: AutoSwap_PRUint16 right; michael@0: AutoSwap_PRInt16 value; michael@0: }; michael@0: michael@0: // Find a kern pair in a Format 0 subtable. michael@0: // The aSubtable parameter points to the subtable itself, NOT its header, michael@0: // as the header structure differs between Windows and Mac (v0 and v1.0) michael@0: // versions of the 'kern' table. michael@0: // aSubtableLen is the length of the subtable EXCLUDING its header. michael@0: // If the pair is found, the kerning value is michael@0: // added to aValue, so that multiple subtables can accumulate a total michael@0: // kerning value for a given pair. michael@0: static void michael@0: GetKernValueFmt0(const void* aSubtable, michael@0: uint32_t aSubtableLen, michael@0: uint16_t aFirstGlyph, michael@0: uint16_t aSecondGlyph, michael@0: int32_t& aValue, michael@0: bool aIsOverride = false, michael@0: bool aIsMinimum = false) michael@0: { michael@0: const KernHeaderFmt0* hdr = michael@0: reinterpret_cast(aSubtable); michael@0: michael@0: const KernPair *lo = reinterpret_cast(hdr + 1); michael@0: const KernPair *hi = lo + uint16_t(hdr->nPairs); michael@0: const KernPair *limit = hi; michael@0: michael@0: if (reinterpret_cast(aSubtable) + aSubtableLen < michael@0: reinterpret_cast(hi)) { michael@0: // subtable is not large enough to contain the claimed number michael@0: // of kern pairs, so just ignore it michael@0: return; michael@0: } michael@0: michael@0: #define KERN_PAIR_KEY(l,r) (uint32_t((uint16_t(l) << 16) + uint16_t(r))) michael@0: michael@0: uint32_t key = KERN_PAIR_KEY(aFirstGlyph, aSecondGlyph); michael@0: while (lo < hi) { michael@0: const KernPair *mid = lo + (hi - lo) / 2; michael@0: if (KERN_PAIR_KEY(mid->left, mid->right) < key) { michael@0: lo = mid + 1; michael@0: } else { michael@0: hi = mid; michael@0: } michael@0: } michael@0: michael@0: if (lo < limit && KERN_PAIR_KEY(lo->left, lo->right) == key) { michael@0: if (aIsOverride) { michael@0: aValue = int16_t(lo->value); michael@0: } else if (aIsMinimum) { michael@0: aValue = std::max(aValue, int32_t(lo->value)); michael@0: } else { michael@0: aValue += int16_t(lo->value); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Get kerning value from Apple (version 1.0) kern table, michael@0: // subtable format 2 (simple N x M array of kerning values) michael@0: michael@0: // See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html michael@0: // for details of version 1.0 format 2 subtable. michael@0: michael@0: struct KernHeaderVersion1Fmt2 { michael@0: KernTableSubtableHeaderVersion1 header; michael@0: AutoSwap_PRUint16 rowWidth; michael@0: AutoSwap_PRUint16 leftOffsetTable; michael@0: AutoSwap_PRUint16 rightOffsetTable; michael@0: AutoSwap_PRUint16 array; michael@0: }; michael@0: michael@0: struct KernClassTableHdr { michael@0: AutoSwap_PRUint16 firstGlyph; michael@0: AutoSwap_PRUint16 nGlyphs; michael@0: AutoSwap_PRUint16 offsets[1]; // actually an array of nGlyphs entries michael@0: }; michael@0: michael@0: static int16_t michael@0: GetKernValueVersion1Fmt2(const void* aSubtable, michael@0: uint32_t aSubtableLen, michael@0: uint16_t aFirstGlyph, michael@0: uint16_t aSecondGlyph) michael@0: { michael@0: if (aSubtableLen < sizeof(KernHeaderVersion1Fmt2)) { michael@0: return 0; michael@0: } michael@0: michael@0: const char* base = reinterpret_cast(aSubtable); michael@0: const char* subtableEnd = base + aSubtableLen; michael@0: michael@0: const KernHeaderVersion1Fmt2* h = michael@0: reinterpret_cast(aSubtable); michael@0: uint32_t offset = h->array; michael@0: michael@0: const KernClassTableHdr* leftClassTable = michael@0: reinterpret_cast(base + michael@0: uint16_t(h->leftOffsetTable)); michael@0: if (reinterpret_cast(leftClassTable) + michael@0: sizeof(KernClassTableHdr) > subtableEnd) { michael@0: return 0; michael@0: } michael@0: if (aFirstGlyph >= uint16_t(leftClassTable->firstGlyph)) { michael@0: aFirstGlyph -= uint16_t(leftClassTable->firstGlyph); michael@0: if (aFirstGlyph < uint16_t(leftClassTable->nGlyphs)) { michael@0: if (reinterpret_cast(leftClassTable) + michael@0: sizeof(KernClassTableHdr) + michael@0: aFirstGlyph * sizeof(uint16_t) >= subtableEnd) { michael@0: return 0; michael@0: } michael@0: offset = uint16_t(leftClassTable->offsets[aFirstGlyph]); michael@0: } michael@0: } michael@0: michael@0: const KernClassTableHdr* rightClassTable = michael@0: reinterpret_cast(base + michael@0: uint16_t(h->rightOffsetTable)); michael@0: if (reinterpret_cast(rightClassTable) + michael@0: sizeof(KernClassTableHdr) > subtableEnd) { michael@0: return 0; michael@0: } michael@0: if (aSecondGlyph >= uint16_t(rightClassTable->firstGlyph)) { michael@0: aSecondGlyph -= uint16_t(rightClassTable->firstGlyph); michael@0: if (aSecondGlyph < uint16_t(rightClassTable->nGlyphs)) { michael@0: if (reinterpret_cast(rightClassTable) + michael@0: sizeof(KernClassTableHdr) + michael@0: aSecondGlyph * sizeof(uint16_t) >= subtableEnd) { michael@0: return 0; michael@0: } michael@0: offset += uint16_t(rightClassTable->offsets[aSecondGlyph]); michael@0: } michael@0: } michael@0: michael@0: const AutoSwap_PRInt16* pval = michael@0: reinterpret_cast(base + offset); michael@0: if (reinterpret_cast(pval + 1) >= subtableEnd) { michael@0: return 0; michael@0: } michael@0: return *pval; michael@0: } michael@0: michael@0: // Get kerning value from Apple (version 1.0) kern table, michael@0: // subtable format 3 (simple N x M array of kerning values) michael@0: michael@0: // See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html michael@0: // for details of version 1.0 format 3 subtable. michael@0: michael@0: struct KernHeaderVersion1Fmt3 { michael@0: KernTableSubtableHeaderVersion1 header; michael@0: AutoSwap_PRUint16 glyphCount; michael@0: uint8_t kernValueCount; michael@0: uint8_t leftClassCount; michael@0: uint8_t rightClassCount; michael@0: uint8_t flags; michael@0: }; michael@0: michael@0: static int16_t michael@0: GetKernValueVersion1Fmt3(const void* aSubtable, michael@0: uint32_t aSubtableLen, michael@0: uint16_t aFirstGlyph, michael@0: uint16_t aSecondGlyph) michael@0: { michael@0: // check that we can safely read the header fields michael@0: if (aSubtableLen < sizeof(KernHeaderVersion1Fmt3)) { michael@0: return 0; michael@0: } michael@0: michael@0: const KernHeaderVersion1Fmt3* hdr = michael@0: reinterpret_cast(aSubtable); michael@0: if (hdr->flags != 0) { michael@0: return 0; michael@0: } michael@0: michael@0: uint16_t glyphCount = hdr->glyphCount; michael@0: michael@0: // check that table is large enough for the arrays michael@0: if (sizeof(KernHeaderVersion1Fmt3) + michael@0: hdr->kernValueCount * sizeof(int16_t) + michael@0: glyphCount + glyphCount + michael@0: hdr->leftClassCount * hdr->rightClassCount > aSubtableLen) { michael@0: return 0; michael@0: } michael@0: michael@0: if (aFirstGlyph >= glyphCount || aSecondGlyph >= glyphCount) { michael@0: // glyphs are out of range for the class tables michael@0: return 0; michael@0: } michael@0: michael@0: // get pointers to the four arrays within the subtable michael@0: const AutoSwap_PRInt16* kernValue = michael@0: reinterpret_cast(hdr + 1); michael@0: const uint8_t* leftClass = michael@0: reinterpret_cast(kernValue + hdr->kernValueCount); michael@0: const uint8_t* rightClass = leftClass + glyphCount; michael@0: const uint8_t* kernIndex = rightClass + glyphCount; michael@0: michael@0: uint8_t lc = leftClass[aFirstGlyph]; michael@0: uint8_t rc = rightClass[aSecondGlyph]; michael@0: if (lc >= hdr->leftClassCount || rc >= hdr->rightClassCount) { michael@0: return 0; michael@0: } michael@0: michael@0: uint8_t ki = kernIndex[leftClass[aFirstGlyph] * hdr->rightClassCount + michael@0: rightClass[aSecondGlyph]]; michael@0: if (ki >= hdr->kernValueCount) { michael@0: return 0; michael@0: } michael@0: michael@0: return kernValue[ki]; michael@0: } michael@0: michael@0: #define KERN0_COVERAGE_HORIZONTAL 0x0001 michael@0: #define KERN0_COVERAGE_MINIMUM 0x0002 michael@0: #define KERN0_COVERAGE_CROSS_STREAM 0x0004 michael@0: #define KERN0_COVERAGE_OVERRIDE 0x0008 michael@0: #define KERN0_COVERAGE_RESERVED 0x00F0 michael@0: michael@0: #define KERN1_COVERAGE_VERTICAL 0x8000 michael@0: #define KERN1_COVERAGE_CROSS_STREAM 0x4000 michael@0: #define KERN1_COVERAGE_VARIATION 0x2000 michael@0: #define KERN1_COVERAGE_RESERVED 0x1F00 michael@0: michael@0: hb_position_t michael@0: gfxHarfBuzzShaper::GetHKerning(uint16_t aFirstGlyph, michael@0: uint16_t aSecondGlyph) const michael@0: { michael@0: // We want to ignore any kern pairs involving , because we are michael@0: // handling words in isolation, the only space characters seen here are michael@0: // the ones artificially added by the textRun code. michael@0: uint32_t spaceGlyph = mFont->GetSpaceGlyph(); michael@0: if (aFirstGlyph == spaceGlyph || aSecondGlyph == spaceGlyph) { michael@0: return 0; michael@0: } michael@0: michael@0: if (!mKernTable) { michael@0: mKernTable = mFont->GetFontEntry()->GetFontTable(TRUETYPE_TAG('k','e','r','n')); michael@0: if (!mKernTable) { michael@0: mKernTable = hb_blob_get_empty(); michael@0: } michael@0: } michael@0: michael@0: uint32_t len; michael@0: const char* base = hb_blob_get_data(mKernTable, &len); michael@0: if (len < sizeof(KernTableVersion0)) { michael@0: return 0; michael@0: } michael@0: int32_t value = 0; michael@0: michael@0: // First try to interpret as "version 0" kern table michael@0: // (see http://www.microsoft.com/typography/otspec/kern.htm) michael@0: const KernTableVersion0* kern0 = michael@0: reinterpret_cast(base); michael@0: if (uint16_t(kern0->version) == 0) { michael@0: uint16_t nTables = kern0->nTables; michael@0: uint32_t offs = sizeof(KernTableVersion0); michael@0: for (uint16_t i = 0; i < nTables; ++i) { michael@0: if (offs + sizeof(KernTableSubtableHeaderVersion0) > len) { michael@0: break; michael@0: } michael@0: const KernTableSubtableHeaderVersion0* st0 = michael@0: reinterpret_cast michael@0: (base + offs); michael@0: uint16_t subtableLen = uint16_t(st0->length); michael@0: if (offs + subtableLen > len) { michael@0: break; michael@0: } michael@0: offs += subtableLen; michael@0: uint16_t coverage = st0->coverage; michael@0: if (!(coverage & KERN0_COVERAGE_HORIZONTAL)) { michael@0: // we only care about horizontal kerning (for now) michael@0: continue; michael@0: } michael@0: if (coverage & michael@0: (KERN0_COVERAGE_CROSS_STREAM | KERN0_COVERAGE_RESERVED)) { michael@0: // we don't support cross-stream kerning, and michael@0: // reserved bits should be zero; michael@0: // ignore the subtable if not michael@0: continue; michael@0: } michael@0: uint8_t format = (coverage >> 8); michael@0: switch (format) { michael@0: case 0: michael@0: GetKernValueFmt0(st0 + 1, subtableLen - sizeof(*st0), michael@0: aFirstGlyph, aSecondGlyph, value, michael@0: (coverage & KERN0_COVERAGE_OVERRIDE) != 0, michael@0: (coverage & KERN0_COVERAGE_MINIMUM) != 0); michael@0: break; michael@0: default: michael@0: // TODO: implement support for other formats, michael@0: // if they're ever used in practice michael@0: #if DEBUG michael@0: { michael@0: char buf[1024]; michael@0: sprintf(buf, "unknown kern subtable in %s: " michael@0: "ver 0 format %d\n", michael@0: NS_ConvertUTF16toUTF8(mFont->GetName()).get(), michael@0: format); michael@0: NS_WARNING(buf); michael@0: } michael@0: #endif michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: // It wasn't a "version 0" table; check if it is Apple version 1.0 michael@0: // (see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) michael@0: const KernTableVersion1* kern1 = michael@0: reinterpret_cast(base); michael@0: if (uint32_t(kern1->version) == 0x00010000) { michael@0: uint32_t nTables = kern1->nTables; michael@0: uint32_t offs = sizeof(KernTableVersion1); michael@0: for (uint32_t i = 0; i < nTables; ++i) { michael@0: if (offs + sizeof(KernTableSubtableHeaderVersion1) > len) { michael@0: break; michael@0: } michael@0: const KernTableSubtableHeaderVersion1* st1 = michael@0: reinterpret_cast michael@0: (base + offs); michael@0: uint32_t subtableLen = uint32_t(st1->length); michael@0: offs += subtableLen; michael@0: uint16_t coverage = st1->coverage; michael@0: if (coverage & michael@0: (KERN1_COVERAGE_VERTICAL | michael@0: KERN1_COVERAGE_CROSS_STREAM | michael@0: KERN1_COVERAGE_VARIATION | michael@0: KERN1_COVERAGE_RESERVED)) { michael@0: // we only care about horizontal kerning (for now), michael@0: // we don't support cross-stream kerning, michael@0: // we don't support variations, michael@0: // reserved bits should be zero; michael@0: // ignore the subtable if not michael@0: continue; michael@0: } michael@0: uint8_t format = (coverage & 0xff); michael@0: switch (format) { michael@0: case 0: michael@0: GetKernValueFmt0(st1 + 1, subtableLen - sizeof(*st1), michael@0: aFirstGlyph, aSecondGlyph, value); michael@0: break; michael@0: case 2: michael@0: value = GetKernValueVersion1Fmt2(st1, subtableLen, michael@0: aFirstGlyph, aSecondGlyph); michael@0: break; michael@0: case 3: michael@0: value = GetKernValueVersion1Fmt3(st1, subtableLen, michael@0: aFirstGlyph, aSecondGlyph); michael@0: break; michael@0: default: michael@0: // TODO: implement support for other formats. michael@0: // Note that format 1 cannot be supported here, michael@0: // as it requires the full glyph array to run the FSM, michael@0: // not just the current glyph pair. michael@0: #if DEBUG michael@0: { michael@0: char buf[1024]; michael@0: sprintf(buf, "unknown kern subtable in %s: " michael@0: "ver 0 format %d\n", michael@0: NS_ConvertUTF16toUTF8(mFont->GetName()).get(), michael@0: format); michael@0: NS_WARNING(buf); michael@0: } michael@0: #endif michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (value != 0) { michael@0: return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * value); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static hb_position_t michael@0: HBGetHKerning(hb_font_t *font, void *font_data, michael@0: hb_codepoint_t first_glyph, hb_codepoint_t second_glyph, michael@0: void *user_data) michael@0: { michael@0: const gfxHarfBuzzShaper::FontCallbackData *fcd = michael@0: static_cast(font_data); michael@0: return fcd->mShaper->GetHKerning(first_glyph, second_glyph); michael@0: } michael@0: michael@0: /* michael@0: * HarfBuzz unicode property callbacks michael@0: */ michael@0: michael@0: static hb_codepoint_t michael@0: HBGetMirroring(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, michael@0: void *user_data) michael@0: { michael@0: return GetMirroredChar(aCh); michael@0: } michael@0: michael@0: static hb_unicode_general_category_t michael@0: HBGetGeneralCategory(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, michael@0: void *user_data) michael@0: { michael@0: return hb_unicode_general_category_t(GetGeneralCategory(aCh)); michael@0: } michael@0: michael@0: static hb_script_t michael@0: HBGetScript(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, void *user_data) michael@0: { michael@0: return hb_script_t(GetScriptTagForCode(GetScriptCode(aCh))); michael@0: } michael@0: michael@0: static hb_unicode_combining_class_t michael@0: HBGetCombiningClass(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, michael@0: void *user_data) michael@0: { michael@0: return hb_unicode_combining_class_t(GetCombiningClass(aCh)); michael@0: } michael@0: michael@0: static unsigned int michael@0: HBGetEastAsianWidth(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, michael@0: void *user_data) michael@0: { michael@0: return GetEastAsianWidth(aCh); michael@0: } michael@0: michael@0: // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA; michael@0: // note that some letters do not have a dagesh presForm encoded michael@0: static const char16_t sDageshForms[0x05EA - 0x05D0 + 1] = { michael@0: 0xFB30, // ALEF michael@0: 0xFB31, // BET michael@0: 0xFB32, // GIMEL michael@0: 0xFB33, // DALET michael@0: 0xFB34, // HE michael@0: 0xFB35, // VAV michael@0: 0xFB36, // ZAYIN michael@0: 0, // HET michael@0: 0xFB38, // TET michael@0: 0xFB39, // YOD michael@0: 0xFB3A, // FINAL KAF michael@0: 0xFB3B, // KAF michael@0: 0xFB3C, // LAMED michael@0: 0, // FINAL MEM michael@0: 0xFB3E, // MEM michael@0: 0, // FINAL NUN michael@0: 0xFB40, // NUN michael@0: 0xFB41, // SAMEKH michael@0: 0, // AYIN michael@0: 0xFB43, // FINAL PE michael@0: 0xFB44, // PE michael@0: 0, // FINAL TSADI michael@0: 0xFB46, // TSADI michael@0: 0xFB47, // QOF michael@0: 0xFB48, // RESH michael@0: 0xFB49, // SHIN michael@0: 0xFB4A // TAV michael@0: }; michael@0: michael@0: static hb_bool_t michael@0: HBUnicodeCompose(hb_unicode_funcs_t *ufuncs, michael@0: hb_codepoint_t a, michael@0: hb_codepoint_t b, michael@0: hb_codepoint_t *ab, michael@0: void *user_data) michael@0: { michael@0: hb_bool_t found = nsUnicodeNormalizer::Compose(a, b, ab); michael@0: michael@0: if (!found && (b & 0x1fff80) == 0x0580) { michael@0: // special-case Hebrew presentation forms that are excluded from michael@0: // standard normalization, but wanted for old fonts michael@0: switch (b) { michael@0: case 0x05B4: // HIRIQ michael@0: if (a == 0x05D9) { // YOD michael@0: *ab = 0xFB1D; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05B7: // patah michael@0: if (a == 0x05F2) { // YIDDISH YOD YOD michael@0: *ab = 0xFB1F; michael@0: found = true; michael@0: } else if (a == 0x05D0) { // ALEF michael@0: *ab = 0xFB2E; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05B8: // QAMATS michael@0: if (a == 0x05D0) { // ALEF michael@0: *ab = 0xFB2F; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05B9: // HOLAM michael@0: if (a == 0x05D5) { // VAV michael@0: *ab = 0xFB4B; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05BC: // DAGESH michael@0: if (a >= 0x05D0 && a <= 0x05EA) { michael@0: *ab = sDageshForms[a - 0x05D0]; michael@0: found = (*ab != 0); michael@0: } else if (a == 0xFB2A) { // SHIN WITH SHIN DOT michael@0: *ab = 0xFB2C; michael@0: found = true; michael@0: } else if (a == 0xFB2B) { // SHIN WITH SIN DOT michael@0: *ab = 0xFB2D; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05BF: // RAFE michael@0: switch (a) { michael@0: case 0x05D1: // BET michael@0: *ab = 0xFB4C; michael@0: found = true; michael@0: break; michael@0: case 0x05DB: // KAF michael@0: *ab = 0xFB4D; michael@0: found = true; michael@0: break; michael@0: case 0x05E4: // PE michael@0: *ab = 0xFB4E; michael@0: found = true; michael@0: break; michael@0: } michael@0: break; michael@0: case 0x05C1: // SHIN DOT michael@0: if (a == 0x05E9) { // SHIN michael@0: *ab = 0xFB2A; michael@0: found = true; michael@0: } else if (a == 0xFB49) { // SHIN WITH DAGESH michael@0: *ab = 0xFB2C; michael@0: found = true; michael@0: } michael@0: break; michael@0: case 0x05C2: // SIN DOT michael@0: if (a == 0x05E9) { // SHIN michael@0: *ab = 0xFB2B; michael@0: found = true; michael@0: } else if (a == 0xFB49) { // SHIN WITH DAGESH michael@0: *ab = 0xFB2D; michael@0: found = true; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return found; michael@0: } michael@0: michael@0: static hb_bool_t michael@0: HBUnicodeDecompose(hb_unicode_funcs_t *ufuncs, michael@0: hb_codepoint_t ab, michael@0: hb_codepoint_t *a, michael@0: hb_codepoint_t *b, michael@0: void *user_data) michael@0: { michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: // Hack for the SamsungDevanagari font, bug 1012365: michael@0: // support U+0972 by decomposing it. michael@0: if (ab == 0x0972) { michael@0: *a = 0x0905; michael@0: *b = 0x0945; michael@0: return true; michael@0: } michael@0: #endif michael@0: return nsUnicodeNormalizer::DecomposeNonRecursively(ab, a, b); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: AddOpenTypeFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg) michael@0: { michael@0: nsTArray* features = static_cast*> (aUserArg); michael@0: michael@0: hb_feature_t feat = { 0, 0, 0, UINT_MAX }; michael@0: feat.tag = aTag; michael@0: feat.value = aValue; michael@0: features->AppendElement(feat); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /* michael@0: * gfxFontShaper override to initialize the text run using HarfBuzz michael@0: */ michael@0: michael@0: static hb_font_funcs_t * sHBFontFuncs = nullptr; michael@0: static hb_unicode_funcs_t * sHBUnicodeFuncs = nullptr; michael@0: static const hb_script_t sMathScript = michael@0: hb_ot_tag_to_script(HB_TAG('m','a','t','h')); michael@0: michael@0: bool michael@0: gfxHarfBuzzShaper::Initialize() michael@0: { michael@0: if (mInitialized) { michael@0: return mHBFont != nullptr; michael@0: } michael@0: mInitialized = true; michael@0: mCallbackData.mShaper = this; michael@0: michael@0: mUseFontGlyphWidths = mFont->ProvidesGlyphWidths(); michael@0: michael@0: if (!sHBFontFuncs) { michael@0: // static function callback pointers, initialized by the first michael@0: // harfbuzz shaper used michael@0: sHBFontFuncs = hb_font_funcs_create(); michael@0: hb_font_funcs_set_glyph_func(sHBFontFuncs, HBGetGlyph, michael@0: nullptr, nullptr); michael@0: hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, michael@0: HBGetGlyphHAdvance, michael@0: nullptr, nullptr); michael@0: hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, michael@0: HBGetContourPoint, michael@0: nullptr, nullptr); michael@0: hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, michael@0: HBGetHKerning, michael@0: nullptr, nullptr); michael@0: michael@0: sHBUnicodeFuncs = michael@0: hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); michael@0: hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, michael@0: HBGetMirroring, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_general_category_func(sHBUnicodeFuncs, michael@0: HBGetGeneralCategory, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_combining_class_func(sHBUnicodeFuncs, michael@0: HBGetCombiningClass, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_eastasian_width_func(sHBUnicodeFuncs, michael@0: HBGetEastAsianWidth, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, michael@0: HBUnicodeCompose, michael@0: nullptr, nullptr); michael@0: hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, michael@0: HBUnicodeDecompose, michael@0: nullptr, nullptr); michael@0: } michael@0: michael@0: gfxFontEntry *entry = mFont->GetFontEntry(); michael@0: if (!mUseFontGetGlyph) { michael@0: // get the cmap table and find offset to our subtable michael@0: mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c','m','a','p')); michael@0: if (!mCmapTable) { michael@0: NS_WARNING("failed to load cmap, glyphs will be missing"); michael@0: return false; michael@0: } michael@0: uint32_t len; michael@0: const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len); michael@0: bool symbol; michael@0: mCmapFormat = gfxFontUtils:: michael@0: FindPreferredSubtable(data, len, michael@0: &mSubtableOffset, &mUVSTableOffset, michael@0: &symbol); michael@0: if (mCmapFormat <= 0) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!mUseFontGlyphWidths) { michael@0: // if font doesn't implement GetGlyphWidth, we will be reading michael@0: // the hmtx table directly; michael@0: // read mNumLongMetrics from hhea table without caching its blob, michael@0: // and preload/cache the hmtx table michael@0: gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h','h','e','a')); michael@0: if (hheaTable) { michael@0: uint32_t len; michael@0: const HMetricsHeader* hhea = michael@0: reinterpret_cast michael@0: (hb_blob_get_data(hheaTable, &len)); michael@0: if (len >= sizeof(HMetricsHeader)) { michael@0: mNumLongMetrics = hhea->numberOfHMetrics; michael@0: if (mNumLongMetrics > 0 && michael@0: int16_t(hhea->metricDataFormat) == 0) { michael@0: // no point reading hmtx if number of entries is zero! michael@0: // in that case, we won't be able to use this font michael@0: // (this method will return FALSE below if mHmtx is null) michael@0: mHmtxTable = michael@0: entry->GetFontTable(TRUETYPE_TAG('h','m','t','x')); michael@0: if (hb_blob_get_length(mHmtxTable) < michael@0: mNumLongMetrics * sizeof(HLongMetric)) { michael@0: // hmtx table is not large enough for the claimed michael@0: // number of entries: invalid, do not use. michael@0: hb_blob_destroy(mHmtxTable); michael@0: mHmtxTable = nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (!mHmtxTable) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: mHBFont = hb_font_create(mHBFace); michael@0: hb_font_set_funcs(mHBFont, sHBFontFuncs, &mCallbackData, nullptr); michael@0: hb_font_set_ppem(mHBFont, mFont->GetAdjustedSize(), mFont->GetAdjustedSize()); michael@0: uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point michael@0: hb_font_set_scale(mHBFont, scale, scale); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxHarfBuzzShaper::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 (!Initialize()) { michael@0: return false; michael@0: } michael@0: michael@0: const gfxFontStyle *style = mFont->GetStyle(); michael@0: michael@0: nsAutoTArray features; michael@0: nsDataHashtable mergedFeatures; michael@0: michael@0: gfxFontEntry *entry = mFont->GetFontEntry(); michael@0: if (MergeFontFeatures(style, michael@0: entry->mFeatureSettings, michael@0: aShapedText->DisableLigatures(), michael@0: entry->FamilyName(), michael@0: mergedFeatures)) michael@0: { michael@0: // enumerate result and insert into hb_feature array michael@0: mergedFeatures.Enumerate(AddOpenTypeFeature, &features); michael@0: } michael@0: michael@0: bool isRightToLeft = aShapedText->IsRightToLeft(); michael@0: hb_buffer_t *buffer = hb_buffer_create(); michael@0: hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs); michael@0: hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL : michael@0: HB_DIRECTION_LTR); michael@0: hb_script_t scriptTag; michael@0: if (aShapedText->Flags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) { michael@0: scriptTag = sMathScript; michael@0: } else if (aScript <= MOZ_SCRIPT_INHERITED) { michael@0: // For unresolved "common" or "inherited" runs, default to Latin for michael@0: // now. (Should we somehow use the language or locale to try and infer michael@0: // a better default?) michael@0: scriptTag = HB_SCRIPT_LATIN; michael@0: } else { michael@0: scriptTag = hb_script_t(GetScriptTagForCode(aScript)); michael@0: } michael@0: hb_buffer_set_script(buffer, scriptTag); michael@0: michael@0: hb_language_t language; michael@0: if (style->languageOverride) { michael@0: language = hb_ot_tag_to_language(style->languageOverride); michael@0: } else if (entry->mLanguageOverride) { michael@0: language = hb_ot_tag_to_language(entry->mLanguageOverride); michael@0: } else { michael@0: nsCString langString; michael@0: style->language->ToUTF8String(langString); michael@0: language = michael@0: hb_language_from_string(langString.get(), langString.Length()); michael@0: } michael@0: hb_buffer_set_language(buffer, language); michael@0: michael@0: uint32_t length = aLength; michael@0: hb_buffer_add_utf16(buffer, michael@0: reinterpret_cast(aText), michael@0: length, 0, length); michael@0: michael@0: hb_shape(mHBFont, buffer, features.Elements(), features.Length()); michael@0: michael@0: if (isRightToLeft) { michael@0: hb_buffer_reverse(buffer); michael@0: } michael@0: michael@0: nsresult rv = SetGlyphsFromRun(aContext, aShapedText, aOffset, aLength, michael@0: aText, buffer); michael@0: michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into gfxShapedWord"); michael@0: hb_buffer_destroy(buffer); michael@0: michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: michael@0: #define SMALL_GLYPH_RUN 128 // some testing indicates that 90%+ of text runs michael@0: // will fit without requiring separate allocation michael@0: // for charToGlyphArray michael@0: michael@0: nsresult michael@0: gfxHarfBuzzShaper::SetGlyphsFromRun(gfxContext *aContext, michael@0: gfxShapedText *aShapedText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: const char16_t *aText, michael@0: hb_buffer_t *aBuffer) michael@0: { michael@0: uint32_t numGlyphs; michael@0: const hb_glyph_info_t *ginfo = hb_buffer_get_glyph_infos(aBuffer, &numGlyphs); michael@0: if (numGlyphs == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoTArray detailedGlyphs; michael@0: michael@0: uint32_t wordLength = aLength; michael@0: static const int32_t NO_GLYPH = -1; michael@0: AutoFallibleTArray charToGlyphArray; michael@0: if (!charToGlyphArray.SetLength(wordLength)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: int32_t *charToGlyph = charToGlyphArray.Elements(); michael@0: for (uint32_t offset = 0; offset < wordLength; ++offset) { michael@0: charToGlyph[offset] = NO_GLYPH; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < numGlyphs; ++i) { michael@0: uint32_t loc = ginfo[i].cluster; michael@0: if (loc < wordLength) { michael@0: charToGlyph[loc] = i; michael@0: } michael@0: } michael@0: michael@0: int32_t glyphStart = 0; // looking for a clump that starts at this glyph michael@0: int32_t charStart = 0; // and this char index within the range of the run michael@0: michael@0: bool roundX; michael@0: bool roundY; michael@0: aContext->GetRoundOffsetsToPixels(&roundX, &roundY); michael@0: michael@0: int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); michael@0: gfxShapedText::CompressedGlyph *charGlyphs = michael@0: aShapedText->GetCharacterGlyphs() + aOffset; michael@0: michael@0: // factor to convert 16.16 fixed-point pixels to app units michael@0: // (only used if not rounding) michael@0: double hb2appUnits = FixedToFloat(aShapedText->GetAppUnitsPerDevUnit()); michael@0: michael@0: // Residual from rounding of previous advance, for use in rounding the michael@0: // subsequent offset or advance appropriately. 16.16 fixed-point michael@0: // michael@0: // When rounding, the goal is to make the distance between glyphs and michael@0: // their base glyph equal to the integral number of pixels closest to that michael@0: // suggested by that shaper. michael@0: // i.e. posInfo[n].x_advance - posInfo[n].x_offset + posInfo[n+1].x_offset michael@0: // michael@0: // The value of the residual is the part of the desired distance that has michael@0: // not been included in integer offsets. michael@0: hb_position_t x_residual = 0; michael@0: michael@0: // keep track of y-position to set glyph offsets if needed michael@0: nscoord yPos = 0; michael@0: michael@0: const hb_glyph_position_t *posInfo = michael@0: hb_buffer_get_glyph_positions(aBuffer, nullptr); michael@0: michael@0: while (glyphStart < int32_t(numGlyphs)) { michael@0: michael@0: int32_t charEnd = ginfo[glyphStart].cluster; michael@0: int32_t glyphEnd = glyphStart; michael@0: int32_t charLimit = wordLength; michael@0: while (charEnd < charLimit) { michael@0: // This is normally executed once for each iteration of the outer loop, michael@0: // but in unusual cases where the character/glyph association is complex, michael@0: // the initial character range might correspond to a non-contiguous michael@0: // glyph range with "holes" in it. If so, we will repeat this loop to michael@0: // extend the character range until we have a contiguous glyph sequence. michael@0: charEnd += 1; michael@0: while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { michael@0: charEnd += 1; michael@0: } michael@0: michael@0: // find the maximum glyph index covered by the clump so far michael@0: for (int32_t i = charStart; i < charEnd; ++i) { michael@0: if (charToGlyph[i] != NO_GLYPH) { michael@0: glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); michael@0: // update extent of glyph range michael@0: } michael@0: } michael@0: michael@0: if (glyphEnd == glyphStart + 1) { michael@0: // for the common case of a single-glyph clump, michael@0: // we can skip the following checks michael@0: break; michael@0: } michael@0: michael@0: if (glyphEnd == glyphStart) { michael@0: // no glyphs, try to extend the clump michael@0: continue; michael@0: } michael@0: michael@0: // check whether all glyphs in the range are associated with the characters michael@0: // in our clump; if not, we have a discontinuous range, and should extend it michael@0: // unless we've reached the end of the text michael@0: bool allGlyphsAreWithinCluster = true; michael@0: for (int32_t i = glyphStart; i < glyphEnd; ++i) { michael@0: int32_t glyphCharIndex = ginfo[i].cluster; michael@0: if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { michael@0: allGlyphsAreWithinCluster = false; michael@0: break; michael@0: } michael@0: } michael@0: if (allGlyphsAreWithinCluster) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(glyphStart < glyphEnd, michael@0: "character/glyph clump contains no glyphs!"); michael@0: NS_ASSERTION(charStart != charEnd, michael@0: "character/glyph clump contains no characters!"); michael@0: michael@0: // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; michael@0: // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), michael@0: // and endCharIndex to the limit (position beyond the last char), michael@0: // adjusting for the offset of the stringRange relative to the textRun. michael@0: int32_t baseCharIndex, endCharIndex; michael@0: while (charEnd < int32_t(wordLength) && charToGlyph[charEnd] == NO_GLYPH) michael@0: charEnd++; michael@0: baseCharIndex = charStart; michael@0: endCharIndex = charEnd; michael@0: michael@0: // Then we check if the clump falls outside our actual string range; michael@0: // if so, just go to the next. michael@0: if (baseCharIndex >= int32_t(wordLength)) { michael@0: glyphStart = glyphEnd; michael@0: charStart = charEnd; michael@0: continue; michael@0: } michael@0: // Ensure we won't try to go beyond the valid length of the textRun's text michael@0: endCharIndex = std::min(endCharIndex, wordLength); michael@0: michael@0: // Now we're ready to set the glyph info in the textRun michael@0: int32_t glyphsInClump = glyphEnd - glyphStart; michael@0: michael@0: // Check for default-ignorable char that didn't get filtered, combined, michael@0: // etc by the shaping process, and remove from the run. michael@0: // (This may be done within harfbuzz eventually.) michael@0: if (glyphsInClump == 1 && baseCharIndex + 1 == endCharIndex && michael@0: aShapedText->FilterIfIgnorable(aOffset + baseCharIndex, michael@0: aText[baseCharIndex])) { michael@0: glyphStart = glyphEnd; michael@0: charStart = charEnd; michael@0: continue; michael@0: } michael@0: michael@0: hb_position_t x_offset = posInfo[glyphStart].x_offset; michael@0: hb_position_t x_advance = posInfo[glyphStart].x_advance; michael@0: nscoord xOffset, advance; michael@0: if (roundX) { michael@0: xOffset = michael@0: appUnitsPerDevUnit * FixedToIntRound(x_offset + x_residual); michael@0: // Desired distance from the base glyph to the next reference point. michael@0: hb_position_t width = x_advance - x_offset; michael@0: int intWidth = FixedToIntRound(width); michael@0: x_residual = width - FloatToFixed(intWidth); michael@0: advance = appUnitsPerDevUnit * intWidth + xOffset; michael@0: } else { michael@0: xOffset = floor(hb2appUnits * x_offset + 0.5); michael@0: advance = floor(hb2appUnits * x_advance + 0.5); michael@0: } michael@0: // Check if it's a simple one-to-one mapping michael@0: if (glyphsInClump == 1 && michael@0: gfxTextRun::CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) && michael@0: gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && michael@0: charGlyphs[baseCharIndex].IsClusterStart() && michael@0: xOffset == 0 && michael@0: posInfo[glyphStart].y_offset == 0 && yPos == 0) michael@0: { michael@0: charGlyphs[baseCharIndex].SetSimpleGlyph(advance, michael@0: ginfo[glyphStart].codepoint); michael@0: } else { michael@0: // collect all glyphs in a list to be assigned to the first char; michael@0: // there must be at least one in the clump, and we already measured michael@0: // its advance, hence the placement of the loop-exit test and the michael@0: // measurement of the next glyph michael@0: while (1) { michael@0: gfxTextRun::DetailedGlyph* details = michael@0: detailedGlyphs.AppendElement(); michael@0: details->mGlyphID = ginfo[glyphStart].codepoint; michael@0: michael@0: details->mXOffset = xOffset; michael@0: details->mAdvance = advance; michael@0: michael@0: hb_position_t y_offset = posInfo[glyphStart].y_offset; michael@0: details->mYOffset = yPos - michael@0: (roundY ? appUnitsPerDevUnit * FixedToIntRound(y_offset) michael@0: : floor(hb2appUnits * y_offset + 0.5)); michael@0: michael@0: hb_position_t y_advance = posInfo[glyphStart].y_advance; michael@0: if (y_advance != 0) { michael@0: yPos -= michael@0: roundY ? appUnitsPerDevUnit * FixedToIntRound(y_advance) michael@0: : floor(hb2appUnits * y_advance + 0.5); michael@0: } michael@0: if (++glyphStart >= glyphEnd) { michael@0: break; michael@0: } michael@0: michael@0: x_offset = posInfo[glyphStart].x_offset; michael@0: x_advance = posInfo[glyphStart].x_advance; michael@0: if (roundX) { michael@0: xOffset = appUnitsPerDevUnit * michael@0: FixedToIntRound(x_offset + x_residual); michael@0: // Desired distance to the next reference point. The michael@0: // residual is considered here, and includes the residual michael@0: // from the base glyph offset and subsequent advances, so michael@0: // that the distance from the base glyph is optimized michael@0: // rather than the distance from combining marks. michael@0: x_advance += x_residual; michael@0: int intAdvance = FixedToIntRound(x_advance); michael@0: x_residual = x_advance - FloatToFixed(intAdvance); michael@0: advance = appUnitsPerDevUnit * intAdvance; michael@0: } else { michael@0: xOffset = floor(hb2appUnits * x_offset + 0.5); michael@0: advance = floor(hb2appUnits * x_advance + 0.5); michael@0: } michael@0: } michael@0: michael@0: gfxShapedText::CompressedGlyph g; michael@0: g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(), michael@0: true, detailedGlyphs.Length()); michael@0: aShapedText->SetGlyphs(aOffset + baseCharIndex, michael@0: g, detailedGlyphs.Elements()); michael@0: michael@0: detailedGlyphs.Clear(); michael@0: } michael@0: michael@0: // the rest of the chars in the group are ligature continuations, michael@0: // no associated glyphs michael@0: while (++baseCharIndex != endCharIndex && michael@0: baseCharIndex < int32_t(wordLength)) { michael@0: gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex]; michael@0: NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); michael@0: g.SetComplex(g.IsClusterStart(), false, 0); michael@0: } michael@0: michael@0: glyphStart = glyphEnd; michael@0: charStart = charEnd; michael@0: } michael@0: michael@0: return NS_OK; michael@0: }