|
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/. */ |
|
5 |
|
6 #include "gfxTypes.h" |
|
7 |
|
8 #include "gfxContext.h" |
|
9 #include "gfxUniscribeShaper.h" |
|
10 #include "gfxWindowsPlatform.h" |
|
11 |
|
12 #include "gfxFontTest.h" |
|
13 |
|
14 #include "cairo.h" |
|
15 #include "cairo-win32.h" |
|
16 |
|
17 #include <windows.h> |
|
18 |
|
19 #include "nsTArray.h" |
|
20 |
|
21 #include "prinit.h" |
|
22 |
|
23 /********************************************************************** |
|
24 * |
|
25 * class gfxUniscribeShaper |
|
26 * |
|
27 **********************************************************************/ |
|
28 |
|
29 #define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16) |
|
30 |
|
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 } |
|
50 |
|
51 ~UniscribeItem() { |
|
52 free(mAlternativeString); |
|
53 } |
|
54 |
|
55 bool AllocateBuffers() { |
|
56 return (mGlyphs.SetLength(mMaxGlyphs) && |
|
57 mClusters.SetLength(mItemLength + 1) && |
|
58 mAttr.SetLength(mMaxGlyphs)); |
|
59 } |
|
60 |
|
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 */ |
|
65 |
|
66 HRESULT Shape() { |
|
67 HRESULT rv; |
|
68 HDC shapeDC = nullptr; |
|
69 |
|
70 char16ptr_t str = mAlternativeString ? mAlternativeString : mItemString; |
|
71 |
|
72 mScriptItem->a.fLogicalOrder = true; |
|
73 SCRIPT_ANALYSIS sa = mScriptItem->a; |
|
74 |
|
75 while (true) { |
|
76 |
|
77 rv = ScriptShape(shapeDC, mShaper->ScriptCache(), |
|
78 str, mItemLength, |
|
79 mMaxGlyphs, &sa, |
|
80 mGlyphs.Elements(), mClusters.Elements(), |
|
81 mAttr.Elements(), &mNumGlyphs); |
|
82 |
|
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 } |
|
91 |
|
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 |
|
98 |
|
99 if (sa.fNoGlyphIndex) { |
|
100 return GDI_ERROR; |
|
101 } |
|
102 |
|
103 if (rv == E_PENDING) { |
|
104 if (shapeDC == mDC) { |
|
105 // we already tried this once, something failed, give up |
|
106 return E_PENDING; |
|
107 } |
|
108 |
|
109 SelectFont(); |
|
110 |
|
111 shapeDC = mDC; |
|
112 continue; |
|
113 } |
|
114 |
|
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 } |
|
128 |
|
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 } |
|
142 |
|
143 return rv; |
|
144 } |
|
145 } |
|
146 |
|
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 } |
|
165 |
|
166 bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) { |
|
167 return (mGlyphs[aGlyphIndex] == aSFP->wgDefault); |
|
168 } |
|
169 |
|
170 |
|
171 HRESULT Place() { |
|
172 HRESULT rv; |
|
173 HDC placeDC = nullptr; |
|
174 |
|
175 if (!mOffsets.SetLength(mNumGlyphs) || |
|
176 !mAdvances.SetLength(mNumGlyphs)) { |
|
177 return E_OUTOFMEMORY; |
|
178 } |
|
179 |
|
180 SCRIPT_ANALYSIS sa = mScriptItem->a; |
|
181 |
|
182 while (true) { |
|
183 rv = ScriptPlace(placeDC, mShaper->ScriptCache(), |
|
184 mGlyphs.Elements(), mNumGlyphs, |
|
185 mAttr.Elements(), &sa, |
|
186 mAdvances.Elements(), mOffsets.Elements(), nullptr); |
|
187 |
|
188 if (rv == E_PENDING) { |
|
189 SelectFont(); |
|
190 placeDC = mDC; |
|
191 continue; |
|
192 } |
|
193 |
|
194 if (rv == USP_E_SCRIPT_NOT_IN_FONT) { |
|
195 sa.eScript = SCRIPT_UNDEFINED; |
|
196 continue; |
|
197 } |
|
198 |
|
199 break; |
|
200 } |
|
201 |
|
202 return rv; |
|
203 } |
|
204 |
|
205 void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) { |
|
206 HRESULT rv; |
|
207 |
|
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 } |
|
218 |
|
219 void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) { |
|
220 uint32_t offsetInRun = mScriptItem->iCharPos; |
|
221 |
|
222 // XXX We should store this in the item and only fetch it once |
|
223 SCRIPT_FONTPROPERTIES sfp; |
|
224 ScriptFontProperties(&sfp); |
|
225 |
|
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 } |
|
303 |
|
304 void SelectFont() { |
|
305 if (mFontSelected) |
|
306 return; |
|
307 |
|
308 cairo_t *cr = mContext->GetCairo(); |
|
309 |
|
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); |
|
314 |
|
315 mFontSelected = true; |
|
316 } |
|
317 |
|
318 private: |
|
319 |
|
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 } |
|
333 |
|
334 private: |
|
335 nsRefPtr<gfxContext> mContext; |
|
336 HDC mDC; |
|
337 gfxUniscribeShaper *mShaper; |
|
338 |
|
339 SCRIPT_ITEM *mScriptItem; |
|
340 WORD mScript; |
|
341 |
|
342 public: |
|
343 // these point to the full string/length of the item |
|
344 const char16_t *mItemString; |
|
345 const uint32_t mItemLength; |
|
346 |
|
347 private: |
|
348 char16_t *mAlternativeString; |
|
349 |
|
350 #define AVERAGE_ITEM_LENGTH 40 |
|
351 |
|
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; |
|
355 |
|
356 AutoFallibleTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets; |
|
357 AutoFallibleTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances; |
|
358 |
|
359 #undef AVERAGE_ITEM_LENGTH |
|
360 |
|
361 int mMaxGlyphs; |
|
362 int mNumGlyphs; |
|
363 uint32_t mIVS; |
|
364 |
|
365 bool mFontSelected; |
|
366 }; |
|
367 |
|
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 } |
|
378 |
|
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 } |
|
387 |
|
388 public: |
|
389 int Itemize() { |
|
390 HRESULT rv; |
|
391 |
|
392 int maxItems = 5; |
|
393 |
|
394 Init(); |
|
395 |
|
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 } |
|
410 |
|
411 return mNumItems; |
|
412 } |
|
413 |
|
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 } |
|
418 |
|
419 private: |
|
420 char16ptr_t mString; |
|
421 gfxShapedText *mShapedText; |
|
422 uint32_t mOffset; |
|
423 uint32_t mLength; |
|
424 |
|
425 SCRIPT_CONTROL mControl; |
|
426 SCRIPT_STATE mState; |
|
427 FallibleTArray<SCRIPT_ITEM> mItems; |
|
428 int mNumItems; |
|
429 }; |
|
430 |
|
431 |
|
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); |
|
441 |
|
442 bool result = true; |
|
443 HRESULT rv; |
|
444 |
|
445 Uniscribe us(aText, aShapedText, aOffset, aLength); |
|
446 |
|
447 /* itemize the string */ |
|
448 int numItems = us.Itemize(); |
|
449 |
|
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; |
|
456 |
|
457 if (ivs) { |
|
458 iCharPos += 2; |
|
459 if (iCharPos >= iCharPosNext) { |
|
460 ivs = 0; |
|
461 continue; |
|
462 } |
|
463 } |
|
464 |
|
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)) { |
|
469 |
|
470 ivs = SURROGATE_TO_UCS4(aText[iCharPosNext], |
|
471 aText[iCharPosNext + 1]); |
|
472 } else { |
|
473 ivs = 0; |
|
474 } |
|
475 |
|
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 } |
|
484 |
|
485 if (!item.ShapingEnabled()) { |
|
486 item.EnableShaping(); |
|
487 } |
|
488 |
|
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 |
|
502 |
|
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 } |
|
512 |
|
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 } |
|
520 |
|
521 item.SaveGlyphs(aShapedText, aOffset); |
|
522 } |
|
523 |
|
524 RestoreDC(aDC, -1); |
|
525 |
|
526 return result; |
|
527 } |