1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/thebes/gfxUniscribeShaper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,527 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "gfxTypes.h" 1.10 + 1.11 +#include "gfxContext.h" 1.12 +#include "gfxUniscribeShaper.h" 1.13 +#include "gfxWindowsPlatform.h" 1.14 + 1.15 +#include "gfxFontTest.h" 1.16 + 1.17 +#include "cairo.h" 1.18 +#include "cairo-win32.h" 1.19 + 1.20 +#include <windows.h> 1.21 + 1.22 +#include "nsTArray.h" 1.23 + 1.24 +#include "prinit.h" 1.25 + 1.26 +/********************************************************************** 1.27 + * 1.28 + * class gfxUniscribeShaper 1.29 + * 1.30 + **********************************************************************/ 1.31 + 1.32 +#define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16) 1.33 + 1.34 +class UniscribeItem 1.35 +{ 1.36 +public: 1.37 + UniscribeItem(gfxContext *aContext, HDC aDC, 1.38 + gfxUniscribeShaper *aShaper, 1.39 + const char16_t *aString, uint32_t aLength, 1.40 + SCRIPT_ITEM *aItem, uint32_t aIVS) : 1.41 + mContext(aContext), mDC(aDC), 1.42 + mShaper(aShaper), 1.43 + mItemString(aString), mItemLength(aLength), 1.44 + mAlternativeString(nullptr), mScriptItem(aItem), 1.45 + mScript(aItem->a.eScript), 1.46 + mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)), 1.47 + mFontSelected(false), mIVS(aIVS) 1.48 + { 1.49 + // See bug 394751 for details. 1.50 + NS_ASSERTION(mMaxGlyphs < 65535, 1.51 + "UniscribeItem is too big, ScriptShape() will fail!"); 1.52 + } 1.53 + 1.54 + ~UniscribeItem() { 1.55 + free(mAlternativeString); 1.56 + } 1.57 + 1.58 + bool AllocateBuffers() { 1.59 + return (mGlyphs.SetLength(mMaxGlyphs) && 1.60 + mClusters.SetLength(mItemLength + 1) && 1.61 + mAttr.SetLength(mMaxGlyphs)); 1.62 + } 1.63 + 1.64 + /* possible return values: 1.65 + * S_OK - things succeeded 1.66 + * GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping() 1.67 + */ 1.68 + 1.69 + HRESULT Shape() { 1.70 + HRESULT rv; 1.71 + HDC shapeDC = nullptr; 1.72 + 1.73 + char16ptr_t str = mAlternativeString ? mAlternativeString : mItemString; 1.74 + 1.75 + mScriptItem->a.fLogicalOrder = true; 1.76 + SCRIPT_ANALYSIS sa = mScriptItem->a; 1.77 + 1.78 + while (true) { 1.79 + 1.80 + rv = ScriptShape(shapeDC, mShaper->ScriptCache(), 1.81 + str, mItemLength, 1.82 + mMaxGlyphs, &sa, 1.83 + mGlyphs.Elements(), mClusters.Elements(), 1.84 + mAttr.Elements(), &mNumGlyphs); 1.85 + 1.86 + if (rv == E_OUTOFMEMORY) { 1.87 + mMaxGlyphs *= 2; 1.88 + if (!mGlyphs.SetLength(mMaxGlyphs) || 1.89 + !mAttr.SetLength(mMaxGlyphs)) { 1.90 + return E_OUTOFMEMORY; 1.91 + } 1.92 + continue; 1.93 + } 1.94 + 1.95 + // Uniscribe can't do shaping with some fonts, so it sets the 1.96 + // fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate 1.97 + // this. This occurs with CFF fonts loaded with 1.98 + // AddFontMemResourceEx but it's not clear what the other cases 1.99 + // are. We return an error so our caller can try fallback shaping. 1.100 + // see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx 1.101 + 1.102 + if (sa.fNoGlyphIndex) { 1.103 + return GDI_ERROR; 1.104 + } 1.105 + 1.106 + if (rv == E_PENDING) { 1.107 + if (shapeDC == mDC) { 1.108 + // we already tried this once, something failed, give up 1.109 + return E_PENDING; 1.110 + } 1.111 + 1.112 + SelectFont(); 1.113 + 1.114 + shapeDC = mDC; 1.115 + continue; 1.116 + } 1.117 + 1.118 + // http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx: 1.119 + // Uniscribe will return this if "the font corresponding to the 1.120 + // DC does not support the script required by the run...". 1.121 + // In this case, we'll set the script code to SCRIPT_UNDEFINED 1.122 + // and try again, so that we'll at least get glyphs even though 1.123 + // they won't necessarily have proper shaping. 1.124 + // (We probably shouldn't have selected this font at all, 1.125 + // but it's too late to fix that here.) 1.126 + if (rv == USP_E_SCRIPT_NOT_IN_FONT) { 1.127 + sa.eScript = SCRIPT_UNDEFINED; 1.128 + NS_WARNING("Uniscribe says font does not support script needed"); 1.129 + continue; 1.130 + } 1.131 + 1.132 + // Prior to Windows 7, Uniscribe didn't support Ideographic Variation 1.133 + // Selectors. Replace the UVS glyph manually. 1.134 + if (mIVS) { 1.135 + uint32_t lastChar = str[mItemLength - 1]; 1.136 + if (NS_IS_LOW_SURROGATE(lastChar) 1.137 + && NS_IS_HIGH_SURROGATE(str[mItemLength - 2])) { 1.138 + lastChar = SURROGATE_TO_UCS4(str[mItemLength - 2], lastChar); 1.139 + } 1.140 + uint16_t glyphId = mShaper->GetFont()->GetUVSGlyph(lastChar, mIVS); 1.141 + if (glyphId) { 1.142 + mGlyphs[mNumGlyphs - 1] = glyphId; 1.143 + } 1.144 + } 1.145 + 1.146 + return rv; 1.147 + } 1.148 + } 1.149 + 1.150 + bool ShapingEnabled() { 1.151 + return (mScriptItem->a.eScript != SCRIPT_UNDEFINED); 1.152 + } 1.153 + void DisableShaping() { 1.154 + mScriptItem->a.eScript = SCRIPT_UNDEFINED; 1.155 + // Note: If we disable the shaping by using SCRIPT_UNDEFINED and 1.156 + // the string has the surrogate pair, ScriptShape API is 1.157 + // *sometimes* crashed. Therefore, we should replace the surrogate 1.158 + // pair to U+FFFD. See bug 341500. 1.159 + GenerateAlternativeString(); 1.160 + } 1.161 + void EnableShaping() { 1.162 + mScriptItem->a.eScript = mScript; 1.163 + if (mAlternativeString) { 1.164 + free(mAlternativeString); 1.165 + mAlternativeString = nullptr; 1.166 + } 1.167 + } 1.168 + 1.169 + bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) { 1.170 + return (mGlyphs[aGlyphIndex] == aSFP->wgDefault); 1.171 + } 1.172 + 1.173 + 1.174 + HRESULT Place() { 1.175 + HRESULT rv; 1.176 + HDC placeDC = nullptr; 1.177 + 1.178 + if (!mOffsets.SetLength(mNumGlyphs) || 1.179 + !mAdvances.SetLength(mNumGlyphs)) { 1.180 + return E_OUTOFMEMORY; 1.181 + } 1.182 + 1.183 + SCRIPT_ANALYSIS sa = mScriptItem->a; 1.184 + 1.185 + while (true) { 1.186 + rv = ScriptPlace(placeDC, mShaper->ScriptCache(), 1.187 + mGlyphs.Elements(), mNumGlyphs, 1.188 + mAttr.Elements(), &sa, 1.189 + mAdvances.Elements(), mOffsets.Elements(), nullptr); 1.190 + 1.191 + if (rv == E_PENDING) { 1.192 + SelectFont(); 1.193 + placeDC = mDC; 1.194 + continue; 1.195 + } 1.196 + 1.197 + if (rv == USP_E_SCRIPT_NOT_IN_FONT) { 1.198 + sa.eScript = SCRIPT_UNDEFINED; 1.199 + continue; 1.200 + } 1.201 + 1.202 + break; 1.203 + } 1.204 + 1.205 + return rv; 1.206 + } 1.207 + 1.208 + void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) { 1.209 + HRESULT rv; 1.210 + 1.211 + memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES)); 1.212 + sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES); 1.213 + rv = ScriptGetFontProperties(nullptr, mShaper->ScriptCache(), 1.214 + sfp); 1.215 + if (rv == E_PENDING) { 1.216 + SelectFont(); 1.217 + rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(), 1.218 + sfp); 1.219 + } 1.220 + } 1.221 + 1.222 + void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) { 1.223 + uint32_t offsetInRun = mScriptItem->iCharPos; 1.224 + 1.225 + // XXX We should store this in the item and only fetch it once 1.226 + SCRIPT_FONTPROPERTIES sfp; 1.227 + ScriptFontProperties(&sfp); 1.228 + 1.229 + uint32_t offset = 0; 1.230 + nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs; 1.231 + gfxShapedText::CompressedGlyph g; 1.232 + gfxShapedText::CompressedGlyph *charGlyphs = 1.233 + aShapedText->GetCharacterGlyphs(); 1.234 + const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); 1.235 + while (offset < mItemLength) { 1.236 + uint32_t runOffset = aOffset + offsetInRun + offset; 1.237 + bool atClusterStart = charGlyphs[runOffset].IsClusterStart(); 1.238 + if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) { 1.239 + gfxShapedText::CompressedGlyph &g = charGlyphs[runOffset]; 1.240 + NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); 1.241 + g.SetComplex(atClusterStart, false, 0); 1.242 + } else { 1.243 + // Count glyphs for this character 1.244 + uint32_t k = mClusters[offset]; 1.245 + uint32_t glyphCount = mNumGlyphs - k; 1.246 + uint32_t nextClusterOffset; 1.247 + bool missing = IsGlyphMissing(&sfp, k); 1.248 + for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) { 1.249 + if (mClusters[nextClusterOffset] > k) { 1.250 + glyphCount = mClusters[nextClusterOffset] - k; 1.251 + break; 1.252 + } 1.253 + } 1.254 + uint32_t j; 1.255 + for (j = 1; j < glyphCount; ++j) { 1.256 + if (IsGlyphMissing(&sfp, k + j)) { 1.257 + missing = true; 1.258 + } 1.259 + } 1.260 + int32_t advance = mAdvances[k]*appUnitsPerDevUnit; 1.261 + WORD glyph = mGlyphs[k]; 1.262 + NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]), 1.263 + "invalid character detected"); 1.264 + if (missing) { 1.265 + if (NS_IS_HIGH_SURROGATE(mItemString[offset]) && 1.266 + offset + 1 < mItemLength && 1.267 + NS_IS_LOW_SURROGATE(mItemString[offset + 1])) { 1.268 + aShapedText->SetMissingGlyph(runOffset, 1.269 + SURROGATE_TO_UCS4(mItemString[offset], 1.270 + mItemString[offset + 1]), 1.271 + mShaper->GetFont()); 1.272 + } else { 1.273 + aShapedText->SetMissingGlyph(runOffset, mItemString[offset], 1.274 + mShaper->GetFont()); 1.275 + } 1.276 + } else if (glyphCount == 1 && advance >= 0 && 1.277 + mOffsets[k].dv == 0 && mOffsets[k].du == 0 && 1.278 + gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) && 1.279 + gfxShapedText::CompressedGlyph::IsSimpleGlyphID(glyph) && 1.280 + atClusterStart) 1.281 + { 1.282 + charGlyphs[runOffset].SetSimpleGlyph(advance, glyph); 1.283 + } else { 1.284 + if (detailedGlyphs.Length() < glyphCount) { 1.285 + if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length())) 1.286 + return; 1.287 + } 1.288 + uint32_t i; 1.289 + for (i = 0; i < glyphCount; ++i) { 1.290 + gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i]; 1.291 + details->mGlyphID = mGlyphs[k + i]; 1.292 + details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit; 1.293 + details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit * 1.294 + aShapedText->GetDirection(); 1.295 + details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit; 1.296 + } 1.297 + aShapedText->SetGlyphs(runOffset, 1.298 + g.SetComplex(atClusterStart, true, 1.299 + glyphCount), 1.300 + detailedGlyphs.Elements()); 1.301 + } 1.302 + } 1.303 + ++offset; 1.304 + } 1.305 + } 1.306 + 1.307 + void SelectFont() { 1.308 + if (mFontSelected) 1.309 + return; 1.310 + 1.311 + cairo_t *cr = mContext->GetCairo(); 1.312 + 1.313 + cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace()); 1.314 + cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize()); 1.315 + cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont(); 1.316 + cairo_win32_scaled_font_select_font(scaledFont, mDC); 1.317 + 1.318 + mFontSelected = true; 1.319 + } 1.320 + 1.321 +private: 1.322 + 1.323 + void GenerateAlternativeString() { 1.324 + if (mAlternativeString) 1.325 + free(mAlternativeString); 1.326 + mAlternativeString = (char16_t *)malloc(mItemLength * sizeof(char16_t)); 1.327 + if (!mAlternativeString) 1.328 + return; 1.329 + memcpy((void *)mAlternativeString, (const void *)mItemString, 1.330 + mItemLength * sizeof(char16_t)); 1.331 + for (uint32_t i = 0; i < mItemLength; i++) { 1.332 + if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i])) 1.333 + mAlternativeString[i] = char16_t(0xFFFD); 1.334 + } 1.335 + } 1.336 + 1.337 +private: 1.338 + nsRefPtr<gfxContext> mContext; 1.339 + HDC mDC; 1.340 + gfxUniscribeShaper *mShaper; 1.341 + 1.342 + SCRIPT_ITEM *mScriptItem; 1.343 + WORD mScript; 1.344 + 1.345 +public: 1.346 + // these point to the full string/length of the item 1.347 + const char16_t *mItemString; 1.348 + const uint32_t mItemLength; 1.349 + 1.350 +private: 1.351 + char16_t *mAlternativeString; 1.352 + 1.353 +#define AVERAGE_ITEM_LENGTH 40 1.354 + 1.355 + AutoFallibleTArray<WORD, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mGlyphs; 1.356 + AutoFallibleTArray<WORD, AVERAGE_ITEM_LENGTH + 1> mClusters; 1.357 + AutoFallibleTArray<SCRIPT_VISATTR, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mAttr; 1.358 + 1.359 + AutoFallibleTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets; 1.360 + AutoFallibleTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances; 1.361 + 1.362 +#undef AVERAGE_ITEM_LENGTH 1.363 + 1.364 + int mMaxGlyphs; 1.365 + int mNumGlyphs; 1.366 + uint32_t mIVS; 1.367 + 1.368 + bool mFontSelected; 1.369 +}; 1.370 + 1.371 +class Uniscribe 1.372 +{ 1.373 +public: 1.374 + Uniscribe(const char16_t *aString, 1.375 + gfxShapedText *aShapedText, 1.376 + uint32_t aOffset, uint32_t aLength): 1.377 + mString(aString), mShapedText(aShapedText), 1.378 + mOffset(aOffset), mLength(aLength) 1.379 + { 1.380 + } 1.381 + 1.382 + void Init() { 1.383 + memset(&mControl, 0, sizeof(SCRIPT_CONTROL)); 1.384 + memset(&mState, 0, sizeof(SCRIPT_STATE)); 1.385 + // Lock the direction. Don't allow the itemizer to change directions 1.386 + // based on character type. 1.387 + mState.uBidiLevel = mShapedText->IsRightToLeft() ? 1 : 0; 1.388 + mState.fOverrideDirection = true; 1.389 + } 1.390 + 1.391 +public: 1.392 + int Itemize() { 1.393 + HRESULT rv; 1.394 + 1.395 + int maxItems = 5; 1.396 + 1.397 + Init(); 1.398 + 1.399 + // Allocate space for one more item than expected, to handle a rare 1.400 + // overflow in ScriptItemize (pre XP SP2). See bug 366643. 1.401 + if (!mItems.SetLength(maxItems + 1)) { 1.402 + return 0; 1.403 + } 1.404 + while ((rv = ScriptItemize(mString, mLength, 1.405 + maxItems, &mControl, &mState, 1.406 + mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) { 1.407 + maxItems *= 2; 1.408 + if (!mItems.SetLength(maxItems + 1)) { 1.409 + return 0; 1.410 + } 1.411 + Init(); 1.412 + } 1.413 + 1.414 + return mNumItems; 1.415 + } 1.416 + 1.417 + SCRIPT_ITEM *ScriptItem(uint32_t i) { 1.418 + NS_ASSERTION(i <= (uint32_t)mNumItems, "Trying to get out of bounds item"); 1.419 + return &mItems[i]; 1.420 + } 1.421 + 1.422 +private: 1.423 + char16ptr_t mString; 1.424 + gfxShapedText *mShapedText; 1.425 + uint32_t mOffset; 1.426 + uint32_t mLength; 1.427 + 1.428 + SCRIPT_CONTROL mControl; 1.429 + SCRIPT_STATE mState; 1.430 + FallibleTArray<SCRIPT_ITEM> mItems; 1.431 + int mNumItems; 1.432 +}; 1.433 + 1.434 + 1.435 +bool 1.436 +gfxUniscribeShaper::ShapeText(gfxContext *aContext, 1.437 + const char16_t *aText, 1.438 + uint32_t aOffset, 1.439 + uint32_t aLength, 1.440 + int32_t aScript, 1.441 + gfxShapedText *aShapedText) 1.442 +{ 1.443 + DCFromContext aDC(aContext); 1.444 + 1.445 + bool result = true; 1.446 + HRESULT rv; 1.447 + 1.448 + Uniscribe us(aText, aShapedText, aOffset, aLength); 1.449 + 1.450 + /* itemize the string */ 1.451 + int numItems = us.Itemize(); 1.452 + 1.453 + uint32_t length = aLength; 1.454 + SaveDC(aDC); 1.455 + uint32_t ivs = 0; 1.456 + for (int i = 0; i < numItems; ++i) { 1.457 + int iCharPos = us.ScriptItem(i)->iCharPos; 1.458 + int iCharPosNext = us.ScriptItem(i+1)->iCharPos; 1.459 + 1.460 + if (ivs) { 1.461 + iCharPos += 2; 1.462 + if (iCharPos >= iCharPosNext) { 1.463 + ivs = 0; 1.464 + continue; 1.465 + } 1.466 + } 1.467 + 1.468 + if (i+1 < numItems && iCharPosNext <= length - 2 1.469 + && aText[iCharPosNext] == H_SURROGATE(kUnicodeVS17) 1.470 + && uint32_t(aText[iCharPosNext + 1]) - L_SURROGATE(kUnicodeVS17) 1.471 + <= L_SURROGATE(kUnicodeVS256) - L_SURROGATE(kUnicodeVS17)) { 1.472 + 1.473 + ivs = SURROGATE_TO_UCS4(aText[iCharPosNext], 1.474 + aText[iCharPosNext + 1]); 1.475 + } else { 1.476 + ivs = 0; 1.477 + } 1.478 + 1.479 + UniscribeItem item(aContext, aDC, this, 1.480 + aText + iCharPos, 1.481 + iCharPosNext - iCharPos, 1.482 + us.ScriptItem(i), ivs); 1.483 + if (!item.AllocateBuffers()) { 1.484 + result = false; 1.485 + break; 1.486 + } 1.487 + 1.488 + if (!item.ShapingEnabled()) { 1.489 + item.EnableShaping(); 1.490 + } 1.491 + 1.492 + rv = item.Shape(); 1.493 + if (FAILED(rv)) { 1.494 + // we know we have the glyphs to display this font already 1.495 + // so Uniscribe just doesn't know how to shape the script. 1.496 + // Render the glyphs without shaping. 1.497 + item.DisableShaping(); 1.498 + rv = item.Shape(); 1.499 + } 1.500 +#ifdef DEBUG 1.501 + if (FAILED(rv)) { 1.502 + NS_WARNING("Uniscribe failed to shape with font"); 1.503 + } 1.504 +#endif 1.505 + 1.506 + if (SUCCEEDED(rv)) { 1.507 + rv = item.Place(); 1.508 +#ifdef DEBUG 1.509 + if (FAILED(rv)) { 1.510 + // crap fonts may fail when placing (e.g. funky free fonts) 1.511 + NS_WARNING("Uniscribe failed to place with font"); 1.512 + } 1.513 +#endif 1.514 + } 1.515 + 1.516 + if (FAILED(rv)) { 1.517 + // Uniscribe doesn't like this font for some reason. 1.518 + // Returning FALSE will make the gfxGDIFont retry with the 1.519 + // "dumb" GDI shaper, unless useUniscribeOnly was set. 1.520 + result = false; 1.521 + break; 1.522 + } 1.523 + 1.524 + item.SaveGlyphs(aShapedText, aOffset); 1.525 + } 1.526 + 1.527 + RestoreDC(aDC, -1); 1.528 + 1.529 + return result; 1.530 +}