michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxTypes.h" michael@0: michael@0: #include "gfxContext.h" michael@0: #include "gfxUniscribeShaper.h" michael@0: #include "gfxWindowsPlatform.h" michael@0: michael@0: #include "gfxFontTest.h" michael@0: michael@0: #include "cairo.h" michael@0: #include "cairo-win32.h" michael@0: michael@0: #include michael@0: michael@0: #include "nsTArray.h" michael@0: michael@0: #include "prinit.h" michael@0: michael@0: /********************************************************************** michael@0: * michael@0: * class gfxUniscribeShaper michael@0: * michael@0: **********************************************************************/ michael@0: michael@0: #define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16) michael@0: michael@0: class UniscribeItem michael@0: { michael@0: public: michael@0: UniscribeItem(gfxContext *aContext, HDC aDC, michael@0: gfxUniscribeShaper *aShaper, michael@0: const char16_t *aString, uint32_t aLength, michael@0: SCRIPT_ITEM *aItem, uint32_t aIVS) : michael@0: mContext(aContext), mDC(aDC), michael@0: mShaper(aShaper), michael@0: mItemString(aString), mItemLength(aLength), michael@0: mAlternativeString(nullptr), mScriptItem(aItem), michael@0: mScript(aItem->a.eScript), michael@0: mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)), michael@0: mFontSelected(false), mIVS(aIVS) michael@0: { michael@0: // See bug 394751 for details. michael@0: NS_ASSERTION(mMaxGlyphs < 65535, michael@0: "UniscribeItem is too big, ScriptShape() will fail!"); michael@0: } michael@0: michael@0: ~UniscribeItem() { michael@0: free(mAlternativeString); michael@0: } michael@0: michael@0: bool AllocateBuffers() { michael@0: return (mGlyphs.SetLength(mMaxGlyphs) && michael@0: mClusters.SetLength(mItemLength + 1) && michael@0: mAttr.SetLength(mMaxGlyphs)); michael@0: } michael@0: michael@0: /* possible return values: michael@0: * S_OK - things succeeded michael@0: * GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping() michael@0: */ michael@0: michael@0: HRESULT Shape() { michael@0: HRESULT rv; michael@0: HDC shapeDC = nullptr; michael@0: michael@0: char16ptr_t str = mAlternativeString ? mAlternativeString : mItemString; michael@0: michael@0: mScriptItem->a.fLogicalOrder = true; michael@0: SCRIPT_ANALYSIS sa = mScriptItem->a; michael@0: michael@0: while (true) { michael@0: michael@0: rv = ScriptShape(shapeDC, mShaper->ScriptCache(), michael@0: str, mItemLength, michael@0: mMaxGlyphs, &sa, michael@0: mGlyphs.Elements(), mClusters.Elements(), michael@0: mAttr.Elements(), &mNumGlyphs); michael@0: michael@0: if (rv == E_OUTOFMEMORY) { michael@0: mMaxGlyphs *= 2; michael@0: if (!mGlyphs.SetLength(mMaxGlyphs) || michael@0: !mAttr.SetLength(mMaxGlyphs)) { michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: // Uniscribe can't do shaping with some fonts, so it sets the michael@0: // fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate michael@0: // this. This occurs with CFF fonts loaded with michael@0: // AddFontMemResourceEx but it's not clear what the other cases michael@0: // are. We return an error so our caller can try fallback shaping. michael@0: // see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx michael@0: michael@0: if (sa.fNoGlyphIndex) { michael@0: return GDI_ERROR; michael@0: } michael@0: michael@0: if (rv == E_PENDING) { michael@0: if (shapeDC == mDC) { michael@0: // we already tried this once, something failed, give up michael@0: return E_PENDING; michael@0: } michael@0: michael@0: SelectFont(); michael@0: michael@0: shapeDC = mDC; michael@0: continue; michael@0: } michael@0: michael@0: // http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx: michael@0: // Uniscribe will return this if "the font corresponding to the michael@0: // DC does not support the script required by the run...". michael@0: // In this case, we'll set the script code to SCRIPT_UNDEFINED michael@0: // and try again, so that we'll at least get glyphs even though michael@0: // they won't necessarily have proper shaping. michael@0: // (We probably shouldn't have selected this font at all, michael@0: // but it's too late to fix that here.) michael@0: if (rv == USP_E_SCRIPT_NOT_IN_FONT) { michael@0: sa.eScript = SCRIPT_UNDEFINED; michael@0: NS_WARNING("Uniscribe says font does not support script needed"); michael@0: continue; michael@0: } michael@0: michael@0: // Prior to Windows 7, Uniscribe didn't support Ideographic Variation michael@0: // Selectors. Replace the UVS glyph manually. michael@0: if (mIVS) { michael@0: uint32_t lastChar = str[mItemLength - 1]; michael@0: if (NS_IS_LOW_SURROGATE(lastChar) michael@0: && NS_IS_HIGH_SURROGATE(str[mItemLength - 2])) { michael@0: lastChar = SURROGATE_TO_UCS4(str[mItemLength - 2], lastChar); michael@0: } michael@0: uint16_t glyphId = mShaper->GetFont()->GetUVSGlyph(lastChar, mIVS); michael@0: if (glyphId) { michael@0: mGlyphs[mNumGlyphs - 1] = glyphId; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: bool ShapingEnabled() { michael@0: return (mScriptItem->a.eScript != SCRIPT_UNDEFINED); michael@0: } michael@0: void DisableShaping() { michael@0: mScriptItem->a.eScript = SCRIPT_UNDEFINED; michael@0: // Note: If we disable the shaping by using SCRIPT_UNDEFINED and michael@0: // the string has the surrogate pair, ScriptShape API is michael@0: // *sometimes* crashed. Therefore, we should replace the surrogate michael@0: // pair to U+FFFD. See bug 341500. michael@0: GenerateAlternativeString(); michael@0: } michael@0: void EnableShaping() { michael@0: mScriptItem->a.eScript = mScript; michael@0: if (mAlternativeString) { michael@0: free(mAlternativeString); michael@0: mAlternativeString = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) { michael@0: return (mGlyphs[aGlyphIndex] == aSFP->wgDefault); michael@0: } michael@0: michael@0: michael@0: HRESULT Place() { michael@0: HRESULT rv; michael@0: HDC placeDC = nullptr; michael@0: michael@0: if (!mOffsets.SetLength(mNumGlyphs) || michael@0: !mAdvances.SetLength(mNumGlyphs)) { michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: SCRIPT_ANALYSIS sa = mScriptItem->a; michael@0: michael@0: while (true) { michael@0: rv = ScriptPlace(placeDC, mShaper->ScriptCache(), michael@0: mGlyphs.Elements(), mNumGlyphs, michael@0: mAttr.Elements(), &sa, michael@0: mAdvances.Elements(), mOffsets.Elements(), nullptr); michael@0: michael@0: if (rv == E_PENDING) { michael@0: SelectFont(); michael@0: placeDC = mDC; michael@0: continue; michael@0: } michael@0: michael@0: if (rv == USP_E_SCRIPT_NOT_IN_FONT) { michael@0: sa.eScript = SCRIPT_UNDEFINED; michael@0: continue; michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) { michael@0: HRESULT rv; michael@0: michael@0: memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES)); michael@0: sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES); michael@0: rv = ScriptGetFontProperties(nullptr, mShaper->ScriptCache(), michael@0: sfp); michael@0: if (rv == E_PENDING) { michael@0: SelectFont(); michael@0: rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(), michael@0: sfp); michael@0: } michael@0: } michael@0: michael@0: void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) { michael@0: uint32_t offsetInRun = mScriptItem->iCharPos; michael@0: michael@0: // XXX We should store this in the item and only fetch it once michael@0: SCRIPT_FONTPROPERTIES sfp; michael@0: ScriptFontProperties(&sfp); michael@0: michael@0: uint32_t offset = 0; michael@0: nsAutoTArray detailedGlyphs; michael@0: gfxShapedText::CompressedGlyph g; michael@0: gfxShapedText::CompressedGlyph *charGlyphs = michael@0: aShapedText->GetCharacterGlyphs(); michael@0: const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); michael@0: while (offset < mItemLength) { michael@0: uint32_t runOffset = aOffset + offsetInRun + offset; michael@0: bool atClusterStart = charGlyphs[runOffset].IsClusterStart(); michael@0: if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) { michael@0: gfxShapedText::CompressedGlyph &g = charGlyphs[runOffset]; michael@0: NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph"); michael@0: g.SetComplex(atClusterStart, false, 0); michael@0: } else { michael@0: // Count glyphs for this character michael@0: uint32_t k = mClusters[offset]; michael@0: uint32_t glyphCount = mNumGlyphs - k; michael@0: uint32_t nextClusterOffset; michael@0: bool missing = IsGlyphMissing(&sfp, k); michael@0: for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) { michael@0: if (mClusters[nextClusterOffset] > k) { michael@0: glyphCount = mClusters[nextClusterOffset] - k; michael@0: break; michael@0: } michael@0: } michael@0: uint32_t j; michael@0: for (j = 1; j < glyphCount; ++j) { michael@0: if (IsGlyphMissing(&sfp, k + j)) { michael@0: missing = true; michael@0: } michael@0: } michael@0: int32_t advance = mAdvances[k]*appUnitsPerDevUnit; michael@0: WORD glyph = mGlyphs[k]; michael@0: NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]), michael@0: "invalid character detected"); michael@0: if (missing) { michael@0: if (NS_IS_HIGH_SURROGATE(mItemString[offset]) && michael@0: offset + 1 < mItemLength && michael@0: NS_IS_LOW_SURROGATE(mItemString[offset + 1])) { michael@0: aShapedText->SetMissingGlyph(runOffset, michael@0: SURROGATE_TO_UCS4(mItemString[offset], michael@0: mItemString[offset + 1]), michael@0: mShaper->GetFont()); michael@0: } else { michael@0: aShapedText->SetMissingGlyph(runOffset, mItemString[offset], michael@0: mShaper->GetFont()); michael@0: } michael@0: } else if (glyphCount == 1 && advance >= 0 && michael@0: mOffsets[k].dv == 0 && mOffsets[k].du == 0 && michael@0: gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) && michael@0: gfxShapedText::CompressedGlyph::IsSimpleGlyphID(glyph) && michael@0: atClusterStart) michael@0: { michael@0: charGlyphs[runOffset].SetSimpleGlyph(advance, glyph); michael@0: } else { michael@0: if (detailedGlyphs.Length() < glyphCount) { michael@0: if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length())) michael@0: return; michael@0: } michael@0: uint32_t i; michael@0: for (i = 0; i < glyphCount; ++i) { michael@0: gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i]; michael@0: details->mGlyphID = mGlyphs[k + i]; michael@0: details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit; michael@0: details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit * michael@0: aShapedText->GetDirection(); michael@0: details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit; michael@0: } michael@0: aShapedText->SetGlyphs(runOffset, michael@0: g.SetComplex(atClusterStart, true, michael@0: glyphCount), michael@0: detailedGlyphs.Elements()); michael@0: } michael@0: } michael@0: ++offset; michael@0: } michael@0: } michael@0: michael@0: void SelectFont() { michael@0: if (mFontSelected) michael@0: return; michael@0: michael@0: cairo_t *cr = mContext->GetCairo(); michael@0: michael@0: cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace()); michael@0: cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize()); michael@0: cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont(); michael@0: cairo_win32_scaled_font_select_font(scaledFont, mDC); michael@0: michael@0: mFontSelected = true; michael@0: } michael@0: michael@0: private: michael@0: michael@0: void GenerateAlternativeString() { michael@0: if (mAlternativeString) michael@0: free(mAlternativeString); michael@0: mAlternativeString = (char16_t *)malloc(mItemLength * sizeof(char16_t)); michael@0: if (!mAlternativeString) michael@0: return; michael@0: memcpy((void *)mAlternativeString, (const void *)mItemString, michael@0: mItemLength * sizeof(char16_t)); michael@0: for (uint32_t i = 0; i < mItemLength; i++) { michael@0: if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i])) michael@0: mAlternativeString[i] = char16_t(0xFFFD); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mContext; michael@0: HDC mDC; michael@0: gfxUniscribeShaper *mShaper; michael@0: michael@0: SCRIPT_ITEM *mScriptItem; michael@0: WORD mScript; michael@0: michael@0: public: michael@0: // these point to the full string/length of the item michael@0: const char16_t *mItemString; michael@0: const uint32_t mItemLength; michael@0: michael@0: private: michael@0: char16_t *mAlternativeString; michael@0: michael@0: #define AVERAGE_ITEM_LENGTH 40 michael@0: michael@0: AutoFallibleTArray mGlyphs; michael@0: AutoFallibleTArray mClusters; michael@0: AutoFallibleTArray mAttr; michael@0: michael@0: AutoFallibleTArray mOffsets; michael@0: AutoFallibleTArray mAdvances; michael@0: michael@0: #undef AVERAGE_ITEM_LENGTH michael@0: michael@0: int mMaxGlyphs; michael@0: int mNumGlyphs; michael@0: uint32_t mIVS; michael@0: michael@0: bool mFontSelected; michael@0: }; michael@0: michael@0: class Uniscribe michael@0: { michael@0: public: michael@0: Uniscribe(const char16_t *aString, michael@0: gfxShapedText *aShapedText, michael@0: uint32_t aOffset, uint32_t aLength): michael@0: mString(aString), mShapedText(aShapedText), michael@0: mOffset(aOffset), mLength(aLength) michael@0: { michael@0: } michael@0: michael@0: void Init() { michael@0: memset(&mControl, 0, sizeof(SCRIPT_CONTROL)); michael@0: memset(&mState, 0, sizeof(SCRIPT_STATE)); michael@0: // Lock the direction. Don't allow the itemizer to change directions michael@0: // based on character type. michael@0: mState.uBidiLevel = mShapedText->IsRightToLeft() ? 1 : 0; michael@0: mState.fOverrideDirection = true; michael@0: } michael@0: michael@0: public: michael@0: int Itemize() { michael@0: HRESULT rv; michael@0: michael@0: int maxItems = 5; michael@0: michael@0: Init(); michael@0: michael@0: // Allocate space for one more item than expected, to handle a rare michael@0: // overflow in ScriptItemize (pre XP SP2). See bug 366643. michael@0: if (!mItems.SetLength(maxItems + 1)) { michael@0: return 0; michael@0: } michael@0: while ((rv = ScriptItemize(mString, mLength, michael@0: maxItems, &mControl, &mState, michael@0: mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) { michael@0: maxItems *= 2; michael@0: if (!mItems.SetLength(maxItems + 1)) { michael@0: return 0; michael@0: } michael@0: Init(); michael@0: } michael@0: michael@0: return mNumItems; michael@0: } michael@0: michael@0: SCRIPT_ITEM *ScriptItem(uint32_t i) { michael@0: NS_ASSERTION(i <= (uint32_t)mNumItems, "Trying to get out of bounds item"); michael@0: return &mItems[i]; michael@0: } michael@0: michael@0: private: michael@0: char16ptr_t mString; michael@0: gfxShapedText *mShapedText; michael@0: uint32_t mOffset; michael@0: uint32_t mLength; michael@0: michael@0: SCRIPT_CONTROL mControl; michael@0: SCRIPT_STATE mState; michael@0: FallibleTArray mItems; michael@0: int mNumItems; michael@0: }; michael@0: michael@0: michael@0: bool michael@0: gfxUniscribeShaper::ShapeText(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText) michael@0: { michael@0: DCFromContext aDC(aContext); michael@0: michael@0: bool result = true; michael@0: HRESULT rv; michael@0: michael@0: Uniscribe us(aText, aShapedText, aOffset, aLength); michael@0: michael@0: /* itemize the string */ michael@0: int numItems = us.Itemize(); michael@0: michael@0: uint32_t length = aLength; michael@0: SaveDC(aDC); michael@0: uint32_t ivs = 0; michael@0: for (int i = 0; i < numItems; ++i) { michael@0: int iCharPos = us.ScriptItem(i)->iCharPos; michael@0: int iCharPosNext = us.ScriptItem(i+1)->iCharPos; michael@0: michael@0: if (ivs) { michael@0: iCharPos += 2; michael@0: if (iCharPos >= iCharPosNext) { michael@0: ivs = 0; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if (i+1 < numItems && iCharPosNext <= length - 2 michael@0: && aText[iCharPosNext] == H_SURROGATE(kUnicodeVS17) michael@0: && uint32_t(aText[iCharPosNext + 1]) - L_SURROGATE(kUnicodeVS17) michael@0: <= L_SURROGATE(kUnicodeVS256) - L_SURROGATE(kUnicodeVS17)) { michael@0: michael@0: ivs = SURROGATE_TO_UCS4(aText[iCharPosNext], michael@0: aText[iCharPosNext + 1]); michael@0: } else { michael@0: ivs = 0; michael@0: } michael@0: michael@0: UniscribeItem item(aContext, aDC, this, michael@0: aText + iCharPos, michael@0: iCharPosNext - iCharPos, michael@0: us.ScriptItem(i), ivs); michael@0: if (!item.AllocateBuffers()) { michael@0: result = false; michael@0: break; michael@0: } michael@0: michael@0: if (!item.ShapingEnabled()) { michael@0: item.EnableShaping(); michael@0: } michael@0: michael@0: rv = item.Shape(); michael@0: if (FAILED(rv)) { michael@0: // we know we have the glyphs to display this font already michael@0: // so Uniscribe just doesn't know how to shape the script. michael@0: // Render the glyphs without shaping. michael@0: item.DisableShaping(); michael@0: rv = item.Shape(); michael@0: } michael@0: #ifdef DEBUG michael@0: if (FAILED(rv)) { michael@0: NS_WARNING("Uniscribe failed to shape with font"); michael@0: } michael@0: #endif michael@0: michael@0: if (SUCCEEDED(rv)) { michael@0: rv = item.Place(); michael@0: #ifdef DEBUG michael@0: if (FAILED(rv)) { michael@0: // crap fonts may fail when placing (e.g. funky free fonts) michael@0: NS_WARNING("Uniscribe failed to place with font"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: if (FAILED(rv)) { michael@0: // Uniscribe doesn't like this font for some reason. michael@0: // Returning FALSE will make the gfxGDIFont retry with the michael@0: // "dumb" GDI shaper, unless useUniscribeOnly was set. michael@0: result = false; michael@0: break; michael@0: } michael@0: michael@0: item.SaveGlyphs(aShapedText, aOffset); michael@0: } michael@0: michael@0: RestoreDC(aDC, -1); michael@0: michael@0: return result; michael@0: }