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 "mozilla/ArrayUtils.h" |
michael@0 | 7 | #include "gfxCoreTextShaper.h" |
michael@0 | 8 | #include "gfxMacFont.h" |
michael@0 | 9 | #include "gfxFontUtils.h" |
michael@0 | 10 | #include "mozilla/gfx/2D.h" |
michael@0 | 11 | |
michael@0 | 12 | #include <algorithm> |
michael@0 | 13 | |
michael@0 | 14 | using namespace mozilla; |
michael@0 | 15 | |
michael@0 | 16 | // standard font descriptors that we construct the first time they're needed |
michael@0 | 17 | CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr; |
michael@0 | 18 | CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr; |
michael@0 | 19 | |
michael@0 | 20 | gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont) |
michael@0 | 21 | : gfxFontShaper(aFont) |
michael@0 | 22 | { |
michael@0 | 23 | // Create our CTFontRef |
michael@0 | 24 | mCTFont = ::CTFontCreateWithGraphicsFont(aFont->GetCGFontRef(), |
michael@0 | 25 | aFont->GetAdjustedSize(), |
michael@0 | 26 | nullptr, |
michael@0 | 27 | GetDefaultFeaturesDescriptor()); |
michael@0 | 28 | |
michael@0 | 29 | // Set up the default attribute dictionary that we will need each time we create a CFAttributedString |
michael@0 | 30 | mAttributesDict = ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 31 | (const void**) &kCTFontAttributeName, |
michael@0 | 32 | (const void**) &mCTFont, |
michael@0 | 33 | 1, // count of attributes |
michael@0 | 34 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 35 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | gfxCoreTextShaper::~gfxCoreTextShaper() |
michael@0 | 39 | { |
michael@0 | 40 | if (mAttributesDict) { |
michael@0 | 41 | ::CFRelease(mAttributesDict); |
michael@0 | 42 | } |
michael@0 | 43 | if (mCTFont) { |
michael@0 | 44 | ::CFRelease(mCTFont); |
michael@0 | 45 | } |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | bool |
michael@0 | 49 | gfxCoreTextShaper::ShapeText(gfxContext *aContext, |
michael@0 | 50 | const char16_t *aText, |
michael@0 | 51 | uint32_t aOffset, |
michael@0 | 52 | uint32_t aLength, |
michael@0 | 53 | int32_t aScript, |
michael@0 | 54 | gfxShapedText *aShapedText) |
michael@0 | 55 | { |
michael@0 | 56 | // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out. |
michael@0 | 57 | |
michael@0 | 58 | bool isRightToLeft = aShapedText->IsRightToLeft(); |
michael@0 | 59 | uint32_t length = aLength; |
michael@0 | 60 | |
michael@0 | 61 | // we need to bidi-wrap the text if the run is RTL, |
michael@0 | 62 | // or if it is an LTR run but may contain (overridden) RTL chars |
michael@0 | 63 | bool bidiWrap = isRightToLeft; |
michael@0 | 64 | if (!bidiWrap && !aShapedText->TextIs8Bit()) { |
michael@0 | 65 | uint32_t i; |
michael@0 | 66 | for (i = 0; i < length; ++i) { |
michael@0 | 67 | if (gfxFontUtils::PotentialRTLChar(aText[i])) { |
michael@0 | 68 | bidiWrap = true; |
michael@0 | 69 | break; |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | // If there's a possibility of any bidi, we wrap the text with direction overrides |
michael@0 | 75 | // to ensure neutrals or characters that were bidi-overridden in HTML behave properly. |
michael@0 | 76 | const UniChar beginLTR[] = { 0x202d, 0x20 }; |
michael@0 | 77 | const UniChar beginRTL[] = { 0x202e, 0x20 }; |
michael@0 | 78 | const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; |
michael@0 | 79 | |
michael@0 | 80 | uint32_t startOffset; |
michael@0 | 81 | CFStringRef stringObj; |
michael@0 | 82 | if (bidiWrap) { |
michael@0 | 83 | startOffset = isRightToLeft ? |
michael@0 | 84 | mozilla::ArrayLength(beginRTL) : mozilla::ArrayLength(beginLTR); |
michael@0 | 85 | CFMutableStringRef mutableString = |
michael@0 | 86 | ::CFStringCreateMutable(kCFAllocatorDefault, |
michael@0 | 87 | length + startOffset + mozilla::ArrayLength(endBidiWrap)); |
michael@0 | 88 | ::CFStringAppendCharacters(mutableString, |
michael@0 | 89 | isRightToLeft ? beginRTL : beginLTR, |
michael@0 | 90 | startOffset); |
michael@0 | 91 | ::CFStringAppendCharacters(mutableString, reinterpret_cast<const UniChar*>(aText), length); |
michael@0 | 92 | ::CFStringAppendCharacters(mutableString, |
michael@0 | 93 | endBidiWrap, mozilla::ArrayLength(endBidiWrap)); |
michael@0 | 94 | stringObj = mutableString; |
michael@0 | 95 | } else { |
michael@0 | 96 | startOffset = 0; |
michael@0 | 97 | stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, |
michael@0 | 98 | reinterpret_cast<const UniChar*>(aText), |
michael@0 | 99 | length, kCFAllocatorNull); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | CFDictionaryRef attrObj; |
michael@0 | 103 | if (aShapedText->DisableLigatures()) { |
michael@0 | 104 | // For letterspacing (or maybe other situations) we need to make a copy of the CTFont |
michael@0 | 105 | // with the ligature feature disabled |
michael@0 | 106 | CTFontRef ctFont = |
michael@0 | 107 | CreateCTFontWithDisabledLigatures(::CTFontGetSize(mCTFont)); |
michael@0 | 108 | |
michael@0 | 109 | attrObj = |
michael@0 | 110 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 111 | (const void**) &kCTFontAttributeName, |
michael@0 | 112 | (const void**) &ctFont, |
michael@0 | 113 | 1, // count of attributes |
michael@0 | 114 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 115 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 116 | // Having created the dict, we're finished with our ligature-disabled CTFontRef |
michael@0 | 117 | ::CFRelease(ctFont); |
michael@0 | 118 | } else { |
michael@0 | 119 | attrObj = mAttributesDict; |
michael@0 | 120 | ::CFRetain(attrObj); |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | // Now we can create an attributed string |
michael@0 | 124 | CFAttributedStringRef attrStringObj = |
michael@0 | 125 | ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); |
michael@0 | 126 | ::CFRelease(stringObj); |
michael@0 | 127 | ::CFRelease(attrObj); |
michael@0 | 128 | |
michael@0 | 129 | // Create the CoreText line from our string, then we're done with it |
michael@0 | 130 | CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); |
michael@0 | 131 | ::CFRelease(attrStringObj); |
michael@0 | 132 | |
michael@0 | 133 | // and finally retrieve the glyph data and store into the gfxTextRun |
michael@0 | 134 | CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); |
michael@0 | 135 | uint32_t numRuns = ::CFArrayGetCount(glyphRuns); |
michael@0 | 136 | |
michael@0 | 137 | // Iterate through the glyph runs. |
michael@0 | 138 | // Note that this includes the bidi wrapper, so we have to be careful |
michael@0 | 139 | // not to include the extra glyphs from there |
michael@0 | 140 | bool success = true; |
michael@0 | 141 | for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { |
michael@0 | 142 | CTRunRef aCTRun = |
michael@0 | 143 | (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); |
michael@0 | 144 | if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, startOffset) != NS_OK) { |
michael@0 | 145 | success = false; |
michael@0 | 146 | break; |
michael@0 | 147 | } |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | ::CFRelease(line); |
michael@0 | 151 | |
michael@0 | 152 | return success; |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | #define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data; |
michael@0 | 156 | // some testing indicates that 90%+ of glyph runs will fit |
michael@0 | 157 | // without requiring a separate allocation |
michael@0 | 158 | |
michael@0 | 159 | nsresult |
michael@0 | 160 | gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText, |
michael@0 | 161 | uint32_t aOffset, |
michael@0 | 162 | uint32_t aLength, |
michael@0 | 163 | CTRunRef aCTRun, |
michael@0 | 164 | int32_t aStringOffset) |
michael@0 | 165 | { |
michael@0 | 166 | // The word has been bidi-wrapped; aStringOffset is the number |
michael@0 | 167 | // of chars at the beginning of the CTLine that we should skip. |
michael@0 | 168 | // aCTRun is a glyph run from the CoreText layout process. |
michael@0 | 169 | |
michael@0 | 170 | int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; |
michael@0 | 171 | |
michael@0 | 172 | int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); |
michael@0 | 173 | if (numGlyphs == 0) { |
michael@0 | 174 | return NS_OK; |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | int32_t wordLength = aLength; |
michael@0 | 178 | |
michael@0 | 179 | // character offsets get really confusing here, as we have to keep track of |
michael@0 | 180 | // (a) the text in the actual textRun we're constructing |
michael@0 | 181 | // (c) the string that was handed to CoreText, which contains the text of the font run |
michael@0 | 182 | // plus directional-override padding |
michael@0 | 183 | // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line |
michael@0 | 184 | // (but may extend beyond the actual font run into the bidi wrapping text). |
michael@0 | 185 | // aStringOffset tells us how many initial characters of the line to ignore. |
michael@0 | 186 | |
michael@0 | 187 | // get the source string range within the CTLine's text |
michael@0 | 188 | CFRange stringRange = ::CTRunGetStringRange(aCTRun); |
michael@0 | 189 | // skip the run if it is entirely outside the actual range of the font run |
michael@0 | 190 | if (stringRange.location - aStringOffset + stringRange.length <= 0 || |
michael@0 | 191 | stringRange.location - aStringOffset >= wordLength) { |
michael@0 | 192 | return NS_OK; |
michael@0 | 193 | } |
michael@0 | 194 | |
michael@0 | 195 | // retrieve the laid-out glyph data from the CTRun |
michael@0 | 196 | nsAutoArrayPtr<CGGlyph> glyphsArray; |
michael@0 | 197 | nsAutoArrayPtr<CGPoint> positionsArray; |
michael@0 | 198 | nsAutoArrayPtr<CFIndex> glyphToCharArray; |
michael@0 | 199 | const CGGlyph* glyphs = nullptr; |
michael@0 | 200 | const CGPoint* positions = nullptr; |
michael@0 | 201 | const CFIndex* glyphToChar = nullptr; |
michael@0 | 202 | |
michael@0 | 203 | // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, |
michael@0 | 204 | // and so allocating a new array and copying data with CTRunGetGlyphs |
michael@0 | 205 | // will be extremely rare. |
michael@0 | 206 | // If this were not the case, we could use an nsAutoTArray<> to |
michael@0 | 207 | // try and avoid the heap allocation for small runs. |
michael@0 | 208 | // It's possible that some future change to CoreText will mean that |
michael@0 | 209 | // CTRunGetGlyphsPtr fails more often; if this happens, nsAutoTArray<> |
michael@0 | 210 | // may become an attractive option. |
michael@0 | 211 | glyphs = ::CTRunGetGlyphsPtr(aCTRun); |
michael@0 | 212 | if (!glyphs) { |
michael@0 | 213 | glyphsArray = new (std::nothrow) CGGlyph[numGlyphs]; |
michael@0 | 214 | if (!glyphsArray) { |
michael@0 | 215 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 216 | } |
michael@0 | 217 | ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); |
michael@0 | 218 | glyphs = glyphsArray.get(); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | positions = ::CTRunGetPositionsPtr(aCTRun); |
michael@0 | 222 | if (!positions) { |
michael@0 | 223 | positionsArray = new (std::nothrow) CGPoint[numGlyphs]; |
michael@0 | 224 | if (!positionsArray) { |
michael@0 | 225 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 226 | } |
michael@0 | 227 | ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); |
michael@0 | 228 | positions = positionsArray.get(); |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | // Remember that the glyphToChar indices relate to the CoreText line, |
michael@0 | 232 | // not to the beginning of the textRun, the font run, |
michael@0 | 233 | // or the stringRange of the glyph run |
michael@0 | 234 | glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); |
michael@0 | 235 | if (!glyphToChar) { |
michael@0 | 236 | glyphToCharArray = new (std::nothrow) CFIndex[numGlyphs]; |
michael@0 | 237 | if (!glyphToCharArray) { |
michael@0 | 238 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 239 | } |
michael@0 | 240 | ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get()); |
michael@0 | 241 | glyphToChar = glyphToCharArray.get(); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), |
michael@0 | 245 | nullptr, nullptr, nullptr); |
michael@0 | 246 | |
michael@0 | 247 | nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs; |
michael@0 | 248 | gfxShapedText::CompressedGlyph g; |
michael@0 | 249 | gfxShapedText::CompressedGlyph *charGlyphs = |
michael@0 | 250 | aShapedText->GetCharacterGlyphs() + aOffset; |
michael@0 | 251 | |
michael@0 | 252 | // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph |
michael@0 | 253 | // to a source text character; we also need the charindex-to-glyphindex mapping to |
michael@0 | 254 | // find the glyph for a given char. Note that some chars may not map to any glyph |
michael@0 | 255 | // (ligature continuations), and some may map to several glyphs (eg Indic split vowels). |
michael@0 | 256 | // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we |
michael@0 | 257 | // record the last glyph index for cases where the char maps to several glyphs, |
michael@0 | 258 | // so that our clumping will include all the glyph fragments for the character. |
michael@0 | 259 | |
michael@0 | 260 | // The charToGlyph array is indexed by char position within the stringRange of the glyph run. |
michael@0 | 261 | |
michael@0 | 262 | static const int32_t NO_GLYPH = -1; |
michael@0 | 263 | AutoFallibleTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray; |
michael@0 | 264 | if (!charToGlyphArray.SetLength(stringRange.length)) { |
michael@0 | 265 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 266 | } |
michael@0 | 267 | int32_t *charToGlyph = charToGlyphArray.Elements(); |
michael@0 | 268 | for (int32_t offset = 0; offset < stringRange.length; ++offset) { |
michael@0 | 269 | charToGlyph[offset] = NO_GLYPH; |
michael@0 | 270 | } |
michael@0 | 271 | for (int32_t i = 0; i < numGlyphs; ++i) { |
michael@0 | 272 | int32_t loc = glyphToChar[i] - stringRange.location; |
michael@0 | 273 | if (loc >= 0 && loc < stringRange.length) { |
michael@0 | 274 | charToGlyph[loc] = i; |
michael@0 | 275 | } |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | // Find character and glyph clumps that correspond, allowing for ligatures, |
michael@0 | 279 | // indic reordering, split glyphs, etc. |
michael@0 | 280 | // |
michael@0 | 281 | // The idea is that we'll find a character sequence starting at the first char of stringRange, |
michael@0 | 282 | // and extend it until it includes the character associated with the first glyph; |
michael@0 | 283 | // we also extend it as long as there are "holes" in the range of glyphs. So we |
michael@0 | 284 | // will eventually have a contiguous sequence of characters, starting at the beginning |
michael@0 | 285 | // of the range, that map to a contiguous sequence of glyphs, starting at the beginning |
michael@0 | 286 | // of the glyph array. That's a clump; then we update the starting positions and repeat. |
michael@0 | 287 | // |
michael@0 | 288 | // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. |
michael@0 | 289 | // |
michael@0 | 290 | |
michael@0 | 291 | // This may find characters that fall outside the range 0:wordLength, |
michael@0 | 292 | // so we won't necessarily use everything we find here. |
michael@0 | 293 | |
michael@0 | 294 | bool isRightToLeft = aShapedText->IsRightToLeft(); |
michael@0 | 295 | int32_t glyphStart = 0; // looking for a clump that starts at this glyph index |
michael@0 | 296 | int32_t charStart = isRightToLeft ? |
michael@0 | 297 | stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run) |
michael@0 | 298 | |
michael@0 | 299 | while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for |
michael@0 | 300 | bool inOrder = true; |
michael@0 | 301 | int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; |
michael@0 | 302 | NS_WARN_IF_FALSE(charEnd >= 0 && charEnd < stringRange.length, |
michael@0 | 303 | "glyph-to-char mapping points outside string range"); |
michael@0 | 304 | // clamp charEnd to the valid range of the string |
michael@0 | 305 | charEnd = std::max(charEnd, 0); |
michael@0 | 306 | charEnd = std::min(charEnd, int32_t(stringRange.length)); |
michael@0 | 307 | |
michael@0 | 308 | int32_t glyphEnd = glyphStart; |
michael@0 | 309 | int32_t charLimit = isRightToLeft ? -1 : stringRange.length; |
michael@0 | 310 | do { |
michael@0 | 311 | // This is normally executed once for each iteration of the outer loop, |
michael@0 | 312 | // but in unusual cases where the character/glyph association is complex, |
michael@0 | 313 | // the initial character range might correspond to a non-contiguous |
michael@0 | 314 | // glyph range with "holes" in it. If so, we will repeat this loop to |
michael@0 | 315 | // extend the character range until we have a contiguous glyph sequence. |
michael@0 | 316 | NS_ASSERTION((direction > 0 && charEnd < charLimit) || |
michael@0 | 317 | (direction < 0 && charEnd > charLimit), |
michael@0 | 318 | "no characters left in range?"); |
michael@0 | 319 | charEnd += direction; |
michael@0 | 320 | while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { |
michael@0 | 321 | charEnd += direction; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | // find the maximum glyph index covered by the clump so far |
michael@0 | 325 | if (isRightToLeft) { |
michael@0 | 326 | for (int32_t i = charStart; i > charEnd; --i) { |
michael@0 | 327 | if (charToGlyph[i] != NO_GLYPH) { |
michael@0 | 328 | // update extent of glyph range |
michael@0 | 329 | glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); |
michael@0 | 330 | } |
michael@0 | 331 | } |
michael@0 | 332 | } else { |
michael@0 | 333 | for (int32_t i = charStart; i < charEnd; ++i) { |
michael@0 | 334 | if (charToGlyph[i] != NO_GLYPH) { |
michael@0 | 335 | // update extent of glyph range |
michael@0 | 336 | glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); |
michael@0 | 337 | } |
michael@0 | 338 | } |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | if (glyphEnd == glyphStart + 1) { |
michael@0 | 342 | // for the common case of a single-glyph clump, we can skip the following checks |
michael@0 | 343 | break; |
michael@0 | 344 | } |
michael@0 | 345 | |
michael@0 | 346 | if (glyphEnd == glyphStart) { |
michael@0 | 347 | // no glyphs, try to extend the clump |
michael@0 | 348 | continue; |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | // check whether all glyphs in the range are associated with the characters |
michael@0 | 352 | // in our clump; if not, we have a discontinuous range, and should extend it |
michael@0 | 353 | // unless we've reached the end of the text |
michael@0 | 354 | bool allGlyphsAreWithinCluster = true; |
michael@0 | 355 | int32_t prevGlyphCharIndex = charStart; |
michael@0 | 356 | for (int32_t i = glyphStart; i < glyphEnd; ++i) { |
michael@0 | 357 | int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; |
michael@0 | 358 | if (isRightToLeft) { |
michael@0 | 359 | if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { |
michael@0 | 360 | allGlyphsAreWithinCluster = false; |
michael@0 | 361 | break; |
michael@0 | 362 | } |
michael@0 | 363 | if (glyphCharIndex > prevGlyphCharIndex) { |
michael@0 | 364 | inOrder = false; |
michael@0 | 365 | } |
michael@0 | 366 | prevGlyphCharIndex = glyphCharIndex; |
michael@0 | 367 | } else { |
michael@0 | 368 | if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { |
michael@0 | 369 | allGlyphsAreWithinCluster = false; |
michael@0 | 370 | break; |
michael@0 | 371 | } |
michael@0 | 372 | if (glyphCharIndex < prevGlyphCharIndex) { |
michael@0 | 373 | inOrder = false; |
michael@0 | 374 | } |
michael@0 | 375 | prevGlyphCharIndex = glyphCharIndex; |
michael@0 | 376 | } |
michael@0 | 377 | } |
michael@0 | 378 | if (allGlyphsAreWithinCluster) { |
michael@0 | 379 | break; |
michael@0 | 380 | } |
michael@0 | 381 | } while (charEnd != charLimit); |
michael@0 | 382 | |
michael@0 | 383 | NS_WARN_IF_FALSE(glyphStart < glyphEnd, |
michael@0 | 384 | "character/glyph clump contains no glyphs!"); |
michael@0 | 385 | if (glyphStart == glyphEnd) { |
michael@0 | 386 | ++glyphStart; // make progress - avoid potential infinite loop |
michael@0 | 387 | charStart = charEnd; |
michael@0 | 388 | continue; |
michael@0 | 389 | } |
michael@0 | 390 | |
michael@0 | 391 | NS_WARN_IF_FALSE(charStart != charEnd, |
michael@0 | 392 | "character/glyph clump contains no characters!"); |
michael@0 | 393 | if (charStart == charEnd) { |
michael@0 | 394 | glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), |
michael@0 | 395 | // as there's nowhere to attach them |
michael@0 | 396 | continue; |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; |
michael@0 | 400 | // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), |
michael@0 | 401 | // and endCharIndex to the limit (position beyond the last char), |
michael@0 | 402 | // adjusting for the offset of the stringRange relative to the textRun. |
michael@0 | 403 | int32_t baseCharIndex, endCharIndex; |
michael@0 | 404 | if (isRightToLeft) { |
michael@0 | 405 | while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { |
michael@0 | 406 | charEnd--; |
michael@0 | 407 | } |
michael@0 | 408 | baseCharIndex = charEnd + stringRange.location - aStringOffset + 1; |
michael@0 | 409 | endCharIndex = charStart + stringRange.location - aStringOffset + 1; |
michael@0 | 410 | } else { |
michael@0 | 411 | while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { |
michael@0 | 412 | charEnd++; |
michael@0 | 413 | } |
michael@0 | 414 | baseCharIndex = charStart + stringRange.location - aStringOffset; |
michael@0 | 415 | endCharIndex = charEnd + stringRange.location - aStringOffset; |
michael@0 | 416 | } |
michael@0 | 417 | |
michael@0 | 418 | // Then we check if the clump falls outside our actual string range; if so, just go to the next. |
michael@0 | 419 | if (endCharIndex <= 0 || baseCharIndex >= wordLength) { |
michael@0 | 420 | glyphStart = glyphEnd; |
michael@0 | 421 | charStart = charEnd; |
michael@0 | 422 | continue; |
michael@0 | 423 | } |
michael@0 | 424 | // Ensure we won't try to go beyond the valid length of the word's text |
michael@0 | 425 | baseCharIndex = std::max(baseCharIndex, 0); |
michael@0 | 426 | endCharIndex = std::min(endCharIndex, wordLength); |
michael@0 | 427 | |
michael@0 | 428 | // Now we're ready to set the glyph info in the textRun; measure the glyph width |
michael@0 | 429 | // of the first (perhaps only) glyph, to see if it is "Simple" |
michael@0 | 430 | int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); |
michael@0 | 431 | double toNextGlyph; |
michael@0 | 432 | if (glyphStart < numGlyphs-1) { |
michael@0 | 433 | toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; |
michael@0 | 434 | } else { |
michael@0 | 435 | toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; |
michael@0 | 436 | } |
michael@0 | 437 | int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); |
michael@0 | 438 | |
michael@0 | 439 | // Check if it's a simple one-to-one mapping |
michael@0 | 440 | int32_t glyphsInClump = glyphEnd - glyphStart; |
michael@0 | 441 | if (glyphsInClump == 1 && |
michael@0 | 442 | gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && |
michael@0 | 443 | gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && |
michael@0 | 444 | charGlyphs[baseCharIndex].IsClusterStart() && |
michael@0 | 445 | positions[glyphStart].y == 0.0) |
michael@0 | 446 | { |
michael@0 | 447 | charGlyphs[baseCharIndex].SetSimpleGlyph(advance, |
michael@0 | 448 | glyphs[glyphStart]); |
michael@0 | 449 | } else { |
michael@0 | 450 | // collect all glyphs in a list to be assigned to the first char; |
michael@0 | 451 | // there must be at least one in the clump, and we already measured its advance, |
michael@0 | 452 | // hence the placement of the loop-exit test and the measurement of the next glyph |
michael@0 | 453 | while (1) { |
michael@0 | 454 | gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement(); |
michael@0 | 455 | details->mGlyphID = glyphs[glyphStart]; |
michael@0 | 456 | details->mXOffset = 0; |
michael@0 | 457 | details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit; |
michael@0 | 458 | details->mAdvance = advance; |
michael@0 | 459 | if (++glyphStart >= glyphEnd) { |
michael@0 | 460 | break; |
michael@0 | 461 | } |
michael@0 | 462 | if (glyphStart < numGlyphs-1) { |
michael@0 | 463 | toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; |
michael@0 | 464 | } else { |
michael@0 | 465 | toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; |
michael@0 | 466 | } |
michael@0 | 467 | advance = int32_t(toNextGlyph * appUnitsPerDevUnit); |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | gfxTextRun::CompressedGlyph g; |
michael@0 | 471 | g.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(), |
michael@0 | 472 | true, detailedGlyphs.Length()); |
michael@0 | 473 | aShapedText->SetGlyphs(aOffset + baseCharIndex, g, detailedGlyphs.Elements()); |
michael@0 | 474 | |
michael@0 | 475 | detailedGlyphs.Clear(); |
michael@0 | 476 | } |
michael@0 | 477 | |
michael@0 | 478 | // the rest of the chars in the group are ligature continuations, no associated glyphs |
michael@0 | 479 | while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { |
michael@0 | 480 | gfxShapedText::CompressedGlyph &g = charGlyphs[baseCharIndex]; |
michael@0 | 481 | NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); |
michael@0 | 482 | g.SetComplex(inOrder && g.IsClusterStart(), false, 0); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | glyphStart = glyphEnd; |
michael@0 | 486 | charStart = charEnd; |
michael@0 | 487 | } |
michael@0 | 488 | |
michael@0 | 489 | return NS_OK; |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | #undef SMALL_GLYPH_RUN |
michael@0 | 493 | |
michael@0 | 494 | // Construct the font attribute descriptor that we'll apply by default when creating a CTFontRef. |
michael@0 | 495 | // This will turn off line-edge swashes by default, because we don't know the actual line breaks |
michael@0 | 496 | // when doing glyph shaping. |
michael@0 | 497 | void |
michael@0 | 498 | gfxCoreTextShaper::CreateDefaultFeaturesDescriptor() |
michael@0 | 499 | { |
michael@0 | 500 | if (sDefaultFeaturesDescriptor != nullptr) { |
michael@0 | 501 | return; |
michael@0 | 502 | } |
michael@0 | 503 | |
michael@0 | 504 | SInt16 val = kSmartSwashType; |
michael@0 | 505 | CFNumberRef swashesType = |
michael@0 | 506 | ::CFNumberCreate(kCFAllocatorDefault, |
michael@0 | 507 | kCFNumberSInt16Type, |
michael@0 | 508 | &val); |
michael@0 | 509 | val = kLineInitialSwashesOffSelector; |
michael@0 | 510 | CFNumberRef lineInitialsOffSelector = |
michael@0 | 511 | ::CFNumberCreate(kCFAllocatorDefault, |
michael@0 | 512 | kCFNumberSInt16Type, |
michael@0 | 513 | &val); |
michael@0 | 514 | |
michael@0 | 515 | CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey, |
michael@0 | 516 | kCTFontFeatureSelectorIdentifierKey }; |
michael@0 | 517 | CFTypeRef values[] = { swashesType, |
michael@0 | 518 | lineInitialsOffSelector }; |
michael@0 | 519 | CFDictionaryRef featureSettings[2]; |
michael@0 | 520 | featureSettings[0] = |
michael@0 | 521 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 522 | (const void **) keys, |
michael@0 | 523 | (const void **) values, |
michael@0 | 524 | ArrayLength(keys), |
michael@0 | 525 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 526 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 527 | ::CFRelease(lineInitialsOffSelector); |
michael@0 | 528 | |
michael@0 | 529 | val = kLineFinalSwashesOffSelector; |
michael@0 | 530 | CFNumberRef lineFinalsOffSelector = |
michael@0 | 531 | ::CFNumberCreate(kCFAllocatorDefault, |
michael@0 | 532 | kCFNumberSInt16Type, |
michael@0 | 533 | &val); |
michael@0 | 534 | values[1] = lineFinalsOffSelector; |
michael@0 | 535 | featureSettings[1] = |
michael@0 | 536 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 537 | (const void **) keys, |
michael@0 | 538 | (const void **) values, |
michael@0 | 539 | ArrayLength(keys), |
michael@0 | 540 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 541 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 542 | ::CFRelease(lineFinalsOffSelector); |
michael@0 | 543 | ::CFRelease(swashesType); |
michael@0 | 544 | |
michael@0 | 545 | CFArrayRef featuresArray = |
michael@0 | 546 | ::CFArrayCreate(kCFAllocatorDefault, |
michael@0 | 547 | (const void **) featureSettings, |
michael@0 | 548 | ArrayLength(featureSettings), |
michael@0 | 549 | &kCFTypeArrayCallBacks); |
michael@0 | 550 | ::CFRelease(featureSettings[0]); |
michael@0 | 551 | ::CFRelease(featureSettings[1]); |
michael@0 | 552 | |
michael@0 | 553 | const CFTypeRef attrKeys[] = { kCTFontFeatureSettingsAttribute }; |
michael@0 | 554 | const CFTypeRef attrValues[] = { featuresArray }; |
michael@0 | 555 | CFDictionaryRef attributesDict = |
michael@0 | 556 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 557 | (const void **) attrKeys, |
michael@0 | 558 | (const void **) attrValues, |
michael@0 | 559 | ArrayLength(attrKeys), |
michael@0 | 560 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 561 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 562 | ::CFRelease(featuresArray); |
michael@0 | 563 | |
michael@0 | 564 | sDefaultFeaturesDescriptor = |
michael@0 | 565 | ::CTFontDescriptorCreateWithAttributes(attributesDict); |
michael@0 | 566 | ::CFRelease(attributesDict); |
michael@0 | 567 | } |
michael@0 | 568 | |
michael@0 | 569 | // Create a CTFontRef, with the Common Ligatures feature disabled |
michael@0 | 570 | CTFontRef |
michael@0 | 571 | gfxCoreTextShaper::CreateCTFontWithDisabledLigatures(CGFloat aSize) |
michael@0 | 572 | { |
michael@0 | 573 | if (sDisableLigaturesDescriptor == nullptr) { |
michael@0 | 574 | // initialize cached descriptor to turn off the Common Ligatures feature |
michael@0 | 575 | SInt16 val = kLigaturesType; |
michael@0 | 576 | CFNumberRef ligaturesType = |
michael@0 | 577 | ::CFNumberCreate(kCFAllocatorDefault, |
michael@0 | 578 | kCFNumberSInt16Type, |
michael@0 | 579 | &val); |
michael@0 | 580 | val = kCommonLigaturesOffSelector; |
michael@0 | 581 | CFNumberRef commonLigaturesOffSelector = |
michael@0 | 582 | ::CFNumberCreate(kCFAllocatorDefault, |
michael@0 | 583 | kCFNumberSInt16Type, |
michael@0 | 584 | &val); |
michael@0 | 585 | |
michael@0 | 586 | const CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey, |
michael@0 | 587 | kCTFontFeatureSelectorIdentifierKey }; |
michael@0 | 588 | const CFTypeRef values[] = { ligaturesType, |
michael@0 | 589 | commonLigaturesOffSelector }; |
michael@0 | 590 | CFDictionaryRef featureSettingDict = |
michael@0 | 591 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 592 | (const void **) keys, |
michael@0 | 593 | (const void **) values, |
michael@0 | 594 | ArrayLength(keys), |
michael@0 | 595 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 596 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 597 | ::CFRelease(ligaturesType); |
michael@0 | 598 | ::CFRelease(commonLigaturesOffSelector); |
michael@0 | 599 | |
michael@0 | 600 | CFArrayRef featuresArray = |
michael@0 | 601 | ::CFArrayCreate(kCFAllocatorDefault, |
michael@0 | 602 | (const void **) &featureSettingDict, |
michael@0 | 603 | 1, |
michael@0 | 604 | &kCFTypeArrayCallBacks); |
michael@0 | 605 | ::CFRelease(featureSettingDict); |
michael@0 | 606 | |
michael@0 | 607 | CFDictionaryRef attributesDict = |
michael@0 | 608 | ::CFDictionaryCreate(kCFAllocatorDefault, |
michael@0 | 609 | (const void **) &kCTFontFeatureSettingsAttribute, |
michael@0 | 610 | (const void **) &featuresArray, |
michael@0 | 611 | 1, // count of keys & values |
michael@0 | 612 | &kCFTypeDictionaryKeyCallBacks, |
michael@0 | 613 | &kCFTypeDictionaryValueCallBacks); |
michael@0 | 614 | ::CFRelease(featuresArray); |
michael@0 | 615 | |
michael@0 | 616 | sDisableLigaturesDescriptor = |
michael@0 | 617 | ::CTFontDescriptorCreateCopyWithAttributes(GetDefaultFeaturesDescriptor(), |
michael@0 | 618 | attributesDict); |
michael@0 | 619 | ::CFRelease(attributesDict); |
michael@0 | 620 | } |
michael@0 | 621 | |
michael@0 | 622 | gfxMacFont *f = static_cast<gfxMacFont*>(mFont); |
michael@0 | 623 | return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr, |
michael@0 | 624 | sDisableLigaturesDescriptor); |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | void |
michael@0 | 628 | gfxCoreTextShaper::Shutdown() // [static] |
michael@0 | 629 | { |
michael@0 | 630 | if (sDisableLigaturesDescriptor != nullptr) { |
michael@0 | 631 | ::CFRelease(sDisableLigaturesDescriptor); |
michael@0 | 632 | sDisableLigaturesDescriptor = nullptr; |
michael@0 | 633 | } |
michael@0 | 634 | if (sDefaultFeaturesDescriptor != nullptr) { |
michael@0 | 635 | ::CFRelease(sDefaultFeaturesDescriptor); |
michael@0 | 636 | sDefaultFeaturesDescriptor = nullptr; |
michael@0 | 637 | } |
michael@0 | 638 | } |