Bug 835381 - Update libnestegg to 38c83d9d4c0c5c84373aa285bd30094a12d6b6f6. r=kinetik
[gecko.git] / gfx / thebes / gfxUniscribeShaper.cpp
blob65d066823e880c1e86e92b4c33be2370366971dd
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "prtypes.h"
7 #include "gfxTypes.h"
9 #include "gfxContext.h"
10 #include "gfxUniscribeShaper.h"
11 #include "gfxWindowsPlatform.h"
13 #include "gfxFontTest.h"
15 #include "cairo.h"
16 #include "cairo-win32.h"
18 #include <windows.h>
20 #include "nsTArray.h"
22 #include "prinit.h"
24 /**********************************************************************
26 * class gfxUniscribeShaper
28 **********************************************************************/
30 #define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16)
32 class UniscribeItem
34 public:
35 UniscribeItem(gfxContext *aContext, HDC aDC,
36 gfxUniscribeShaper *aShaper,
37 const PRUnichar *aString, uint32_t aLength,
38 SCRIPT_ITEM *aItem, uint32_t aIVS) :
39 mContext(aContext), mDC(aDC),
40 mShaper(aShaper),
41 mItemString(aString), mItemLength(aLength),
42 mAlternativeString(nullptr), mScriptItem(aItem),
43 mScript(aItem->a.eScript),
44 mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)),
45 mFontSelected(false), mIVS(aIVS)
47 // See bug 394751 for details.
48 NS_ASSERTION(mMaxGlyphs < 65535,
49 "UniscribeItem is too big, ScriptShape() will fail!");
52 ~UniscribeItem() {
53 free(mAlternativeString);
56 bool AllocateBuffers() {
57 return (mGlyphs.SetLength(mMaxGlyphs) &&
58 mClusters.SetLength(mItemLength + 1) &&
59 mAttr.SetLength(mMaxGlyphs));
62 /* possible return values:
63 * S_OK - things succeeded
64 * GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping()
67 HRESULT Shape() {
68 HRESULT rv;
69 HDC shapeDC = nullptr;
71 const PRUnichar *str = mAlternativeString ? mAlternativeString : mItemString;
73 mScriptItem->a.fLogicalOrder = true;
74 SCRIPT_ANALYSIS sa = mScriptItem->a;
76 while (true) {
78 rv = ScriptShape(shapeDC, mShaper->ScriptCache(),
79 str, mItemLength,
80 mMaxGlyphs, &sa,
81 mGlyphs.Elements(), mClusters.Elements(),
82 mAttr.Elements(), &mNumGlyphs);
84 if (rv == E_OUTOFMEMORY) {
85 mMaxGlyphs *= 2;
86 if (!mGlyphs.SetLength(mMaxGlyphs) ||
87 !mAttr.SetLength(mMaxGlyphs)) {
88 return E_OUTOFMEMORY;
90 continue;
93 // Uniscribe can't do shaping with some fonts, so it sets the
94 // fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate
95 // this. This occurs with CFF fonts loaded with
96 // AddFontMemResourceEx but it's not clear what the other cases
97 // are. We return an error so our caller can try fallback shaping.
98 // see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx
100 if (sa.fNoGlyphIndex) {
101 return GDI_ERROR;
104 if (rv == E_PENDING) {
105 if (shapeDC == mDC) {
106 // we already tried this once, something failed, give up
107 return E_PENDING;
110 SelectFont();
112 shapeDC = mDC;
113 continue;
116 // http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx:
117 // Uniscribe will return this if "the font corresponding to the
118 // DC does not support the script required by the run...".
119 // In this case, we'll set the script code to SCRIPT_UNDEFINED
120 // and try again, so that we'll at least get glyphs even though
121 // they won't necessarily have proper shaping.
122 // (We probably shouldn't have selected this font at all,
123 // but it's too late to fix that here.)
124 if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
125 sa.eScript = SCRIPT_UNDEFINED;
126 NS_WARNING("Uniscribe says font does not support script needed");
127 continue;
130 // Prior to Windows 7, Uniscribe didn't support Ideographic Variation
131 // Selectors. Replace the UVS glyph manually.
132 if (mIVS) {
133 uint32_t lastChar = str[mItemLength - 1];
134 if (NS_IS_LOW_SURROGATE(lastChar)
135 && NS_IS_HIGH_SURROGATE(str[mItemLength - 2])) {
136 lastChar = SURROGATE_TO_UCS4(str[mItemLength - 2], lastChar);
138 uint16_t glyphId = mShaper->GetFont()->GetUVSGlyph(lastChar, mIVS);
139 if (glyphId) {
140 mGlyphs[mNumGlyphs - 1] = glyphId;
144 return rv;
148 bool ShapingEnabled() {
149 return (mScriptItem->a.eScript != SCRIPT_UNDEFINED);
151 void DisableShaping() {
152 mScriptItem->a.eScript = SCRIPT_UNDEFINED;
153 // Note: If we disable the shaping by using SCRIPT_UNDEFINED and
154 // the string has the surrogate pair, ScriptShape API is
155 // *sometimes* crashed. Therefore, we should replace the surrogate
156 // pair to U+FFFD. See bug 341500.
157 GenerateAlternativeString();
159 void EnableShaping() {
160 mScriptItem->a.eScript = mScript;
161 if (mAlternativeString) {
162 free(mAlternativeString);
163 mAlternativeString = nullptr;
167 bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) {
168 return (mGlyphs[aGlyphIndex] == aSFP->wgDefault);
172 HRESULT Place() {
173 HRESULT rv;
174 HDC placeDC = nullptr;
176 if (!mOffsets.SetLength(mNumGlyphs) ||
177 !mAdvances.SetLength(mNumGlyphs)) {
178 return E_OUTOFMEMORY;
181 SCRIPT_ANALYSIS sa = mScriptItem->a;
183 while (true) {
184 rv = ScriptPlace(placeDC, mShaper->ScriptCache(),
185 mGlyphs.Elements(), mNumGlyphs,
186 mAttr.Elements(), &sa,
187 mAdvances.Elements(), mOffsets.Elements(), NULL);
189 if (rv == E_PENDING) {
190 SelectFont();
191 placeDC = mDC;
192 continue;
195 if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
196 sa.eScript = SCRIPT_UNDEFINED;
197 continue;
200 break;
203 return rv;
206 void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) {
207 HRESULT rv;
209 memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES));
210 sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES);
211 rv = ScriptGetFontProperties(NULL, mShaper->ScriptCache(),
212 sfp);
213 if (rv == E_PENDING) {
214 SelectFont();
215 rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(),
216 sfp);
220 void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) {
221 uint32_t offsetInRun = mScriptItem->iCharPos;
223 // XXX We should store this in the item and only fetch it once
224 SCRIPT_FONTPROPERTIES sfp;
225 ScriptFontProperties(&sfp);
227 uint32_t offset = 0;
228 nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
229 gfxShapedText::CompressedGlyph g;
230 gfxShapedText::CompressedGlyph *charGlyphs =
231 aShapedText->GetCharacterGlyphs();
232 const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
233 while (offset < mItemLength) {
234 uint32_t runOffset = aOffset + offsetInRun + offset;
235 bool atClusterStart = charGlyphs[runOffset].IsClusterStart();
236 if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
237 gfxShapedText::CompressedGlyph &g = charGlyphs[runOffset];
238 NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
239 g.SetComplex(atClusterStart, false, 0);
240 } else {
241 // Count glyphs for this character
242 uint32_t k = mClusters[offset];
243 uint32_t glyphCount = mNumGlyphs - k;
244 uint32_t nextClusterOffset;
245 bool missing = IsGlyphMissing(&sfp, k);
246 for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) {
247 if (mClusters[nextClusterOffset] > k) {
248 glyphCount = mClusters[nextClusterOffset] - k;
249 break;
252 uint32_t j;
253 for (j = 1; j < glyphCount; ++j) {
254 if (IsGlyphMissing(&sfp, k + j)) {
255 missing = true;
258 int32_t advance = mAdvances[k]*appUnitsPerDevUnit;
259 WORD glyph = mGlyphs[k];
260 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]),
261 "invalid character detected");
262 if (missing) {
263 if (NS_IS_HIGH_SURROGATE(mItemString[offset]) &&
264 offset + 1 < mItemLength &&
265 NS_IS_LOW_SURROGATE(mItemString[offset + 1])) {
266 aShapedText->SetMissingGlyph(runOffset,
267 SURROGATE_TO_UCS4(mItemString[offset],
268 mItemString[offset + 1]),
269 mShaper->GetFont());
270 } else {
271 aShapedText->SetMissingGlyph(runOffset, mItemString[offset],
272 mShaper->GetFont());
274 } else if (glyphCount == 1 && advance >= 0 &&
275 mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
276 gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) &&
277 gfxShapedText::CompressedGlyph::IsSimpleGlyphID(glyph) &&
278 atClusterStart)
280 charGlyphs[runOffset].SetSimpleGlyph(advance, glyph);
281 } else {
282 if (detailedGlyphs.Length() < glyphCount) {
283 if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length()))
284 return;
286 uint32_t i;
287 for (i = 0; i < glyphCount; ++i) {
288 gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
289 details->mGlyphID = mGlyphs[k + i];
290 details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit;
291 details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit *
292 aShapedText->GetDirection();
293 details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit;
295 aShapedText->SetGlyphs(runOffset,
296 g.SetComplex(atClusterStart, true,
297 glyphCount),
298 detailedGlyphs.Elements());
301 ++offset;
305 void SelectFont() {
306 if (mFontSelected)
307 return;
309 cairo_t *cr = mContext->GetCairo();
311 cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace());
312 cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize());
313 cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont();
314 cairo_win32_scaled_font_select_font(scaledFont, mDC);
316 mFontSelected = true;
319 private:
321 void GenerateAlternativeString() {
322 if (mAlternativeString)
323 free(mAlternativeString);
324 mAlternativeString = (PRUnichar *)malloc(mItemLength * sizeof(PRUnichar));
325 if (!mAlternativeString)
326 return;
327 memcpy((void *)mAlternativeString, (const void *)mItemString,
328 mItemLength * sizeof(PRUnichar));
329 for (uint32_t i = 0; i < mItemLength; i++) {
330 if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i]))
331 mAlternativeString[i] = PRUnichar(0xFFFD);
335 private:
336 nsRefPtr<gfxContext> mContext;
337 HDC mDC;
338 gfxUniscribeShaper *mShaper;
340 SCRIPT_ITEM *mScriptItem;
341 WORD mScript;
343 public:
344 // these point to the full string/length of the item
345 const PRUnichar *mItemString;
346 const uint32_t mItemLength;
348 private:
349 PRUnichar *mAlternativeString;
351 #define AVERAGE_ITEM_LENGTH 40
353 nsAutoTArray<WORD, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mGlyphs;
354 nsAutoTArray<WORD, AVERAGE_ITEM_LENGTH + 1> mClusters;
355 nsAutoTArray<SCRIPT_VISATTR, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mAttr;
357 nsAutoTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets;
358 nsAutoTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances;
360 #undef AVERAGE_ITEM_LENGTH
362 int mMaxGlyphs;
363 int mNumGlyphs;
364 uint32_t mIVS;
366 bool mFontSelected;
369 class Uniscribe
371 public:
372 Uniscribe(const PRUnichar *aString,
373 gfxShapedText *aShapedText,
374 uint32_t aOffset, uint32_t aLength):
375 mString(aString), mShapedText(aShapedText),
376 mOffset(aOffset), mLength(aLength)
380 void Init() {
381 memset(&mControl, 0, sizeof(SCRIPT_CONTROL));
382 memset(&mState, 0, sizeof(SCRIPT_STATE));
383 // Lock the direction. Don't allow the itemizer to change directions
384 // based on character type.
385 mState.uBidiLevel = mShapedText->IsRightToLeft() ? 1 : 0;
386 mState.fOverrideDirection = true;
389 public:
390 int Itemize() {
391 HRESULT rv;
393 int maxItems = 5;
395 Init();
397 // Allocate space for one more item than expected, to handle a rare
398 // overflow in ScriptItemize (pre XP SP2). See bug 366643.
399 if (!mItems.SetLength(maxItems + 1)) {
400 return 0;
402 while ((rv = ScriptItemize(mString, mLength,
403 maxItems, &mControl, &mState,
404 mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) {
405 maxItems *= 2;
406 if (!mItems.SetLength(maxItems + 1)) {
407 return 0;
409 Init();
412 return mNumItems;
415 SCRIPT_ITEM *ScriptItem(uint32_t i) {
416 NS_ASSERTION(i <= (uint32_t)mNumItems, "Trying to get out of bounds item");
417 return &mItems[i];
420 private:
421 const PRUnichar *mString;
422 gfxShapedText *mShapedText;
423 uint32_t mOffset;
424 uint32_t mLength;
426 SCRIPT_CONTROL mControl;
427 SCRIPT_STATE mState;
428 nsTArray<SCRIPT_ITEM> mItems;
429 int mNumItems;
433 bool
434 gfxUniscribeShaper::ShapeText(gfxContext *aContext,
435 const PRUnichar *aText,
436 uint32_t aOffset,
437 uint32_t aLength,
438 int32_t aScript,
439 gfxShapedText *aShapedText)
441 DCFromContext aDC(aContext);
443 bool result = true;
444 HRESULT rv;
446 Uniscribe us(aText, aShapedText, aOffset, aLength);
448 /* itemize the string */
449 int numItems = us.Itemize();
451 uint32_t length = aLength;
452 SaveDC(aDC);
453 uint32_t ivs = 0;
454 for (int i = 0; i < numItems; ++i) {
455 int iCharPos = us.ScriptItem(i)->iCharPos;
456 int iCharPosNext = us.ScriptItem(i+1)->iCharPos;
458 if (ivs) {
459 iCharPos += 2;
460 if (iCharPos >= iCharPosNext) {
461 ivs = 0;
462 continue;
466 if (i+1 < numItems && iCharPosNext <= length - 2
467 && aText[iCharPosNext] == H_SURROGATE(kUnicodeVS17)
468 && uint32_t(aText[iCharPosNext + 1]) - L_SURROGATE(kUnicodeVS17)
469 <= L_SURROGATE(kUnicodeVS256) - L_SURROGATE(kUnicodeVS17)) {
471 ivs = SURROGATE_TO_UCS4(aText[iCharPosNext],
472 aText[iCharPosNext + 1]);
473 } else {
474 ivs = 0;
477 UniscribeItem item(aContext, aDC, this,
478 aText + iCharPos,
479 iCharPosNext - iCharPos,
480 us.ScriptItem(i), ivs);
481 if (!item.AllocateBuffers()) {
482 result = false;
483 break;
486 if (!item.ShapingEnabled()) {
487 item.EnableShaping();
490 rv = item.Shape();
491 if (FAILED(rv)) {
492 // we know we have the glyphs to display this font already
493 // so Uniscribe just doesn't know how to shape the script.
494 // Render the glyphs without shaping.
495 item.DisableShaping();
496 rv = item.Shape();
498 #ifdef DEBUG
499 if (FAILED(rv)) {
500 NS_WARNING("Uniscribe failed to shape with font");
502 #endif
504 if (SUCCEEDED(rv)) {
505 rv = item.Place();
506 #ifdef DEBUG
507 if (FAILED(rv)) {
508 // crap fonts may fail when placing (e.g. funky free fonts)
509 NS_WARNING("Uniscribe failed to place with font");
511 #endif
514 if (FAILED(rv)) {
515 // Uniscribe doesn't like this font for some reason.
516 // Returning FALSE will make the gfxGDIFont retry with the
517 // "dumb" GDI shaper, unless useUniscribeOnly was set.
518 result = false;
519 break;
522 item.SaveGlyphs(aShapedText, aOffset);
525 RestoreDC(aDC, -1);
527 return result;