/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Foundation code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Stuart Parmenter * Masayuki Nakano * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef DEBUG_smontagu #define DEBUG_pavlov #endif //#define FORCE_UNISCRIBE 1 #define FORCE_PR_LOG #include "prtypes.h" #include "gfxTypes.h" #include "gfxContext.h" #include "gfxWindowsFonts.h" #include "gfxWindowsSurface.h" #include "gfxWindowsPlatform.h" #ifdef MOZ_ENABLE_GLITZ #include "gfxGlitzSurface.h" #endif #include "gfxFontTest.h" #include "cairo.h" #include "cairo-win32.h" #include #include "nsUnicodeRange.h" #include "nsUnicharUtils.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsServiceManagerUtils.h" #include "nsCRT.h" #include #include "prlog.h" static PRLogModuleInfo *gFontLog = PR_NewLogModule("winfonts"); #define ROUND(x) floor((x) + 0.5) inline HDC GetDCFromSurface(gfxASurface *aSurface) { if (aSurface->GetType() != gfxASurface::SurfaceTypeWin32) { NS_ERROR("GetDCFromSurface: Context target is not win32!"); return nsnull; } return static_cast(aSurface)->GetDC(); } /********************************************************************** * * class gfxWindowsFont * **********************************************************************/ gfxWindowsFont::gfxWindowsFont(const nsAString& aName, const gfxFontStyle *aFontStyle) : gfxFont(aName, aFontStyle), mFont(nsnull), mAdjustedSize(0), mScriptCache(nsnull), mFontFace(nsnull), mScaledFont(nsnull), mMetrics(nsnull) { // XXX we should work to get this passed in rather than having to find it again. mFontEntry = gfxWindowsPlatform::GetPlatform()->FindFontEntry(aName); NS_ASSERTION(mFontEntry, "Unable to find font entry for font. Something is whack."); mFont = MakeHFONT(); // create the HFONT, compute metrics, etc NS_ASSERTION(mFont, "Failed to make HFONT"); } gfxWindowsFont::~gfxWindowsFont() { if (mFontFace) cairo_font_face_destroy(mFontFace); if (mScaledFont) cairo_scaled_font_destroy(mScaledFont); if (mFont) DeleteObject(mFont); ScriptFreeCache(&mScriptCache); delete mMetrics; } const gfxFont::Metrics& gfxWindowsFont::GetMetrics() { if (!mMetrics) ComputeMetrics(); return *mMetrics; } cairo_font_face_t * gfxWindowsFont::CairoFontFace() { if (!mFontFace) mFontFace = cairo_win32_font_face_create_for_logfontw_hfont(&mLogFont, mFont); NS_ASSERTION(mFontFace, "Failed to make font face"); return mFontFace; } cairo_scaled_font_t * gfxWindowsFont::CairoScaledFont() { if (!mScaledFont) { cairo_matrix_t sizeMatrix; cairo_matrix_t identityMatrix; cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize); cairo_matrix_init_identity(&identityMatrix); cairo_font_options_t *fontOptions = cairo_font_options_create(); mScaledFont = cairo_scaled_font_create(CairoFontFace(), &sizeMatrix, &identityMatrix, fontOptions); cairo_font_options_destroy(fontOptions); } NS_ASSERTION(mScaledFont, "Failed to make scaled font"); return mScaledFont; } HFONT gfxWindowsFont::MakeHFONT() { if (mFont) return mFont; PRInt8 baseWeight, weightDistance; GetStyle()->ComputeWeightAndOffset(&baseWeight, &weightDistance); HDC dc = nsnull; PRUint32 chosenWeight = 0; PRUint8 direction = (weightDistance >= 0) ? 1 : -1; for (PRUint8 i = baseWeight, k = 0; i < 10 && i >= 1; i+=direction) { if (mFontEntry->mWeightTable.HasWeight(i)) { k++; chosenWeight = i * 100; } else if (mFontEntry->mWeightTable.TriedWeight(i)) { continue; } else { const PRUint32 tryWeight = i * 100; if (!dc) dc = GetDC((HWND)nsnull); FillLogFont(GetStyle()->size, tryWeight); mFont = CreateFontIndirectW(&mLogFont); HGDIOBJ oldFont = SelectObject(dc, mFont); TEXTMETRIC metrics; GetTextMetrics(dc, &metrics); PRBool hasWeight = (metrics.tmWeight == tryWeight); mFontEntry->mWeightTable.SetWeight(i, hasWeight); if (hasWeight) { chosenWeight = i * 100; k++; } SelectObject(dc, oldFont); if (k <= abs(weightDistance)) { DeleteObject(mFont); mFont = nsnull; } } if (k > abs(weightDistance)) { chosenWeight = i * 100; break; } } if (chosenWeight == 0) chosenWeight = baseWeight * 100; mAdjustedSize = GetStyle()->size; if (GetStyle()->sizeAdjust > 0) { if (!mFont) { FillLogFont(mAdjustedSize, chosenWeight); mFont = CreateFontIndirectW(&mLogFont); } Metrics *oldMetrics = mMetrics; ComputeMetrics(); gfxFloat aspect = mMetrics->xHeight / mMetrics->emHeight; mAdjustedSize = GetStyle()->GetAdjustedSize(aspect); if (mMetrics != oldMetrics) { delete mMetrics; mMetrics = oldMetrics; } DeleteObject(mFont); mFont = nsnull; } if (!mFont) { FillLogFont(mAdjustedSize, chosenWeight); mFont = CreateFontIndirectW(&mLogFont); } if (dc) ReleaseDC((HWND)nsnull, dc); return mFont; } void gfxWindowsFont::ComputeMetrics() { if (!mMetrics) mMetrics = new gfxFont::Metrics; else NS_WARNING("Calling ComputeMetrics multiple times"); HDC dc = GetDC((HWND)nsnull); HGDIOBJ oldFont = SelectObject(dc, mFont); // Get font metrics OUTLINETEXTMETRIC oMetrics; TEXTMETRIC& metrics = oMetrics.otmTextMetrics; if (0 < GetOutlineTextMetrics(dc, sizeof(oMetrics), &oMetrics)) { mMetrics->superscriptOffset = (double)oMetrics.otmptSuperscriptOffset.y; mMetrics->subscriptOffset = (double)oMetrics.otmptSubscriptOffset.y; mMetrics->strikeoutSize = PR_MAX(1, (double)oMetrics.otmsStrikeoutSize); mMetrics->strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition; mMetrics->underlineSize = PR_MAX(1, (double)oMetrics.otmsUnderscoreSize); mMetrics->underlineOffset = (double)oMetrics.otmsUnderscorePosition; const MAT2 kIdentityMatrix = { {0, 1}, {0, 0}, {0, 0}, {0, 1} }; GLYPHMETRICS gm; DWORD len = GetGlyphOutlineW(dc, PRUnichar('x'), GGO_METRICS, &gm, 0, nsnull, &kIdentityMatrix); if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) { // 56% of ascent, best guess for true type mMetrics->xHeight = ROUND((double)metrics.tmAscent * 0.56); } else { mMetrics->xHeight = gm.gmptGlyphOrigin.y; } // The MS (P)Gothic and MS (P)Mincho are not having suitable values // in them super script offset. If the values are not suitable, // we should use x-height instead of them. // See https://bugzilla.mozilla.org/show_bug.cgi?id=353632 if (mMetrics->superscriptOffset == 0 || mMetrics->superscriptOffset >= metrics.tmAscent) { mMetrics->superscriptOffset = mMetrics->xHeight; } // And also checking the case of sub script offset. // The old gfx has checked this too. if (mMetrics->subscriptOffset == 0 || mMetrics->subscriptOffset >= metrics.tmAscent) { mMetrics->subscriptOffset = mMetrics->xHeight; } } else { // Make a best-effort guess at extended metrics // this is based on general typographic guidelines GetTextMetrics(dc, &metrics); mMetrics->xHeight = ROUND((float)metrics.tmAscent * 0.56f); // 56% of ascent, best guess for non-true type mMetrics->superscriptOffset = mMetrics->xHeight; mMetrics->subscriptOffset = mMetrics->xHeight; mMetrics->strikeoutSize = 1; mMetrics->strikeoutOffset = ROUND(mMetrics->xHeight / 2.0f); // 50% of xHeight mMetrics->underlineSize = 1; mMetrics->underlineOffset = -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent } mMetrics->internalLeading = metrics.tmInternalLeading; mMetrics->externalLeading = metrics.tmExternalLeading; mMetrics->emHeight = (metrics.tmHeight - metrics.tmInternalLeading); mMetrics->emAscent = (metrics.tmAscent - metrics.tmInternalLeading); mMetrics->emDescent = metrics.tmDescent; mMetrics->maxHeight = metrics.tmHeight; mMetrics->maxAscent = metrics.tmAscent; mMetrics->maxDescent = metrics.tmDescent; mMetrics->maxAdvance = metrics.tmMaxCharWidth; mMetrics->aveCharWidth = PR_MAX(1, metrics.tmAveCharWidth); // Cache the width of a single space. SIZE size; GetTextExtentPoint32(dc, " ", 1, &size); mMetrics->spaceWidth = ROUND(size.cx); mSpaceGlyph = 0; if (metrics.tmPitchAndFamily & TMPF_TRUETYPE) { WORD glyph; DWORD ret = GetGlyphIndicesA(dc, " ", 1, &glyph, GGI_MARK_NONEXISTING_GLYPHS); if (ret != GDI_ERROR && glyph != 0xFFFF) { mSpaceGlyph = glyph; } } SelectObject(dc, oldFont); ReleaseDC((HWND)nsnull, dc); } void gfxWindowsFont::FillLogFont(gfxFloat aSize, PRInt16 aWeight) { #define CLIP_TURNOFF_FONTASSOCIATION 0x40 mLogFont.lfHeight = (LONG)-ROUND(aSize); if (mLogFont.lfHeight == 0) mLogFont.lfHeight = -1; // Fill in logFont structure mLogFont.lfWidth = 0; mLogFont.lfEscapement = 0; mLogFont.lfOrientation = 0; mLogFont.lfUnderline = FALSE; mLogFont.lfStrikeOut = FALSE; mLogFont.lfCharSet = DEFAULT_CHARSET; #ifndef WINCE mLogFont.lfOutPrecision = OUT_TT_PRECIS; #else mLogFont.lfOutPrecision = OUT_DEFAULT_PRECIS; #endif mLogFont.lfClipPrecision = CLIP_TURNOFF_FONTASSOCIATION; mLogFont.lfQuality = DEFAULT_QUALITY; mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; mLogFont.lfItalic = (GetStyle()->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)) ? TRUE : FALSE; mLogFont.lfWeight = aWeight; int len = PR_MIN(mName.Length(), LF_FACESIZE - 1); memcpy(mLogFont.lfFaceName, nsPromiseFlatString(mName).get(), len * 2); mLogFont.lfFaceName[len] = '\0'; } nsString gfxWindowsFont::GetUniqueName() { nsString uniqueName; // start with the family name uniqueName.Assign(mName); // append the weight code if (mLogFont.lfWeight != 400) { uniqueName.AppendLiteral(":"); uniqueName.AppendInt((PRInt32)mLogFont.lfWeight); } // append italic? if (mLogFont.lfItalic) uniqueName.AppendLiteral(":Italic"); if (mLogFont.lfUnderline) uniqueName.AppendLiteral(":Underline"); if (mLogFont.lfStrikeOut) uniqueName.AppendLiteral(":StrikeOut"); return uniqueName; } void gfxWindowsFont::Draw(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, gfxContext *aContext, PRBool aDrawToPath, gfxPoint *aBaselineOrigin, Spacing *aSpacing) { // XXX stuart may want us to do something faster here gfxFont::Draw(aTextRun, aStart, aEnd, aContext, aDrawToPath, aBaselineOrigin, aSpacing); } void gfxWindowsFont::SetupCairoFont(cairo_t *aCR) { cairo_set_scaled_font(aCR, CairoScaledFont()); } /********************************************************************** * * class gfxWindowsFontGroup * **********************************************************************/ /** * Look up the font in the gfxFont cache. If we don't find it, create one. * In either case, add a ref, append it to the aFonts array, and return it --- * except for OOM in which case we do nothing and return null. */ static already_AddRefed GetOrMakeFont(FontEntry *aFontEntry, const gfxFontStyle *aStyle) { nsRefPtr font = gfxFontCache::GetCache()->Lookup(aFontEntry->mName, aStyle); if (!font) { font = new gfxWindowsFont(aFontEntry->mName, aStyle); if (!font) return nsnull; gfxFontCache::GetCache()->AddNew(font); } gfxFont *f = nsnull; font.swap(f); return static_cast(f); } static PRBool AddFontEntryToArray(const nsAString& aName, const nsACString& aGenericName, void *closure) { if (!aName.IsEmpty()) { nsTArray > *list = static_cast >*>(closure); nsRefPtr fe = gfxWindowsPlatform::GetPlatform()->FindFontEntry(aName); if (list->IndexOf(fe) == list->NoIndex) list->AppendElement(fe); } return PR_TRUE; } gfxWindowsFontGroup::gfxWindowsFontGroup(const nsAString& aFamilies, const gfxFontStyle *aStyle) : gfxFontGroup(aFamilies, aStyle) { ForEachFont(AddFontEntryToArray, &mFontEntries); if (mFonts.Length() == 0) { // Should append default GUI font if there are no available fonts. HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT); LOGFONTW logFont; if (!hGDI || !::GetObjectW(hGDI, sizeof(logFont), &logFont)) { NS_ERROR("Failed to create font group"); return; } nsRefPtr fe = gfxWindowsPlatform::GetPlatform()->FindFontEntry(nsDependentString(logFont.lfFaceName)); mFontEntries.AppendElement(fe); } mFonts.AppendElements(mFontEntries.Length()); } gfxWindowsFontGroup::~gfxWindowsFontGroup() { } gfxWindowsFont * gfxWindowsFontGroup::GetFontAt(PRInt32 i) { if (!mFonts[i]) { nsRefPtr font = GetOrMakeFont(mFontEntries[i], &mStyle); mFonts[i] = font; } return static_cast(mFonts[i].get()); } gfxFontGroup * gfxWindowsFontGroup::Copy(const gfxFontStyle *aStyle) { return new gfxWindowsFontGroup(mFamilies, aStyle); } gfxTextRun * gfxWindowsFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { // XXX comment out the assertion for now since it fires too much // NS_ASSERTION(!(mFlags & TEXT_NEED_BOUNDING_BOX), // "Glyph extents not yet supported"); gfxTextRun *textRun = new gfxTextRun(aParams, aString, aLength, this, aFlags); if (!textRun) return nsnull; NS_ASSERTION(aParams->mContext, "MakeTextRun called without a gfxContext"); textRun->RecordSurrogates(aString); #ifdef FORCE_UNISCRIBE const PRBool isComplex = PR_TRUE; #else const PRBool isComplex = ScriptIsComplex(aString, aLength, SIC_COMPLEX) == S_OK || textRun->IsRightToLeft(); #endif if (isComplex) InitTextRunUniscribe(aParams->mContext, textRun, aString, aLength); else InitTextRunGDI(aParams->mContext, textRun, aString, aLength); return textRun; } gfxTextRun * gfxWindowsFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength, const Parameters *aParams, PRUint32 aFlags) { NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit"); gfxTextRun *textRun = new gfxTextRun(aParams, aString, aLength, this, aFlags); if (!textRun) return nsnull; NS_ASSERTION(aParams->mContext, "MakeTextRun called without a gfxContext"); #ifdef FORCE_UNISCRIBE const PRBool isComplex = PR_TRUE; #else const PRBool isComplex = textRun->IsRightToLeft(); #endif /* We can only call GDI "A" functions if this is a true 7bit ASCII string, because they interpret code points from 0x80-0xFF as if they were in the system code page. */ if (!isComplex && (aFlags & TEXT_IS_ASCII)) { InitTextRunGDI(aParams->mContext, textRun, reinterpret_cast(aString), aLength); } else { nsDependentCSubstring cString(reinterpret_cast(aString), reinterpret_cast(aString + aLength)); nsAutoString utf16; AppendASCIItoUTF16(cString, utf16); if (isComplex) { InitTextRunUniscribe(aParams->mContext, textRun, utf16.get(), aLength); } else { InitTextRunGDI(aParams->mContext, textRun, utf16.get(), aLength); } } return textRun; } /** * Set the font in the DC for aContext's surface. Return the DC if we're * successful. If it's not a win32 surface or something goes wrong or if * the font is not a Truetype font (hence GetGlyphIndices may be buggy) then * we're not successful and return 0. */ static HDC SetupContextFont(gfxContext *aContext, gfxWindowsFont *aFont) { nsRefPtr surf = aContext->CurrentSurface(); HDC dc = GetDCFromSurface(surf); if (!dc) return 0; HFONT hfont = aFont->GetHFONT(); if (!hfont) return 0; SelectObject(dc, hfont); /* GetGlyphIndices is buggy for bitmap and vector fonts, so send them to uniscribe */ TEXTMETRIC metrics; GetTextMetrics(dc, &metrics); if ((metrics.tmPitchAndFamily & (TMPF_TRUETYPE)) == 0) return 0; return dc; } static PRBool IsAnyGlyphMissing(WCHAR *aGlyphs, PRUint32 aLength) { PRUint32 i; for (i = 0; i < aLength; ++i) { if (aGlyphs[i] == 0xFFFF) return PR_TRUE; } return PR_FALSE; } static PRBool SetupTextRunFromGlyphs(gfxTextRun *aRun, WCHAR *aGlyphs, HDC aDC, gfxWindowsFont *aFont) { PRUint32 length = aRun->GetLength(); if (IsAnyGlyphMissing(aGlyphs, length)) return PR_FALSE; SIZE size; nsAutoTArray partialWidthArray; if (!partialWidthArray.AppendElements(length)) return PR_FALSE; BOOL success = GetTextExtentExPointI(aDC, (WORD*) aGlyphs, length, INT_MAX, NULL, partialWidthArray.Elements(), &size); if (!success) return PR_FALSE; aRun->AddGlyphRun(aFont, 0); gfxTextRun::CompressedGlyph g; PRUint32 i; PRInt32 lastWidth = 0; PRUint32 appUnitsPerDevPixel = aRun->GetAppUnitsPerDevUnit(); for (i = 0; i < length; ++i) { PRInt32 advancePixels = partialWidthArray[i] - lastWidth; lastWidth = partialWidthArray[i]; PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel; WCHAR glyph = aGlyphs[i]; NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aRun->GetChar(i)), "Invalid character detected!"); if (advanceAppUnits >= 0 && gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) && gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) { aRun->SetCharacterGlyph(i, g.SetSimpleGlyph(advanceAppUnits, glyph)); } else { gfxTextRun::DetailedGlyph details; details.mIsLastGlyph = PR_TRUE; details.mGlyphID = glyph; details.mAdvance = advanceAppUnits; details.mXOffset = 0; details.mYOffset = 0; aRun->SetDetailedGlyphs(i, &details, 1); } } return PR_TRUE; } void gfxWindowsFontGroup::InitTextRunGDI(gfxContext *aContext, gfxTextRun *aRun, const char *aString, PRUint32 aLength) { nsRefPtr font = GetFontAt(0); HDC dc = SetupContextFont(aContext, font); if (dc) { nsAutoTArray glyphArray; if (!glyphArray.AppendElements(aLength)) return; DWORD ret = GetGlyphIndicesA(dc, aString, aLength, (WORD*) glyphArray.Elements(), GGI_MARK_NONEXISTING_GLYPHS); if (ret != GDI_ERROR && SetupTextRunFromGlyphs(aRun, glyphArray.Elements(), dc, font)) return; } nsDependentCSubstring cString(aString, aString + aLength); nsAutoString utf16; AppendASCIItoUTF16(cString, utf16); InitTextRunUniscribe(aContext, aRun, utf16.get(), aLength); } void gfxWindowsFontGroup::InitTextRunGDI(gfxContext *aContext, gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength) { nsRefPtr font = GetFontAt(0); HDC dc = SetupContextFont(aContext, font); if (dc) { nsAutoTArray glyphArray; if (!glyphArray.AppendElements(aLength)) return; DWORD ret = GetGlyphIndicesW(dc, aString, aLength, (WORD*) glyphArray.Elements(), GGI_MARK_NONEXISTING_GLYPHS); if (ret != GDI_ERROR && SetupTextRunFromGlyphs(aRun, glyphArray.Elements(), dc, font)) return; } InitTextRunUniscribe(aContext, aRun, aString, aLength); } /******************* * Uniscribe *******************/ /* we map primary language id's to this to look up language codes */ struct ScriptPropertyEntry { const char *value; const char *langCode; }; static const struct ScriptPropertyEntry gScriptToText[] = { { nsnull, nsnull }, { "LANG_ARABIC", "ara" }, { "LANG_BULGARIAN", "bul" }, { "LANG_CATALAN", "cat" }, { "LANG_CHINESE", "zh-CN" }, //XXX right lang code? { "LANG_CZECH", "cze" }, // cze/ces { "LANG_DANISH", "dan" }, { "LANG_GERMAN", "ger" }, // ger/deu { "LANG_GREEK", "el" }, // gre/ell { "LANG_ENGLISH", "x-western" }, { "LANG_SPANISH", "spa" }, { "LANG_FINNISH", "fin" }, { "LANG_FRENCH", "fre" }, // fre/fra { "LANG_HEBREW", "he" }, // heb { "LANG_HUNGARIAN", "hun" }, { "LANG_ICELANDIC", "ice" }, // ice/isl { "LANG_ITALIAN", "ita" }, { "LANG_JAPANESE", "ja" }, // jpn { "LANG_KOREAN", "ko" }, // kor { "LANG_DUTCH", "dut" }, // dut/nld { "LANG_NORWEGIAN", "nor" }, { "LANG_POLISH", "pol" }, { "LANG_PORTUGUESE", "por" }, { nsnull, nsnull }, { "LANG_ROMANIAN", "rum" }, // rum/ron { "LANG_RUSSIAN", "rus" }, { "LANG_SERBIAN", "scc" }, // scc/srp { "LANG_SLOVAK", "slo" }, // slo/slk { "LANG_ALBANIAN", "alb" }, // alb/sqi { "LANG_SWEDISH", "swe" }, { "LANG_THAI", "th" }, // tha { "LANG_TURKISH", "tr" }, // tur { "LANG_URDU", "urd" }, { "LANG_INDONESIAN", "ind" }, { "LANG_UKRAINIAN", "ukr" }, { "LANG_BELARUSIAN", "bel" }, { "LANG_SLOVENIAN", "slv" }, { "LANG_ESTONIAN", "est" }, { "LANG_LATVIAN", "lav" }, { "LANG_LITHUANIAN", "lit" }, { nsnull, nsnull }, { "LANG_FARSI", "per" }, // per/fas { "LANG_VIETNAMESE", "vie" }, { "LANG_ARMENIAN", "x-armn" }, // arm/hye { "LANG_AZERI", "aze" }, { "LANG_BASQUE", "baq" }, // baq/eus { nsnull, nsnull }, { "LANG_MACEDONIAN", "mac" }, // mac/mkd { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { "LANG_AFRIKAANS", "afr" }, { "LANG_GEORGIAN", "x-geor" }, // geo { "LANG_FAEROESE", "fao" }, { "LANG_HINDI", "x-devanagari" }, // hin { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { "LANG_MALAY", "may" }, // may/msa { "LANG_KAZAK", "kaz" }, // listed as kazakh? { "LANG_KYRGYZ", "kis" }, { "LANG_SWAHILI", "swa" }, { nsnull, nsnull }, { "LANG_UZBEK", "uzb" }, { "LANG_TATAR", "tat" }, { "LANG_BENGALI", "x-beng" }, // ben { "LANG_PUNJABI", "x-guru" }, // pan -- XXX x-guru is for Gurmukhi which isn't just Punjabi { "LANG_GUJARATI", "x-gujr" }, // guj { "LANG_ORIYA", "ori" }, { "LANG_TAMIL", "x-tamil" }, // tam { "LANG_TELUGU", "tel" }, { "LANG_KANNADA", "kan" }, { "LANG_MALAYALAM", "x-mlym" }, // mal { "LANG_ASSAMESE", "asm" }, { "LANG_MARATHI", "mar" }, { "LANG_SANSKRIT", "san" }, { "LANG_MONGOLIAN", "mon" }, { "TIBETAN", "tib" }, // tib/bod { nsnull, nsnull }, { "KHMER", "x-khmr" }, // khm { "LAO", "lao" }, { "MYANMAR", "bur" }, // bur/mya { "LANG_GALICIAN", "glg" }, { "LANG_KONKANI", "kok" }, { "LANG_MANIPURI", "mni" }, { "LANG_SINDHI", "x-devanagari" }, // snd { "LANG_SYRIAC", "syr" }, { "SINHALESE", "sin" }, { "CHEROKEE", "chr" }, { "INUKTITUT", "x-cans" }, // iku { "ETHIOPIC", "x-ethi" }, // amh -- this is both Amharic and Tigrinya { nsnull, nsnull }, { "LANG_KASHMIRI", "x-devanagari" }, // kas { "LANG_NEPALI", "x-devanagari" }, // nep { nsnull, nsnull }, { nsnull, nsnull }, { nsnull, nsnull }, { "LANG_DIVEHI", "div" } }; static const char *sCJKLangGroup[] = { "ja", "ko", "zh-CN", "zh-HK", "zh-TW" }; #define COUNT_OF_CJK_LANG_GROUP 5 #define CJK_LANG_JA sCJKLangGroup[0] #define CJK_LANG_KO sCJKLangGroup[1] #define CJK_LANG_ZH_CN sCJKLangGroup[2] #define CJK_LANG_ZH_HK sCJKLangGroup[3] #define CJK_LANG_ZH_TW sCJKLangGroup[4] #define STATIC_STRING_LENGTH 100 /** * XXX We could use a bunch of nsAutoTArrays to avoid memory allocation * for non-huge strings. */ class UniscribeItem { public: UniscribeItem(gfxContext *aContext, HDC aDC, const PRUnichar *aString, PRUint32 aLength, SCRIPT_ITEM *aItem, gfxWindowsFontGroup *aGroup) : mContext(aContext), mDC(aDC), mRangeString(nsnull), mRangeLength(0), mItemString(aString), mItemLength(aLength), mAlternativeString(nsnull), mScriptItem(aItem), mScript(aItem->a.eScript), mGroup(aGroup), mGlyphs(nsnull), mClusters(nsnull), mAttr(nsnull), mNumGlyphs(0), mMaxGlyphs((int)(1.5 * aLength) + 16), mOffsets(nsnull), mAdvances(nsnull), mFontSelected(PR_FALSE) { mGlyphs = (WORD *)malloc(mMaxGlyphs * sizeof(WORD)); mClusters = (WORD *)malloc((mItemLength + 1) * sizeof(WORD)); mAttr = (SCRIPT_VISATTR *)malloc(mMaxGlyphs * sizeof(SCRIPT_VISATTR)); } ~UniscribeItem() { free(mGlyphs); free(mClusters); free(mAttr); free(mOffsets); free(mAdvances); free(mAlternativeString); } /* possible return values: * S_OK - things succeeded * GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping() */ HRESULT Shape() { HRESULT rv; HDC shapeDC = nsnull; const PRUnichar *str = mAlternativeString ? mAlternativeString : mRangeString; while (PR_TRUE) { mScriptItem->a.fLogicalOrder = PR_TRUE; rv = ScriptShape(shapeDC, mCurrentFont->ScriptCache(), str, mRangeLength, mMaxGlyphs, &mScriptItem->a, mGlyphs, mClusters, mAttr, &mNumGlyphs); if (rv == E_OUTOFMEMORY) { mMaxGlyphs *= 2; mGlyphs = (WORD *)realloc(mGlyphs, mMaxGlyphs * sizeof(WORD)); mAttr = (SCRIPT_VISATTR *)realloc(mAttr, mMaxGlyphs * sizeof(SCRIPT_VISATTR)); continue; } if (rv == E_PENDING) { SelectFont(); shapeDC = mDC; continue; } #ifdef DEBUG_pavlov if (rv == USP_E_SCRIPT_NOT_IN_FONT) { ScriptGetCMap(mDC, mCurrentFont->ScriptCache(), str, mRangeString, 0, mGlyphs); PRUnichar foo[LF_FACESIZE+1]; GetTextFaceW(mDC, LF_FACESIZE, foo); printf("bah\n"); } else if (FAILED(rv)) printf("%d\n", rv); #endif return rv; } } PRBool ShapingEnabled() { return (mScriptItem->a.eScript != SCRIPT_UNDEFINED); } void DisableShaping() { mScriptItem->a.eScript = SCRIPT_UNDEFINED; // Note: If we disable the shaping by using SCRIPT_UNDEFINED and // the string has the surrogate pair, ScriptShape API is // *sometimes* crashed. Therefore, we should replace the surrogate // pair to U+FFFD. See bug 341500. GenerateAlternativeString(); } void EnableShaping() { mScriptItem->a.eScript = mScript; if (mAlternativeString) { free(mAlternativeString); mAlternativeString = nsnull; } } PRBool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, PRUint32 aGlyphIndex) { if (mGlyphs[aGlyphIndex] == aSFP->wgDefault) return PR_TRUE; return PR_FALSE; } HRESULT Place() { HRESULT rv; mOffsets = (GOFFSET *)malloc(mNumGlyphs * sizeof(GOFFSET)); mAdvances = (int *)malloc(mNumGlyphs * sizeof(int)); HDC placeDC = nsnull; while (PR_TRUE) { rv = ScriptPlace(placeDC, mCurrentFont->ScriptCache(), mGlyphs, mNumGlyphs, mAttr, &mScriptItem->a, mAdvances, mOffsets, NULL); if (rv == E_PENDING) { SelectFont(); placeDC = mDC; continue; } break; } return rv; } const SCRIPT_PROPERTIES *ScriptProperties() { /* we can use this to figure out in some cases the language of the item */ static const SCRIPT_PROPERTIES **gScriptProperties; static int gMaxScript = -1; if (gMaxScript == -1) { ScriptGetProperties(&gScriptProperties, &gMaxScript); } return gScriptProperties[mScript]; } void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) { HRESULT rv; memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES)); sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES); rv = ScriptGetFontProperties(NULL, mCurrentFont->ScriptCache(), sfp); if (rv == E_PENDING) { SelectFont(); rv = ScriptGetFontProperties(mDC, mCurrentFont->ScriptCache(), sfp); } } void SetupClusterBoundaries(gfxTextRun *aRun, PRUint32 aOffsetInRun) { if (aRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) return; nsAutoTArray logAttr; if (!logAttr.AppendElements(mRangeLength)) return; HRESULT rv = ScriptBreak(mRangeString, mRangeLength, &mScriptItem->a, logAttr.Elements()); if (FAILED(rv)) return; gfxTextRun::CompressedGlyph g; // The first character is never inside a cluster. Windows might tell us // that it should be, but we have no before-character to cluster // it with so we just can't cluster it. So skip it here. for (PRUint32 i = 1; i < mRangeLength; ++i) { if (!logAttr[i].fCharStop) { aRun->SetCharacterGlyph(i + aOffsetInRun, g.SetClusterContinuation()); } } } void SaveGlyphs(gfxTextRun *aRun) { PRUint32 offsetInRun = mScriptItem->iCharPos + (mRangeString - mItemString); SetupClusterBoundaries(aRun, offsetInRun); aRun->AddGlyphRun(GetCurrentFont(), offsetInRun); // XXX We should store this in the item and only fetch it once SCRIPT_FONTPROPERTIES sfp; ScriptFontProperties(&sfp); PRUint32 offset = 0; nsAutoTArray detailedGlyphs; gfxTextRun::CompressedGlyph g; const PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit(); while (offset < mRangeLength) { PRUint32 runOffset = offsetInRun + offset; if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) { if (!aRun->GetCharacterGlyphs()[runOffset].IsClusterContinuation()) { // No glyphs for character 'index', it must be a ligature continuation aRun->SetCharacterGlyph(runOffset, g.SetLigatureContinuation()); } } else { // Count glyphs for this character PRUint32 k = mClusters[offset]; PRUint32 glyphCount = mNumGlyphs - k; PRUint32 nextClusterOffset; PRBool missing = IsGlyphMissing(&sfp, k); for (nextClusterOffset = offset + 1; nextClusterOffset < mRangeLength; ++nextClusterOffset) { if (mClusters[nextClusterOffset] > k) { glyphCount = mClusters[nextClusterOffset] - k; break; } } PRUint32 j; for (j = 1; j < glyphCount; ++j) { if (IsGlyphMissing(&sfp, k + j)) { missing = PR_TRUE; } } PRInt32 advance = mAdvances[k]*appUnitsPerDevUnit; WORD glyph = mGlyphs[k]; NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mRangeString[offset]), "invalid character detected"); if (missing) { aRun->SetMissingGlyph(runOffset, mRangeString[offset]); } else if (glyphCount == 1 && advance >= 0 && mOffsets[k].dv == 0 && mOffsets[k].du == 0 && gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) { aRun->SetCharacterGlyph(runOffset, g.SetSimpleGlyph(advance, glyph)); } else { if (detailedGlyphs.Length() < glyphCount) { if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length())) return; } PRUint32 i; for (i = 0; i < glyphCount; ++i) { gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i]; details->mIsLastGlyph = i == glyphCount - 1; details->mGlyphID = mGlyphs[k + i]; details->mAdvance = mAdvances[k + i]*appUnitsPerDevUnit; details->mXOffset = float(mOffsets[k + i].du)*appUnitsPerDevUnit*aRun->GetDirection(); details->mYOffset = float(mOffsets[k + i].dv)*appUnitsPerDevUnit; } aRun->SetDetailedGlyphs(runOffset, detailedGlyphs.Elements(), glyphCount); } } ++offset; } } void SetCurrentFont(gfxWindowsFont *aFont) { if (mCurrentFont != aFont) { mCurrentFont = aFont; cairo_win32_scaled_font_done_font(mCurrentFont->CairoScaledFont()); mFontSelected = PR_FALSE; } } gfxWindowsFont *GetCurrentFont() { return mCurrentFont; } void SelectFont() { if (mFontSelected) return; cairo_t *cr = mContext->GetCairo(); cairo_set_font_face(cr, mCurrentFont->CairoFontFace()); cairo_set_font_size(cr, mCurrentFont->GetAdjustedSize()); cairo_win32_scaled_font_select_font(mCurrentFont->CairoScaledFont(), mDC); mFontSelected = PR_TRUE; } struct TextRange { TextRange(PRUint32 aStart, PRUint32 aEnd) : start(aStart), end(aEnd) { } PRUint32 Length() const { return end - start; } nsRefPtr font; PRUint32 start, end; }; void SetRange(PRUint32 i) { nsRefPtr fe; if (mRanges[i].font) fe = mRanges[i].font; else fe = mGroup->GetFontEntryAt(0); nsRefPtr font = GetOrMakeFont(fe, mGroup->GetStyle()); SetCurrentFont(font); mRangeString = mItemString + mRanges[i].start; mRangeLength = mRanges[i].Length(); } static inline FontEntry *WhichFontSupportsChar(const nsTArray >& fonts, PRUint32 ch) { for (PRUint32 i = 0; i < fonts.Length(); i++) { nsRefPtr fe = fonts[i]; if (fe->mCharacterMap.test(ch)) return fe; } return nsnull; } static inline bool IsJoiner(PRUint32 ch) { return (ch == 0x200C || ch == 0x200D || ch == 0x2060); } inline FontEntry *FindFontForChar(PRUint32 ch, PRUint32 prevCh, PRUint32 nextCh, FontEntry *aFont) { nsRefPtr selectedFont; // if this character or the next one is a joiner use the // same font as the previous range if we can if (IsJoiner(ch) || IsJoiner(prevCh) || IsJoiner(nextCh)) { if (aFont && aFont->mCharacterMap.test(ch)) return aFont; } // check the list of fonts selectedFont = WhichFontSupportsChar(mGroup->GetFontList(), ch); // otherwise search prefs if (!selectedFont) { /* first check with the script properties to see what they think */ const SCRIPT_PROPERTIES *sp = ScriptProperties(); if (!sp->fAmbiguousCharSet) { WORD primaryId = PRIMARYLANGID(sp->langid); const char *langGroup = gScriptToText[primaryId].langCode; if (langGroup) { PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Trying to find fonts for: %s (%s)", langGroup, gScriptToText[primaryId].value)); nsTArray > fonts; this->GetPrefFonts(langGroup, fonts); selectedFont = WhichFontSupportsChar(fonts, ch); } } else if (ch <= 0xFFFF) { PRUint32 unicodeRange = FindCharUnicodeRange(ch); /* special case CJK */ if (unicodeRange == kRangeSetCJK) { if (PR_LOG_TEST(gFontLog, PR_LOG_DEBUG)) PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Trying to find fonts for: CJK")); nsTArray > fonts; this->GetCJKPrefFonts(fonts); selectedFont = WhichFontSupportsChar(fonts, ch); } else { const char *langGroup = LangGroupFromUnicodeRange(unicodeRange); if (langGroup) { PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Trying to find fonts for: %s", langGroup)); nsTArray > fonts; this->GetPrefFonts(langGroup, fonts); selectedFont = WhichFontSupportsChar(fonts, ch); } } } } // before searching for something else check the font used for the previous character if (!selectedFont && aFont && aFont->mCharacterMap.test(ch)) selectedFont = aFont; // otherwise look for other stuff if (!selectedFont) { PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Looking for best match")); nsRefPtr refFont = mGroup->GetFontAt(0); gfxWindowsPlatform *platform = gfxWindowsPlatform::GetPlatform(); PRUnichar str[2]; PRUint32 len; if (ch > 0xFFFF) { str[0] = H_SURROGATE(ch); str[1] = L_SURROGATE(ch); len = 2; } else { str[0] = ch; len = 1; } selectedFont = platform->FindFontForString(str, len, refFont); } return selectedFont; } PRUint32 ComputeRanges() { if (mItemLength == 0) return 0; /* disable font fallback when using symbol fonts */ if (mGroup->GetFontEntryAt(0)->mSymbolFont) { TextRange r(0,mItemLength); mRanges.AppendElement(r); return 1; } PR_LOG(gFontLog, PR_LOG_DEBUG, ("Computing ranges for string: (len = %d)", mItemLength)); PRUint32 prevCh = 0; for (PRUint32 i = 0; i < mItemLength; i++) { const PRUint32 origI = i; // save off incase we increase for surrogate PRUint32 ch = mItemString[i]; if ((i+1 < mItemLength) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(mItemString[i+1])) { i++; ch = SURROGATE_TO_UCS4(ch, mItemString[i]); } PR_LOG(gFontLog, PR_LOG_DEBUG, (" 0x%04x - ", ch)); PRUint32 nextCh = 0; if (i+1 < mItemLength) { nextCh = mItemString[i+1]; if ((i+2 < mItemLength) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(mItemString[i+2])) nextCh = SURROGATE_TO_UCS4(nextCh, mItemString[i+2]); } nsRefPtr fe = FindFontForChar(ch, prevCh, nextCh, (mRanges.Length() == 0) ? nsnull : mRanges[mRanges.Length() - 1].font); prevCh = ch; if (mRanges.Length() == 0) { TextRange r(0,1); r.font = fe; mRanges.AppendElement(r); } else { TextRange& prevRange = mRanges[mRanges.Length() - 1]; if (prevRange.font != fe) { // close out the previous range prevRange.end = origI; TextRange r(i, i+1); r.font = fe; mRanges.AppendElement(r); } } if (PR_LOG_TEST(gFontLog, PR_LOG_DEBUG)) { if (fe) PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Using %s", NS_LossyConvertUTF16toASCII(fe->mName).get())); else PR_LOG(gFontLog, PR_LOG_DEBUG, (" - Unable to find font")); } } mRanges[mRanges.Length()-1].end = mItemLength; PRUint32 nranges = mRanges.Length(); PR_LOG(gFontLog, PR_LOG_DEBUG, (" Found %d ranges", nranges)); return nranges; } private: static PRInt32 GetCJKLangGroupIndex(const char *aLangGroup) { PRInt32 i; for (i = 0; i < COUNT_OF_CJK_LANG_GROUP; i++) { if (!PL_strcasecmp(aLangGroup, sCJKLangGroup[i])) return i; } return -1; } void GetPrefFonts(const char *aLangGroup, nsTArray >& array) { NS_ASSERTION(aLangGroup, "aLangGroup is null"); gfxPlatform *platform = gfxPlatform::GetPlatform(); nsString fonts; platform->GetPrefFonts(aLangGroup, fonts); if (fonts.IsEmpty()) return; gfxFontGroup::ForEachFont(fonts, nsDependentCString(aLangGroup), AddFontEntryToArray, &array); } void GetCJKPrefFonts(nsTArray >& array) { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (!prefs) return; nsCOMPtr prefBranch; prefs->GetBranch(0, getter_AddRefs(prefBranch)); if (!prefBranch) return; // Add by the order of accept languages. nsXPIDLCString list; nsresult rv = prefBranch->GetCharPref("intl.accept_languages", getter_Copies(list)); if (NS_SUCCEEDED(rv) && !list.IsEmpty()) { const char kComma = ','; const char *p, *p_end; list.BeginReading(p); list.EndReading(p_end); while (p < p_end) { while (nsCRT::IsAsciiSpace(*p)) { if (++p == p_end) break; } if (p == p_end) break; const char *start = p; while (++p != p_end && *p != kComma) /* nothing */ ; nsCAutoString lang(Substring(start, p)); lang.CompressWhitespace(PR_FALSE, PR_TRUE); PRInt32 index = GetCJKLangGroupIndex(lang.get()); if (index >= 0) GetPrefFonts(sCJKLangGroup[index], array); p++; } } // Add the system locale switch (::GetACP()) { case 932: GetPrefFonts(CJK_LANG_JA, array); break; case 936: GetPrefFonts(CJK_LANG_ZH_CN, array); break; case 949: GetPrefFonts(CJK_LANG_KO, array); break; // XXX Don't we need to append CJK_LANG_ZH_HK if the codepage is 950? case 950: GetPrefFonts(CJK_LANG_ZH_TW, array); break; } // last resort... GetPrefFonts(CJK_LANG_JA, array); GetPrefFonts(CJK_LANG_KO, array); GetPrefFonts(CJK_LANG_ZH_CN, array); GetPrefFonts(CJK_LANG_ZH_HK, array); GetPrefFonts(CJK_LANG_ZH_TW, array); } void GenerateAlternativeString() { if (mAlternativeString) free(mAlternativeString); mAlternativeString = (PRUnichar *)malloc(mRangeLength * sizeof(PRUnichar)); memcpy((void *)mAlternativeString, (const void *)mRangeString, mRangeLength * sizeof(PRUnichar)); for (PRUint32 i = 0; i < mRangeLength; i++) { if (NS_IS_HIGH_SURROGATE(mRangeString[i]) || NS_IS_LOW_SURROGATE(mRangeString[i])) mAlternativeString[i] = PRUnichar(0xFFFD); } } private: nsRefPtr mContext; HDC mDC; SCRIPT_ITEM *mScriptItem; WORD mScript; // these point to the current range const PRUnichar *mRangeString; PRUint32 mRangeLength; // these point to the full string/length of the item const PRUnichar *mItemString; const PRUint32 mItemLength; PRUnichar *mAlternativeString; gfxWindowsFontGroup *mGroup; WORD *mGlyphs; WORD *mClusters; SCRIPT_VISATTR *mAttr; int mMaxGlyphs; int mNumGlyphs; GOFFSET *mOffsets; int *mAdvances; nsTArray< nsRefPtr > mFonts; nsRefPtr mCurrentFont; PRPackedBool mFontSelected; nsTArray mRanges; }; class Uniscribe { public: Uniscribe(gfxContext *aContext, HDC aDC, const PRUnichar *aString, PRUint32 aLength, PRBool aIsRTL) : mContext(aContext), mDC(aDC), mString(aString), mLength(aLength), mIsRTL(aIsRTL), mItems(nsnull) { } ~Uniscribe() { if (mItems) free(mItems); } void Init() { memset(&mControl, 0, sizeof(SCRIPT_CONTROL)); memset(&mState, 0, sizeof(SCRIPT_STATE)); // Lock the direction. Don't allow the itemizer to change directions // based on character type. mState.uBidiLevel = mIsRTL; mState.fOverrideDirection = PR_TRUE; } int Itemize() { HRESULT rv; int maxItems = 5; Init(); // Allocate space for one more item than expected, to handle a rare // overflow in ScriptItemize (pre XP SP2). See bug 366643. mItems = (SCRIPT_ITEM *)malloc((maxItems + 1) * sizeof(SCRIPT_ITEM)); while ((rv = ScriptItemize(mString, mLength, maxItems, &mControl, &mState, mItems, &mNumItems)) == E_OUTOFMEMORY) { maxItems *= 2; mItems = (SCRIPT_ITEM *)realloc(mItems, (maxItems + 1) * sizeof(SCRIPT_ITEM)); Init(); } return mNumItems; } PRUint32 ItemsLength() { return mNumItems; } // XXX Why do we dynamically allocate this? We could just fill in an object // on the stack. UniscribeItem *GetItem(PRUint32 i, gfxWindowsFontGroup *aGroup) { NS_ASSERTION(i < (PRUint32)mNumItems, "Trying to get out of bounds item"); UniscribeItem *item = new UniscribeItem(mContext, mDC, mString + mItems[i].iCharPos, mItems[i+1].iCharPos - mItems[i].iCharPos, &mItems[i], aGroup); return item; } private: nsRefPtr mContext; HDC mDC; const PRUnichar *mString; const PRUint32 mLength; const PRBool mIsRTL; SCRIPT_CONTROL mControl; SCRIPT_STATE mState; SCRIPT_ITEM *mItems; int mNumItems; }; void gfxWindowsFontGroup::InitTextRunUniscribe(gfxContext *aContext, gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength) { nsRefPtr surf = aContext->CurrentSurface(); HDC aDC = GetDCFromSurface(surf); NS_ASSERTION(aDC, "No DC"); const PRBool isRTL = aRun->IsRightToLeft(); HRESULT rv; Uniscribe us(aContext, aDC, aString, aLength, isRTL); /* itemize the string */ int numItems = us.Itemize(); for (int i = 0; i < numItems; ++i) { SaveDC(aDC); UniscribeItem *item = us.GetItem(i, this); PRUint32 nranges = item->ComputeRanges(); for (PRUint32 j = 0; j < nranges; ++j) { item->SetRange(j); if (!item->ShapingEnabled()) item->EnableShaping(); while (FAILED(item->Shape())) { PR_LOG(gFontLog, PR_LOG_DEBUG, ("shaping failed")); // we know we have the glyphs to display this font already // so Uniscribe just doesn't know how to shape the script. // Render the glyphs without shaping. item->DisableShaping(); } NS_ASSERTION(SUCCEEDED(rv), "Failed to shape -- we should never hit this"); rv = item->Place(); NS_ASSERTION(SUCCEEDED(rv), "Failed to place -- this is pretty bad."); item->SaveGlyphs(aRun); } delete item; RestoreDC(aDC, -1); } }