Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "gfxTypes.h"
8 #include "gfxContext.h"
9 #include "gfxUniscribeShaper.h"
10 #include "gfxWindowsPlatform.h"
12 #include "gfxFontTest.h"
14 #include "cairo.h"
15 #include "cairo-win32.h"
17 #include <windows.h>
19 #include "nsTArray.h"
21 #include "prinit.h"
23 /**********************************************************************
24 *
25 * class gfxUniscribeShaper
26 *
27 **********************************************************************/
29 #define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16)
31 class UniscribeItem
32 {
33 public:
34 UniscribeItem(gfxContext *aContext, HDC aDC,
35 gfxUniscribeShaper *aShaper,
36 const char16_t *aString, uint32_t aLength,
37 SCRIPT_ITEM *aItem, uint32_t aIVS) :
38 mContext(aContext), mDC(aDC),
39 mShaper(aShaper),
40 mItemString(aString), mItemLength(aLength),
41 mAlternativeString(nullptr), mScriptItem(aItem),
42 mScript(aItem->a.eScript),
43 mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)),
44 mFontSelected(false), mIVS(aIVS)
45 {
46 // See bug 394751 for details.
47 NS_ASSERTION(mMaxGlyphs < 65535,
48 "UniscribeItem is too big, ScriptShape() will fail!");
49 }
51 ~UniscribeItem() {
52 free(mAlternativeString);
53 }
55 bool AllocateBuffers() {
56 return (mGlyphs.SetLength(mMaxGlyphs) &&
57 mClusters.SetLength(mItemLength + 1) &&
58 mAttr.SetLength(mMaxGlyphs));
59 }
61 /* possible return values:
62 * S_OK - things succeeded
63 * GDI_ERROR - things failed to shape. Might want to try again after calling DisableShaping()
64 */
66 HRESULT Shape() {
67 HRESULT rv;
68 HDC shapeDC = nullptr;
70 char16ptr_t str = mAlternativeString ? mAlternativeString : mItemString;
72 mScriptItem->a.fLogicalOrder = true;
73 SCRIPT_ANALYSIS sa = mScriptItem->a;
75 while (true) {
77 rv = ScriptShape(shapeDC, mShaper->ScriptCache(),
78 str, mItemLength,
79 mMaxGlyphs, &sa,
80 mGlyphs.Elements(), mClusters.Elements(),
81 mAttr.Elements(), &mNumGlyphs);
83 if (rv == E_OUTOFMEMORY) {
84 mMaxGlyphs *= 2;
85 if (!mGlyphs.SetLength(mMaxGlyphs) ||
86 !mAttr.SetLength(mMaxGlyphs)) {
87 return E_OUTOFMEMORY;
88 }
89 continue;
90 }
92 // Uniscribe can't do shaping with some fonts, so it sets the
93 // fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate
94 // this. This occurs with CFF fonts loaded with
95 // AddFontMemResourceEx but it's not clear what the other cases
96 // are. We return an error so our caller can try fallback shaping.
97 // see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx
99 if (sa.fNoGlyphIndex) {
100 return GDI_ERROR;
101 }
103 if (rv == E_PENDING) {
104 if (shapeDC == mDC) {
105 // we already tried this once, something failed, give up
106 return E_PENDING;
107 }
109 SelectFont();
111 shapeDC = mDC;
112 continue;
113 }
115 // http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx:
116 // Uniscribe will return this if "the font corresponding to the
117 // DC does not support the script required by the run...".
118 // In this case, we'll set the script code to SCRIPT_UNDEFINED
119 // and try again, so that we'll at least get glyphs even though
120 // they won't necessarily have proper shaping.
121 // (We probably shouldn't have selected this font at all,
122 // but it's too late to fix that here.)
123 if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
124 sa.eScript = SCRIPT_UNDEFINED;
125 NS_WARNING("Uniscribe says font does not support script needed");
126 continue;
127 }
129 // Prior to Windows 7, Uniscribe didn't support Ideographic Variation
130 // Selectors. Replace the UVS glyph manually.
131 if (mIVS) {
132 uint32_t lastChar = str[mItemLength - 1];
133 if (NS_IS_LOW_SURROGATE(lastChar)
134 && NS_IS_HIGH_SURROGATE(str[mItemLength - 2])) {
135 lastChar = SURROGATE_TO_UCS4(str[mItemLength - 2], lastChar);
136 }
137 uint16_t glyphId = mShaper->GetFont()->GetUVSGlyph(lastChar, mIVS);
138 if (glyphId) {
139 mGlyphs[mNumGlyphs - 1] = glyphId;
140 }
141 }
143 return rv;
144 }
145 }
147 bool ShapingEnabled() {
148 return (mScriptItem->a.eScript != SCRIPT_UNDEFINED);
149 }
150 void DisableShaping() {
151 mScriptItem->a.eScript = SCRIPT_UNDEFINED;
152 // Note: If we disable the shaping by using SCRIPT_UNDEFINED and
153 // the string has the surrogate pair, ScriptShape API is
154 // *sometimes* crashed. Therefore, we should replace the surrogate
155 // pair to U+FFFD. See bug 341500.
156 GenerateAlternativeString();
157 }
158 void EnableShaping() {
159 mScriptItem->a.eScript = mScript;
160 if (mAlternativeString) {
161 free(mAlternativeString);
162 mAlternativeString = nullptr;
163 }
164 }
166 bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) {
167 return (mGlyphs[aGlyphIndex] == aSFP->wgDefault);
168 }
171 HRESULT Place() {
172 HRESULT rv;
173 HDC placeDC = nullptr;
175 if (!mOffsets.SetLength(mNumGlyphs) ||
176 !mAdvances.SetLength(mNumGlyphs)) {
177 return E_OUTOFMEMORY;
178 }
180 SCRIPT_ANALYSIS sa = mScriptItem->a;
182 while (true) {
183 rv = ScriptPlace(placeDC, mShaper->ScriptCache(),
184 mGlyphs.Elements(), mNumGlyphs,
185 mAttr.Elements(), &sa,
186 mAdvances.Elements(), mOffsets.Elements(), nullptr);
188 if (rv == E_PENDING) {
189 SelectFont();
190 placeDC = mDC;
191 continue;
192 }
194 if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
195 sa.eScript = SCRIPT_UNDEFINED;
196 continue;
197 }
199 break;
200 }
202 return rv;
203 }
205 void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) {
206 HRESULT rv;
208 memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES));
209 sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES);
210 rv = ScriptGetFontProperties(nullptr, mShaper->ScriptCache(),
211 sfp);
212 if (rv == E_PENDING) {
213 SelectFont();
214 rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(),
215 sfp);
216 }
217 }
219 void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) {
220 uint32_t offsetInRun = mScriptItem->iCharPos;
222 // XXX We should store this in the item and only fetch it once
223 SCRIPT_FONTPROPERTIES sfp;
224 ScriptFontProperties(&sfp);
226 uint32_t offset = 0;
227 nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
228 gfxShapedText::CompressedGlyph g;
229 gfxShapedText::CompressedGlyph *charGlyphs =
230 aShapedText->GetCharacterGlyphs();
231 const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
232 while (offset < mItemLength) {
233 uint32_t runOffset = aOffset + offsetInRun + offset;
234 bool atClusterStart = charGlyphs[runOffset].IsClusterStart();
235 if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
236 gfxShapedText::CompressedGlyph &g = charGlyphs[runOffset];
237 NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
238 g.SetComplex(atClusterStart, false, 0);
239 } else {
240 // Count glyphs for this character
241 uint32_t k = mClusters[offset];
242 uint32_t glyphCount = mNumGlyphs - k;
243 uint32_t nextClusterOffset;
244 bool missing = IsGlyphMissing(&sfp, k);
245 for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) {
246 if (mClusters[nextClusterOffset] > k) {
247 glyphCount = mClusters[nextClusterOffset] - k;
248 break;
249 }
250 }
251 uint32_t j;
252 for (j = 1; j < glyphCount; ++j) {
253 if (IsGlyphMissing(&sfp, k + j)) {
254 missing = true;
255 }
256 }
257 int32_t advance = mAdvances[k]*appUnitsPerDevUnit;
258 WORD glyph = mGlyphs[k];
259 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]),
260 "invalid character detected");
261 if (missing) {
262 if (NS_IS_HIGH_SURROGATE(mItemString[offset]) &&
263 offset + 1 < mItemLength &&
264 NS_IS_LOW_SURROGATE(mItemString[offset + 1])) {
265 aShapedText->SetMissingGlyph(runOffset,
266 SURROGATE_TO_UCS4(mItemString[offset],
267 mItemString[offset + 1]),
268 mShaper->GetFont());
269 } else {
270 aShapedText->SetMissingGlyph(runOffset, mItemString[offset],
271 mShaper->GetFont());
272 }
273 } else if (glyphCount == 1 && advance >= 0 &&
274 mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
275 gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) &&
276 gfxShapedText::CompressedGlyph::IsSimpleGlyphID(glyph) &&
277 atClusterStart)
278 {
279 charGlyphs[runOffset].SetSimpleGlyph(advance, glyph);
280 } else {
281 if (detailedGlyphs.Length() < glyphCount) {
282 if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length()))
283 return;
284 }
285 uint32_t i;
286 for (i = 0; i < glyphCount; ++i) {
287 gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
288 details->mGlyphID = mGlyphs[k + i];
289 details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit;
290 details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit *
291 aShapedText->GetDirection();
292 details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit;
293 }
294 aShapedText->SetGlyphs(runOffset,
295 g.SetComplex(atClusterStart, true,
296 glyphCount),
297 detailedGlyphs.Elements());
298 }
299 }
300 ++offset;
301 }
302 }
304 void SelectFont() {
305 if (mFontSelected)
306 return;
308 cairo_t *cr = mContext->GetCairo();
310 cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace());
311 cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize());
312 cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont();
313 cairo_win32_scaled_font_select_font(scaledFont, mDC);
315 mFontSelected = true;
316 }
318 private:
320 void GenerateAlternativeString() {
321 if (mAlternativeString)
322 free(mAlternativeString);
323 mAlternativeString = (char16_t *)malloc(mItemLength * sizeof(char16_t));
324 if (!mAlternativeString)
325 return;
326 memcpy((void *)mAlternativeString, (const void *)mItemString,
327 mItemLength * sizeof(char16_t));
328 for (uint32_t i = 0; i < mItemLength; i++) {
329 if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i]))
330 mAlternativeString[i] = char16_t(0xFFFD);
331 }
332 }
334 private:
335 nsRefPtr<gfxContext> mContext;
336 HDC mDC;
337 gfxUniscribeShaper *mShaper;
339 SCRIPT_ITEM *mScriptItem;
340 WORD mScript;
342 public:
343 // these point to the full string/length of the item
344 const char16_t *mItemString;
345 const uint32_t mItemLength;
347 private:
348 char16_t *mAlternativeString;
350 #define AVERAGE_ITEM_LENGTH 40
352 AutoFallibleTArray<WORD, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mGlyphs;
353 AutoFallibleTArray<WORD, AVERAGE_ITEM_LENGTH + 1> mClusters;
354 AutoFallibleTArray<SCRIPT_VISATTR, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mAttr;
356 AutoFallibleTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets;
357 AutoFallibleTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances;
359 #undef AVERAGE_ITEM_LENGTH
361 int mMaxGlyphs;
362 int mNumGlyphs;
363 uint32_t mIVS;
365 bool mFontSelected;
366 };
368 class Uniscribe
369 {
370 public:
371 Uniscribe(const char16_t *aString,
372 gfxShapedText *aShapedText,
373 uint32_t aOffset, uint32_t aLength):
374 mString(aString), mShapedText(aShapedText),
375 mOffset(aOffset), mLength(aLength)
376 {
377 }
379 void Init() {
380 memset(&mControl, 0, sizeof(SCRIPT_CONTROL));
381 memset(&mState, 0, sizeof(SCRIPT_STATE));
382 // Lock the direction. Don't allow the itemizer to change directions
383 // based on character type.
384 mState.uBidiLevel = mShapedText->IsRightToLeft() ? 1 : 0;
385 mState.fOverrideDirection = true;
386 }
388 public:
389 int Itemize() {
390 HRESULT rv;
392 int maxItems = 5;
394 Init();
396 // Allocate space for one more item than expected, to handle a rare
397 // overflow in ScriptItemize (pre XP SP2). See bug 366643.
398 if (!mItems.SetLength(maxItems + 1)) {
399 return 0;
400 }
401 while ((rv = ScriptItemize(mString, mLength,
402 maxItems, &mControl, &mState,
403 mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) {
404 maxItems *= 2;
405 if (!mItems.SetLength(maxItems + 1)) {
406 return 0;
407 }
408 Init();
409 }
411 return mNumItems;
412 }
414 SCRIPT_ITEM *ScriptItem(uint32_t i) {
415 NS_ASSERTION(i <= (uint32_t)mNumItems, "Trying to get out of bounds item");
416 return &mItems[i];
417 }
419 private:
420 char16ptr_t mString;
421 gfxShapedText *mShapedText;
422 uint32_t mOffset;
423 uint32_t mLength;
425 SCRIPT_CONTROL mControl;
426 SCRIPT_STATE mState;
427 FallibleTArray<SCRIPT_ITEM> mItems;
428 int mNumItems;
429 };
432 bool
433 gfxUniscribeShaper::ShapeText(gfxContext *aContext,
434 const char16_t *aText,
435 uint32_t aOffset,
436 uint32_t aLength,
437 int32_t aScript,
438 gfxShapedText *aShapedText)
439 {
440 DCFromContext aDC(aContext);
442 bool result = true;
443 HRESULT rv;
445 Uniscribe us(aText, aShapedText, aOffset, aLength);
447 /* itemize the string */
448 int numItems = us.Itemize();
450 uint32_t length = aLength;
451 SaveDC(aDC);
452 uint32_t ivs = 0;
453 for (int i = 0; i < numItems; ++i) {
454 int iCharPos = us.ScriptItem(i)->iCharPos;
455 int iCharPosNext = us.ScriptItem(i+1)->iCharPos;
457 if (ivs) {
458 iCharPos += 2;
459 if (iCharPos >= iCharPosNext) {
460 ivs = 0;
461 continue;
462 }
463 }
465 if (i+1 < numItems && iCharPosNext <= length - 2
466 && aText[iCharPosNext] == H_SURROGATE(kUnicodeVS17)
467 && uint32_t(aText[iCharPosNext + 1]) - L_SURROGATE(kUnicodeVS17)
468 <= L_SURROGATE(kUnicodeVS256) - L_SURROGATE(kUnicodeVS17)) {
470 ivs = SURROGATE_TO_UCS4(aText[iCharPosNext],
471 aText[iCharPosNext + 1]);
472 } else {
473 ivs = 0;
474 }
476 UniscribeItem item(aContext, aDC, this,
477 aText + iCharPos,
478 iCharPosNext - iCharPos,
479 us.ScriptItem(i), ivs);
480 if (!item.AllocateBuffers()) {
481 result = false;
482 break;
483 }
485 if (!item.ShapingEnabled()) {
486 item.EnableShaping();
487 }
489 rv = item.Shape();
490 if (FAILED(rv)) {
491 // we know we have the glyphs to display this font already
492 // so Uniscribe just doesn't know how to shape the script.
493 // Render the glyphs without shaping.
494 item.DisableShaping();
495 rv = item.Shape();
496 }
497 #ifdef DEBUG
498 if (FAILED(rv)) {
499 NS_WARNING("Uniscribe failed to shape with font");
500 }
501 #endif
503 if (SUCCEEDED(rv)) {
504 rv = item.Place();
505 #ifdef DEBUG
506 if (FAILED(rv)) {
507 // crap fonts may fail when placing (e.g. funky free fonts)
508 NS_WARNING("Uniscribe failed to place with font");
509 }
510 #endif
511 }
513 if (FAILED(rv)) {
514 // Uniscribe doesn't like this font for some reason.
515 // Returning FALSE will make the gfxGDIFont retry with the
516 // "dumb" GDI shaper, unless useUniscribeOnly was set.
517 result = false;
518 break;
519 }
521 item.SaveGlyphs(aShapedText, aOffset);
522 }
524 RestoreDC(aDC, -1);
526 return result;
527 }