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/. */
9 #include "gfxContext.h"
10 #include "gfxUniscribeShaper.h"
11 #include "gfxWindowsPlatform.h"
13 #include "gfxFontTest.h"
16 #include "cairo-win32.h"
24 /**********************************************************************
26 * class gfxUniscribeShaper
28 **********************************************************************/
30 #define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16)
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
),
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!");
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()
69 HDC shapeDC
= nullptr;
71 const PRUnichar
*str
= mAlternativeString
? mAlternativeString
: mItemString
;
73 mScriptItem
->a
.fLogicalOrder
= true;
74 SCRIPT_ANALYSIS sa
= mScriptItem
->a
;
78 rv
= ScriptShape(shapeDC
, mShaper
->ScriptCache(),
81 mGlyphs
.Elements(), mClusters
.Elements(),
82 mAttr
.Elements(), &mNumGlyphs
);
84 if (rv
== E_OUTOFMEMORY
) {
86 if (!mGlyphs
.SetLength(mMaxGlyphs
) ||
87 !mAttr
.SetLength(mMaxGlyphs
)) {
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
) {
104 if (rv
== E_PENDING
) {
105 if (shapeDC
== mDC
) {
106 // we already tried this once, something failed, give up
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");
130 // Prior to Windows 7, Uniscribe didn't support Ideographic Variation
131 // Selectors. Replace the UVS glyph manually.
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
);
140 mGlyphs
[mNumGlyphs
- 1] = glyphId
;
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
);
174 HDC placeDC
= nullptr;
176 if (!mOffsets
.SetLength(mNumGlyphs
) ||
177 !mAdvances
.SetLength(mNumGlyphs
)) {
178 return E_OUTOFMEMORY
;
181 SCRIPT_ANALYSIS sa
= mScriptItem
->a
;
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
) {
195 if (rv
== USP_E_SCRIPT_NOT_IN_FONT
) {
196 sa
.eScript
= SCRIPT_UNDEFINED
;
206 void ScriptFontProperties(SCRIPT_FONTPROPERTIES
*sfp
) {
209 memset(sfp
, 0, sizeof(SCRIPT_FONTPROPERTIES
));
210 sfp
->cBytes
= sizeof(SCRIPT_FONTPROPERTIES
);
211 rv
= ScriptGetFontProperties(NULL
, mShaper
->ScriptCache(),
213 if (rv
== E_PENDING
) {
215 rv
= ScriptGetFontProperties(mDC
, mShaper
->ScriptCache(),
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
);
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);
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
;
253 for (j
= 1; j
< glyphCount
; ++j
) {
254 if (IsGlyphMissing(&sfp
, k
+ j
)) {
258 int32_t advance
= mAdvances
[k
]*appUnitsPerDevUnit
;
259 WORD glyph
= mGlyphs
[k
];
260 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString
[offset
]),
261 "invalid character detected");
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]),
271 aShapedText
->SetMissingGlyph(runOffset
, mItemString
[offset
],
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
) &&
280 charGlyphs
[runOffset
].SetSimpleGlyph(advance
, glyph
);
282 if (detailedGlyphs
.Length() < glyphCount
) {
283 if (!detailedGlyphs
.AppendElements(glyphCount
- detailedGlyphs
.Length()))
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,
298 detailedGlyphs
.Elements());
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;
321 void GenerateAlternativeString() {
322 if (mAlternativeString
)
323 free(mAlternativeString
);
324 mAlternativeString
= (PRUnichar
*)malloc(mItemLength
* sizeof(PRUnichar
));
325 if (!mAlternativeString
)
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);
336 nsRefPtr
<gfxContext
> mContext
;
338 gfxUniscribeShaper
*mShaper
;
340 SCRIPT_ITEM
*mScriptItem
;
344 // these point to the full string/length of the item
345 const PRUnichar
*mItemString
;
346 const uint32_t mItemLength
;
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
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
)
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;
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)) {
402 while ((rv
= ScriptItemize(mString
, mLength
,
403 maxItems
, &mControl
, &mState
,
404 mItems
.Elements(), &mNumItems
)) == E_OUTOFMEMORY
) {
406 if (!mItems
.SetLength(maxItems
+ 1)) {
415 SCRIPT_ITEM
*ScriptItem(uint32_t i
) {
416 NS_ASSERTION(i
<= (uint32_t)mNumItems
, "Trying to get out of bounds item");
421 const PRUnichar
*mString
;
422 gfxShapedText
*mShapedText
;
426 SCRIPT_CONTROL mControl
;
428 nsTArray
<SCRIPT_ITEM
> mItems
;
434 gfxUniscribeShaper::ShapeText(gfxContext
*aContext
,
435 const PRUnichar
*aText
,
439 gfxShapedText
*aShapedText
)
441 DCFromContext
aDC(aContext
);
446 Uniscribe
us(aText
, aShapedText
, aOffset
, aLength
);
448 /* itemize the string */
449 int numItems
= us
.Itemize();
451 uint32_t length
= aLength
;
454 for (int i
= 0; i
< numItems
; ++i
) {
455 int iCharPos
= us
.ScriptItem(i
)->iCharPos
;
456 int iCharPosNext
= us
.ScriptItem(i
+1)->iCharPos
;
460 if (iCharPos
>= iCharPosNext
) {
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]);
477 UniscribeItem
item(aContext
, aDC
, this,
479 iCharPosNext
- iCharPos
,
480 us
.ScriptItem(i
), ivs
);
481 if (!item
.AllocateBuffers()) {
486 if (!item
.ShapingEnabled()) {
487 item
.EnableShaping();
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();
500 NS_WARNING("Uniscribe failed to shape with font");
508 // crap fonts may fail when placing (e.g. funky free fonts)
509 NS_WARNING("Uniscribe failed to place with font");
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.
522 item
.SaveGlyphs(aShapedText
, aOffset
);