Bug 463806 - [PATCH][@font-face] Downloaded font activation on Mac may fail due to...
[wine-gecko.git] / gfx / thebes / src / gfxAtsuiFonts.cpp
blob8841e85d3eb6d7bbd7fa3a6a4b84a7045cd746ab
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Corporation code.
17 * The Initial Developer of the Original Code is Mozilla Corporation.
18 * Portions created by the Initial Developer are Copyright (C) 2006
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
22 * Vladimir Vukicevic <vladimir@pobox.com>
23 * Masayuki Nakano <masayuki@d-toybox.com>
24 * John Daggett <jdaggett@mozilla.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "prtypes.h"
41 #include "prmem.h"
42 #include "nsString.h"
43 #include "nsBidiUtils.h"
45 #include "gfxTypes.h"
47 #include "nsPromiseFlatString.h"
49 #include "gfxContext.h"
50 #include "gfxPlatform.h"
51 #include "gfxPlatformMac.h"
52 #include "gfxAtsuiFonts.h"
54 #include "gfxFontTest.h"
55 #include "gfxFontUtils.h"
57 #include "cairo-quartz.h"
59 #include "gfxQuartzSurface.h"
60 #include "gfxQuartzFontCache.h"
61 #include "gfxUserFontSet.h"
63 #include "nsUnicodeRange.h"
65 // Uncomment this to dump all text runs created to stdout
66 // #define DUMP_TEXT_RUNS
68 #ifdef DUMP_TEXT_RUNS
69 static PRLogModuleInfo *gAtsuiTextRunLog = PR_NewLogModule("atsuiTextRun");
70 #endif
72 #define ROUND(x) (floor((x) + 0.5))
74 /* 10.5 SDK includes a funky new definition of FloatToFixed, so reset to old-style definition */
75 #ifdef FloatToFixed
76 #undef FloatToFixed
77 #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
78 #endif
80 /* We might still need this for fast-pathing, but we'll see */
81 #if 0
82 OSStatus ATSUGetStyleGroup(ATSUStyle style, void **styleGroup);
83 OSStatus ATSUDisposeStyleGroup(void *styleGroup);
84 OSStatus ATSUConvertCharToGlyphs(void *styleGroup,
85 PRunichar *buffer
86 unsigned int bufferLength,
87 void *glyphVector);
88 OSStatus ATSInitializeGlyphVector(int size, void *glyphVectorPtr);
89 OSStatus ATSClearGlyphVector(void *glyphVectorPtr);
90 #endif
92 eFontPrefLang GetFontPrefLangFor(PRUint8 aUnicodeRange);
94 gfxAtsuiFont::gfxAtsuiFont(MacOSFontEntry *aFontEntry,
95 const gfxFontStyle *fontStyle, PRBool aNeedsBold)
96 : gfxFont(aFontEntry, fontStyle),
97 mFontStyle(fontStyle), mATSUStyle(nsnull),
98 mHasMirroring(PR_FALSE), mHasMirroringLookedUp(PR_FALSE), mAdjustedSize(0.0f)
100 ATSUFontID fontID = aFontEntry->GetFontID();
101 ATSFontRef fontRef = FMGetATSFontRefFromFont(fontID);
103 // determine whether synthetic bolding is needed
104 PRInt8 baseWeight, weightDistance;
105 mFontStyle->ComputeWeightAndOffset(&baseWeight, &weightDistance);
106 PRUint16 targetWeight = (baseWeight * 100) + (weightDistance * 100);
108 // synthetic bolding occurs when font itself is not a bold-face and either the absolute weight
109 // is at least 600 or the relative weight (e.g. 402) implies a darker face than the ones available.
110 // note: this means that (1) lighter styles *never* synthetic bold and (2) synthetic bolding always occurs
111 // at the first bolder step beyond available faces, no matter how light the boldest face
112 if (!aFontEntry->IsBold()
113 && ((weightDistance == 0 && targetWeight >= 600) || (weightDistance > 0 && aNeedsBold)))
115 mSyntheticBoldOffset = 1; // devunit offset when double-striking text to fake boldness
118 InitMetrics(fontID, fontRef);
120 mFontFace = cairo_quartz_font_face_create_for_atsu_font_id(fontID);
122 cairo_matrix_t sizeMatrix, ctm;
123 cairo_matrix_init_identity(&ctm);
124 cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
126 // synthetic oblique by skewing via the font matrix
127 PRBool needsOblique = (!aFontEntry->IsItalic() && (mFontStyle->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)));
129 if (needsOblique) {
130 double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0);
132 cairo_matrix_t style;
133 cairo_matrix_init(&style,
134 1, //xx
135 0, //yx
136 -1 * skewfactor, //xy
137 1, //yy
138 0, //x0
139 0); //y0
140 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
143 cairo_font_options_t *fontOptions = cairo_font_options_create();
145 // turn off font anti-aliasing based on user pref setting
146 if (mAdjustedSize <= (float) gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
147 cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
148 //printf("font: %s, size: %f, disabling anti-aliasing\n", NS_ConvertUTF16toUTF8(GetName()).get(), mAdjustedSize);
151 mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, fontOptions);
152 cairo_font_options_destroy(fontOptions);
154 cairo_status_t cairoerr = cairo_scaled_font_status(mScaledFont);
155 if (cairoerr != CAIRO_STATUS_SUCCESS) {
156 mIsValid = PR_FALSE;
158 #ifdef DEBUG
159 char warnBuf[1024];
160 sprintf(warnBuf, "Failed to create scaled font: %s status: %d", NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
161 NS_WARNING(warnBuf);
162 #endif
167 ATSUFontID gfxAtsuiFont::GetATSUFontID()
169 return GetFontEntry()->GetFontID();
172 static void
173 DisableUncommonLigaturesAndLineBoundarySwashes(ATSUStyle aStyle)
175 static const ATSUFontFeatureType types[] = {
176 kLigaturesType,
177 kLigaturesType,
178 kLigaturesType,
179 kLigaturesType,
180 kLigaturesType,
181 kLigaturesType,
182 kSmartSwashType,
183 kSmartSwashType
185 static const ATSUFontFeatureType selectors[NS_ARRAY_LENGTH(types)] = {
186 kRareLigaturesOffSelector,
187 kLogosOffSelector,
188 kRebusPicturesOffSelector,
189 kDiphthongLigaturesOffSelector,
190 kSquaredLigaturesOffSelector,
191 kAbbrevSquaredLigaturesOffSelector,
192 kLineInitialSwashesOffSelector,
193 kLineFinalSwashesOffSelector
195 ATSUSetFontFeatures(aStyle, NS_ARRAY_LENGTH(types), types, selectors);
198 static void
199 DisableCommonLigatures(ATSUStyle aStyle)
201 static const ATSUFontFeatureType types[] = {
202 kLigaturesType
204 static const ATSUFontFeatureType selectors[NS_ARRAY_LENGTH(types)] = {
205 kCommonLigaturesOffSelector
207 ATSUSetFontFeatures(aStyle, NS_ARRAY_LENGTH(types), types, selectors);
210 static double
211 RoundToNearestMultiple(double aValue, double aFraction)
213 return floor(aValue/aFraction + 0.5)*aFraction;
216 void
217 gfxAtsuiFont::InitMetrics(ATSUFontID aFontID, ATSFontRef aFontRef)
219 /* Create the ATSUStyle */
221 gfxFloat size =
222 PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : GetStyle()->size), 1.0f);
224 //fprintf (stderr, "string: '%s', size: %f\n", NS_ConvertUTF16toUTF8(aString).get(), size);
226 if (mATSUStyle)
227 ATSUDisposeStyle(mATSUStyle);
229 ATSUFontID fid = aFontID;
230 // fSize is in points (72dpi)
231 Fixed fSize = FloatToFixed(size);
232 // make the font render right-side up
233 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
235 static const ATSUAttributeTag styleTags[] = {
236 kATSUFontTag,
237 kATSUSizeTag,
238 kATSUFontMatrixTag
240 const ATSUAttributeValuePtr styleArgs[NS_ARRAY_LENGTH(styleTags)] = {
241 &fid,
242 &fSize,
243 &transform
245 static const ByteCount styleArgSizes[NS_ARRAY_LENGTH(styleTags)] = {
246 sizeof(ATSUFontID),
247 sizeof(Fixed),
248 sizeof(CGAffineTransform)
251 ATSUCreateStyle(&mATSUStyle);
252 ATSUSetAttributes(mATSUStyle,
253 NS_ARRAY_LENGTH(styleTags),
254 styleTags,
255 styleArgSizes,
256 styleArgs);
257 // Disable uncommon ligatures, but *don't* enable common ones;
258 // the font may have default settings that disable common ligatures
259 // and we want to respect that.
260 // Also disable line boundary swashes because we can't handle them properly;
261 // we don't know where the line-breaks are at the time we're applying shaping,
262 // and it would be bad to put words with line-end swashes into the text-run
263 // cache until we have a way to distinguish them from mid-line occurrences.
264 DisableUncommonLigaturesAndLineBoundarySwashes(mATSUStyle);
266 /* Now pull out the metrics */
268 ATSFontMetrics atsMetrics;
269 ATSFontGetHorizontalMetrics(aFontRef, kATSOptionFlagsDefault,
270 &atsMetrics);
272 if (atsMetrics.xHeight)
273 mMetrics.xHeight = atsMetrics.xHeight * size;
274 else
275 mMetrics.xHeight = GetCharHeight('x');
277 if (mAdjustedSize == 0.0f) {
278 if (mMetrics.xHeight != 0.0f && GetStyle()->sizeAdjust != 0.0f) {
279 gfxFloat aspect = mMetrics.xHeight / size;
280 mAdjustedSize = GetStyle()->GetAdjustedSize(aspect);
281 InitMetrics(aFontID, aFontRef);
282 return;
284 mAdjustedSize = size;
287 mMetrics.emHeight = size;
289 mMetrics.maxAscent =
290 NS_ceil(RoundToNearestMultiple(atsMetrics.ascent*size, 1/1024.0));
291 mMetrics.maxDescent =
292 NS_ceil(-RoundToNearestMultiple(atsMetrics.descent*size, 1/1024.0));
294 mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
296 if (mMetrics.maxHeight - mMetrics.emHeight > 0)
297 mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
298 else
299 mMetrics.internalLeading = 0.0;
300 mMetrics.externalLeading = atsMetrics.leading * size;
302 mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
303 mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
305 mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
307 float xWidth = GetCharWidth('x');
308 if (atsMetrics.avgAdvanceWidth != 0.0)
309 mMetrics.aveCharWidth =
310 PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
311 else
312 mMetrics.aveCharWidth = xWidth;
314 mMetrics.aveCharWidth += mSyntheticBoldOffset;
316 if (GetFontEntry()->IsFixedPitch()) {
317 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
318 // advance than the average character width... this forces
319 // those fonts to be recognized like fixed pitch fonts by layout.
320 mMetrics.maxAdvance = mMetrics.aveCharWidth;
323 mMetrics.underlineOffset = atsMetrics.underlinePosition * size;
324 mMetrics.underlineSize = atsMetrics.underlineThickness * size;
326 mMetrics.subscriptOffset = mMetrics.xHeight;
327 mMetrics.superscriptOffset = mMetrics.xHeight;
329 mMetrics.strikeoutOffset = mMetrics.xHeight / 2.0;
330 mMetrics.strikeoutSize = mMetrics.underlineSize;
332 PRUint32 glyphID;
333 mMetrics.spaceWidth = GetCharWidth(' ', &glyphID);
334 mSpaceGlyph = glyphID;
336 mMetrics.zeroOrAveCharWidth = GetCharWidth('0', &glyphID);
337 if (glyphID == 0) // no zero in this font
338 mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
340 SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
342 #if 0
343 fprintf (stderr, "Font: %p size: %f (fixed: %d)", this, size, gfxQuartzFontCache::SharedFontCache()->IsFixedPitch(aFontID));
344 fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
345 fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
346 fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading);
347 fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
348 fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f suOff: %f suSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize, mMetrics.superscriptOffset, mMetrics.subscriptOffset);
349 #endif
352 PRBool
353 gfxAtsuiFont::SetupCairoFont(gfxContext *aContext)
355 cairo_scaled_font_t *scaledFont = CairoScaledFont();
356 if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
357 // Don't cairo_set_scaled_font as that would propagate the error to
358 // the cairo_t, precluding any further drawing.
359 return PR_FALSE;
361 cairo_set_scaled_font(aContext->GetCairo(), scaledFont);
362 return PR_TRUE;
365 nsString
366 gfxAtsuiFont::GetUniqueName()
368 return GetName();
371 float
372 gfxAtsuiFont::GetCharWidth(PRUnichar c, PRUint32 *aGlyphID)
374 // this sucks. There is a faster way to go from a char -> glyphs, but it
375 // requires using oodles of apple private interfaces. If we start caching
376 // gfxAtsuiFonts, then it might make sense to do that.
377 ATSUTextLayout layout;
379 UniCharCount one = 1;
380 ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
382 ATSTrapezoid trap;
383 ItemCount numBounds;
384 ATSUGetGlyphBounds(layout, FloatToFixed(0.0), FloatToFixed(0.0),
385 0, 1, kATSUseFractionalOrigins, 1, &trap, &numBounds);
387 float f =
388 FixedToFloat(PR_MAX(trap.upperRight.x, trap.lowerRight.x)) -
389 FixedToFloat(PR_MIN(trap.upperLeft.x, trap.lowerLeft.x));
391 if (aGlyphID) {
392 ATSUGlyphInfoArray glyphInfo;
393 ByteCount bytes = sizeof(glyphInfo);
394 ATSUGetGlyphInfo(layout, 0, 1, &bytes, &glyphInfo);
395 *aGlyphID = glyphInfo.glyphs[0].glyphID;
398 ATSUDisposeTextLayout(layout);
400 return f;
403 float
404 gfxAtsuiFont::GetCharHeight(PRUnichar c)
406 // this sucks. There is a faster way to go from a char -> glyphs, but it
407 // requires using oodles of apple private interfaces. If we start caching
408 // gfxAtsuiFonts, then it might make sense to do that.
409 ATSUTextLayout layout;
411 UniCharCount one = 1;
412 ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
414 Rect rect;
415 ATSUMeasureTextImage(layout, 0, 1, 0, 0, &rect);
417 ATSUDisposeTextLayout(layout);
419 return rect.bottom - rect.top;
422 gfxAtsuiFont::~gfxAtsuiFont()
424 cairo_scaled_font_destroy(mScaledFont);
425 cairo_font_face_destroy(mFontFace);
427 ATSUDisposeStyle(mATSUStyle);
430 const gfxFont::Metrics&
431 gfxAtsuiFont::GetMetrics()
433 return mMetrics;
436 void
437 gfxAtsuiFont::SetupGlyphExtents(gfxContext *aContext, PRUint32 aGlyphID,
438 PRBool aNeedTight, gfxGlyphExtents *aExtents)
440 ATSGlyphScreenMetrics metrics;
441 GlyphID glyph = aGlyphID;
442 OSStatus err = ATSUGlyphGetScreenMetrics(mATSUStyle, 1, &glyph, 0, false, false,
443 &metrics);
444 if (err != noErr)
445 return;
446 PRUint32 appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
448 if (!aNeedTight && metrics.topLeft.x >= 0 &&
449 -metrics.topLeft.y + metrics.height <= mMetrics.maxAscent &&
450 metrics.topLeft.y <= mMetrics.maxDescent) {
451 PRUint32 appUnitsWidth =
452 PRUint32(NS_ceil((metrics.topLeft.x + metrics.width)*appUnitsPerDevUnit));
453 if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
454 aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, PRUint16(appUnitsWidth));
455 return;
459 double d2a = appUnitsPerDevUnit;
460 gfxRect bounds(metrics.topLeft.x*d2a, (metrics.topLeft.y - metrics.height)*d2a,
461 metrics.width*d2a, metrics.height*d2a);
462 aExtents->SetTightGlyphExtents(aGlyphID, bounds);
465 PRBool
466 gfxAtsuiFont::HasMirroringInfo()
468 if (!mHasMirroringLookedUp) {
469 OSStatus status;
470 ByteCount size;
472 // 361695 - if the font has a 'prop' table, assume that ATSUI will handle glyph mirroring
473 status = ATSFontGetTable(GetATSUFontID(), 'prop', 0, 0, 0, &size);
474 mHasMirroring = (status == noErr);
475 mHasMirroringLookedUp = PR_TRUE;
478 return mHasMirroring;
481 PRBool gfxAtsuiFont::TestCharacterMap(PRUint32 aCh) {
482 if (!mIsValid) return PR_FALSE;
483 return GetFontEntry()->TestCharacterMap(aCh);
486 MacOSFontEntry*
487 gfxAtsuiFont::GetFontEntry()
489 return static_cast< MacOSFontEntry*> (mFontEntry.get());
493 * Look up the font in the gfxFont cache. If we don't find it, create one.
494 * In either case, add a ref and return it ---
495 * except for OOM in which case we do nothing and return null.
498 static already_AddRefed<gfxAtsuiFont>
499 GetOrMakeFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aStyle, PRBool aNeedsBold)
501 // the font entry name is the psname, not the family name
502 nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(aFontEntry->Name(), aStyle);
503 if (!font) {
504 gfxAtsuiFont *newFont = new gfxAtsuiFont(aFontEntry, aStyle, aNeedsBold);
505 if (!newFont)
506 return nsnull;
507 if (!newFont->Valid()) {
508 delete newFont;
509 return nsnull;
511 font = newFont;
512 gfxFontCache::GetCache()->AddNew(font);
514 gfxFont *f = nsnull;
515 font.swap(f);
516 return static_cast<gfxAtsuiFont *>(f);
520 gfxAtsuiFontGroup::gfxAtsuiFontGroup(const nsAString& families,
521 const gfxFontStyle *aStyle,
522 gfxUserFontSet *aUserFontSet)
523 : gfxFontGroup(families, aStyle, aUserFontSet)
525 ForEachFont(FindATSUFont, this);
527 if (mFonts.Length() == 0) {
528 // XXX this will generate a list of the lang groups for which we have no
529 // default fonts for on the mac; we should fix this!
530 // Known:
531 // ja x-beng x-devanagari x-tamil x-geor x-ethi x-gujr x-mlym x-armn
532 // x-orya x-telu x-knda x-sinh
534 //fprintf (stderr, "gfxAtsuiFontGroup: %s [%s] -> %d fonts found\n", NS_ConvertUTF16toUTF8(families).get(), aStyle->langGroup.get(), mFonts.Length());
536 // If we get here, we most likely didn't have a default font for
537 // a specific langGroup. Let's just pick the default OSX
538 // user font.
540 PRBool needsBold;
541 MacOSFontEntry *defaultFont = gfxQuartzFontCache::SharedFontCache()->GetDefaultFont(aStyle, needsBold);
542 NS_ASSERTION(defaultFont, "invalid default font returned by GetDefaultFont");
544 nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(defaultFont, aStyle, needsBold);
546 if (font) {
547 mFonts.AppendElement(font);
551 mPageLang = gfxPlatform::GetFontPrefLangFor(mStyle.langGroup.get());
553 if (!mStyle.systemFont) {
554 for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
555 gfxAtsuiFont* font = static_cast<gfxAtsuiFont*>(mFonts[i].get());
556 if (font->GetFontEntry()->mIsBadUnderlineFont) {
557 gfxFloat first = mFonts[0]->GetMetrics().underlineOffset;
558 gfxFloat bad = font->GetMetrics().underlineOffset;
559 mUnderlineOffset = PR_MIN(first, bad);
560 break;
566 PRBool
567 gfxAtsuiFontGroup::FindATSUFont(const nsAString& aName,
568 const nsACString& aGenericName,
569 void *closure)
571 gfxAtsuiFontGroup *fontGroup = (gfxAtsuiFontGroup*) closure;
572 const gfxFontStyle *fontStyle = fontGroup->GetStyle();
575 PRBool needsBold;
576 MacOSFontEntry *fe = nsnull;
578 // first, look up in the user font set
579 gfxUserFontSet *fs = fontGroup->GetUserFontSet();
580 gfxFontEntry *gfe;
581 if (fs && (gfe = fs->FindFontEntry(aName, *fontStyle, needsBold))) {
582 // assume for now platform font if not SVG
583 fe = static_cast<MacOSFontEntry*> (gfe);
586 // nothing in the user font set ==> check system fonts
587 if (!fe) {
588 gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
589 fe = fc->FindFontForFamily(aName, fontStyle, needsBold);
592 if (fe && !fontGroup->HasFont(fe->GetFontID())) {
593 nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(fe, fontStyle, needsBold);
594 if (font) {
595 fontGroup->mFonts.AppendElement(font);
599 return PR_TRUE;
602 gfxFontGroup *
603 gfxAtsuiFontGroup::Copy(const gfxFontStyle *aStyle)
605 return new gfxAtsuiFontGroup(mFamilies, aStyle, mUserFontSet);
608 static void
609 SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
611 TextBreakLocatorRef locator;
612 OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask,
613 &locator);
614 if (status != noErr)
615 return;
616 UniCharArrayOffset breakOffset = 0;
617 UCTextBreakOptions options = kUCTextBreakLeadingEdgeMask;
618 PRUint32 length = aTextRun->GetLength();
619 while (breakOffset < length) {
620 UniCharArrayOffset next;
621 status = UCFindTextBreak(locator, kUCTextBreakClusterMask, options,
622 aString, length, breakOffset, &next);
623 if (status != noErr)
624 break;
625 options |= kUCTextBreakIterateMask;
626 PRUint32 i;
627 for (i = breakOffset + 1; i < next; ++i) {
628 gfxTextRun::CompressedGlyph g;
629 // Remember that this character is not the start of a cluster by
630 // setting its glyph data to "not a cluster start", "is a
631 // ligature start", with no glyphs.
632 aTextRun->SetGlyphs(i, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
634 breakOffset = next;
636 UCDisposeTextBreakLocator(&locator);
639 #define UNICODE_LRO 0x202d
640 #define UNICODE_RLO 0x202e
641 #define UNICODE_PDF 0x202c
643 static void
644 AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString)
646 static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO };
647 aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);
650 // Returns the number of trailing characters that should be part of the
651 // layout but should be ignored
652 static PRUint32
653 AppendDirectionalIndicatorEnd(PRBool aNeedDirection, nsAString& aString)
655 // Ensure that we compute the full advance width for the last character
656 // in the string --- we don't want that character to form a kerning
657 // pair (or a ligature) with the '.' we may append next,
658 // so we append a space now.
659 // Even if the character is the last character in the layout,
660 // we want its width to be determined as if it had a space after it,
661 // for consistency with the bidi path and during textrun caching etc.
662 aString.Append(' ');
663 if (!aNeedDirection)
664 return 1;
666 // Ensure that none of the whitespace in the run is considered "trailing"
667 // by ATSUI's bidi algorithm
668 aString.Append('.');
669 aString.Append(UNICODE_PDF);
670 return 2;
674 * Given a textrun and an offset into that textrun, we need to choose a length
675 * for the substring of the textrun that we should analyze next. The length
676 * should be <= aMaxLength if possible. It must always end at a cluster
677 * boundary and it should end at the end of the textrun or at the
678 * boundary of a space if possible.
680 static PRUint32
681 FindTextRunSegmentLength(gfxTextRun *aTextRun, PRUint32 aOffset, PRUint32 aMaxLength)
683 if (aOffset + aMaxLength >= aTextRun->GetLength()) {
684 // The remaining part of the textrun fits within the max length,
685 // so just use it.
686 return aTextRun->GetLength() - aOffset;
689 // Try to end the segment before or after a space, since spaces don't kern
690 // or ligate.
691 PRUint32 end;
692 for (end = aOffset + aMaxLength; end > aOffset; --end) {
693 if (aTextRun->IsClusterStart(end) &&
694 (aTextRun->GetChar(end) == ' ' || aTextRun->GetChar(end - 1) == ' '))
695 return end - aOffset;
698 // Try to end the segment at the last cluster boundary.
699 for (end = aOffset + aMaxLength; end > aOffset; --end) {
700 if (aTextRun->IsClusterStart(end))
701 return end - aOffset;
704 // I guess we just have to return a segment that's the entire cluster
705 // starting at aOffset.
706 for (end = aOffset + 1; end < aTextRun->GetLength(); ++end) {
707 if (aTextRun->IsClusterStart(end))
708 return end - aOffset;
710 return aTextRun->GetLength() - aOffset;
713 PRUint32
714 gfxAtsuiFontGroup::GuessMaximumStringLength()
716 // ATSUI can't handle offsets of more than 32K pixels. This function
717 // guesses a string length that ATSUI will be able to handle. We want to
718 // get the right answer most of the time, but if we're wrong in either
719 // direction, we won't break things: if we guess too large, our glyph
720 // processing will detect ATSUI's failure and retry with a smaller limit.
721 // If we guess too small, we'll just break the string into more pieces
722 // than we strictly needed to.
723 // The basic calculation is just 32k pixels divided by the font max-advance,
724 // but we need to be a bit careful to avoid math errors.
725 PRUint32 maxAdvance = PRUint32(GetFontAt(0)->GetMetrics().maxAdvance);
726 PRUint32 chars = 0x7FFF/PR_MAX(1, maxAdvance);
728 PRUint32 realGuessMax = PR_MAX(1, chars);
730 // bug 436663 - ATSUI crashes on 10.5.3 with certain character sequences
731 // at around 512 characters, so for safety sake max out at 500 characters
732 if (gfxPlatformMac::GetPlatform()->OSXVersion() >= MAC_OS_X_VERSION_10_5_HEX) {
733 realGuessMax = PR_MIN(500, realGuessMax);
736 return realGuessMax;
740 * ATSUI can't handle more than 32K pixels of text. We can easily have
741 * textruns longer than that. Our strategy here is to divide the textrun up
742 * into pieces each of which is less than 32K pixels wide. We pick a number
743 * of characters 'maxLen' such that the first font's max-advance times that
744 * number of characters is less than 32K pixels; then we try glyph conversion
745 * of the string broken up into chunks each with no more than 'maxLen'
746 * characters. That could fail (e.g. if fallback fonts are used); if it does,
747 * we retry with a smaller maxLen. When breaking up the string into chunks
748 * we prefer to break at space boundaries because spaces don't kern or ligate
749 * with other characters, usually. We insist on breaking at cluster boundaries.
750 * If the font size is incredibly huge and/or clusters are very large, this
751 * could mean that we actually put more than 'maxLen' characters in a chunk.
754 gfxTextRun *
755 gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
756 const Parameters *aParams, PRUint32 aFlags)
758 gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
759 if (!textRun)
760 return nsnull;
762 textRun->RecordSurrogates(aString);
763 SetupClusterBoundaries(textRun, aString);
765 PRUint32 maxLen;
766 nsAutoString utf16;
767 for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
768 PRUint32 start = 0;
769 while (start < aLength) {
770 PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
772 utf16.Truncate();
773 AppendDirectionalIndicatorStart(aFlags, utf16);
774 PRUint32 layoutStart = utf16.Length();
775 utf16.Append(aString + start, len);
776 // Ensure that none of the whitespace in the run is considered "trailing"
777 // by ATSUI's bidi algorithm
778 PRUint32 trailingCharsToIgnore =
779 AppendDirectionalIndicatorEnd(PR_TRUE, utf16);
780 PRUint32 layoutLength = len + trailingCharsToIgnore;
781 if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
782 layoutStart, layoutLength,
783 start, len) && maxLen > 1)
784 break;
785 start += len;
787 if (start == aLength)
788 break;
789 textRun->ResetGlyphRuns();
792 textRun->FetchGlyphExtents(aParams->mContext);
794 return textRun;
797 gfxTextRun *
798 gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
799 const Parameters *aParams, PRUint32 aFlags)
801 NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit");
802 gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
803 if (!textRun)
804 return nsnull;
806 PRUint32 maxLen;
807 nsAutoString utf16;
808 for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
809 PRUint32 start = 0;
810 while (start < aLength) {
811 PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
813 nsDependentCSubstring cString(reinterpret_cast<const char*>(aString + start),
814 reinterpret_cast<const char*>(aString + start + len));
815 utf16.Truncate();
816 PRBool wrapBidi = (aFlags & TEXT_IS_RTL) != 0;
817 if (wrapBidi) {
818 AppendDirectionalIndicatorStart(aFlags, utf16);
820 PRUint32 layoutStart = utf16.Length();
821 AppendASCIItoUTF16(cString, utf16);
822 PRUint32 trailingCharsToIgnore =
823 AppendDirectionalIndicatorEnd(wrapBidi, utf16);
824 PRUint32 layoutLength = len + trailingCharsToIgnore;
825 if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
826 layoutStart, layoutLength,
827 start, len) && maxLen > 1)
828 break;
829 start += len;
831 if (start == aLength)
832 break;
833 textRun->ResetGlyphRuns();
836 textRun->FetchGlyphExtents(aParams->mContext);
838 return textRun;
841 PRBool
842 gfxAtsuiFontGroup::HasFont(ATSUFontID fid)
844 for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
845 if (fid == static_cast<gfxAtsuiFont *>(mFonts.ElementAt(i).get())->GetATSUFontID())
846 return PR_TRUE;
848 return PR_FALSE;
851 struct PrefFontCallbackData {
852 PrefFontCallbackData(nsTArray<nsRefPtr<MacOSFamilyEntry> >& aFamiliesArray)
853 : mPrefFamilies(aFamiliesArray)
856 nsTArray<nsRefPtr<MacOSFamilyEntry> >& mPrefFamilies;
858 static PRBool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure)
860 PrefFontCallbackData *prefFontData = (PrefFontCallbackData*) aClosure;
862 MacOSFamilyEntry *family = gfxQuartzFontCache::SharedFontCache()->FindFamily(aName);
863 if (family) {
864 prefFontData->mPrefFamilies.AppendElement(family);
866 return PR_TRUE;
871 already_AddRefed<gfxFont>
872 gfxAtsuiFontGroup::WhichPrefFontSupportsChar(PRUint32 aCh)
874 gfxFont *font;
876 // FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs
877 if (aCh > 0xFFFF)
878 return nsnull;
880 // get the pref font list if it hasn't been set up already
881 PRUint32 unicodeRange = FindCharUnicodeRange(aCh);
882 eFontPrefLang charLang = GetFontPrefLangFor(unicodeRange);
884 // if the last pref font was the first family in the pref list, no need to recheck through a list of families
885 if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && mLastPrefFont->TestCharacterMap(aCh)) {
886 font = mLastPrefFont;
887 NS_ADDREF(font);
888 return font;
891 // based on char lang and page lang, set up list of pref lang fonts to check
892 eFontPrefLang prefLangs[kMaxLenPrefLangList];
893 PRUint32 i, numLangs = 0;
895 gfxPlatformMac *macPlatform = gfxPlatformMac::GetPlatform();
896 macPlatform->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
898 for (i = 0; i < numLangs; i++) {
899 nsAutoTArray<nsRefPtr<MacOSFamilyEntry>, 5> families;
900 eFontPrefLang currentLang = prefLangs[i];
902 gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
904 // get the pref families for a single pref lang
905 if (!fc->GetPrefFontFamilyEntries(currentLang, &families)) {
906 eFontPrefLang prefLangsToSearch[1] = { currentLang };
907 PrefFontCallbackData prefFontData(families);
908 gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry,
909 &prefFontData);
910 fc->SetPrefFontFamilyEntries(currentLang, families);
913 // find the first pref font that includes the character
914 PRUint32 i, numPrefs;
915 numPrefs = families.Length();
916 for (i = 0; i < numPrefs; i++) {
917 // look up the appropriate face
918 MacOSFamilyEntry *family = families[i];
919 if (!family) continue;
921 // if a pref font is used, it's likely to be used again in the same text run.
922 // the style doesn't change so the face lookup can be cached rather than calling
923 // GetOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent
924 // pref font lookups
925 if (family == mLastPrefFamily && mLastPrefFont->TestCharacterMap(aCh)) {
926 font = mLastPrefFont;
927 NS_ADDREF(font);
928 return font;
931 PRBool needsBold;
932 MacOSFontEntry *fe = family->FindFont(&mStyle, needsBold);
933 // if ch in cmap, create and return a gfxFont
934 if (fe && fe->TestCharacterMap(aCh)) {
935 nsRefPtr<gfxAtsuiFont> prefFont = GetOrMakeFont(fe, &mStyle, needsBold);
936 if (!prefFont) continue;
937 mLastPrefFamily = family;
938 mLastPrefFont = prefFont;
939 mLastPrefLang = charLang;
940 mLastPrefFirstFont = (i == 0);
941 nsRefPtr<gfxFont> font2 = (gfxFont*) prefFont;
942 return font2.forget();
948 return nsnull;
951 already_AddRefed<gfxFont>
952 gfxAtsuiFontGroup::WhichSystemFontSupportsChar(PRUint32 aCh)
954 MacOSFontEntry *fe;
956 fe = gfxQuartzFontCache::SharedFontCache()->FindFontForChar(aCh, GetFontAt(0));
957 if (fe) {
958 nsRefPtr<gfxAtsuiFont> atsuiFont = GetOrMakeFont(fe, &mStyle, PR_FALSE); // ignore bolder considerations in system fallback case...
959 nsRefPtr<gfxFont> font = (gfxFont*) atsuiFont;
960 return font.forget();
963 return nsnull;
966 void
967 gfxAtsuiFontGroup::UpdateFontList()
969 // if user font set is set, check to see if font list needs updating
970 if (mUserFontSet && mCurrGeneration != GetGeneration()) {
971 // xxx - can probably improve this to detect when all fonts were found, so no need to update list
972 mFonts.Clear();
973 ForEachFont(FindATSUFont, this);
974 mCurrGeneration = GetGeneration();
980 * Simple wrapper for ATSU "direct data arrays"
982 class AutoLayoutDataArrayPtr {
983 public:
984 AutoLayoutDataArrayPtr(ATSULineRef aLineRef,
985 ATSUDirectDataSelector aSelector)
986 : mLineRef(aLineRef), mSelector(aSelector)
988 OSStatus status =
989 ATSUDirectGetLayoutDataArrayPtrFromLineRef(aLineRef,
990 aSelector, PR_FALSE, &mArray, &mItemCount);
991 if (status != noErr) {
992 mArray = NULL;
993 mItemCount = 0;
996 ~AutoLayoutDataArrayPtr() {
997 if (mArray) {
998 ATSUDirectReleaseLayoutDataArrayPtr(mLineRef, mSelector, &mArray);
1002 void *mArray;
1003 ItemCount mItemCount;
1005 private:
1006 ATSULineRef mLineRef;
1007 ATSUDirectDataSelector mSelector;
1010 #define ATSUI_SPECIAL_GLYPH_ID 0xFFFF
1012 * This flag seems to be set on glyphs that have overrun the 32K pixel
1013 * limit in ATSUI.
1015 #define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
1018 * Calculate the advance in appunits of a run of ATSUI glyphs
1020 static PRInt32
1021 GetAdvanceAppUnits(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
1022 PRUint32 aAppUnitsPerDevUnit)
1024 Fixed fixedAdvance = aGlyphs[aGlyphCount].realPos - aGlyphs->realPos;
1025 return PRInt32((PRInt64(fixedAdvance)*aAppUnitsPerDevUnit + (1 << 15)) >> 16);
1029 * Given a run of ATSUI glyphs that should be treated as a single cluster/ligature,
1030 * store them in the textrun at the appropriate character and set the
1031 * other characters involved to be ligature/cluster continuations as appropriate.
1033 static void
1034 SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
1035 Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit,
1036 gfxTextRun *aRun, PRUint32 aOffsetInTextRun,
1037 const PRPackedBool *aUnmatched,
1038 const PRUnichar *aString,
1039 const PRUint32 aLength)
1041 NS_ASSERTION(aGlyphCount > 0, "Must set at least one glyph");
1042 PRUint32 firstOffset = aGlyphs[0].originalOffset;
1043 PRUint32 lastOffset = firstOffset;
1044 PRUint32 i;
1045 PRUint32 regularGlyphCount = 0;
1046 ATSLayoutRecord *displayGlyph = nsnull;
1047 PRBool inOrder = PR_TRUE;
1048 PRBool allMatched = PR_TRUE;
1050 for (i = 0; i < aGlyphCount; ++i) {
1051 ATSLayoutRecord *glyph = &aGlyphs[i];
1052 PRUint32 offset = glyph->originalOffset;
1053 firstOffset = PR_MIN(firstOffset, offset);
1054 lastOffset = PR_MAX(lastOffset, offset);
1055 if (aUnmatched && aUnmatched[offset/2]) {
1056 allMatched = PR_FALSE;
1058 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
1059 ++regularGlyphCount;
1060 displayGlyph = glyph;
1062 if (i > 0 && aRun->IsRightToLeft() != (offset < aGlyphs[i - 1].originalOffset)) { // XXXkt allow == in RTL
1063 inOrder = PR_FALSE;
1067 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString[firstOffset/2]),
1068 "Invalid char passed in");
1070 if (!allMatched) {
1071 for (i = firstOffset; i <= lastOffset; ++i) {
1072 PRUint32 index = i/2;
1073 if (NS_IS_HIGH_SURROGATE(aString[index]) &&
1074 index + 1 < aLength &&
1075 NS_IS_LOW_SURROGATE(aString[index + 1])) {
1076 aRun->SetMissingGlyph(aOffsetInTextRun + index,
1077 SURROGATE_TO_UCS4(aString[index],
1078 aString[index + 1]));
1079 } else {
1080 aRun->SetMissingGlyph(aOffsetInTextRun + index, aString[index]);
1083 return;
1086 gfxTextRun::CompressedGlyph g;
1087 PRUint32 offset;
1088 // Make all but the first character in the group NOT be a ligature boundary,
1089 // i.e. fuse the group into a ligature.
1090 // Also make them not be cluster boundaries, i.e., fuse them into a cluster,
1091 // if the glyphs are out of character order.
1092 for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
1093 PRUint32 charIndex = aOffsetInTextRun + offset/2;
1094 PRBool makeClusterStart = inOrder && aRun->IsClusterStart(charIndex);
1095 g.SetComplex(makeClusterStart, PR_FALSE, 0);
1096 aRun->SetGlyphs(charIndex, g, nsnull);
1099 // Grab total advance for all glyphs
1100 PRInt32 advance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
1101 PRUint32 charIndex = aOffsetInTextRun + firstOffset/2;
1102 if (regularGlyphCount == 1) {
1103 if (advance >= 0 &&
1104 (!aBaselineDeltas || aBaselineDeltas[displayGlyph - aGlyphs] == 0) &&
1105 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
1106 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(displayGlyph->glyphID) &&
1107 aRun->IsClusterStart(charIndex)) {
1108 aRun->SetSimpleGlyph(charIndex, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
1109 return;
1113 nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
1114 ATSLayoutRecord *advanceStart = aGlyphs;
1115 for (i = 0; i < aGlyphCount; ++i) {
1116 ATSLayoutRecord *glyph = &aGlyphs[i];
1117 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
1118 if (glyph->originalOffset > firstOffset) {
1119 PRUint32 glyphCharIndex = aOffsetInTextRun + glyph->originalOffset/2;
1120 PRUint32 glyphRunIndex = aRun->FindFirstGlyphRunContaining(glyphCharIndex);
1121 PRUint32 numGlyphRuns;
1122 const gfxTextRun::GlyphRun *glyphRun = aRun->GetGlyphRuns(&numGlyphRuns) + glyphRunIndex;
1124 if (glyphRun->mCharacterOffset > charIndex) {
1125 // The font has changed inside the character group. This might
1126 // happen in some weird situations, e.g. if
1127 // ATSUI decides in LTR text to put the glyph for character
1128 // 1 before the glyph for character 0, AND decides to
1129 // give character 1's glyph a different font from character
1130 // 0. This sucks because we can't then safely move this
1131 // glyph to be associated with our first character.
1132 // To handle this we'd have to do some funky hacking with
1133 // glyph advances and offsets so that the glyphs stay
1134 // associated with the right characters but they are
1135 // displayed out of order. Let's not do this for now,
1136 // in the hope that it doesn't come up. If it does come up,
1137 // at least we can fix it right here without changing
1138 // any other code.
1139 NS_ERROR("Font change inside character group!");
1140 // Be safe, just throw out this glyph
1141 continue;
1145 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
1146 if (!details)
1147 return;
1148 details->mAdvance = 0;
1149 details->mGlyphID = glyph->glyphID;
1150 details->mXOffset = 0;
1151 if (detailedGlyphs.Length() > 1) {
1152 details->mXOffset +=
1153 GetAdvanceAppUnits(advanceStart, glyph - advanceStart,
1154 aAppUnitsPerDevUnit);
1156 details->mYOffset = !aBaselineDeltas ? 0.0f
1157 : FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
1160 if (detailedGlyphs.Length() == 0) {
1161 NS_WARNING("No glyphs visible at all!");
1162 aRun->SetGlyphs(aOffsetInTextRun + charIndex, g.SetMissing(0), nsnull);
1163 return;
1166 // The advance width for the whole cluster
1167 PRInt32 clusterAdvance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
1168 if (aRun->IsRightToLeft())
1169 detailedGlyphs[0].mAdvance = clusterAdvance;
1170 else
1171 detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = clusterAdvance;
1172 g.SetComplex(aRun->IsClusterStart(charIndex), PR_TRUE, detailedGlyphs.Length());
1173 aRun->SetGlyphs(charIndex, g, detailedGlyphs.Elements());
1177 * Returns true if there are overrunning glyphs
1179 static PRBool
1180 PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
1181 const PRUnichar *aString, PRUint32 aLayoutLength,
1182 PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun,
1183 const PRPackedBool *aUnmatched)
1185 // AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
1186 // Fixed *advanceDeltas = static_cast<Fixed *>(advanceDeltasArray.mArray);
1187 // AutoLayoutDataArrayPtr deviceDeltasArray(aLine, kATSUDirectDataDeviceDeltaSInt16Array);
1188 AutoLayoutDataArrayPtr baselineDeltasArray(aLine, kATSUDirectDataBaselineDeltaFixedArray);
1189 Fixed *baselineDeltas = static_cast<Fixed *>(baselineDeltasArray.mArray);
1190 AutoLayoutDataArrayPtr glyphRecordsArray(aLine, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent);
1192 PRUint32 numGlyphs = glyphRecordsArray.mItemCount;
1193 if (numGlyphs == 0 || !glyphRecordsArray.mArray) {
1194 NS_WARNING("Failed to retrieve key glyph data");
1195 return PR_FALSE;
1197 ATSLayoutRecord *glyphRecords = static_cast<ATSLayoutRecord *>(glyphRecordsArray.mArray);
1198 NS_ASSERTION(!baselineDeltas || baselineDeltasArray.mItemCount == numGlyphs,
1199 "Mismatched glyph counts");
1200 NS_ASSERTION(glyphRecords[numGlyphs - 1].flags & kATSGlyphInfoTerminatorGlyph,
1201 "Last glyph should be a terminator glyph");
1202 --numGlyphs;
1203 if (numGlyphs == 0)
1204 return PR_FALSE;
1206 PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
1207 PRBool isRTL = aRun->IsRightToLeft();
1209 PRUint32 trailingCharactersToIgnore = aLayoutLength - aLengthInTextRun;
1210 if (trailingCharactersToIgnore > 0) {
1211 // The glyph array includes a glyph for the artificial trailing
1212 // non-whitespace character. Strip that glyph from the array now.
1213 if (isRTL) {
1214 NS_ASSERTION(glyphRecords[trailingCharactersToIgnore - 1].originalOffset == aLengthInTextRun*2,
1215 "Couldn't find glyph for trailing marker");
1216 glyphRecords += trailingCharactersToIgnore;
1217 } else {
1218 NS_ASSERTION(glyphRecords[numGlyphs - trailingCharactersToIgnore].originalOffset == aLengthInTextRun*2,
1219 "Couldn't find glyph for trailing marker");
1221 numGlyphs -= trailingCharactersToIgnore;
1222 if (numGlyphs == 0)
1223 return PR_FALSE;
1226 PRUint32 allFlags = 0;
1227 // Now process the glyphs, which should basically be in
1228 // the textrun's desired order, so process them in textrun order
1229 PRInt32 direction = PRInt32(aRun->GetDirection());
1230 while (numGlyphs > 0) {
1231 PRUint32 glyphIndex = isRTL ? numGlyphs - 1 : 0;
1232 PRUint32 lastOffset = glyphRecords[glyphIndex].originalOffset;
1233 PRUint32 glyphCount = 1;
1234 // Determine the glyphs for this ligature group
1235 while (glyphCount < numGlyphs) {
1236 ATSLayoutRecord *glyph = &glyphRecords[glyphIndex + direction*glyphCount];
1237 PRUint32 glyphOffset = glyph->originalOffset;
1238 PRUint32 nextIndex = isRTL ? glyphIndex - 1 : glyphIndex + 1;
1239 PRUint32 nextOffset;
1240 if (nextIndex >= 0 && nextIndex < numGlyphs) {
1241 ATSLayoutRecord *nextGlyph = &glyphRecords[nextIndex + direction*glyphCount];
1242 nextOffset = nextGlyph->originalOffset;
1244 else
1245 nextOffset = glyphOffset;
1246 allFlags |= glyph->flags;
1247 if (glyphOffset <= lastOffset || nextOffset <= lastOffset) {
1248 // Always add the current glyph to the ligature group if it's for the same
1249 // character as a character whose glyph is already in the group,
1250 // or an earlier character. The latter can happen because ATSUI
1251 // sometimes visually reorders glyphs. One case of this is that DEVANAGARI
1252 // VOWEL I can have its glyph displayed before the glyph for the consonant
1253 // that it's logically after (even though this is all left-to-right text).
1254 // Another case is that a sequence of RA; VIRAMA; <consonant> ; <vowel> is
1255 // reordered to <consonant> ; <vowel> ; RA; VIRAMA.
1256 // In these cases we need to make sure that the whole sequence of glyphs is
1257 // processed as a single cluster.
1258 } else {
1259 // We could be at the end of a character group
1260 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
1261 // Next character is a normal character, stop the group here
1262 break;
1264 if (aUnmatched && aUnmatched[glyphOffset/2]) {
1265 // Next character is ummatched, so definitely stop the group here
1266 break;
1268 // Otherwise the next glyph is, we assume, a ligature continuation.
1269 // Record that this character too is part of the group
1270 lastOffset = glyphOffset;
1272 ++glyphCount;
1274 if (isRTL) {
1275 SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - glyphCount,
1276 glyphCount,
1277 baselineDeltas ? baselineDeltas + numGlyphs - glyphCount : nsnull,
1278 appUnitsPerDevUnit, aRun, aOffsetInTextRun,
1279 aUnmatched, aString, aLengthInTextRun);
1280 } else {
1281 SetGlyphsForCharacterGroup(glyphRecords,
1282 glyphCount, baselineDeltas,
1283 appUnitsPerDevUnit, aRun, aOffsetInTextRun,
1284 aUnmatched, aString, aLengthInTextRun);
1285 glyphRecords += glyphCount;
1286 if (baselineDeltas) {
1287 baselineDeltas += glyphCount;
1290 numGlyphs -= glyphCount;
1293 return (allFlags & ATSUI_OVERRUNNING_GLYPH_FLAG) != 0;
1296 struct PostLayoutCallbackClosure {
1297 gfxTextRun *mTextRun;
1298 const PRUnichar *mString;
1299 PRUint32 mLayoutLength;
1300 PRUint32 mOffsetInTextRun;
1301 PRUint32 mLengthInTextRun;
1302 // Either null or an array of stringlength booleans set to true for
1303 // each character that did not match any fonts
1304 nsAutoArrayPtr<PRPackedBool> mUnmatchedChars;
1305 // The callback *sets* this to indicate whether there were overrunning glyphs
1306 PRPackedBool mOverrunningGlyphs;
1309 // This is really disgusting, but the ATSUI refCon thing is also disgusting
1310 static PostLayoutCallbackClosure *gCallbackClosure = nsnull;
1312 static OSStatus
1313 PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
1314 ATSULineRef iLineRef,
1315 UInt32 iRefCon,
1316 void *iOperationCallbackParameterPtr,
1317 ATSULayoutOperationCallbackStatus *oCallbackStatus)
1319 gCallbackClosure->mOverrunningGlyphs =
1320 PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
1321 gCallbackClosure->mString,
1322 gCallbackClosure->mLayoutLength,
1323 gCallbackClosure->mOffsetInTextRun,
1324 gCallbackClosure->mLengthInTextRun,
1325 gCallbackClosure->mUnmatchedChars);
1326 *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
1327 return noErr;
1330 // xxx - leaving this here for now, probably belongs in platform code somewhere
1332 eFontPrefLang
1333 GetFontPrefLangFor(PRUint8 aUnicodeRange)
1335 switch (aUnicodeRange) {
1336 case kRangeSetLatin: return eFontPrefLang_Western;
1337 case kRangeCyrillic: return eFontPrefLang_Cyrillic;
1338 case kRangeGreek: return eFontPrefLang_Greek;
1339 case kRangeTurkish: return eFontPrefLang_Turkish;
1340 case kRangeHebrew: return eFontPrefLang_Hebrew;
1341 case kRangeArabic: return eFontPrefLang_Arabic;
1342 case kRangeBaltic: return eFontPrefLang_Baltic;
1343 case kRangeThai: return eFontPrefLang_Thai;
1344 case kRangeKorean: return eFontPrefLang_Korean;
1345 case kRangeJapanese: return eFontPrefLang_Japanese;
1346 case kRangeSChinese: return eFontPrefLang_ChineseCN;
1347 case kRangeTChinese: return eFontPrefLang_ChineseTW;
1348 case kRangeDevanagari: return eFontPrefLang_Devanagari;
1349 case kRangeTamil: return eFontPrefLang_Tamil;
1350 case kRangeArmenian: return eFontPrefLang_Armenian;
1351 case kRangeBengali: return eFontPrefLang_Bengali;
1352 case kRangeCanadian: return eFontPrefLang_Canadian;
1353 case kRangeEthiopic: return eFontPrefLang_Ethiopic;
1354 case kRangeGeorgian: return eFontPrefLang_Georgian;
1355 case kRangeGujarati: return eFontPrefLang_Gujarati;
1356 case kRangeGurmukhi: return eFontPrefLang_Gurmukhi;
1357 case kRangeKhmer: return eFontPrefLang_Khmer;
1358 case kRangeMalayalam: return eFontPrefLang_Malayalam;
1359 case kRangeSetCJK: return eFontPrefLang_CJKSet;
1360 default: return eFontPrefLang_Others;
1364 // 361695 - ATSUI only does glyph mirroring when the font contains a 'prop' table
1365 // with glyph mirroring info, the character mirroring has to be done manually in the
1366 // fallback case. Only used for RTL text runs. The autoptr for the mirrored copy
1367 // is owned by the calling routine.
1369 // MirrorSubstring - Do Unicode mirroring on characters within a substring. If mirroring
1370 // needs to be done, copy the original string and change the ATSUI layout to use the mirrored copy.
1372 // @param layout ATSUI layout for the entire text run
1373 // @param mirrorStr container used for mirror string, null until a mirrored character is found
1374 // @param aString original string
1375 // @param aLength length of the original string
1376 // @param runStart start offset of substring to be mirrored
1377 // @param runLength length of substring to be mirrored
1379 static void MirrorSubstring(ATSUTextLayout layout, nsAutoArrayPtr<PRUnichar>& mirroredStr,
1380 const PRUnichar *aString, PRUint32 aLength,
1381 UniCharArrayOffset runStart, UniCharCount runLength)
1383 UniCharArrayOffset off;
1385 // do the mirroring manually!!
1386 for (off = runStart; off < runStart + runLength; off++) {
1387 PRUnichar mirroredChar;
1389 mirroredChar = (PRUnichar) SymmSwap(aString[off]);
1390 if (mirroredChar != aString[off]) {
1391 // string contains characters that need to be mirrored
1392 if (mirroredStr == NULL) {
1394 // copy the string
1395 mirroredStr = new PRUnichar[aLength];
1396 memcpy(mirroredStr, aString, sizeof(PRUnichar) * aLength);
1398 // adjust the layout
1399 ATSUTextMoved(layout, mirroredStr);
1402 mirroredStr[off] = mirroredChar;
1409 static ATSUStyle
1410 SetLayoutRangeToFont(ATSUTextLayout layout, ATSUStyle mainStyle, UniCharArrayOffset offset,
1411 UniCharCount length, ATSUFontID fontID)
1413 ATSUStyle subStyle;
1414 ATSUCreateStyle (&subStyle);
1415 ATSUCopyAttributes (mainStyle, subStyle);
1417 ATSUAttributeTag fontTags[] = { kATSUFontTag };
1418 ByteCount fontArgSizes[] = { sizeof(ATSUFontID) };
1419 ATSUAttributeValuePtr fontArgs[] = { &fontID };
1421 ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
1423 // apply the new style to the layout for the changed substring
1424 ATSUSetRunStyle (layout, subStyle, offset, length);
1426 return subStyle;
1429 PRBool
1430 gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
1431 const PRUnichar *aString, PRUint32 aLength,
1432 PRUint32 aLayoutStart, PRUint32 aLayoutLength,
1433 PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun)
1435 OSStatus status;
1436 gfxAtsuiFont *firstFont = GetFontAt(0);
1437 ATSUStyle mainStyle = firstFont->GetATSUStyle();
1438 nsTArray<ATSUStyle> stylesToDispose;
1439 const PRUnichar *layoutString = aString + aLayoutStart;
1441 #ifdef DUMP_TEXT_RUNS
1442 NS_ConvertUTF16toUTF8 str(layoutString, aLengthInTextRun);
1443 NS_ConvertUTF16toUTF8 families(mFamilies);
1444 PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,\
1445 ("InitTextRun %p fontgroup %p (%s) lang: %s len %d TEXTRUN \"%s\" ENDTEXTRUN\n",
1446 aRun, this, families.get(), mStyle.langGroup.get(), aLengthInTextRun, str.get()) );
1447 PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,
1448 ("InitTextRun font: %s user font set: %p (%8.8x)\n",
1449 NS_ConvertUTF16toUTF8(firstFont->GetUniqueName()).get(), mUserFontSet, PRUint32(mCurrGeneration)) );
1450 #endif
1452 if (aRun->GetFlags() & TEXT_DISABLE_OPTIONAL_LIGATURES) {
1453 status = ATSUCreateAndCopyStyle(mainStyle, &mainStyle);
1454 if (status == noErr) {
1455 stylesToDispose.AppendElement(mainStyle);
1456 DisableCommonLigatures(mainStyle);
1460 UniCharCount runLengths = aLengthInTextRun;
1461 ATSUTextLayout layout;
1462 // Create the text layout for the whole string, but only produce glyphs
1463 // for the text inside LRO/RLO - PDF, if present. For wrapped strings
1464 // we do need to produce glyphs for the trailing non-whitespace
1465 // character to ensure that ATSUI treats all whitespace as non-trailing.
1466 status = ATSUCreateTextLayoutWithTextPtr
1467 (aString,
1468 aLayoutStart,
1469 aLayoutLength,
1470 aLength,
1472 &runLengths,
1473 &mainStyle,
1474 &layout);
1475 // XXX error check here?
1477 PostLayoutCallbackClosure closure;
1478 closure.mTextRun = aRun;
1479 closure.mString = layoutString;
1480 closure.mLayoutLength = aLayoutLength;
1481 closure.mOffsetInTextRun = aOffsetInTextRun;
1482 closure.mLengthInTextRun = aLengthInTextRun;
1483 NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
1484 gCallbackClosure = &closure;
1486 ATSULayoutOperationOverrideSpecifier override;
1487 override.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
1488 override.overrideUPP = PostLayoutOperationCallback;
1490 // Set up our layout attributes
1491 ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
1493 static ATSUAttributeTag layoutTags[] = {
1494 kATSULineLayoutOptionsTag,
1495 kATSULayoutOperationOverrideTag
1497 static ByteCount layoutArgSizes[] = {
1498 sizeof(ATSLineLayoutOptions),
1499 sizeof(ATSULayoutOperationOverrideSpecifier)
1502 ATSUAttributeValuePtr layoutArgs[] = {
1503 &lineLayoutOptions,
1504 &override
1506 ATSUSetLayoutControls(layout,
1507 NS_ARRAY_LENGTH(layoutTags),
1508 layoutTags,
1509 layoutArgSizes,
1510 layoutArgs);
1512 /* Now go through and update the styles for the text, based on font matching. */
1514 nsAutoArrayPtr<PRUnichar> mirroredStr;
1516 UniCharArrayOffset runStart = aLayoutStart;
1517 UniCharCount runLength = aLengthInTextRun;
1519 /// ---- match fonts using cmap info instead of ATSUI ----
1521 nsTArray<gfxTextRange> fontRanges;
1523 ComputeRanges(fontRanges, aString, runStart, runStart + runLength);
1525 PRUint32 r, numRanges = fontRanges.Length();
1527 for (r = 0; r < numRanges; r++) {
1528 const gfxTextRange& range = fontRanges[r];
1530 gfxAtsuiFont *matchedFont;
1531 UniCharCount matchedLength;
1533 // match a range of text
1534 matchedLength = range.Length();
1535 matchedFont = static_cast<gfxAtsuiFont*> (range.font ? range.font.get() : nsnull);
1537 #ifdef DUMP_TEXT_RUNS
1538 PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG, ("InitTextRun %p fontgroup %p font %p match %s (%d-%d)", aRun, this, matchedFont, (matchedFont ? NS_ConvertUTF16toUTF8(matchedFont->GetUniqueName()).get() : "<null>"), runStart, matchedLength));
1539 #endif
1540 //printf("Matched: %s [%d, %d)\n", (matchedFont ? NS_ConvertUTF16toUTF8(matchedFont->GetUniqueName()).get() : "<null>"), runStart, runStart + matchedLength);
1542 // in the RTL case, handle fallback mirroring
1543 if (aRun->IsRightToLeft() && matchedFont && !matchedFont->HasMirroringInfo()) {
1544 MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
1547 // if no matched font, mark as unmatched
1548 if (!matchedFont) {
1550 aRun->AddGlyphRun(firstFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
1552 if (!closure.mUnmatchedChars) {
1553 closure.mUnmatchedChars = new PRPackedBool[aLength];
1554 if (closure.mUnmatchedChars) {
1555 //printf("initializing %d\n", aLength);
1556 memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
1560 if (closure.mUnmatchedChars) {
1561 //printf("setting %d unmatched from %d\n", matchedLength, runStart - headerChars);
1562 memset(closure.mUnmatchedChars.get() + runStart - aLayoutStart,
1563 PR_TRUE, matchedLength);
1566 } else {
1568 if (matchedFont != firstFont) {
1569 // create a new sub-style and add it to the layout
1570 ATSUStyle subStyle = SetLayoutRangeToFont(layout, mainStyle, runStart, matchedLength, matchedFont->GetATSUFontID());
1571 stylesToDispose.AppendElement(subStyle);
1574 // add a glyph run for the matched substring
1575 aRun->AddGlyphRun(matchedFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
1578 runStart += matchedLength;
1579 runLength -= matchedLength;
1583 /// -------------------------------------------------
1585 // xxx - for some reason, this call appears to be needed to avoid assertions about glyph runs not being coalesced properly
1586 // this appears to occur when there are unmatched characters in the text run
1587 aRun->SortGlyphRuns();
1589 // Trigger layout so that our callback fires. We don't actually care about
1590 // the result of this call.
1591 ATSTrapezoid trap;
1592 ItemCount trapCount;
1593 ATSUGetGlyphBounds(layout, 0, 0, aLayoutStart, aLengthInTextRun,
1594 kATSUseFractionalOrigins, 1, &trap, &trapCount);
1596 ATSUDisposeTextLayout(layout);
1598 aRun->AdjustAdvancesForSyntheticBold(aOffsetInTextRun, aLengthInTextRun);
1600 PRUint32 i;
1601 for (i = 0; i < stylesToDispose.Length(); ++i) {
1602 ATSUDisposeStyle(stylesToDispose[i]);
1604 gCallbackClosure = nsnull;
1605 return !closure.mOverrunningGlyphs;