Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "nsString.h" |
michael@0 | 7 | #include "gfxContext.h" |
michael@0 | 8 | #include "gfxHarfBuzzShaper.h" |
michael@0 | 9 | #include "gfxFontUtils.h" |
michael@0 | 10 | #include "nsUnicodeProperties.h" |
michael@0 | 11 | #include "nsUnicodeScriptCodes.h" |
michael@0 | 12 | #include "nsUnicodeNormalizer.h" |
michael@0 | 13 | |
michael@0 | 14 | #include "harfbuzz/hb.h" |
michael@0 | 15 | #include "harfbuzz/hb-ot.h" |
michael@0 | 16 | |
michael@0 | 17 | #include <algorithm> |
michael@0 | 18 | |
michael@0 | 19 | #define FloatToFixed(f) (65536 * (f)) |
michael@0 | 20 | #define FixedToFloat(f) ((f) * (1.0 / 65536.0)) |
michael@0 | 21 | // Right shifts of negative (signed) integers are undefined, as are overflows |
michael@0 | 22 | // when converting unsigned to negative signed integers. |
michael@0 | 23 | // (If speed were an issue we could make some 2's complement assumptions.) |
michael@0 | 24 | #define FixedToIntRound(f) ((f) > 0 ? ((32768 + (f)) >> 16) \ |
michael@0 | 25 | : -((32767 - (f)) >> 16)) |
michael@0 | 26 | |
michael@0 | 27 | using namespace mozilla; // for AutoSwap_* types |
michael@0 | 28 | using namespace mozilla::unicode; // for Unicode property lookup |
michael@0 | 29 | |
michael@0 | 30 | /* |
michael@0 | 31 | * Creation and destruction; on deletion, release any font tables we're holding |
michael@0 | 32 | */ |
michael@0 | 33 | |
michael@0 | 34 | gfxHarfBuzzShaper::gfxHarfBuzzShaper(gfxFont *aFont) |
michael@0 | 35 | : gfxFontShaper(aFont), |
michael@0 | 36 | mHBFace(aFont->GetFontEntry()->GetHBFace()), |
michael@0 | 37 | mHBFont(nullptr), |
michael@0 | 38 | mKernTable(nullptr), |
michael@0 | 39 | mHmtxTable(nullptr), |
michael@0 | 40 | mNumLongMetrics(0), |
michael@0 | 41 | mCmapTable(nullptr), |
michael@0 | 42 | mCmapFormat(-1), |
michael@0 | 43 | mSubtableOffset(0), |
michael@0 | 44 | mUVSTableOffset(0), |
michael@0 | 45 | mUseFontGetGlyph(aFont->ProvidesGetGlyph()), |
michael@0 | 46 | mUseFontGlyphWidths(false), |
michael@0 | 47 | mInitialized(false) |
michael@0 | 48 | { |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | gfxHarfBuzzShaper::~gfxHarfBuzzShaper() |
michael@0 | 52 | { |
michael@0 | 53 | if (mCmapTable) { |
michael@0 | 54 | hb_blob_destroy(mCmapTable); |
michael@0 | 55 | } |
michael@0 | 56 | if (mHmtxTable) { |
michael@0 | 57 | hb_blob_destroy(mHmtxTable); |
michael@0 | 58 | } |
michael@0 | 59 | if (mKernTable) { |
michael@0 | 60 | hb_blob_destroy(mKernTable); |
michael@0 | 61 | } |
michael@0 | 62 | if (mHBFont) { |
michael@0 | 63 | hb_font_destroy(mHBFont); |
michael@0 | 64 | } |
michael@0 | 65 | if (mHBFace) { |
michael@0 | 66 | hb_face_destroy(mHBFace); |
michael@0 | 67 | } |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | #define UNICODE_BMP_LIMIT 0x10000 |
michael@0 | 71 | |
michael@0 | 72 | hb_codepoint_t |
michael@0 | 73 | gfxHarfBuzzShaper::GetGlyph(hb_codepoint_t unicode, |
michael@0 | 74 | hb_codepoint_t variation_selector) const |
michael@0 | 75 | { |
michael@0 | 76 | hb_codepoint_t gid = 0; |
michael@0 | 77 | |
michael@0 | 78 | if (mUseFontGetGlyph) { |
michael@0 | 79 | gid = mFont->GetGlyph(unicode, variation_selector); |
michael@0 | 80 | } else { |
michael@0 | 81 | // we only instantiate a harfbuzz shaper if there's a cmap available |
michael@0 | 82 | NS_ASSERTION(mFont->GetFontEntry()->HasCmapTable(), |
michael@0 | 83 | "we cannot be using this font!"); |
michael@0 | 84 | |
michael@0 | 85 | NS_ASSERTION(mCmapTable && (mCmapFormat > 0) && (mSubtableOffset > 0), |
michael@0 | 86 | "cmap data not correctly set up, expect disaster"); |
michael@0 | 87 | |
michael@0 | 88 | const uint8_t* data = |
michael@0 | 89 | (const uint8_t*)hb_blob_get_data(mCmapTable, nullptr); |
michael@0 | 90 | |
michael@0 | 91 | if (variation_selector) { |
michael@0 | 92 | if (mUVSTableOffset) { |
michael@0 | 93 | gid = |
michael@0 | 94 | gfxFontUtils::MapUVSToGlyphFormat14(data + mUVSTableOffset, |
michael@0 | 95 | unicode, |
michael@0 | 96 | variation_selector); |
michael@0 | 97 | } |
michael@0 | 98 | if (!gid) { |
michael@0 | 99 | uint32_t compat = |
michael@0 | 100 | gfxFontUtils::GetUVSFallback(unicode, variation_selector); |
michael@0 | 101 | if (compat) { |
michael@0 | 102 | switch (mCmapFormat) { |
michael@0 | 103 | case 4: |
michael@0 | 104 | if (compat < UNICODE_BMP_LIMIT) { |
michael@0 | 105 | gid = gfxFontUtils::MapCharToGlyphFormat4(data + mSubtableOffset, |
michael@0 | 106 | compat); |
michael@0 | 107 | } |
michael@0 | 108 | break; |
michael@0 | 109 | case 12: |
michael@0 | 110 | gid = gfxFontUtils::MapCharToGlyphFormat12(data + mSubtableOffset, |
michael@0 | 111 | compat); |
michael@0 | 112 | break; |
michael@0 | 113 | } |
michael@0 | 114 | } |
michael@0 | 115 | } |
michael@0 | 116 | // If the variation sequence was not supported, return zero here; |
michael@0 | 117 | // harfbuzz will call us again for the base character alone |
michael@0 | 118 | return gid; |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | switch (mCmapFormat) { |
michael@0 | 122 | case 4: |
michael@0 | 123 | gid = unicode < UNICODE_BMP_LIMIT ? |
michael@0 | 124 | gfxFontUtils::MapCharToGlyphFormat4(data + mSubtableOffset, |
michael@0 | 125 | unicode) : 0; |
michael@0 | 126 | break; |
michael@0 | 127 | case 12: |
michael@0 | 128 | gid = gfxFontUtils::MapCharToGlyphFormat12(data + mSubtableOffset, |
michael@0 | 129 | unicode); |
michael@0 | 130 | break; |
michael@0 | 131 | default: |
michael@0 | 132 | NS_WARNING("unsupported cmap format, glyphs will be missing"); |
michael@0 | 133 | break; |
michael@0 | 134 | } |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | if (!gid) { |
michael@0 | 138 | // if there's no glyph for , just use the space glyph instead |
michael@0 | 139 | if (unicode == 0xA0) { |
michael@0 | 140 | gid = mFont->GetSpaceGlyph(); |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | return gid; |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | static hb_bool_t |
michael@0 | 148 | HBGetGlyph(hb_font_t *font, void *font_data, |
michael@0 | 149 | hb_codepoint_t unicode, hb_codepoint_t variation_selector, |
michael@0 | 150 | hb_codepoint_t *glyph, |
michael@0 | 151 | void *user_data) |
michael@0 | 152 | { |
michael@0 | 153 | const gfxHarfBuzzShaper::FontCallbackData *fcd = |
michael@0 | 154 | static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); |
michael@0 | 155 | *glyph = fcd->mShaper->GetGlyph(unicode, variation_selector); |
michael@0 | 156 | return *glyph != 0; |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | struct HMetricsHeader { |
michael@0 | 160 | AutoSwap_PRUint32 tableVersionNumber; |
michael@0 | 161 | AutoSwap_PRInt16 ascender; |
michael@0 | 162 | AutoSwap_PRInt16 descender; |
michael@0 | 163 | AutoSwap_PRInt16 lineGap; |
michael@0 | 164 | AutoSwap_PRUint16 advanceWidthMax; |
michael@0 | 165 | AutoSwap_PRInt16 minLeftSideBearing; |
michael@0 | 166 | AutoSwap_PRInt16 minRightSideBearing; |
michael@0 | 167 | AutoSwap_PRInt16 xMaxExtent; |
michael@0 | 168 | AutoSwap_PRInt16 caretSlopeRise; |
michael@0 | 169 | AutoSwap_PRInt16 caretSlopeRun; |
michael@0 | 170 | AutoSwap_PRInt16 caretOffset; |
michael@0 | 171 | AutoSwap_PRInt16 reserved[4]; |
michael@0 | 172 | AutoSwap_PRInt16 metricDataFormat; |
michael@0 | 173 | AutoSwap_PRUint16 numberOfHMetrics; |
michael@0 | 174 | }; |
michael@0 | 175 | |
michael@0 | 176 | struct HLongMetric { |
michael@0 | 177 | AutoSwap_PRUint16 advanceWidth; |
michael@0 | 178 | AutoSwap_PRInt16 lsb; |
michael@0 | 179 | }; |
michael@0 | 180 | |
michael@0 | 181 | struct HMetrics { |
michael@0 | 182 | HLongMetric metrics[1]; // actually numberOfHMetrics |
michael@0 | 183 | // the variable-length metrics[] array is immediately followed by: |
michael@0 | 184 | // AutoSwap_PRUint16 leftSideBearing[]; |
michael@0 | 185 | }; |
michael@0 | 186 | |
michael@0 | 187 | hb_position_t |
michael@0 | 188 | gfxHarfBuzzShaper::GetGlyphHAdvance(gfxContext *aContext, |
michael@0 | 189 | hb_codepoint_t glyph) const |
michael@0 | 190 | { |
michael@0 | 191 | // font did not implement GetHintedGlyphWidth, so get an unhinted value |
michael@0 | 192 | // directly from the font tables |
michael@0 | 193 | |
michael@0 | 194 | NS_ASSERTION((mNumLongMetrics > 0) && mHmtxTable != nullptr, |
michael@0 | 195 | "font is lacking metrics, we shouldn't be here"); |
michael@0 | 196 | |
michael@0 | 197 | if (glyph >= uint32_t(mNumLongMetrics)) { |
michael@0 | 198 | glyph = mNumLongMetrics - 1; |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | // glyph must be valid now, because we checked during initialization |
michael@0 | 202 | // that mNumLongMetrics is > 0, and that the hmtx table is large enough |
michael@0 | 203 | // to contain mNumLongMetrics records |
michael@0 | 204 | const HMetrics* hmtx = |
michael@0 | 205 | reinterpret_cast<const HMetrics*>(hb_blob_get_data(mHmtxTable, nullptr)); |
michael@0 | 206 | return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * |
michael@0 | 207 | uint16_t(hmtx->metrics[glyph].advanceWidth)); |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | /* static */ |
michael@0 | 211 | hb_position_t |
michael@0 | 212 | gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t *font, void *font_data, |
michael@0 | 213 | hb_codepoint_t glyph, void *user_data) |
michael@0 | 214 | { |
michael@0 | 215 | const gfxHarfBuzzShaper::FontCallbackData *fcd = |
michael@0 | 216 | static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); |
michael@0 | 217 | gfxFont *gfxfont = fcd->mShaper->GetFont(); |
michael@0 | 218 | if (gfxfont->ProvidesGlyphWidths()) { |
michael@0 | 219 | return gfxfont->GetGlyphWidth(fcd->mContext, glyph); |
michael@0 | 220 | } else { |
michael@0 | 221 | return fcd->mShaper->GetGlyphHAdvance(fcd->mContext, glyph); |
michael@0 | 222 | } |
michael@0 | 223 | } |
michael@0 | 224 | |
michael@0 | 225 | static hb_bool_t |
michael@0 | 226 | HBGetContourPoint(hb_font_t *font, void *font_data, |
michael@0 | 227 | unsigned int point_index, hb_codepoint_t glyph, |
michael@0 | 228 | hb_position_t *x, hb_position_t *y, |
michael@0 | 229 | void *user_data) |
michael@0 | 230 | { |
michael@0 | 231 | /* not yet implemented - no support for used of hinted contour points |
michael@0 | 232 | to fine-tune anchor positions in GPOS AnchorFormat2 */ |
michael@0 | 233 | return false; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | struct KernHeaderFmt0 { |
michael@0 | 237 | AutoSwap_PRUint16 nPairs; |
michael@0 | 238 | AutoSwap_PRUint16 searchRange; |
michael@0 | 239 | AutoSwap_PRUint16 entrySelector; |
michael@0 | 240 | AutoSwap_PRUint16 rangeShift; |
michael@0 | 241 | }; |
michael@0 | 242 | |
michael@0 | 243 | struct KernPair { |
michael@0 | 244 | AutoSwap_PRUint16 left; |
michael@0 | 245 | AutoSwap_PRUint16 right; |
michael@0 | 246 | AutoSwap_PRInt16 value; |
michael@0 | 247 | }; |
michael@0 | 248 | |
michael@0 | 249 | // Find a kern pair in a Format 0 subtable. |
michael@0 | 250 | // The aSubtable parameter points to the subtable itself, NOT its header, |
michael@0 | 251 | // as the header structure differs between Windows and Mac (v0 and v1.0) |
michael@0 | 252 | // versions of the 'kern' table. |
michael@0 | 253 | // aSubtableLen is the length of the subtable EXCLUDING its header. |
michael@0 | 254 | // If the pair <aFirstGlyph,aSecondGlyph> is found, the kerning value is |
michael@0 | 255 | // added to aValue, so that multiple subtables can accumulate a total |
michael@0 | 256 | // kerning value for a given pair. |
michael@0 | 257 | static void |
michael@0 | 258 | GetKernValueFmt0(const void* aSubtable, |
michael@0 | 259 | uint32_t aSubtableLen, |
michael@0 | 260 | uint16_t aFirstGlyph, |
michael@0 | 261 | uint16_t aSecondGlyph, |
michael@0 | 262 | int32_t& aValue, |
michael@0 | 263 | bool aIsOverride = false, |
michael@0 | 264 | bool aIsMinimum = false) |
michael@0 | 265 | { |
michael@0 | 266 | const KernHeaderFmt0* hdr = |
michael@0 | 267 | reinterpret_cast<const KernHeaderFmt0*>(aSubtable); |
michael@0 | 268 | |
michael@0 | 269 | const KernPair *lo = reinterpret_cast<const KernPair*>(hdr + 1); |
michael@0 | 270 | const KernPair *hi = lo + uint16_t(hdr->nPairs); |
michael@0 | 271 | const KernPair *limit = hi; |
michael@0 | 272 | |
michael@0 | 273 | if (reinterpret_cast<const char*>(aSubtable) + aSubtableLen < |
michael@0 | 274 | reinterpret_cast<const char*>(hi)) { |
michael@0 | 275 | // subtable is not large enough to contain the claimed number |
michael@0 | 276 | // of kern pairs, so just ignore it |
michael@0 | 277 | return; |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | #define KERN_PAIR_KEY(l,r) (uint32_t((uint16_t(l) << 16) + uint16_t(r))) |
michael@0 | 281 | |
michael@0 | 282 | uint32_t key = KERN_PAIR_KEY(aFirstGlyph, aSecondGlyph); |
michael@0 | 283 | while (lo < hi) { |
michael@0 | 284 | const KernPair *mid = lo + (hi - lo) / 2; |
michael@0 | 285 | if (KERN_PAIR_KEY(mid->left, mid->right) < key) { |
michael@0 | 286 | lo = mid + 1; |
michael@0 | 287 | } else { |
michael@0 | 288 | hi = mid; |
michael@0 | 289 | } |
michael@0 | 290 | } |
michael@0 | 291 | |
michael@0 | 292 | if (lo < limit && KERN_PAIR_KEY(lo->left, lo->right) == key) { |
michael@0 | 293 | if (aIsOverride) { |
michael@0 | 294 | aValue = int16_t(lo->value); |
michael@0 | 295 | } else if (aIsMinimum) { |
michael@0 | 296 | aValue = std::max(aValue, int32_t(lo->value)); |
michael@0 | 297 | } else { |
michael@0 | 298 | aValue += int16_t(lo->value); |
michael@0 | 299 | } |
michael@0 | 300 | } |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | // Get kerning value from Apple (version 1.0) kern table, |
michael@0 | 304 | // subtable format 2 (simple N x M array of kerning values) |
michael@0 | 305 | |
michael@0 | 306 | // See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html |
michael@0 | 307 | // for details of version 1.0 format 2 subtable. |
michael@0 | 308 | |
michael@0 | 309 | struct KernHeaderVersion1Fmt2 { |
michael@0 | 310 | KernTableSubtableHeaderVersion1 header; |
michael@0 | 311 | AutoSwap_PRUint16 rowWidth; |
michael@0 | 312 | AutoSwap_PRUint16 leftOffsetTable; |
michael@0 | 313 | AutoSwap_PRUint16 rightOffsetTable; |
michael@0 | 314 | AutoSwap_PRUint16 array; |
michael@0 | 315 | }; |
michael@0 | 316 | |
michael@0 | 317 | struct KernClassTableHdr { |
michael@0 | 318 | AutoSwap_PRUint16 firstGlyph; |
michael@0 | 319 | AutoSwap_PRUint16 nGlyphs; |
michael@0 | 320 | AutoSwap_PRUint16 offsets[1]; // actually an array of nGlyphs entries |
michael@0 | 321 | }; |
michael@0 | 322 | |
michael@0 | 323 | static int16_t |
michael@0 | 324 | GetKernValueVersion1Fmt2(const void* aSubtable, |
michael@0 | 325 | uint32_t aSubtableLen, |
michael@0 | 326 | uint16_t aFirstGlyph, |
michael@0 | 327 | uint16_t aSecondGlyph) |
michael@0 | 328 | { |
michael@0 | 329 | if (aSubtableLen < sizeof(KernHeaderVersion1Fmt2)) { |
michael@0 | 330 | return 0; |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | const char* base = reinterpret_cast<const char*>(aSubtable); |
michael@0 | 334 | const char* subtableEnd = base + aSubtableLen; |
michael@0 | 335 | |
michael@0 | 336 | const KernHeaderVersion1Fmt2* h = |
michael@0 | 337 | reinterpret_cast<const KernHeaderVersion1Fmt2*>(aSubtable); |
michael@0 | 338 | uint32_t offset = h->array; |
michael@0 | 339 | |
michael@0 | 340 | const KernClassTableHdr* leftClassTable = |
michael@0 | 341 | reinterpret_cast<const KernClassTableHdr*>(base + |
michael@0 | 342 | uint16_t(h->leftOffsetTable)); |
michael@0 | 343 | if (reinterpret_cast<const char*>(leftClassTable) + |
michael@0 | 344 | sizeof(KernClassTableHdr) > subtableEnd) { |
michael@0 | 345 | return 0; |
michael@0 | 346 | } |
michael@0 | 347 | if (aFirstGlyph >= uint16_t(leftClassTable->firstGlyph)) { |
michael@0 | 348 | aFirstGlyph -= uint16_t(leftClassTable->firstGlyph); |
michael@0 | 349 | if (aFirstGlyph < uint16_t(leftClassTable->nGlyphs)) { |
michael@0 | 350 | if (reinterpret_cast<const char*>(leftClassTable) + |
michael@0 | 351 | sizeof(KernClassTableHdr) + |
michael@0 | 352 | aFirstGlyph * sizeof(uint16_t) >= subtableEnd) { |
michael@0 | 353 | return 0; |
michael@0 | 354 | } |
michael@0 | 355 | offset = uint16_t(leftClassTable->offsets[aFirstGlyph]); |
michael@0 | 356 | } |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | const KernClassTableHdr* rightClassTable = |
michael@0 | 360 | reinterpret_cast<const KernClassTableHdr*>(base + |
michael@0 | 361 | uint16_t(h->rightOffsetTable)); |
michael@0 | 362 | if (reinterpret_cast<const char*>(rightClassTable) + |
michael@0 | 363 | sizeof(KernClassTableHdr) > subtableEnd) { |
michael@0 | 364 | return 0; |
michael@0 | 365 | } |
michael@0 | 366 | if (aSecondGlyph >= uint16_t(rightClassTable->firstGlyph)) { |
michael@0 | 367 | aSecondGlyph -= uint16_t(rightClassTable->firstGlyph); |
michael@0 | 368 | if (aSecondGlyph < uint16_t(rightClassTable->nGlyphs)) { |
michael@0 | 369 | if (reinterpret_cast<const char*>(rightClassTable) + |
michael@0 | 370 | sizeof(KernClassTableHdr) + |
michael@0 | 371 | aSecondGlyph * sizeof(uint16_t) >= subtableEnd) { |
michael@0 | 372 | return 0; |
michael@0 | 373 | } |
michael@0 | 374 | offset += uint16_t(rightClassTable->offsets[aSecondGlyph]); |
michael@0 | 375 | } |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | const AutoSwap_PRInt16* pval = |
michael@0 | 379 | reinterpret_cast<const AutoSwap_PRInt16*>(base + offset); |
michael@0 | 380 | if (reinterpret_cast<const char*>(pval + 1) >= subtableEnd) { |
michael@0 | 381 | return 0; |
michael@0 | 382 | } |
michael@0 | 383 | return *pval; |
michael@0 | 384 | } |
michael@0 | 385 | |
michael@0 | 386 | // Get kerning value from Apple (version 1.0) kern table, |
michael@0 | 387 | // subtable format 3 (simple N x M array of kerning values) |
michael@0 | 388 | |
michael@0 | 389 | // See http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html |
michael@0 | 390 | // for details of version 1.0 format 3 subtable. |
michael@0 | 391 | |
michael@0 | 392 | struct KernHeaderVersion1Fmt3 { |
michael@0 | 393 | KernTableSubtableHeaderVersion1 header; |
michael@0 | 394 | AutoSwap_PRUint16 glyphCount; |
michael@0 | 395 | uint8_t kernValueCount; |
michael@0 | 396 | uint8_t leftClassCount; |
michael@0 | 397 | uint8_t rightClassCount; |
michael@0 | 398 | uint8_t flags; |
michael@0 | 399 | }; |
michael@0 | 400 | |
michael@0 | 401 | static int16_t |
michael@0 | 402 | GetKernValueVersion1Fmt3(const void* aSubtable, |
michael@0 | 403 | uint32_t aSubtableLen, |
michael@0 | 404 | uint16_t aFirstGlyph, |
michael@0 | 405 | uint16_t aSecondGlyph) |
michael@0 | 406 | { |
michael@0 | 407 | // check that we can safely read the header fields |
michael@0 | 408 | if (aSubtableLen < sizeof(KernHeaderVersion1Fmt3)) { |
michael@0 | 409 | return 0; |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | const KernHeaderVersion1Fmt3* hdr = |
michael@0 | 413 | reinterpret_cast<const KernHeaderVersion1Fmt3*>(aSubtable); |
michael@0 | 414 | if (hdr->flags != 0) { |
michael@0 | 415 | return 0; |
michael@0 | 416 | } |
michael@0 | 417 | |
michael@0 | 418 | uint16_t glyphCount = hdr->glyphCount; |
michael@0 | 419 | |
michael@0 | 420 | // check that table is large enough for the arrays |
michael@0 | 421 | if (sizeof(KernHeaderVersion1Fmt3) + |
michael@0 | 422 | hdr->kernValueCount * sizeof(int16_t) + |
michael@0 | 423 | glyphCount + glyphCount + |
michael@0 | 424 | hdr->leftClassCount * hdr->rightClassCount > aSubtableLen) { |
michael@0 | 425 | return 0; |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | if (aFirstGlyph >= glyphCount || aSecondGlyph >= glyphCount) { |
michael@0 | 429 | // glyphs are out of range for the class tables |
michael@0 | 430 | return 0; |
michael@0 | 431 | } |
michael@0 | 432 | |
michael@0 | 433 | // get pointers to the four arrays within the subtable |
michael@0 | 434 | const AutoSwap_PRInt16* kernValue = |
michael@0 | 435 | reinterpret_cast<const AutoSwap_PRInt16*>(hdr + 1); |
michael@0 | 436 | const uint8_t* leftClass = |
michael@0 | 437 | reinterpret_cast<const uint8_t*>(kernValue + hdr->kernValueCount); |
michael@0 | 438 | const uint8_t* rightClass = leftClass + glyphCount; |
michael@0 | 439 | const uint8_t* kernIndex = rightClass + glyphCount; |
michael@0 | 440 | |
michael@0 | 441 | uint8_t lc = leftClass[aFirstGlyph]; |
michael@0 | 442 | uint8_t rc = rightClass[aSecondGlyph]; |
michael@0 | 443 | if (lc >= hdr->leftClassCount || rc >= hdr->rightClassCount) { |
michael@0 | 444 | return 0; |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | uint8_t ki = kernIndex[leftClass[aFirstGlyph] * hdr->rightClassCount + |
michael@0 | 448 | rightClass[aSecondGlyph]]; |
michael@0 | 449 | if (ki >= hdr->kernValueCount) { |
michael@0 | 450 | return 0; |
michael@0 | 451 | } |
michael@0 | 452 | |
michael@0 | 453 | return kernValue[ki]; |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | #define KERN0_COVERAGE_HORIZONTAL 0x0001 |
michael@0 | 457 | #define KERN0_COVERAGE_MINIMUM 0x0002 |
michael@0 | 458 | #define KERN0_COVERAGE_CROSS_STREAM 0x0004 |
michael@0 | 459 | #define KERN0_COVERAGE_OVERRIDE 0x0008 |
michael@0 | 460 | #define KERN0_COVERAGE_RESERVED 0x00F0 |
michael@0 | 461 | |
michael@0 | 462 | #define KERN1_COVERAGE_VERTICAL 0x8000 |
michael@0 | 463 | #define KERN1_COVERAGE_CROSS_STREAM 0x4000 |
michael@0 | 464 | #define KERN1_COVERAGE_VARIATION 0x2000 |
michael@0 | 465 | #define KERN1_COVERAGE_RESERVED 0x1F00 |
michael@0 | 466 | |
michael@0 | 467 | hb_position_t |
michael@0 | 468 | gfxHarfBuzzShaper::GetHKerning(uint16_t aFirstGlyph, |
michael@0 | 469 | uint16_t aSecondGlyph) const |
michael@0 | 470 | { |
michael@0 | 471 | // We want to ignore any kern pairs involving <space>, because we are |
michael@0 | 472 | // handling words in isolation, the only space characters seen here are |
michael@0 | 473 | // the ones artificially added by the textRun code. |
michael@0 | 474 | uint32_t spaceGlyph = mFont->GetSpaceGlyph(); |
michael@0 | 475 | if (aFirstGlyph == spaceGlyph || aSecondGlyph == spaceGlyph) { |
michael@0 | 476 | return 0; |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | if (!mKernTable) { |
michael@0 | 480 | mKernTable = mFont->GetFontEntry()->GetFontTable(TRUETYPE_TAG('k','e','r','n')); |
michael@0 | 481 | if (!mKernTable) { |
michael@0 | 482 | mKernTable = hb_blob_get_empty(); |
michael@0 | 483 | } |
michael@0 | 484 | } |
michael@0 | 485 | |
michael@0 | 486 | uint32_t len; |
michael@0 | 487 | const char* base = hb_blob_get_data(mKernTable, &len); |
michael@0 | 488 | if (len < sizeof(KernTableVersion0)) { |
michael@0 | 489 | return 0; |
michael@0 | 490 | } |
michael@0 | 491 | int32_t value = 0; |
michael@0 | 492 | |
michael@0 | 493 | // First try to interpret as "version 0" kern table |
michael@0 | 494 | // (see http://www.microsoft.com/typography/otspec/kern.htm) |
michael@0 | 495 | const KernTableVersion0* kern0 = |
michael@0 | 496 | reinterpret_cast<const KernTableVersion0*>(base); |
michael@0 | 497 | if (uint16_t(kern0->version) == 0) { |
michael@0 | 498 | uint16_t nTables = kern0->nTables; |
michael@0 | 499 | uint32_t offs = sizeof(KernTableVersion0); |
michael@0 | 500 | for (uint16_t i = 0; i < nTables; ++i) { |
michael@0 | 501 | if (offs + sizeof(KernTableSubtableHeaderVersion0) > len) { |
michael@0 | 502 | break; |
michael@0 | 503 | } |
michael@0 | 504 | const KernTableSubtableHeaderVersion0* st0 = |
michael@0 | 505 | reinterpret_cast<const KernTableSubtableHeaderVersion0*> |
michael@0 | 506 | (base + offs); |
michael@0 | 507 | uint16_t subtableLen = uint16_t(st0->length); |
michael@0 | 508 | if (offs + subtableLen > len) { |
michael@0 | 509 | break; |
michael@0 | 510 | } |
michael@0 | 511 | offs += subtableLen; |
michael@0 | 512 | uint16_t coverage = st0->coverage; |
michael@0 | 513 | if (!(coverage & KERN0_COVERAGE_HORIZONTAL)) { |
michael@0 | 514 | // we only care about horizontal kerning (for now) |
michael@0 | 515 | continue; |
michael@0 | 516 | } |
michael@0 | 517 | if (coverage & |
michael@0 | 518 | (KERN0_COVERAGE_CROSS_STREAM | KERN0_COVERAGE_RESERVED)) { |
michael@0 | 519 | // we don't support cross-stream kerning, and |
michael@0 | 520 | // reserved bits should be zero; |
michael@0 | 521 | // ignore the subtable if not |
michael@0 | 522 | continue; |
michael@0 | 523 | } |
michael@0 | 524 | uint8_t format = (coverage >> 8); |
michael@0 | 525 | switch (format) { |
michael@0 | 526 | case 0: |
michael@0 | 527 | GetKernValueFmt0(st0 + 1, subtableLen - sizeof(*st0), |
michael@0 | 528 | aFirstGlyph, aSecondGlyph, value, |
michael@0 | 529 | (coverage & KERN0_COVERAGE_OVERRIDE) != 0, |
michael@0 | 530 | (coverage & KERN0_COVERAGE_MINIMUM) != 0); |
michael@0 | 531 | break; |
michael@0 | 532 | default: |
michael@0 | 533 | // TODO: implement support for other formats, |
michael@0 | 534 | // if they're ever used in practice |
michael@0 | 535 | #if DEBUG |
michael@0 | 536 | { |
michael@0 | 537 | char buf[1024]; |
michael@0 | 538 | sprintf(buf, "unknown kern subtable in %s: " |
michael@0 | 539 | "ver 0 format %d\n", |
michael@0 | 540 | NS_ConvertUTF16toUTF8(mFont->GetName()).get(), |
michael@0 | 541 | format); |
michael@0 | 542 | NS_WARNING(buf); |
michael@0 | 543 | } |
michael@0 | 544 | #endif |
michael@0 | 545 | break; |
michael@0 | 546 | } |
michael@0 | 547 | } |
michael@0 | 548 | } else { |
michael@0 | 549 | // It wasn't a "version 0" table; check if it is Apple version 1.0 |
michael@0 | 550 | // (see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) |
michael@0 | 551 | const KernTableVersion1* kern1 = |
michael@0 | 552 | reinterpret_cast<const KernTableVersion1*>(base); |
michael@0 | 553 | if (uint32_t(kern1->version) == 0x00010000) { |
michael@0 | 554 | uint32_t nTables = kern1->nTables; |
michael@0 | 555 | uint32_t offs = sizeof(KernTableVersion1); |
michael@0 | 556 | for (uint32_t i = 0; i < nTables; ++i) { |
michael@0 | 557 | if (offs + sizeof(KernTableSubtableHeaderVersion1) > len) { |
michael@0 | 558 | break; |
michael@0 | 559 | } |
michael@0 | 560 | const KernTableSubtableHeaderVersion1* st1 = |
michael@0 | 561 | reinterpret_cast<const KernTableSubtableHeaderVersion1*> |
michael@0 | 562 | (base + offs); |
michael@0 | 563 | uint32_t subtableLen = uint32_t(st1->length); |
michael@0 | 564 | offs += subtableLen; |
michael@0 | 565 | uint16_t coverage = st1->coverage; |
michael@0 | 566 | if (coverage & |
michael@0 | 567 | (KERN1_COVERAGE_VERTICAL | |
michael@0 | 568 | KERN1_COVERAGE_CROSS_STREAM | |
michael@0 | 569 | KERN1_COVERAGE_VARIATION | |
michael@0 | 570 | KERN1_COVERAGE_RESERVED)) { |
michael@0 | 571 | // we only care about horizontal kerning (for now), |
michael@0 | 572 | // we don't support cross-stream kerning, |
michael@0 | 573 | // we don't support variations, |
michael@0 | 574 | // reserved bits should be zero; |
michael@0 | 575 | // ignore the subtable if not |
michael@0 | 576 | continue; |
michael@0 | 577 | } |
michael@0 | 578 | uint8_t format = (coverage & 0xff); |
michael@0 | 579 | switch (format) { |
michael@0 | 580 | case 0: |
michael@0 | 581 | GetKernValueFmt0(st1 + 1, subtableLen - sizeof(*st1), |
michael@0 | 582 | aFirstGlyph, aSecondGlyph, value); |
michael@0 | 583 | break; |
michael@0 | 584 | case 2: |
michael@0 | 585 | value = GetKernValueVersion1Fmt2(st1, subtableLen, |
michael@0 | 586 | aFirstGlyph, aSecondGlyph); |
michael@0 | 587 | break; |
michael@0 | 588 | case 3: |
michael@0 | 589 | value = GetKernValueVersion1Fmt3(st1, subtableLen, |
michael@0 | 590 | aFirstGlyph, aSecondGlyph); |
michael@0 | 591 | break; |
michael@0 | 592 | default: |
michael@0 | 593 | // TODO: implement support for other formats. |
michael@0 | 594 | // Note that format 1 cannot be supported here, |
michael@0 | 595 | // as it requires the full glyph array to run the FSM, |
michael@0 | 596 | // not just the current glyph pair. |
michael@0 | 597 | #if DEBUG |
michael@0 | 598 | { |
michael@0 | 599 | char buf[1024]; |
michael@0 | 600 | sprintf(buf, "unknown kern subtable in %s: " |
michael@0 | 601 | "ver 0 format %d\n", |
michael@0 | 602 | NS_ConvertUTF16toUTF8(mFont->GetName()).get(), |
michael@0 | 603 | format); |
michael@0 | 604 | NS_WARNING(buf); |
michael@0 | 605 | } |
michael@0 | 606 | #endif |
michael@0 | 607 | break; |
michael@0 | 608 | } |
michael@0 | 609 | } |
michael@0 | 610 | } |
michael@0 | 611 | } |
michael@0 | 612 | |
michael@0 | 613 | if (value != 0) { |
michael@0 | 614 | return FloatToFixed(mFont->FUnitsToDevUnitsFactor() * value); |
michael@0 | 615 | } |
michael@0 | 616 | return 0; |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | static hb_position_t |
michael@0 | 620 | HBGetHKerning(hb_font_t *font, void *font_data, |
michael@0 | 621 | hb_codepoint_t first_glyph, hb_codepoint_t second_glyph, |
michael@0 | 622 | void *user_data) |
michael@0 | 623 | { |
michael@0 | 624 | const gfxHarfBuzzShaper::FontCallbackData *fcd = |
michael@0 | 625 | static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data); |
michael@0 | 626 | return fcd->mShaper->GetHKerning(first_glyph, second_glyph); |
michael@0 | 627 | } |
michael@0 | 628 | |
michael@0 | 629 | /* |
michael@0 | 630 | * HarfBuzz unicode property callbacks |
michael@0 | 631 | */ |
michael@0 | 632 | |
michael@0 | 633 | static hb_codepoint_t |
michael@0 | 634 | HBGetMirroring(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, |
michael@0 | 635 | void *user_data) |
michael@0 | 636 | { |
michael@0 | 637 | return GetMirroredChar(aCh); |
michael@0 | 638 | } |
michael@0 | 639 | |
michael@0 | 640 | static hb_unicode_general_category_t |
michael@0 | 641 | HBGetGeneralCategory(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, |
michael@0 | 642 | void *user_data) |
michael@0 | 643 | { |
michael@0 | 644 | return hb_unicode_general_category_t(GetGeneralCategory(aCh)); |
michael@0 | 645 | } |
michael@0 | 646 | |
michael@0 | 647 | static hb_script_t |
michael@0 | 648 | HBGetScript(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, void *user_data) |
michael@0 | 649 | { |
michael@0 | 650 | return hb_script_t(GetScriptTagForCode(GetScriptCode(aCh))); |
michael@0 | 651 | } |
michael@0 | 652 | |
michael@0 | 653 | static hb_unicode_combining_class_t |
michael@0 | 654 | HBGetCombiningClass(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, |
michael@0 | 655 | void *user_data) |
michael@0 | 656 | { |
michael@0 | 657 | return hb_unicode_combining_class_t(GetCombiningClass(aCh)); |
michael@0 | 658 | } |
michael@0 | 659 | |
michael@0 | 660 | static unsigned int |
michael@0 | 661 | HBGetEastAsianWidth(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh, |
michael@0 | 662 | void *user_data) |
michael@0 | 663 | { |
michael@0 | 664 | return GetEastAsianWidth(aCh); |
michael@0 | 665 | } |
michael@0 | 666 | |
michael@0 | 667 | // Hebrew presentation forms with dagesh, for characters 0x05D0..0x05EA; |
michael@0 | 668 | // note that some letters do not have a dagesh presForm encoded |
michael@0 | 669 | static const char16_t sDageshForms[0x05EA - 0x05D0 + 1] = { |
michael@0 | 670 | 0xFB30, // ALEF |
michael@0 | 671 | 0xFB31, // BET |
michael@0 | 672 | 0xFB32, // GIMEL |
michael@0 | 673 | 0xFB33, // DALET |
michael@0 | 674 | 0xFB34, // HE |
michael@0 | 675 | 0xFB35, // VAV |
michael@0 | 676 | 0xFB36, // ZAYIN |
michael@0 | 677 | 0, // HET |
michael@0 | 678 | 0xFB38, // TET |
michael@0 | 679 | 0xFB39, // YOD |
michael@0 | 680 | 0xFB3A, // FINAL KAF |
michael@0 | 681 | 0xFB3B, // KAF |
michael@0 | 682 | 0xFB3C, // LAMED |
michael@0 | 683 | 0, // FINAL MEM |
michael@0 | 684 | 0xFB3E, // MEM |
michael@0 | 685 | 0, // FINAL NUN |
michael@0 | 686 | 0xFB40, // NUN |
michael@0 | 687 | 0xFB41, // SAMEKH |
michael@0 | 688 | 0, // AYIN |
michael@0 | 689 | 0xFB43, // FINAL PE |
michael@0 | 690 | 0xFB44, // PE |
michael@0 | 691 | 0, // FINAL TSADI |
michael@0 | 692 | 0xFB46, // TSADI |
michael@0 | 693 | 0xFB47, // QOF |
michael@0 | 694 | 0xFB48, // RESH |
michael@0 | 695 | 0xFB49, // SHIN |
michael@0 | 696 | 0xFB4A // TAV |
michael@0 | 697 | }; |
michael@0 | 698 | |
michael@0 | 699 | static hb_bool_t |
michael@0 | 700 | HBUnicodeCompose(hb_unicode_funcs_t *ufuncs, |
michael@0 | 701 | hb_codepoint_t a, |
michael@0 | 702 | hb_codepoint_t b, |
michael@0 | 703 | hb_codepoint_t *ab, |
michael@0 | 704 | void *user_data) |
michael@0 | 705 | { |
michael@0 | 706 | hb_bool_t found = nsUnicodeNormalizer::Compose(a, b, ab); |
michael@0 | 707 | |
michael@0 | 708 | if (!found && (b & 0x1fff80) == 0x0580) { |
michael@0 | 709 | // special-case Hebrew presentation forms that are excluded from |
michael@0 | 710 | // standard normalization, but wanted for old fonts |
michael@0 | 711 | switch (b) { |
michael@0 | 712 | case 0x05B4: // HIRIQ |
michael@0 | 713 | if (a == 0x05D9) { // YOD |
michael@0 | 714 | *ab = 0xFB1D; |
michael@0 | 715 | found = true; |
michael@0 | 716 | } |
michael@0 | 717 | break; |
michael@0 | 718 | case 0x05B7: // patah |
michael@0 | 719 | if (a == 0x05F2) { // YIDDISH YOD YOD |
michael@0 | 720 | *ab = 0xFB1F; |
michael@0 | 721 | found = true; |
michael@0 | 722 | } else if (a == 0x05D0) { // ALEF |
michael@0 | 723 | *ab = 0xFB2E; |
michael@0 | 724 | found = true; |
michael@0 | 725 | } |
michael@0 | 726 | break; |
michael@0 | 727 | case 0x05B8: // QAMATS |
michael@0 | 728 | if (a == 0x05D0) { // ALEF |
michael@0 | 729 | *ab = 0xFB2F; |
michael@0 | 730 | found = true; |
michael@0 | 731 | } |
michael@0 | 732 | break; |
michael@0 | 733 | case 0x05B9: // HOLAM |
michael@0 | 734 | if (a == 0x05D5) { // VAV |
michael@0 | 735 | *ab = 0xFB4B; |
michael@0 | 736 | found = true; |
michael@0 | 737 | } |
michael@0 | 738 | break; |
michael@0 | 739 | case 0x05BC: // DAGESH |
michael@0 | 740 | if (a >= 0x05D0 && a <= 0x05EA) { |
michael@0 | 741 | *ab = sDageshForms[a - 0x05D0]; |
michael@0 | 742 | found = (*ab != 0); |
michael@0 | 743 | } else if (a == 0xFB2A) { // SHIN WITH SHIN DOT |
michael@0 | 744 | *ab = 0xFB2C; |
michael@0 | 745 | found = true; |
michael@0 | 746 | } else if (a == 0xFB2B) { // SHIN WITH SIN DOT |
michael@0 | 747 | *ab = 0xFB2D; |
michael@0 | 748 | found = true; |
michael@0 | 749 | } |
michael@0 | 750 | break; |
michael@0 | 751 | case 0x05BF: // RAFE |
michael@0 | 752 | switch (a) { |
michael@0 | 753 | case 0x05D1: // BET |
michael@0 | 754 | *ab = 0xFB4C; |
michael@0 | 755 | found = true; |
michael@0 | 756 | break; |
michael@0 | 757 | case 0x05DB: // KAF |
michael@0 | 758 | *ab = 0xFB4D; |
michael@0 | 759 | found = true; |
michael@0 | 760 | break; |
michael@0 | 761 | case 0x05E4: // PE |
michael@0 | 762 | *ab = 0xFB4E; |
michael@0 | 763 | found = true; |
michael@0 | 764 | break; |
michael@0 | 765 | } |
michael@0 | 766 | break; |
michael@0 | 767 | case 0x05C1: // SHIN DOT |
michael@0 | 768 | if (a == 0x05E9) { // SHIN |
michael@0 | 769 | *ab = 0xFB2A; |
michael@0 | 770 | found = true; |
michael@0 | 771 | } else if (a == 0xFB49) { // SHIN WITH DAGESH |
michael@0 | 772 | *ab = 0xFB2C; |
michael@0 | 773 | found = true; |
michael@0 | 774 | } |
michael@0 | 775 | break; |
michael@0 | 776 | case 0x05C2: // SIN DOT |
michael@0 | 777 | if (a == 0x05E9) { // SHIN |
michael@0 | 778 | *ab = 0xFB2B; |
michael@0 | 779 | found = true; |
michael@0 | 780 | } else if (a == 0xFB49) { // SHIN WITH DAGESH |
michael@0 | 781 | *ab = 0xFB2D; |
michael@0 | 782 | found = true; |
michael@0 | 783 | } |
michael@0 | 784 | break; |
michael@0 | 785 | } |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | return found; |
michael@0 | 789 | } |
michael@0 | 790 | |
michael@0 | 791 | static hb_bool_t |
michael@0 | 792 | HBUnicodeDecompose(hb_unicode_funcs_t *ufuncs, |
michael@0 | 793 | hb_codepoint_t ab, |
michael@0 | 794 | hb_codepoint_t *a, |
michael@0 | 795 | hb_codepoint_t *b, |
michael@0 | 796 | void *user_data) |
michael@0 | 797 | { |
michael@0 | 798 | #ifdef MOZ_WIDGET_ANDROID |
michael@0 | 799 | // Hack for the SamsungDevanagari font, bug 1012365: |
michael@0 | 800 | // support U+0972 by decomposing it. |
michael@0 | 801 | if (ab == 0x0972) { |
michael@0 | 802 | *a = 0x0905; |
michael@0 | 803 | *b = 0x0945; |
michael@0 | 804 | return true; |
michael@0 | 805 | } |
michael@0 | 806 | #endif |
michael@0 | 807 | return nsUnicodeNormalizer::DecomposeNonRecursively(ab, a, b); |
michael@0 | 808 | } |
michael@0 | 809 | |
michael@0 | 810 | static PLDHashOperator |
michael@0 | 811 | AddOpenTypeFeature(const uint32_t& aTag, uint32_t& aValue, void *aUserArg) |
michael@0 | 812 | { |
michael@0 | 813 | nsTArray<hb_feature_t>* features = static_cast<nsTArray<hb_feature_t>*> (aUserArg); |
michael@0 | 814 | |
michael@0 | 815 | hb_feature_t feat = { 0, 0, 0, UINT_MAX }; |
michael@0 | 816 | feat.tag = aTag; |
michael@0 | 817 | feat.value = aValue; |
michael@0 | 818 | features->AppendElement(feat); |
michael@0 | 819 | return PL_DHASH_NEXT; |
michael@0 | 820 | } |
michael@0 | 821 | |
michael@0 | 822 | /* |
michael@0 | 823 | * gfxFontShaper override to initialize the text run using HarfBuzz |
michael@0 | 824 | */ |
michael@0 | 825 | |
michael@0 | 826 | static hb_font_funcs_t * sHBFontFuncs = nullptr; |
michael@0 | 827 | static hb_unicode_funcs_t * sHBUnicodeFuncs = nullptr; |
michael@0 | 828 | static const hb_script_t sMathScript = |
michael@0 | 829 | hb_ot_tag_to_script(HB_TAG('m','a','t','h')); |
michael@0 | 830 | |
michael@0 | 831 | bool |
michael@0 | 832 | gfxHarfBuzzShaper::Initialize() |
michael@0 | 833 | { |
michael@0 | 834 | if (mInitialized) { |
michael@0 | 835 | return mHBFont != nullptr; |
michael@0 | 836 | } |
michael@0 | 837 | mInitialized = true; |
michael@0 | 838 | mCallbackData.mShaper = this; |
michael@0 | 839 | |
michael@0 | 840 | mUseFontGlyphWidths = mFont->ProvidesGlyphWidths(); |
michael@0 | 841 | |
michael@0 | 842 | if (!sHBFontFuncs) { |
michael@0 | 843 | // static function callback pointers, initialized by the first |
michael@0 | 844 | // harfbuzz shaper used |
michael@0 | 845 | sHBFontFuncs = hb_font_funcs_create(); |
michael@0 | 846 | hb_font_funcs_set_glyph_func(sHBFontFuncs, HBGetGlyph, |
michael@0 | 847 | nullptr, nullptr); |
michael@0 | 848 | hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs, |
michael@0 | 849 | HBGetGlyphHAdvance, |
michael@0 | 850 | nullptr, nullptr); |
michael@0 | 851 | hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs, |
michael@0 | 852 | HBGetContourPoint, |
michael@0 | 853 | nullptr, nullptr); |
michael@0 | 854 | hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs, |
michael@0 | 855 | HBGetHKerning, |
michael@0 | 856 | nullptr, nullptr); |
michael@0 | 857 | |
michael@0 | 858 | sHBUnicodeFuncs = |
michael@0 | 859 | hb_unicode_funcs_create(hb_unicode_funcs_get_empty()); |
michael@0 | 860 | hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs, |
michael@0 | 861 | HBGetMirroring, |
michael@0 | 862 | nullptr, nullptr); |
michael@0 | 863 | hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript, |
michael@0 | 864 | nullptr, nullptr); |
michael@0 | 865 | hb_unicode_funcs_set_general_category_func(sHBUnicodeFuncs, |
michael@0 | 866 | HBGetGeneralCategory, |
michael@0 | 867 | nullptr, nullptr); |
michael@0 | 868 | hb_unicode_funcs_set_combining_class_func(sHBUnicodeFuncs, |
michael@0 | 869 | HBGetCombiningClass, |
michael@0 | 870 | nullptr, nullptr); |
michael@0 | 871 | hb_unicode_funcs_set_eastasian_width_func(sHBUnicodeFuncs, |
michael@0 | 872 | HBGetEastAsianWidth, |
michael@0 | 873 | nullptr, nullptr); |
michael@0 | 874 | hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs, |
michael@0 | 875 | HBUnicodeCompose, |
michael@0 | 876 | nullptr, nullptr); |
michael@0 | 877 | hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs, |
michael@0 | 878 | HBUnicodeDecompose, |
michael@0 | 879 | nullptr, nullptr); |
michael@0 | 880 | } |
michael@0 | 881 | |
michael@0 | 882 | gfxFontEntry *entry = mFont->GetFontEntry(); |
michael@0 | 883 | if (!mUseFontGetGlyph) { |
michael@0 | 884 | // get the cmap table and find offset to our subtable |
michael@0 | 885 | mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c','m','a','p')); |
michael@0 | 886 | if (!mCmapTable) { |
michael@0 | 887 | NS_WARNING("failed to load cmap, glyphs will be missing"); |
michael@0 | 888 | return false; |
michael@0 | 889 | } |
michael@0 | 890 | uint32_t len; |
michael@0 | 891 | const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len); |
michael@0 | 892 | bool symbol; |
michael@0 | 893 | mCmapFormat = gfxFontUtils:: |
michael@0 | 894 | FindPreferredSubtable(data, len, |
michael@0 | 895 | &mSubtableOffset, &mUVSTableOffset, |
michael@0 | 896 | &symbol); |
michael@0 | 897 | if (mCmapFormat <= 0) { |
michael@0 | 898 | return false; |
michael@0 | 899 | } |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | if (!mUseFontGlyphWidths) { |
michael@0 | 903 | // if font doesn't implement GetGlyphWidth, we will be reading |
michael@0 | 904 | // the hmtx table directly; |
michael@0 | 905 | // read mNumLongMetrics from hhea table without caching its blob, |
michael@0 | 906 | // and preload/cache the hmtx table |
michael@0 | 907 | gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h','h','e','a')); |
michael@0 | 908 | if (hheaTable) { |
michael@0 | 909 | uint32_t len; |
michael@0 | 910 | const HMetricsHeader* hhea = |
michael@0 | 911 | reinterpret_cast<const HMetricsHeader*> |
michael@0 | 912 | (hb_blob_get_data(hheaTable, &len)); |
michael@0 | 913 | if (len >= sizeof(HMetricsHeader)) { |
michael@0 | 914 | mNumLongMetrics = hhea->numberOfHMetrics; |
michael@0 | 915 | if (mNumLongMetrics > 0 && |
michael@0 | 916 | int16_t(hhea->metricDataFormat) == 0) { |
michael@0 | 917 | // no point reading hmtx if number of entries is zero! |
michael@0 | 918 | // in that case, we won't be able to use this font |
michael@0 | 919 | // (this method will return FALSE below if mHmtx is null) |
michael@0 | 920 | mHmtxTable = |
michael@0 | 921 | entry->GetFontTable(TRUETYPE_TAG('h','m','t','x')); |
michael@0 | 922 | if (hb_blob_get_length(mHmtxTable) < |
michael@0 | 923 | mNumLongMetrics * sizeof(HLongMetric)) { |
michael@0 | 924 | // hmtx table is not large enough for the claimed |
michael@0 | 925 | // number of entries: invalid, do not use. |
michael@0 | 926 | hb_blob_destroy(mHmtxTable); |
michael@0 | 927 | mHmtxTable = nullptr; |
michael@0 | 928 | } |
michael@0 | 929 | } |
michael@0 | 930 | } |
michael@0 | 931 | } |
michael@0 | 932 | if (!mHmtxTable) { |
michael@0 | 933 | return false; |
michael@0 | 934 | } |
michael@0 | 935 | } |
michael@0 | 936 | |
michael@0 | 937 | mHBFont = hb_font_create(mHBFace); |
michael@0 | 938 | hb_font_set_funcs(mHBFont, sHBFontFuncs, &mCallbackData, nullptr); |
michael@0 | 939 | hb_font_set_ppem(mHBFont, mFont->GetAdjustedSize(), mFont->GetAdjustedSize()); |
michael@0 | 940 | uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point |
michael@0 | 941 | hb_font_set_scale(mHBFont, scale, scale); |
michael@0 | 942 | |
michael@0 | 943 | return true; |
michael@0 | 944 | } |
michael@0 | 945 | |
michael@0 | 946 | bool |
michael@0 | 947 | gfxHarfBuzzShaper::ShapeText(gfxContext *aContext, |
michael@0 | 948 | const char16_t *aText, |
michael@0 | 949 | uint32_t aOffset, |
michael@0 | 950 | uint32_t aLength, |
michael@0 | 951 | int32_t aScript, |
michael@0 | 952 | gfxShapedText *aShapedText) |
michael@0 | 953 | { |
michael@0 | 954 | // some font back-ends require this in order to get proper hinted metrics |
michael@0 | 955 | if (!mFont->SetupCairoFont(aContext)) { |
michael@0 | 956 | return false; |
michael@0 | 957 | } |
michael@0 | 958 | |
michael@0 | 959 | mCallbackData.mContext = aContext; |
michael@0 | 960 | |
michael@0 | 961 | if (!Initialize()) { |
michael@0 | 962 | return false; |
michael@0 | 963 | } |
michael@0 | 964 | |
michael@0 | 965 | const gfxFontStyle *style = mFont->GetStyle(); |
michael@0 | 966 | |
michael@0 | 967 | nsAutoTArray<hb_feature_t,20> features; |
michael@0 | 968 | nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures; |
michael@0 | 969 | |
michael@0 | 970 | gfxFontEntry *entry = mFont->GetFontEntry(); |
michael@0 | 971 | if (MergeFontFeatures(style, |
michael@0 | 972 | entry->mFeatureSettings, |
michael@0 | 973 | aShapedText->DisableLigatures(), |
michael@0 | 974 | entry->FamilyName(), |
michael@0 | 975 | mergedFeatures)) |
michael@0 | 976 | { |
michael@0 | 977 | // enumerate result and insert into hb_feature array |
michael@0 | 978 | mergedFeatures.Enumerate(AddOpenTypeFeature, &features); |
michael@0 | 979 | } |
michael@0 | 980 | |
michael@0 | 981 | bool isRightToLeft = aShapedText->IsRightToLeft(); |
michael@0 | 982 | hb_buffer_t *buffer = hb_buffer_create(); |
michael@0 | 983 | hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs); |
michael@0 | 984 | hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL : |
michael@0 | 985 | HB_DIRECTION_LTR); |
michael@0 | 986 | hb_script_t scriptTag; |
michael@0 | 987 | if (aShapedText->Flags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) { |
michael@0 | 988 | scriptTag = sMathScript; |
michael@0 | 989 | } else if (aScript <= MOZ_SCRIPT_INHERITED) { |
michael@0 | 990 | // For unresolved "common" or "inherited" runs, default to Latin for |
michael@0 | 991 | // now. (Should we somehow use the language or locale to try and infer |
michael@0 | 992 | // a better default?) |
michael@0 | 993 | scriptTag = HB_SCRIPT_LATIN; |
michael@0 | 994 | } else { |
michael@0 | 995 | scriptTag = hb_script_t(GetScriptTagForCode(aScript)); |
michael@0 | 996 | } |
michael@0 | 997 | hb_buffer_set_script(buffer, scriptTag); |
michael@0 | 998 | |
michael@0 | 999 | hb_language_t language; |
michael@0 | 1000 | if (style->languageOverride) { |
michael@0 | 1001 | language = hb_ot_tag_to_language(style->languageOverride); |
michael@0 | 1002 | } else if (entry->mLanguageOverride) { |
michael@0 | 1003 | language = hb_ot_tag_to_language(entry->mLanguageOverride); |
michael@0 | 1004 | } else { |
michael@0 | 1005 | nsCString langString; |
michael@0 | 1006 | style->language->ToUTF8String(langString); |
michael@0 | 1007 | language = |
michael@0 | 1008 | hb_language_from_string(langString.get(), langString.Length()); |
michael@0 | 1009 | } |
michael@0 | 1010 | hb_buffer_set_language(buffer, language); |
michael@0 | 1011 | |
michael@0 | 1012 | uint32_t length = aLength; |
michael@0 | 1013 | hb_buffer_add_utf16(buffer, |
michael@0 | 1014 | reinterpret_cast<const uint16_t*>(aText), |
michael@0 | 1015 | length, 0, length); |
michael@0 | 1016 | |
michael@0 | 1017 | hb_shape(mHBFont, buffer, features.Elements(), features.Length()); |
michael@0 | 1018 | |
michael@0 | 1019 | if (isRightToLeft) { |
michael@0 | 1020 | hb_buffer_reverse(buffer); |
michael@0 | 1021 | } |
michael@0 | 1022 | |
michael@0 | 1023 | nsresult rv = SetGlyphsFromRun(aContext, aShapedText, aOffset, aLength, |
michael@0 | 1024 | aText, buffer); |
michael@0 | 1025 | |
michael@0 | 1026 | NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to store glyphs into gfxShapedWord"); |
michael@0 | 1027 | hb_buffer_destroy(buffer); |
michael@0 | 1028 | |
michael@0 | 1029 | return NS_SUCCEEDED(rv); |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | #define SMALL_GLYPH_RUN 128 // some testing indicates that 90%+ of text runs |
michael@0 | 1033 | // will fit without requiring separate allocation |
michael@0 | 1034 | // for charToGlyphArray |
michael@0 | 1035 | |
michael@0 | 1036 | nsresult |
michael@0 | 1037 | gfxHarfBuzzShaper::SetGlyphsFromRun(gfxContext *aContext, |
michael@0 | 1038 | gfxShapedText *aShapedText, |
michael@0 | 1039 | uint32_t aOffset, |
michael@0 | 1040 | uint32_t aLength, |
michael@0 | 1041 | const char16_t *aText, |
michael@0 | 1042 | hb_buffer_t *aBuffer) |
michael@0 | 1043 | { |
michael@0 | 1044 | uint32_t numGlyphs; |
michael@0 | 1045 | const hb_glyph_info_t *ginfo = hb_buffer_get_glyph_infos(aBuffer, &numGlyphs); |
michael@0 | 1046 | if (numGlyphs == 0) { |
michael@0 | 1047 | return NS_OK; |
michael@0 | 1048 | } |
michael@0 | 1049 | |
michael@0 | 1050 | nsAutoTArray<gfxTextRun::DetailedGlyph,1> detailedGlyphs; |
michael@0 | 1051 | |
michael@0 | 1052 | uint32_t wordLength = aLength; |
michael@0 | 1053 | static const int32_t NO_GLYPH = -1; |
michael@0 | 1054 | AutoFallibleTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray; |
michael@0 | 1055 | if (!charToGlyphArray.SetLength(wordLength)) { |
michael@0 | 1056 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1057 | } |
michael@0 | 1058 | |
michael@0 | 1059 | int32_t *charToGlyph = charToGlyphArray.Elements(); |
michael@0 | 1060 | for (uint32_t offset = 0; offset < wordLength; ++offset) { |
michael@0 | 1061 | charToGlyph[offset] = NO_GLYPH; |
michael@0 | 1062 | } |
michael@0 | 1063 | |
michael@0 | 1064 | for (uint32_t i = 0; i < numGlyphs; ++i) { |
michael@0 | 1065 | uint32_t loc = ginfo[i].cluster; |
michael@0 | 1066 | if (loc < wordLength) { |
michael@0 | 1067 | charToGlyph[loc] = i; |
michael@0 | 1068 | } |
michael@0 | 1069 | } |
michael@0 | 1070 | |
michael@0 | 1071 | int32_t glyphStart = 0; // looking for a clump that starts at this glyph |
michael@0 | 1072 | int32_t charStart = 0; // and this char index within the range of the run |
michael@0 | 1073 | |
michael@0 | 1074 | bool roundX; |
michael@0 | 1075 | bool roundY; |
michael@0 | 1076 | aContext->GetRoundOffsetsToPixels(&roundX, &roundY); |
michael@0 | 1077 | |
michael@0 | 1078 | int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); |
michael@0 | 1079 | gfxShapedText::CompressedGlyph *charGlyphs = |
michael@0 | 1080 | aShapedText->GetCharacterGlyphs() + aOffset; |
michael@0 | 1081 | |
michael@0 | 1082 | // factor to convert 16.16 fixed-point pixels to app units |
michael@0 | 1083 | // (only used if not rounding) |
michael@0 | 1084 | double hb2appUnits = FixedToFloat(aShapedText->GetAppUnitsPerDevUnit()); |
michael@0 | 1085 | |
michael@0 | 1086 | // Residual from rounding of previous advance, for use in rounding the |
michael@0 | 1087 | // subsequent offset or advance appropriately. 16.16 fixed-point |
michael@0 | 1088 | // |
michael@0 | 1089 | // When rounding, the goal is to make the distance between glyphs and |
michael@0 | 1090 | // their base glyph equal to the integral number of pixels closest to that |
michael@0 | 1091 | // suggested by that shaper. |
michael@0 | 1092 | // i.e. posInfo[n].x_advance - posInfo[n].x_offset + posInfo[n+1].x_offset |
michael@0 | 1093 | // |
michael@0 | 1094 | // The value of the residual is the part of the desired distance that has |
michael@0 | 1095 | // not been included in integer offsets. |
michael@0 | 1096 | hb_position_t x_residual = 0; |
michael@0 | 1097 | |
michael@0 | 1098 | // keep track of y-position to set glyph offsets if needed |
michael@0 | 1099 | nscoord yPos = 0; |
michael@0 | 1100 | |
michael@0 | 1101 | const hb_glyph_position_t *posInfo = |
michael@0 | 1102 | hb_buffer_get_glyph_positions(aBuffer, nullptr); |
michael@0 | 1103 | |
michael@0 | 1104 | while (glyphStart < int32_t(numGlyphs)) { |
michael@0 | 1105 | |
michael@0 | 1106 | int32_t charEnd = ginfo[glyphStart].cluster; |
michael@0 | 1107 | int32_t glyphEnd = glyphStart; |
michael@0 | 1108 | int32_t charLimit = wordLength; |
michael@0 | 1109 | while (charEnd < charLimit) { |
michael@0 | 1110 | // This is normally executed once for each iteration of the outer loop, |
michael@0 | 1111 | // but in unusual cases where the character/glyph association is complex, |
michael@0 | 1112 | // the initial character range might correspond to a non-contiguous |
michael@0 | 1113 | // glyph range with "holes" in it. If so, we will repeat this loop to |
michael@0 | 1114 | // extend the character range until we have a contiguous glyph sequence. |
michael@0 | 1115 | charEnd += 1; |
michael@0 | 1116 | while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { |
michael@0 | 1117 | charEnd += 1; |
michael@0 | 1118 | } |
michael@0 | 1119 | |
michael@0 | 1120 | // find the maximum glyph index covered by the clump so far |
michael@0 | 1121 | for (int32_t i = charStart; i < charEnd; ++i) { |
michael@0 | 1122 | if (charToGlyph[i] != NO_GLYPH) { |
michael@0 | 1123 | glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); |
michael@0 | 1124 | // update extent of glyph range |
michael@0 | 1125 | } |
michael@0 | 1126 | } |
michael@0 | 1127 | |
michael@0 | 1128 | if (glyphEnd == glyphStart + 1) { |
michael@0 | 1129 | // for the common case of a single-glyph clump, |
michael@0 | 1130 | // we can skip the following checks |
michael@0 | 1131 | break; |
michael@0 | 1132 | } |
michael@0 | 1133 | |
michael@0 | 1134 | if (glyphEnd == glyphStart) { |
michael@0 | 1135 | // no glyphs, try to extend the clump |
michael@0 | 1136 | continue; |
michael@0 | 1137 | } |
michael@0 | 1138 | |
michael@0 | 1139 | // check whether all glyphs in the range are associated with the characters |
michael@0 | 1140 | // in our clump; if not, we have a discontinuous range, and should extend it |
michael@0 | 1141 | // unless we've reached the end of the text |
michael@0 | 1142 | bool allGlyphsAreWithinCluster = true; |
michael@0 | 1143 | for (int32_t i = glyphStart; i < glyphEnd; ++i) { |
michael@0 | 1144 | int32_t glyphCharIndex = ginfo[i].cluster; |
michael@0 | 1145 | if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { |
michael@0 | 1146 | allGlyphsAreWithinCluster = false; |
michael@0 | 1147 | break; |
michael@0 | 1148 | } |
michael@0 | 1149 | } |
michael@0 | 1150 | if (allGlyphsAreWithinCluster) { |
michael@0 | 1151 | break; |
michael@0 | 1152 | } |
michael@0 | 1153 | } |
michael@0 | 1154 | |
michael@0 | 1155 | NS_ASSERTION(glyphStart < glyphEnd, |
michael@0 | 1156 | "character/glyph clump contains no glyphs!"); |
michael@0 | 1157 | NS_ASSERTION(charStart != charEnd, |
michael@0 | 1158 | "character/glyph clump contains no characters!"); |
michael@0 | 1159 | |
michael@0 | 1160 | // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; |
michael@0 | 1161 | // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), |
michael@0 | 1162 | // and endCharIndex to the limit (position beyond the last char), |
michael@0 | 1163 | // adjusting for the offset of the stringRange relative to the textRun. |
michael@0 | 1164 | int32_t baseCharIndex, endCharIndex; |
michael@0 | 1165 | while (charEnd < int32_t(wordLength) && charToGlyph[charEnd] == NO_GLYPH) |
michael@0 | 1166 | charEnd++; |
michael@0 | 1167 | baseCharIndex = charStart; |
michael@0 | 1168 | endCharIndex = charEnd; |
michael@0 | 1169 | |
michael@0 | 1170 | // Then we check if the clump falls outside our actual string range; |
michael@0 | 1171 | // if so, just go to the next. |
michael@0 | 1172 | if (baseCharIndex >= int32_t(wordLength)) { |
michael@0 | 1173 | glyphStart = glyphEnd; |
michael@0 | 1174 | charStart = charEnd; |
michael@0 | 1175 | continue; |
michael@0 | 1176 | } |
michael@0 | 1177 | // Ensure we won't try to go beyond the valid length of the textRun's text |
michael@0 | 1178 | endCharIndex = std::min<int32_t>(endCharIndex, wordLength); |
michael@0 | 1179 | |
michael@0 | 1180 | // Now we're ready to set the glyph info in the textRun |
michael@0 | 1181 | int32_t glyphsInClump = glyphEnd - glyphStart; |
michael@0 | 1182 | |
michael@0 | 1183 | // Check for default-ignorable char that didn't get filtered, combined, |
michael@0 | 1184 | // etc by the shaping process, and remove from the run. |
michael@0 | 1185 | // (This may be done within harfbuzz eventually.) |
michael@0 | 1186 | if (glyphsInClump == 1 && baseCharIndex + 1 == endCharIndex && |
michael@0 | 1187 | aShapedText->FilterIfIgnorable(aOffset + baseCharIndex, |
michael@0 | 1188 | aText[baseCharIndex])) { |
michael@0 | 1189 | glyphStart = glyphEnd; |
michael@0 | 1190 | charStart = charEnd; |
michael@0 | 1191 | continue; |
michael@0 | 1192 | } |
michael@0 | 1193 | |
michael@0 | 1194 | hb_position_t x_offset = posInfo[glyphStart].x_offset; |
michael@0 | 1195 | hb_position_t x_advance = posInfo[glyphStart].x_advance; |
michael@0 | 1196 | nscoord xOffset, advance; |
michael@0 | 1197 | if (roundX) { |
michael@0 | 1198 | xOffset = |
michael@0 | 1199 | appUnitsPerDevUnit * FixedToIntRound(x_offset + x_residual); |
michael@0 | 1200 | // Desired distance from the base glyph to the next reference point. |
michael@0 | 1201 | hb_position_t width = x_advance - x_offset; |
michael@0 | 1202 | int intWidth = FixedToIntRound(width); |
michael@0 | 1203 | x_residual = width - FloatToFixed(intWidth); |
michael@0 | 1204 | advance = appUnitsPerDevUnit * intWidth + xOffset; |
michael@0 | 1205 | } else { |
michael@0 | 1206 | xOffset = floor(hb2appUnits * x_offset + 0.5); |
michael@0 | 1207 | advance = floor(hb2appUnits * x_advance + 0.5); |
michael@0 | 1208 | } |
michael@0 | 1209 | // Check if it's a simple one-to-one mapping |
michael@0 | 1210 | if (glyphsInClump == 1 && |
michael@0 | 1211 | gfxTextRun::CompressedGlyph::IsSimpleGlyphID(ginfo[glyphStart].codepoint) && |
michael@0 | 1212 | gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && |
michael@0 | 1213 | charGlyphs[baseCharIndex].IsClusterStart() && |
michael@0 | 1214 | xOffset == 0 && |
michael@0 | 1215 | posInfo[glyphStart].y_offset == 0 && yPos == 0) |
michael@0 | 1216 | { |
michael@0 | 1217 | charGlyphs[baseCharIndex].SetSimpleGlyph(advance, |
michael@0 | 1218 | ginfo[glyphStart].codepoint); |
michael@0 | 1219 | } else { |
michael@0 | 1220 | // collect all glyphs in a list to be assigned to the first char; |
michael@0 | 1221 | // there must be at least one in the clump, and we already measured |
michael@0 | 1222 | // its advance, hence the placement of the loop-exit test and the |
michael@0 | 1223 | // measurement of the next glyph |
michael@0 | 1224 | while (1) { |
michael@0 | 1225 | gfxTextRun::DetailedGlyph* details = |
michael@0 | 1226 | detailedGlyphs.AppendElement(); |
michael@0 | 1227 | details->mGlyphID = ginfo[glyphStart].codepoint; |
michael@0 | 1228 | |
michael@0 | 1229 | details->mXOffset = xOffset; |
michael@0 | 1230 | details->mAdvance = advance; |
michael@0 | 1231 | |
michael@0 | 1232 | hb_position_t y_offset = posInfo[glyphStart].y_offset; |
michael@0 | 1233 | details->mYOffset = yPos - |
michael@0 | 1234 | (roundY ? appUnitsPerDevUnit * FixedToIntRound(y_offset) |
michael@0 | 1235 | : floor(hb2appUnits * y_offset + 0.5)); |
michael@0 | 1236 | |
michael@0 | 1237 | hb_position_t y_advance = posInfo[glyphStart].y_advance; |
michael@0 | 1238 | if (y_advance != 0) { |
michael@0 | 1239 | yPos -= |
michael@0 | 1240 | roundY ? appUnitsPerDevUnit * FixedToIntRound(y_advance) |
michael@0 | 1241 | : floor(hb2appUnits * y_advance + 0.5); |
michael@0 | 1242 | } |
michael@0 | 1243 | if (++glyphStart >= glyphEnd) { |
michael@0 | 1244 | break; |
michael@0 | 1245 | } |
michael@0 | 1246 | |
michael@0 | 1247 | x_offset = posInfo[glyphStart].x_offset; |
michael@0 | 1248 | x_advance = posInfo[glyphStart].x_advance; |
michael@0 | 1249 | if (roundX) { |
michael@0 | 1250 | xOffset = appUnitsPerDevUnit * |
michael@0 | 1251 | FixedToIntRound(x_offset + x_residual); |
michael@0 | 1252 | // Desired distance to the next reference point. The |
michael@0 | 1253 | // residual is considered here, and includes the residual |
michael@0 | 1254 | // from the base glyph offset and subsequent advances, so |
michael@0 | 1255 | // that the distance from the base glyph is optimized |
michael@0 | 1256 | // rather than the distance from combining marks. |
michael@0 | 1257 | x_advance += x_residual; |
michael@0 | 1258 | int intAdvance = FixedToIntRound(x_advance); |
michael@0 | 1259 | x_residual = x_advance - FloatToFixed(intAdvance); |
michael@0 | 1260 | advance = appUnitsPerDevUnit * intAdvance; |
michael@0 | 1261 | } else { |
michael@0 | 1262 | xOffset = floor(hb2appUnits * x_offset + 0.5); |
michael@0 | 1263 | advance = floor(hb2appUnits * x_advance + 0.5); |
michael@0 | 1264 | } |
michael@0 | 1265 | } |
michael@0 | 1266 | |
michael@0 | 1267 | gfxShapedText::CompressedGlyph g; |
michael@0 | 1268 | g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(), |
michael@0 | 1269 | true, detailedGlyphs.Length()); |
michael@0 | 1270 | aShapedText->SetGlyphs(aOffset + baseCharIndex, |
michael@0 | 1271 | g, detailedGlyphs.Elements()); |
michael@0 | 1272 | |
michael@0 | 1273 | detailedGlyphs.Clear(); |
michael@0 | 1274 | } |
michael@0 | 1275 | |
michael@0 | 1276 | // the rest of the chars in the group are ligature continuations, |
michael@0 | 1277 | // no associated glyphs |
michael@0 | 1278 | while (++baseCharIndex != endCharIndex && |
michael@0 | 1279 | baseCharIndex < int32_t(wordLength)) { |
michael@0 | 1280 | gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex]; |
michael@0 | 1281 | NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); |
michael@0 | 1282 | g.SetComplex(g.IsClusterStart(), false, 0); |
michael@0 | 1283 | } |
michael@0 | 1284 | |
michael@0 | 1285 | glyphStart = glyphEnd; |
michael@0 | 1286 | charStart = charEnd; |
michael@0 | 1287 | } |
michael@0 | 1288 | |
michael@0 | 1289 | return NS_OK; |
michael@0 | 1290 | } |