Bug 435739 Poor performance of Firefox 3 with no X RENDER extension
[wine-gecko.git] / gfx / thebes / src / gfxAtsuiFonts.cpp
blob5241195ce049605a53d1e573daf18ee52def7a01
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"
62 #include "nsUnicodeRange.h"
64 // Uncomment this to dump all text runs created to stdout
65 // #define DUMP_TEXT_RUNS
67 #ifdef DUMP_TEXT_RUNS
68 static PRLogModuleInfo *gAtsuiTextRunLog = PR_NewLogModule("atsuiTextRun");
69 #endif
71 #define ROUND(x) (floor((x) + 0.5))
73 /* 10.5 SDK includes a funky new definition of FloatToFixed, so reset to old-style definition */
74 #ifdef FloatToFixed
75 #undef FloatToFixed
76 #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
77 #endif
79 /* We might still need this for fast-pathing, but we'll see */
80 #if 0
81 OSStatus ATSUGetStyleGroup(ATSUStyle style, void **styleGroup);
82 OSStatus ATSUDisposeStyleGroup(void *styleGroup);
83 OSStatus ATSUConvertCharToGlyphs(void *styleGroup,
84 PRunichar *buffer
85 unsigned int bufferLength,
86 void *glyphVector);
87 OSStatus ATSInitializeGlyphVector(int size, void *glyphVectorPtr);
88 OSStatus ATSClearGlyphVector(void *glyphVectorPtr);
89 #endif
91 eFontPrefLang GetFontPrefLangFor(PRUint8 aUnicodeRange);
93 gfxAtsuiFont::gfxAtsuiFont(MacOSFontEntry *aFontEntry,
94 const gfxFontStyle *fontStyle, PRBool aNeedsBold)
95 : gfxFont(aFontEntry->Name(), fontStyle),
96 mFontStyle(fontStyle), mATSUStyle(nsnull), mFontEntry(aFontEntry),
97 mHasMirroring(PR_FALSE), mHasMirroringLookedUp(PR_FALSE), mAdjustedSize(0.0f)
99 ATSUFontID fontID = mFontEntry->GetFontID();
100 ATSFontRef fontRef = FMGetATSFontRefFromFont(fontID);
102 // determine whether synthetic bolding is needed
103 PRInt8 baseWeight, weightDistance;
104 mFontStyle->ComputeWeightAndOffset(&baseWeight, &weightDistance);
105 PRUint16 targetWeight = (baseWeight * 100) + (weightDistance * 100);
107 // synthetic bolding occurs when font itself is not a bold-face and either the absolute weight
108 // is at least 600 or the relative weight (e.g. 402) implies a darker face than the ones available.
109 // note: this means that (1) lighter styles *never* synthetic bold and (2) synthetic bolding always occurs
110 // at the first bolder step beyond available faces, no matter how light the boldest face
111 if (!mFontEntry->IsBold()
112 && ((weightDistance == 0 && targetWeight >= 600) || (weightDistance > 0 && aNeedsBold)))
114 mSyntheticBoldOffset = 1; // devunit offset when double-striking text to fake boldness
117 InitMetrics(fontID, fontRef);
119 mFontFace = cairo_quartz_font_face_create_for_atsu_font_id(fontID);
121 cairo_matrix_t sizeMatrix, ctm;
122 cairo_matrix_init_identity(&ctm);
123 cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
125 // synthetic oblique by skewing via the font matrix
126 PRBool needsOblique = (!mFontEntry->IsItalicStyle() && (mFontStyle->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)));
128 if (needsOblique) {
129 double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0);
131 cairo_matrix_t style;
132 cairo_matrix_init(&style,
133 1, //xx
134 0, //yx
135 -1 * skewfactor, //xy
136 1, //yy
137 0, //x0
138 0); //y0
139 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
142 cairo_font_options_t *fontOptions = cairo_font_options_create();
144 // turn off font anti-aliasing based on user pref setting
145 if (mAdjustedSize <= (float) gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
146 cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
147 //printf("font: %s, size: %f, disabling anti-aliasing\n", NS_ConvertUTF16toUTF8(mName).get(), mAdjustedSize);
150 mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, fontOptions);
151 cairo_font_options_destroy(fontOptions);
152 NS_ASSERTION(cairo_scaled_font_status(mScaledFont) == CAIRO_STATUS_SUCCESS,
153 "Failed to create scaled font");
157 ATSUFontID gfxAtsuiFont::GetATSUFontID()
159 return mFontEntry->GetFontID();
162 void
163 gfxAtsuiFont::InitMetrics(ATSUFontID aFontID, ATSFontRef aFontRef)
165 /* Create the ATSUStyle */
167 ATSUAttributeTag styleTags[] = {
168 kATSUFontTag,
169 kATSUSizeTag,
170 kATSUFontMatrixTag
173 ByteCount styleArgSizes[] = {
174 sizeof(ATSUFontID),
175 sizeof(Fixed),
176 sizeof(CGAffineTransform),
177 sizeof(Fract)
180 gfxFloat size =
181 PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : GetStyle()->size), 1.0f);
183 //fprintf (stderr, "string: '%s', size: %f\n", NS_ConvertUTF16toUTF8(aString).get(), size);
185 // fSize is in points (72dpi)
186 Fixed fSize = FloatToFixed(size);
187 ATSUFontID fid = aFontID;
189 // make the font render right-side up
190 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
192 ATSUAttributeValuePtr styleArgs[] = {
193 &fid,
194 &fSize,
195 &transform,
198 if (mATSUStyle)
199 ATSUDisposeStyle(mATSUStyle);
201 ATSUCreateStyle(&mATSUStyle);
202 ATSUSetAttributes(mATSUStyle,
203 sizeof(styleTags)/sizeof(ATSUAttributeTag),
204 styleTags,
205 styleArgSizes,
206 styleArgs);
208 /* Now pull out the metrics */
210 ATSFontMetrics atsMetrics;
211 ATSFontGetHorizontalMetrics(aFontRef, kATSOptionFlagsDefault,
212 &atsMetrics);
214 if (atsMetrics.xHeight)
215 mMetrics.xHeight = atsMetrics.xHeight * size;
216 else
217 mMetrics.xHeight = GetCharHeight('x');
219 if (mAdjustedSize == 0.0f) {
220 if (mMetrics.xHeight != 0.0f && GetStyle()->sizeAdjust != 0.0f) {
221 gfxFloat aspect = mMetrics.xHeight / size;
222 mAdjustedSize = GetStyle()->GetAdjustedSize(aspect);
223 InitMetrics(aFontID, aFontRef);
224 return;
226 mAdjustedSize = size;
229 mMetrics.emHeight = size;
231 mMetrics.maxAscent = NS_ceil(atsMetrics.ascent * size);
232 mMetrics.maxDescent = NS_ceil(- (atsMetrics.descent * size));
234 mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
236 if (mMetrics.maxHeight - mMetrics.emHeight > 0)
237 mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
238 else
239 mMetrics.internalLeading = 0.0;
240 mMetrics.externalLeading = atsMetrics.leading * size;
242 mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
243 mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
245 mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
247 float xWidth = GetCharWidth('x');
248 if (atsMetrics.avgAdvanceWidth != 0.0)
249 mMetrics.aveCharWidth =
250 PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
251 else
252 mMetrics.aveCharWidth = xWidth;
254 mMetrics.aveCharWidth += mSyntheticBoldOffset;
256 if (mFontEntry->IsFixedPitch()) {
257 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
258 // advance than the average character width... this forces
259 // those fonts to be recognized like fixed pitch fonts by layout.
260 mMetrics.maxAdvance = mMetrics.aveCharWidth;
263 mMetrics.underlineOffset = atsMetrics.underlinePosition * size;
264 mMetrics.underlineSize = atsMetrics.underlineThickness * size;
266 mMetrics.subscriptOffset = mMetrics.xHeight;
267 mMetrics.superscriptOffset = mMetrics.xHeight;
269 mMetrics.strikeoutOffset = mMetrics.xHeight / 2.0;
270 mMetrics.strikeoutSize = mMetrics.underlineSize;
272 PRUint32 glyphID;
273 mMetrics.spaceWidth = GetCharWidth(' ', &glyphID);
274 mSpaceGlyph = glyphID;
276 SanitizeMetrics(&mMetrics, mFontEntry->FamilyEntry()->IsBadUnderlineFontFamily());
278 #if 0
279 fprintf (stderr, "Font: %p size: %f (fixed: %d)", this, size, gfxQuartzFontCache::SharedFontCache()->IsFixedPitch(aFontID));
280 fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
281 fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
282 fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading);
283 fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
284 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);
285 #endif
288 PRBool
289 gfxAtsuiFont::SetupCairoFont(gfxContext *aContext)
291 cairo_scaled_font_t *scaledFont = CairoScaledFont();
292 if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
293 // Don't cairo_set_scaled_font as that would propagate the error to
294 // the cairo_t, precluding any further drawing.
295 return PR_FALSE;
297 cairo_set_scaled_font(aContext->GetCairo(), scaledFont);
298 return PR_TRUE;
301 nsString
302 gfxAtsuiFont::GetUniqueName()
304 return mName;
307 float
308 gfxAtsuiFont::GetCharWidth(PRUnichar c, PRUint32 *aGlyphID)
310 // this sucks. There is a faster way to go from a char -> glyphs, but it
311 // requires using oodles of apple private interfaces. If we start caching
312 // gfxAtsuiFonts, then it might make sense to do that.
313 ATSUTextLayout layout;
315 UniCharCount one = 1;
316 ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
318 ATSTrapezoid trap;
319 ItemCount numBounds;
320 ATSUGetGlyphBounds(layout, FloatToFixed(0.0), FloatToFixed(0.0),
321 0, 1, kATSUseFractionalOrigins, 1, &trap, &numBounds);
323 float f =
324 FixedToFloat(PR_MAX(trap.upperRight.x, trap.lowerRight.x)) -
325 FixedToFloat(PR_MIN(trap.upperLeft.x, trap.lowerLeft.x));
327 if (aGlyphID) {
328 ATSUGlyphInfoArray glyphInfo;
329 ByteCount bytes = sizeof(glyphInfo);
330 ATSUGetGlyphInfo(layout, 0, 1, &bytes, &glyphInfo);
331 *aGlyphID = glyphInfo.glyphs[0].glyphID;
334 ATSUDisposeTextLayout(layout);
336 return f;
339 float
340 gfxAtsuiFont::GetCharHeight(PRUnichar c)
342 // this sucks. There is a faster way to go from a char -> glyphs, but it
343 // requires using oodles of apple private interfaces. If we start caching
344 // gfxAtsuiFonts, then it might make sense to do that.
345 ATSUTextLayout layout;
347 UniCharCount one = 1;
348 ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
350 Rect rect;
351 ATSUMeasureTextImage(layout, 0, 1, 0, 0, &rect);
353 ATSUDisposeTextLayout(layout);
355 return rect.bottom - rect.top;
358 gfxAtsuiFont::~gfxAtsuiFont()
360 cairo_scaled_font_destroy(mScaledFont);
361 cairo_font_face_destroy(mFontFace);
363 ATSUDisposeStyle(mATSUStyle);
366 const gfxFont::Metrics&
367 gfxAtsuiFont::GetMetrics()
369 return mMetrics;
372 void
373 gfxAtsuiFont::SetupGlyphExtents(gfxContext *aContext, PRUint32 aGlyphID,
374 PRBool aNeedTight, gfxGlyphExtents *aExtents)
376 ATSGlyphScreenMetrics metrics;
377 GlyphID glyph = aGlyphID;
378 OSStatus err = ATSUGlyphGetScreenMetrics(mATSUStyle, 1, &glyph, 0, false, false,
379 &metrics);
380 if (err != noErr)
381 return;
382 PRUint32 appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
384 if (!aNeedTight && metrics.topLeft.x >= 0 &&
385 -metrics.topLeft.y + metrics.height <= mMetrics.maxAscent &&
386 metrics.topLeft.y <= mMetrics.maxDescent) {
387 PRUint32 appUnitsWidth =
388 PRUint32(NS_ceil((metrics.topLeft.x + metrics.width)*appUnitsPerDevUnit));
389 if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
390 aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, PRUint16(appUnitsWidth));
391 return;
395 double d2a = appUnitsPerDevUnit;
396 gfxRect bounds(metrics.topLeft.x*d2a, (metrics.topLeft.y - metrics.height)*d2a,
397 metrics.width*d2a, metrics.height*d2a);
398 aExtents->SetTightGlyphExtents(aGlyphID, bounds);
401 PRBool
402 gfxAtsuiFont::HasMirroringInfo()
404 if (!mHasMirroringLookedUp) {
405 OSStatus status;
406 ByteCount size;
408 // 361695 - if the font has a 'prop' table, assume that ATSUI will handle glyph mirroring
409 status = ATSFontGetTable(GetATSUFontID(), 'prop', 0, 0, 0, &size);
410 mHasMirroring = (status == noErr);
411 mHasMirroringLookedUp = PR_TRUE;
414 return mHasMirroring;
417 PRBool gfxAtsuiFont::TestCharacterMap(PRUint32 aCh) {
418 return mFontEntry->TestCharacterMap(aCh);
421 MacOSFontEntry*
422 gfxAtsuiFont::GetFontEntry()
424 return mFontEntry.get();
428 * Look up the font in the gfxFont cache. If we don't find it, create one.
429 * In either case, add a ref and return it ---
430 * except for OOM in which case we do nothing and return null.
433 static already_AddRefed<gfxAtsuiFont>
434 GetOrMakeFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aStyle, PRBool aNeedsBold)
436 // the font entry name is the psname, not the family name
437 nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(aFontEntry->Name(), aStyle);
438 if (!font) {
439 font = new gfxAtsuiFont(aFontEntry, aStyle, aNeedsBold);
440 if (!font)
441 return nsnull;
442 gfxFontCache::GetCache()->AddNew(font);
444 gfxFont *f = nsnull;
445 font.swap(f);
446 return static_cast<gfxAtsuiFont *>(f);
449 gfxAtsuiFontGroup::gfxAtsuiFontGroup(const nsAString& families,
450 const gfxFontStyle *aStyle)
451 : gfxFontGroup(families, aStyle)
453 ForEachFont(FindATSUFont, this);
455 if (mFonts.Length() == 0) {
456 // XXX this will generate a list of the lang groups for which we have no
457 // default fonts for on the mac; we should fix this!
458 // Known:
459 // ja x-beng x-devanagari x-tamil x-geor x-ethi x-gujr x-mlym x-armn
460 // x-orya x-telu x-knda x-sinh
462 //fprintf (stderr, "gfxAtsuiFontGroup: %s [%s] -> %d fonts found\n", NS_ConvertUTF16toUTF8(families).get(), aStyle->langGroup.get(), mFonts.Length());
464 // If we get here, we most likely didn't have a default font for
465 // a specific langGroup. Let's just pick the default OSX
466 // user font.
468 PRBool needsBold;
469 MacOSFontEntry *defaultFont = gfxQuartzFontCache::SharedFontCache()->GetDefaultFont(aStyle, needsBold);
470 NS_ASSERTION(defaultFont, "invalid default font returned by GetDefaultFont");
472 nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(defaultFont, aStyle, needsBold);
474 if (font) {
475 mFonts.AppendElement(font);
479 mPageLang = gfxPlatform::GetFontPrefLangFor(mStyle.langGroup.get());
481 if (!mStyle.systemFont) {
482 for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
483 gfxAtsuiFont* font = static_cast<gfxAtsuiFont*>(mFonts[i].get());
484 if (font->GetFontEntry()->FamilyEntry()->IsBadUnderlineFontFamily()) {
485 gfxFloat first = mFonts[0]->GetMetrics().underlineOffset;
486 gfxFloat bad = font->GetMetrics().underlineOffset;
487 mUnderlineOffset = PR_MIN(first, bad);
488 break;
494 PRBool
495 gfxAtsuiFontGroup::FindATSUFont(const nsAString& aName,
496 const nsACString& aGenericName,
497 void *closure)
499 gfxAtsuiFontGroup *fontGroup = (gfxAtsuiFontGroup*) closure;
500 const gfxFontStyle *fontStyle = fontGroup->GetStyle();
502 gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
504 PRBool needsBold;
505 MacOSFontEntry *fe = fc->FindFontForFamily(aName, fontStyle, needsBold);
507 if (fe && !fontGroup->HasFont(fe->GetFontID())) {
508 nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(fe, fontStyle, needsBold);
509 if (font) {
510 fontGroup->mFonts.AppendElement(font);
514 return PR_TRUE;
517 gfxFontGroup *
518 gfxAtsuiFontGroup::Copy(const gfxFontStyle *aStyle)
520 return new gfxAtsuiFontGroup(mFamilies, aStyle);
523 static void
524 SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
526 TextBreakLocatorRef locator;
527 OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask,
528 &locator);
529 if (status != noErr)
530 return;
531 UniCharArrayOffset breakOffset = 0;
532 UCTextBreakOptions options = kUCTextBreakLeadingEdgeMask;
533 PRUint32 length = aTextRun->GetLength();
534 while (breakOffset < length) {
535 UniCharArrayOffset next;
536 status = UCFindTextBreak(locator, kUCTextBreakClusterMask, options,
537 aString, length, breakOffset, &next);
538 if (status != noErr)
539 break;
540 options |= kUCTextBreakIterateMask;
541 PRUint32 i;
542 for (i = breakOffset + 1; i < next; ++i) {
543 gfxTextRun::CompressedGlyph g;
544 // Remember that this character is not the start of a cluster by
545 // setting its glyph data to "not a cluster start", "is a
546 // ligature start", with no glyphs.
547 aTextRun->SetGlyphs(i, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
549 breakOffset = next;
551 UCDisposeTextBreakLocator(&locator);
554 #define UNICODE_LRO 0x202d
555 #define UNICODE_RLO 0x202e
556 #define UNICODE_PDF 0x202c
558 static void
559 AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString)
561 static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO };
562 aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);
565 // Returns the number of trailing characters that should be part of the
566 // layout but should be ignored
567 static PRUint32
568 AppendDirectionalIndicatorEnd(PRBool aNeedDirection, nsAString& aString)
570 // Ensure that we compute the full advance width for the last character
571 // in the string --- we don't want that character to form a kerning
572 // pair (or a ligature) with the '.' we may append next,
573 // so we append a space now.
574 // Even if the character is the last character in the layout,
575 // we want its width to be determined as if it had a space after it,
576 // for consistency with the bidi path and during textrun caching etc.
577 aString.Append(' ');
578 if (!aNeedDirection)
579 return 1;
581 // Ensure that none of the whitespace in the run is considered "trailing"
582 // by ATSUI's bidi algorithm
583 aString.Append('.');
584 aString.Append(UNICODE_PDF);
585 return 2;
589 * Given a textrun and an offset into that textrun, we need to choose a length
590 * for the substring of the textrun that we should analyze next. The length
591 * should be <= aMaxLength if possible. It must always end at a cluster
592 * boundary and it should end at the end of the textrun or at the
593 * boundary of a space if possible.
595 static PRUint32
596 FindTextRunSegmentLength(gfxTextRun *aTextRun, PRUint32 aOffset, PRUint32 aMaxLength)
598 if (aOffset + aMaxLength >= aTextRun->GetLength()) {
599 // The remaining part of the textrun fits within the max length,
600 // so just use it.
601 return aTextRun->GetLength() - aOffset;
604 // Try to end the segment before or after a space, since spaces don't kern
605 // or ligate.
606 PRUint32 end;
607 for (end = aOffset + aMaxLength; end > aOffset; --end) {
608 if (aTextRun->IsClusterStart(end) &&
609 (aTextRun->GetChar(end) == ' ' || aTextRun->GetChar(end - 1) == ' '))
610 return end - aOffset;
613 // Try to end the segment at the last cluster boundary.
614 for (end = aOffset + aMaxLength; end > aOffset; --end) {
615 if (aTextRun->IsClusterStart(end))
616 return end - aOffset;
619 // I guess we just have to return a segment that's the entire cluster
620 // starting at aOffset.
621 for (end = aOffset + 1; end < aTextRun->GetLength(); ++end) {
622 if (aTextRun->IsClusterStart(end))
623 return end - aOffset;
625 return aTextRun->GetLength() - aOffset;
628 PRUint32
629 gfxAtsuiFontGroup::GuessMaximumStringLength()
631 // ATSUI can't handle offsets of more than 32K pixels. This function
632 // guesses a string length that ATSUI will be able to handle. We want to
633 // get the right answer most of the time, but if we're wrong in either
634 // direction, we won't break things: if we guess too large, our glyph
635 // processing will detect ATSUI's failure and retry with a smaller limit.
636 // If we guess too small, we'll just break the string into more pieces
637 // than we strictly needed to.
638 // The basic calculation is just 32k pixels divided by the font max-advance,
639 // but we need to be a bit careful to avoid math errors.
640 PRUint32 maxAdvance = PRUint32(GetFontAt(0)->GetMetrics().maxAdvance);
641 PRUint32 chars = 0x7FFF/PR_MAX(1, maxAdvance);
642 return PR_MAX(1, chars);
646 * ATSUI can't handle more than 32K pixels of text. We can easily have
647 * textruns longer than that. Our strategy here is to divide the textrun up
648 * into pieces each of which is less than 32K pixels wide. We pick a number
649 * of characters 'maxLen' such that the first font's max-advance times that
650 * number of characters is less than 32K pixels; then we try glyph conversion
651 * of the string broken up into chunks each with no more than 'maxLen'
652 * characters. That could fail (e.g. if fallback fonts are used); if it does,
653 * we retry with a smaller maxLen. When breaking up the string into chunks
654 * we prefer to break at space boundaries because spaces don't kern or ligate
655 * with other characters, usually. We insist on breaking at cluster boundaries.
656 * If the font size is incredibly huge and/or clusters are very large, this
657 * could mean that we actually put more than 'maxLen' characters in a chunk.
660 gfxTextRun *
661 gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
662 const Parameters *aParams, PRUint32 aFlags)
664 gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
665 if (!textRun)
666 return nsnull;
668 textRun->RecordSurrogates(aString);
669 SetupClusterBoundaries(textRun, aString);
671 PRUint32 maxLen;
672 nsAutoString utf16;
673 for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
674 PRUint32 start = 0;
675 while (start < aLength) {
676 PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
678 utf16.Truncate();
679 AppendDirectionalIndicatorStart(aFlags, utf16);
680 PRUint32 layoutStart = utf16.Length();
681 utf16.Append(aString + start, len);
682 // Ensure that none of the whitespace in the run is considered "trailing"
683 // by ATSUI's bidi algorithm
684 PRUint32 trailingCharsToIgnore =
685 AppendDirectionalIndicatorEnd(PR_TRUE, utf16);
686 PRUint32 layoutLength = len + trailingCharsToIgnore;
687 if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
688 layoutStart, layoutLength,
689 start, len) && maxLen > 1)
690 break;
691 start += len;
693 if (start == aLength)
694 break;
695 textRun->ResetGlyphRuns();
698 textRun->FetchGlyphExtents(aParams->mContext);
700 return textRun;
703 gfxTextRun *
704 gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
705 const Parameters *aParams, PRUint32 aFlags)
707 NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit");
708 gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
709 if (!textRun)
710 return nsnull;
712 PRUint32 maxLen;
713 nsAutoString utf16;
714 for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
715 PRUint32 start = 0;
716 while (start < aLength) {
717 PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
719 nsDependentCSubstring cString(reinterpret_cast<const char*>(aString + start),
720 reinterpret_cast<const char*>(aString + start + len));
721 utf16.Truncate();
722 PRBool wrapBidi = (aFlags & TEXT_IS_RTL) != 0;
723 if (wrapBidi) {
724 AppendDirectionalIndicatorStart(aFlags, utf16);
726 PRUint32 layoutStart = utf16.Length();
727 AppendASCIItoUTF16(cString, utf16);
728 PRUint32 trailingCharsToIgnore =
729 AppendDirectionalIndicatorEnd(wrapBidi, utf16);
730 PRUint32 layoutLength = len + trailingCharsToIgnore;
731 if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
732 layoutStart, layoutLength,
733 start, len) && maxLen > 1)
734 break;
735 start += len;
737 if (start == aLength)
738 break;
739 textRun->ResetGlyphRuns();
742 textRun->FetchGlyphExtents(aParams->mContext);
744 return textRun;
747 PRBool
748 gfxAtsuiFontGroup::HasFont(ATSUFontID fid)
750 for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
751 if (fid == static_cast<gfxAtsuiFont *>(mFonts.ElementAt(i).get())->GetATSUFontID())
752 return PR_TRUE;
754 return PR_FALSE;
757 struct PrefFontCallbackData {
758 PrefFontCallbackData(nsTArray<nsRefPtr<MacOSFamilyEntry> >& aFamiliesArray)
759 : mPrefFamilies(aFamiliesArray)
762 nsTArray<nsRefPtr<MacOSFamilyEntry> >& mPrefFamilies;
764 static PRBool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure)
766 PrefFontCallbackData *prefFontData = (PrefFontCallbackData*) aClosure;
768 MacOSFamilyEntry *family = gfxQuartzFontCache::SharedFontCache()->FindFamily(aName);
769 if (family) {
770 prefFontData->mPrefFamilies.AppendElement(family);
772 return PR_TRUE;
777 already_AddRefed<gfxAtsuiFont>
778 gfxAtsuiFontGroup::WhichPrefFontSupportsChar(PRUint32 aCh)
780 // FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs
781 if (aCh > 0xFFFF)
782 return nsnull;
784 // get the pref font list if it hasn't been set up already
785 PRUint32 unicodeRange = FindCharUnicodeRange(aCh);
786 eFontPrefLang charLang = GetFontPrefLangFor(unicodeRange);
788 // if the last pref font was the first family in the pref list, no need to recheck through a list of families
789 if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && mLastPrefFont->TestCharacterMap(aCh)) {
790 nsRefPtr<gfxAtsuiFont> prefFont = mLastPrefFont;
791 return prefFont.forget();
794 // based on char lang and page lang, set up list of pref lang fonts to check
795 eFontPrefLang prefLangs[kMaxLenPrefLangList];
796 PRUint32 i, numLangs = 0;
798 gfxPlatformMac *macPlatform = gfxPlatformMac::GetPlatform();
799 macPlatform->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
801 for (i = 0; i < numLangs; i++) {
802 nsAutoTArray<nsRefPtr<MacOSFamilyEntry>, 5> families;
803 eFontPrefLang currentLang = prefLangs[i];
805 gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
807 // get the pref families for a single pref lang
808 if (!fc->GetPrefFontFamilyEntries(currentLang, &families)) {
809 eFontPrefLang prefLangsToSearch[1] = { currentLang };
810 PrefFontCallbackData prefFontData(families);
811 gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry,
812 &prefFontData);
813 fc->SetPrefFontFamilyEntries(currentLang, families);
816 // find the first pref font that includes the character
817 PRUint32 i, numPrefs;
818 numPrefs = families.Length();
819 for (i = 0; i < numPrefs; i++) {
820 // look up the appropriate face
821 MacOSFamilyEntry *family = families[i];
822 if (!family) continue;
824 // if a pref font is used, it's likely to be used again in the same text run.
825 // the style doesn't change so the face lookup can be cached rather than calling
826 // GetOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent
827 // pref font lookups
828 if (family == mLastPrefFamily && mLastPrefFont->TestCharacterMap(aCh)) {
829 nsRefPtr<gfxAtsuiFont> prefFont = mLastPrefFont;
830 return prefFont.forget();
833 PRBool needsBold;
834 MacOSFontEntry *fe = family->FindFont(&mStyle, needsBold);
835 // if ch in cmap, create and return a gfxFont
836 if (fe && fe->TestCharacterMap(aCh)) {
837 nsRefPtr<gfxAtsuiFont> prefFont = GetOrMakeFont(fe, &mStyle, needsBold);
838 mLastPrefFamily = family;
839 mLastPrefFont = prefFont;
840 mLastPrefLang = charLang;
841 mLastPrefFirstFont = (i == 0);
842 return prefFont.forget();
848 return nsnull;
851 already_AddRefed<gfxAtsuiFont>
852 gfxAtsuiFontGroup::FindFontForChar(PRUint32 aCh, PRUint32 aPrevCh, PRUint32 aNextCh, gfxAtsuiFont* aPrevMatchedFont)
854 nsRefPtr<gfxAtsuiFont> selectedFont;
856 // if this character or the next one is a joiner use the
857 // same font as the previous range if we can
858 if (gfxFontUtils::IsJoiner(aCh) || gfxFontUtils::IsJoiner(aPrevCh) || gfxFontUtils::IsJoiner(aNextCh)) {
859 if (aPrevMatchedFont && aPrevMatchedFont->TestCharacterMap(aCh)) {
860 selectedFont = aPrevMatchedFont;
861 return selectedFont.forget();
865 // 1. check fonts in the font group
866 selectedFont = WhichFontSupportsChar(mFonts, aCh);
868 // don't look in other fonts if the character is in a Private Use Area
869 if ((aCh >= 0xE000 && aCh <= 0xF8FF) ||
870 (aCh >= 0xF0000 && aCh <= 0x10FFFD))
871 return selectedFont.forget();
872 if ( selectedFont )
873 return selectedFont.forget();
875 // 2. search pref fonts if none of the font group fonts match
876 // FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs
877 if ((selectedFont = WhichPrefFontSupportsChar(aCh))) {
878 return selectedFont.forget();
881 // 3. use fallback fonts
882 // -- before searching for something else check the font used for the previous character
883 if (!selectedFont && aPrevMatchedFont && aPrevMatchedFont->TestCharacterMap(aCh)) {
884 selectedFont = aPrevMatchedFont;
885 return selectedFont.forget();
888 // -- otherwise look for other stuff
889 if (!selectedFont) {
890 MacOSFontEntry *fe;
892 fe = gfxQuartzFontCache::SharedFontCache()->FindFontForChar(aCh, GetFontAt(0));
893 if (fe) {
894 selectedFont = GetOrMakeFont(fe, &mStyle, PR_FALSE); // ignore bolder considerations in system fallback case...
895 return selectedFont.forget();
899 return nsnull;
903 * Simple wrapper for ATSU "direct data arrays"
905 class AutoLayoutDataArrayPtr {
906 public:
907 AutoLayoutDataArrayPtr(ATSULineRef aLineRef,
908 ATSUDirectDataSelector aSelector)
909 : mLineRef(aLineRef), mSelector(aSelector)
911 OSStatus status =
912 ATSUDirectGetLayoutDataArrayPtrFromLineRef(aLineRef,
913 aSelector, PR_FALSE, &mArray, &mItemCount);
914 if (status != noErr) {
915 mArray = NULL;
916 mItemCount = 0;
919 ~AutoLayoutDataArrayPtr() {
920 if (mArray) {
921 ATSUDirectReleaseLayoutDataArrayPtr(mLineRef, mSelector, &mArray);
925 void *mArray;
926 ItemCount mItemCount;
928 private:
929 ATSULineRef mLineRef;
930 ATSUDirectDataSelector mSelector;
933 #define ATSUI_SPECIAL_GLYPH_ID 0xFFFF
935 * This flag seems to be set on glyphs that have overrun the 32K pixel
936 * limit in ATSUI.
938 #define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
941 * Calculate the advance in appunits of a run of ATSUI glyphs
943 static PRInt32
944 GetAdvanceAppUnits(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
945 PRUint32 aAppUnitsPerDevUnit)
947 Fixed fixedAdvance = aGlyphs[aGlyphCount].realPos - aGlyphs->realPos;
948 return PRInt32((PRInt64(fixedAdvance)*aAppUnitsPerDevUnit + (1 << 15)) >> 16);
952 * Given a run of ATSUI glyphs that should be treated as a single cluster/ligature,
953 * store them in the textrun at the appropriate character and set the
954 * other characters involved to be ligature/cluster continuations as appropriate.
956 static void
957 SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
958 Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit,
959 gfxTextRun *aRun, PRUint32 aOffsetInTextRun,
960 const PRPackedBool *aUnmatched,
961 const PRUnichar *aString,
962 const PRUint32 aLength)
964 NS_ASSERTION(aGlyphCount > 0, "Must set at least one glyph");
965 PRUint32 firstOffset = aGlyphs[0].originalOffset;
966 PRUint32 lastOffset = firstOffset;
967 PRUint32 i;
968 PRUint32 regularGlyphCount = 0;
969 ATSLayoutRecord *displayGlyph = nsnull;
970 PRBool inOrder = PR_TRUE;
971 PRBool allMatched = PR_TRUE;
973 for (i = 0; i < aGlyphCount; ++i) {
974 ATSLayoutRecord *glyph = &aGlyphs[i];
975 PRUint32 offset = glyph->originalOffset;
976 firstOffset = PR_MIN(firstOffset, offset);
977 lastOffset = PR_MAX(lastOffset, offset);
978 if (aUnmatched && aUnmatched[offset/2]) {
979 allMatched = PR_FALSE;
981 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
982 ++regularGlyphCount;
983 displayGlyph = glyph;
985 if (i > 0 && aRun->IsRightToLeft() != (offset < aGlyphs[i - 1].originalOffset)) { // XXXkt allow == in RTL
986 inOrder = PR_FALSE;
990 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString[firstOffset/2]),
991 "Invalid char passed in");
993 if (!allMatched) {
994 for (i = firstOffset; i <= lastOffset; ++i) {
995 PRUint32 index = i/2;
996 if (NS_IS_HIGH_SURROGATE(aString[index]) &&
997 index + 1 < aLength &&
998 NS_IS_LOW_SURROGATE(aString[index + 1])) {
999 aRun->SetMissingGlyph(aOffsetInTextRun + index,
1000 SURROGATE_TO_UCS4(aString[index],
1001 aString[index + 1]));
1002 } else {
1003 aRun->SetMissingGlyph(aOffsetInTextRun + index, aString[index]);
1006 return;
1009 gfxTextRun::CompressedGlyph g;
1010 PRUint32 offset;
1011 // Make all but the first character in the group NOT be a ligature boundary,
1012 // i.e. fuse the group into a ligature.
1013 // Also make them not be cluster boundaries, i.e., fuse them into a cluster,
1014 // if the glyphs are out of character order.
1015 for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
1016 PRUint32 charIndex = aOffsetInTextRun + offset/2;
1017 PRBool makeClusterStart = inOrder && aRun->IsClusterStart(charIndex);
1018 g.SetComplex(makeClusterStart, PR_FALSE, 0);
1019 aRun->SetGlyphs(charIndex, g, nsnull);
1022 // Grab total advance for all glyphs
1023 PRInt32 advance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
1024 PRUint32 charIndex = aOffsetInTextRun + firstOffset/2;
1025 if (regularGlyphCount == 1) {
1026 if (advance >= 0 &&
1027 (!aBaselineDeltas || aBaselineDeltas[displayGlyph - aGlyphs] == 0) &&
1028 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
1029 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(displayGlyph->glyphID) &&
1030 aRun->IsClusterStart(charIndex)) {
1031 aRun->SetSimpleGlyph(charIndex, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
1032 return;
1036 nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
1037 ATSLayoutRecord *advanceStart = aGlyphs;
1038 for (i = 0; i < aGlyphCount; ++i) {
1039 ATSLayoutRecord *glyph = &aGlyphs[i];
1040 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
1041 if (glyph->originalOffset > firstOffset) {
1042 PRUint32 glyphCharIndex = aOffsetInTextRun + glyph->originalOffset/2;
1043 PRUint32 glyphRunIndex = aRun->FindFirstGlyphRunContaining(glyphCharIndex);
1044 PRUint32 numGlyphRuns;
1045 const gfxTextRun::GlyphRun *glyphRun = aRun->GetGlyphRuns(&numGlyphRuns) + glyphRunIndex;
1047 if (glyphRun->mCharacterOffset > charIndex) {
1048 // The font has changed inside the character group. This might
1049 // happen in some weird situations, e.g. if
1050 // ATSUI decides in LTR text to put the glyph for character
1051 // 1 before the glyph for character 0, AND decides to
1052 // give character 1's glyph a different font from character
1053 // 0. This sucks because we can't then safely move this
1054 // glyph to be associated with our first character.
1055 // To handle this we'd have to do some funky hacking with
1056 // glyph advances and offsets so that the glyphs stay
1057 // associated with the right characters but they are
1058 // displayed out of order. Let's not do this for now,
1059 // in the hope that it doesn't come up. If it does come up,
1060 // at least we can fix it right here without changing
1061 // any other code.
1062 NS_ERROR("Font change inside character group!");
1063 // Be safe, just throw out this glyph
1064 continue;
1068 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
1069 if (!details)
1070 return;
1071 details->mAdvance = 0;
1072 details->mGlyphID = glyph->glyphID;
1073 details->mXOffset = 0;
1074 if (detailedGlyphs.Length() > 1) {
1075 details->mXOffset +=
1076 GetAdvanceAppUnits(advanceStart, glyph - advanceStart,
1077 aAppUnitsPerDevUnit);
1079 details->mYOffset = !aBaselineDeltas ? 0.0f
1080 : FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
1083 if (detailedGlyphs.Length() == 0) {
1084 NS_WARNING("No glyphs visible at all!");
1085 aRun->SetGlyphs(aOffsetInTextRun + charIndex, g.SetMissing(0), nsnull);
1086 return;
1089 // The advance width for the whole cluster
1090 PRInt32 clusterAdvance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
1091 if (aRun->IsRightToLeft())
1092 detailedGlyphs[0].mAdvance = clusterAdvance;
1093 else
1094 detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = clusterAdvance;
1095 g.SetComplex(aRun->IsClusterStart(charIndex), PR_TRUE, detailedGlyphs.Length());
1096 aRun->SetGlyphs(charIndex, g, detailedGlyphs.Elements());
1100 * Returns true if there are overrunning glyphs
1102 static PRBool
1103 PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
1104 const PRUnichar *aString, PRUint32 aLayoutLength,
1105 PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun,
1106 const PRPackedBool *aUnmatched)
1108 // AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
1109 // Fixed *advanceDeltas = static_cast<Fixed *>(advanceDeltasArray.mArray);
1110 // AutoLayoutDataArrayPtr deviceDeltasArray(aLine, kATSUDirectDataDeviceDeltaSInt16Array);
1111 AutoLayoutDataArrayPtr baselineDeltasArray(aLine, kATSUDirectDataBaselineDeltaFixedArray);
1112 Fixed *baselineDeltas = static_cast<Fixed *>(baselineDeltasArray.mArray);
1113 AutoLayoutDataArrayPtr glyphRecordsArray(aLine, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent);
1115 PRUint32 numGlyphs = glyphRecordsArray.mItemCount;
1116 if (numGlyphs == 0 || !glyphRecordsArray.mArray) {
1117 NS_WARNING("Failed to retrieve key glyph data");
1118 return PR_FALSE;
1120 ATSLayoutRecord *glyphRecords = static_cast<ATSLayoutRecord *>(glyphRecordsArray.mArray);
1121 NS_ASSERTION(!baselineDeltas || baselineDeltasArray.mItemCount == numGlyphs,
1122 "Mismatched glyph counts");
1123 NS_ASSERTION(glyphRecords[numGlyphs - 1].flags & kATSGlyphInfoTerminatorGlyph,
1124 "Last glyph should be a terminator glyph");
1125 --numGlyphs;
1126 if (numGlyphs == 0)
1127 return PR_FALSE;
1129 PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
1130 PRBool isRTL = aRun->IsRightToLeft();
1132 PRUint32 trailingCharactersToIgnore = aLayoutLength - aLengthInTextRun;
1133 if (trailingCharactersToIgnore > 0) {
1134 // The glyph array includes a glyph for the artificial trailing
1135 // non-whitespace character. Strip that glyph from the array now.
1136 if (isRTL) {
1137 NS_ASSERTION(glyphRecords[trailingCharactersToIgnore - 1].originalOffset == aLengthInTextRun*2,
1138 "Couldn't find glyph for trailing marker");
1139 glyphRecords += trailingCharactersToIgnore;
1140 } else {
1141 NS_ASSERTION(glyphRecords[numGlyphs - trailingCharactersToIgnore].originalOffset == aLengthInTextRun*2,
1142 "Couldn't find glyph for trailing marker");
1144 numGlyphs -= trailingCharactersToIgnore;
1145 if (numGlyphs == 0)
1146 return PR_FALSE;
1149 PRUint32 allFlags = 0;
1150 // Now process the glyphs, which should basically be in
1151 // the textrun's desired order, so process them in textrun order
1152 PRInt32 direction = PRInt32(aRun->GetDirection());
1153 while (numGlyphs > 0) {
1154 PRUint32 glyphIndex = isRTL ? numGlyphs - 1 : 0;
1155 PRUint32 lastOffset = glyphRecords[glyphIndex].originalOffset;
1156 PRUint32 glyphCount = 1;
1157 // Determine the glyphs for this ligature group
1158 while (glyphCount < numGlyphs) {
1159 ATSLayoutRecord *glyph = &glyphRecords[glyphIndex + direction*glyphCount];
1160 PRUint32 glyphOffset = glyph->originalOffset;
1161 allFlags |= glyph->flags;
1162 if (glyphOffset <= lastOffset) {
1163 // Always add the current glyph to the ligature group if it's for the same
1164 // character as a character whose glyph is already in the group,
1165 // or an earlier character. The latter can happen because ATSUI
1166 // sometimes visually reorders glyphs; e.g. DEVANAGARI VOWEL I
1167 // can have its glyph displayed before the glyph for the consonant that's
1168 // it's logically after (even though this is all left-to-right text).
1169 // In this case we need to make sure the glyph for the consonant
1170 // is added to the group containing the vowel.
1171 } else {
1172 // We could be at the end of a character group
1173 if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
1174 // Next character is a normal character, stop the group here
1175 break;
1177 if (aUnmatched && aUnmatched[glyphOffset/2]) {
1178 // Next character is ummatched, so definitely stop the group here
1179 break;
1181 // Otherwise the next glyph is, we assume, a ligature continuation.
1182 // Record that this character too is part of the group
1183 lastOffset = glyphOffset;
1185 ++glyphCount;
1187 if (isRTL) {
1188 SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - glyphCount,
1189 glyphCount,
1190 baselineDeltas ? baselineDeltas + numGlyphs - glyphCount : nsnull,
1191 appUnitsPerDevUnit, aRun, aOffsetInTextRun,
1192 aUnmatched, aString, aLengthInTextRun);
1193 } else {
1194 SetGlyphsForCharacterGroup(glyphRecords,
1195 glyphCount, baselineDeltas,
1196 appUnitsPerDevUnit, aRun, aOffsetInTextRun,
1197 aUnmatched, aString, aLengthInTextRun);
1198 glyphRecords += glyphCount;
1199 if (baselineDeltas) {
1200 baselineDeltas += glyphCount;
1203 numGlyphs -= glyphCount;
1206 return (allFlags & ATSUI_OVERRUNNING_GLYPH_FLAG) != 0;
1209 struct PostLayoutCallbackClosure {
1210 gfxTextRun *mTextRun;
1211 const PRUnichar *mString;
1212 PRUint32 mLayoutLength;
1213 PRUint32 mOffsetInTextRun;
1214 PRUint32 mLengthInTextRun;
1215 // Either null or an array of stringlength booleans set to true for
1216 // each character that did not match any fonts
1217 nsAutoArrayPtr<PRPackedBool> mUnmatchedChars;
1218 // The callback *sets* this to indicate whether there were overrunning glyphs
1219 PRPackedBool mOverrunningGlyphs;
1222 // This is really disgusting, but the ATSUI refCon thing is also disgusting
1223 static PostLayoutCallbackClosure *gCallbackClosure = nsnull;
1225 static OSStatus
1226 PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
1227 ATSULineRef iLineRef,
1228 UInt32 iRefCon,
1229 void *iOperationCallbackParameterPtr,
1230 ATSULayoutOperationCallbackStatus *oCallbackStatus)
1232 gCallbackClosure->mOverrunningGlyphs =
1233 PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
1234 gCallbackClosure->mString,
1235 gCallbackClosure->mLayoutLength,
1236 gCallbackClosure->mOffsetInTextRun,
1237 gCallbackClosure->mLengthInTextRun,
1238 gCallbackClosure->mUnmatchedChars);
1239 *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
1240 return noErr;
1243 // xxx - leaving this here for now, probably belongs in platform code somewhere
1245 eFontPrefLang
1246 GetFontPrefLangFor(PRUint8 aUnicodeRange)
1248 switch (aUnicodeRange) {
1249 case kRangeSetLatin: return eFontPrefLang_Western;
1250 case kRangeCyrillic: return eFontPrefLang_Cyrillic;
1251 case kRangeGreek: return eFontPrefLang_Greek;
1252 case kRangeTurkish: return eFontPrefLang_Turkish;
1253 case kRangeHebrew: return eFontPrefLang_Hebrew;
1254 case kRangeArabic: return eFontPrefLang_Arabic;
1255 case kRangeBaltic: return eFontPrefLang_Baltic;
1256 case kRangeThai: return eFontPrefLang_Thai;
1257 case kRangeKorean: return eFontPrefLang_Korean;
1258 case kRangeJapanese: return eFontPrefLang_Japanese;
1259 case kRangeSChinese: return eFontPrefLang_ChineseCN;
1260 case kRangeTChinese: return eFontPrefLang_ChineseTW;
1261 case kRangeDevanagari: return eFontPrefLang_Devanagari;
1262 case kRangeTamil: return eFontPrefLang_Tamil;
1263 case kRangeArmenian: return eFontPrefLang_Armenian;
1264 case kRangeBengali: return eFontPrefLang_Bengali;
1265 case kRangeCanadian: return eFontPrefLang_Canadian;
1266 case kRangeEthiopic: return eFontPrefLang_Ethiopic;
1267 case kRangeGeorgian: return eFontPrefLang_Georgian;
1268 case kRangeGujarati: return eFontPrefLang_Gujarati;
1269 case kRangeGurmukhi: return eFontPrefLang_Gurmukhi;
1270 case kRangeKhmer: return eFontPrefLang_Khmer;
1271 case kRangeMalayalam: return eFontPrefLang_Malayalam;
1272 case kRangeSetCJK: return eFontPrefLang_CJKSet;
1273 default: return eFontPrefLang_Others;
1277 static void
1278 DisableOptionalLigaturesInStyle(ATSUStyle aStyle)
1280 static ATSUFontFeatureType selectors[] = {
1281 kCommonLigaturesOffSelector,
1282 kRareLigaturesOffSelector,
1283 kLogosOffSelector,
1284 kRebusPicturesOffSelector,
1285 kDiphthongLigaturesOffSelector,
1286 kSquaredLigaturesOffSelector,
1287 kAbbrevSquaredLigaturesOffSelector,
1288 kSymbolLigaturesOffSelector
1290 static ATSUFontFeatureType types[NS_ARRAY_LENGTH(selectors)] = {
1291 kLigaturesType,
1292 kLigaturesType,
1293 kLigaturesType,
1294 kLigaturesType,
1295 kLigaturesType,
1296 kLigaturesType,
1297 kLigaturesType,
1298 kLigaturesType
1300 ATSUSetFontFeatures(aStyle, NS_ARRAY_LENGTH(selectors), types, selectors);
1303 // 361695 - ATSUI only does glyph mirroring when the font contains a 'prop' table
1304 // with glyph mirroring info, the character mirroring has to be done manually in the
1305 // fallback case. Only used for RTL text runs. The autoptr for the mirrored copy
1306 // is owned by the calling routine.
1308 // MirrorSubstring - Do Unicode mirroring on characters within a substring. If mirroring
1309 // needs to be done, copy the original string and change the ATSUI layout to use the mirrored copy.
1311 // @param layout ATSUI layout for the entire text run
1312 // @param mirrorStr container used for mirror string, null until a mirrored character is found
1313 // @param aString original string
1314 // @param aLength length of the original string
1315 // @param runStart start offset of substring to be mirrored
1316 // @param runLength length of substring to be mirrored
1318 static void MirrorSubstring(ATSUTextLayout layout, nsAutoArrayPtr<PRUnichar>& mirroredStr,
1319 const PRUnichar *aString, PRUint32 aLength,
1320 UniCharArrayOffset runStart, UniCharCount runLength)
1322 UniCharArrayOffset off;
1324 // do the mirroring manually!!
1325 for (off = runStart; off < runStart + runLength; off++) {
1326 PRUnichar mirroredChar;
1328 mirroredChar = (PRUnichar) SymmSwap(aString[off]);
1329 if (mirroredChar != aString[off]) {
1330 // string contains characters that need to be mirrored
1331 if (mirroredStr == NULL) {
1333 // copy the string
1334 mirroredStr = new PRUnichar[aLength];
1335 memcpy(mirroredStr, aString, sizeof(PRUnichar) * aLength);
1337 // adjust the layout
1338 ATSUTextMoved(layout, mirroredStr);
1341 mirroredStr[off] = mirroredChar;
1346 // match fonts with character sequence based on font cmap tables
1347 class CmapFontMatcher {
1348 public:
1349 CmapFontMatcher(const PRUnichar *aString, PRUint32 aBeginOffset, PRUint32 aEndOffset, gfxAtsuiFontGroup* aFontGroup) :
1350 mString(aString), mOffset(aBeginOffset), mPrevOffset(aBeginOffset), mEndOffset(aEndOffset), mPrevCh(0), mFirstRange(PR_TRUE), mFontGroup(aFontGroup), mMatchedFont(0), mNextMatchedFont(0)
1353 // match the next substring that uses the same font, returns the length matched
1354 PRUint32 MatchNextRange()
1356 PRUint32 matchStartOffset, chStartOffset, ch, nextCh;
1357 nsRefPtr<gfxAtsuiFont> font;
1359 matchStartOffset = mPrevOffset;
1361 if ( !mFirstRange ) {
1362 mMatchedFont = mNextMatchedFont;
1365 while ( mOffset < mEndOffset ) {
1366 chStartOffset = mOffset;
1368 // set up current ch
1369 ch = mString[mOffset];
1370 if ((mOffset+1 < mEndOffset) && NS_IS_HIGH_SURROGATE(ch) && NS_IS_LOW_SURROGATE(mString[mOffset+1])) {
1371 mOffset++;
1372 ch = SURROGATE_TO_UCS4(ch, mString[mOffset]);
1375 // set up next ch
1376 nextCh = 0;
1377 if (mOffset+1 < mEndOffset) {
1378 nextCh = mString[mOffset+1];
1379 if ((mOffset+2 < mEndOffset) && NS_IS_HIGH_SURROGATE(nextCh) && NS_IS_LOW_SURROGATE(mString[mOffset+2]))
1380 nextCh = SURROGATE_TO_UCS4(nextCh, mString[mOffset+2]);
1383 // find the font for this char
1384 font = mFontGroup->FindFontForChar(ch, mPrevCh, nextCh, mMatchedFont);
1385 mOffset++;
1386 mPrevCh = ch;
1388 // no previous match, set one up
1389 if ( mFirstRange ) {
1390 mMatchedFont = font;
1391 mFirstRange = PR_FALSE;
1392 } else if ( font != mMatchedFont ) {
1393 mPrevOffset = chStartOffset;
1394 mNextMatchedFont = font;
1395 return chStartOffset - matchStartOffset;
1400 // reached the end of the string
1401 mPrevOffset = mEndOffset;
1402 mNextMatchedFont = nsnull;
1403 return mOffset - matchStartOffset;
1406 inline gfxAtsuiFont* MatchedFont() { return mMatchedFont.get(); }
1408 private:
1409 const PRUnichar *mString;
1410 PRUint32 mOffset;
1411 PRUint32 mPrevOffset;
1412 PRUint32 mEndOffset;
1413 PRUint32 mPrevCh;
1414 PRBool mFirstRange;
1415 gfxAtsuiFontGroup *mFontGroup;
1416 nsRefPtr<gfxAtsuiFont> mMatchedFont;
1417 nsRefPtr<gfxAtsuiFont> mNextMatchedFont;
1421 static ATSUStyle
1422 SetLayoutRangeToFont(ATSUTextLayout layout, ATSUStyle mainStyle, UniCharArrayOffset offset,
1423 UniCharCount length, ATSUFontID fontID)
1425 ATSUStyle subStyle;
1426 ATSUCreateStyle (&subStyle);
1427 ATSUCopyAttributes (mainStyle, subStyle);
1429 ATSUAttributeTag fontTags[] = { kATSUFontTag };
1430 ByteCount fontArgSizes[] = { sizeof(ATSUFontID) };
1431 ATSUAttributeValuePtr fontArgs[] = { &fontID };
1433 ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
1435 // apply the new style to the layout for the changed substring
1436 ATSUSetRunStyle (layout, subStyle, offset, length);
1438 return subStyle;
1441 PRBool
1442 gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
1443 const PRUnichar *aString, PRUint32 aLength,
1444 PRUint32 aLayoutStart, PRUint32 aLayoutLength,
1445 PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun)
1447 OSStatus status;
1448 gfxAtsuiFont *firstFont = GetFontAt(0);
1449 ATSUStyle mainStyle = firstFont->GetATSUStyle();
1450 nsTArray<ATSUStyle> stylesToDispose;
1451 const PRUnichar *layoutString = aString + aLayoutStart;
1453 #ifdef DUMP_TEXT_RUNS
1454 NS_ConvertUTF16toUTF8 str(layoutString, aLengthInTextRun);
1455 NS_ConvertUTF16toUTF8 families(mFamilies);
1456 PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,\
1457 ("InitTextRun %p fontgroup %p (%s) lang: %s len %d TEXTRUN \"%s\" ENDTEXTRUN\n",
1458 aRun, this, families.get(), mStyle.langGroup.get(), aLengthInTextRun, str.get()) );
1459 PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,
1460 ("InitTextRun font: %s\n",
1461 NS_ConvertUTF16toUTF8(firstFont->GetUniqueName()).get()) );
1462 #endif
1464 if (aRun->GetFlags() & TEXT_DISABLE_OPTIONAL_LIGATURES) {
1465 status = ATSUCreateAndCopyStyle(mainStyle, &mainStyle);
1466 if (status == noErr) {
1467 stylesToDispose.AppendElement(mainStyle);
1468 DisableOptionalLigaturesInStyle(mainStyle);
1472 UniCharCount runLengths = aLengthInTextRun;
1473 ATSUTextLayout layout;
1474 // Create the text layout for the whole string, but only produce glyphs
1475 // for the text inside LRO/RLO - PDF, if present. For wrapped strings
1476 // we do need to produce glyphs for the trailing non-whitespace
1477 // character to ensure that ATSUI treats all whitespace as non-trailing.
1478 status = ATSUCreateTextLayoutWithTextPtr
1479 (aString,
1480 aLayoutStart,
1481 aLayoutLength,
1482 aLength,
1484 &runLengths,
1485 &mainStyle,
1486 &layout);
1487 // XXX error check here?
1489 PostLayoutCallbackClosure closure;
1490 closure.mTextRun = aRun;
1491 closure.mString = layoutString;
1492 closure.mLayoutLength = aLayoutLength;
1493 closure.mOffsetInTextRun = aOffsetInTextRun;
1494 closure.mLengthInTextRun = aLengthInTextRun;
1495 NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
1496 gCallbackClosure = &closure;
1498 ATSULayoutOperationOverrideSpecifier override;
1499 override.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
1500 override.overrideUPP = PostLayoutOperationCallback;
1502 // Set up our layout attributes
1503 ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
1505 static ATSUAttributeTag layoutTags[] = {
1506 kATSULineLayoutOptionsTag,
1507 kATSULayoutOperationOverrideTag
1509 static ByteCount layoutArgSizes[] = {
1510 sizeof(ATSLineLayoutOptions),
1511 sizeof(ATSULayoutOperationOverrideSpecifier)
1514 ATSUAttributeValuePtr layoutArgs[] = {
1515 &lineLayoutOptions,
1516 &override
1518 ATSUSetLayoutControls(layout,
1519 NS_ARRAY_LENGTH(layoutTags),
1520 layoutTags,
1521 layoutArgSizes,
1522 layoutArgs);
1524 /* Now go through and update the styles for the text, based on font matching. */
1526 nsAutoArrayPtr<PRUnichar> mirroredStr;
1528 UniCharArrayOffset runStart = aLayoutStart;
1529 UniCharCount runLength = aLengthInTextRun;
1530 UniCharCount totalLength = aLayoutStart + aLengthInTextRun;
1532 /// ---- match fonts using cmap info instead of ATSUI ----
1534 CmapFontMatcher fontMatcher(aString, runStart, runStart + runLength, this);
1536 while (runStart < totalLength) {
1537 gfxAtsuiFont *matchedFont;
1538 UniCharCount matchedLength;
1540 // match a range of text
1541 matchedLength = fontMatcher.MatchNextRange();
1542 matchedFont = fontMatcher.MatchedFont();
1544 #ifdef DUMP_TEXT_RUNS
1545 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));
1546 #endif
1547 //printf("Matched: %s [%d, %d)\n", (matchedFont ? NS_ConvertUTF16toUTF8(matchedFont->GetUniqueName()).get() : "<null>"), runStart, runStart + matchedLength);
1549 // in the RTL case, handle fallback mirroring
1550 if (aRun->IsRightToLeft() && matchedFont && !matchedFont->HasMirroringInfo()) {
1551 MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
1554 // if no matched font, mark as unmatched
1555 if (!matchedFont) {
1557 aRun->AddGlyphRun(firstFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
1559 if (!closure.mUnmatchedChars) {
1560 closure.mUnmatchedChars = new PRPackedBool[aLength];
1561 if (closure.mUnmatchedChars) {
1562 //printf("initializing %d\n", aLength);
1563 memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
1567 if (closure.mUnmatchedChars) {
1568 //printf("setting %d unmatched from %d\n", matchedLength, runStart - headerChars);
1569 memset(closure.mUnmatchedChars.get() + runStart - aLayoutStart,
1570 PR_TRUE, matchedLength);
1573 } else {
1575 if (matchedFont != firstFont) {
1576 // create a new sub-style and add it to the layout
1577 ATSUStyle subStyle = SetLayoutRangeToFont(layout, mainStyle, runStart, matchedLength, matchedFont->GetATSUFontID());
1578 stylesToDispose.AppendElement(subStyle);
1581 // add a glyph run for the matched substring
1582 aRun->AddGlyphRun(matchedFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
1585 runStart += matchedLength;
1586 runLength -= matchedLength;
1590 /// -------------------------------------------------
1592 // xxx - for some reason, this call appears to be needed to avoid assertions about glyph runs not being coalesced properly
1593 // this appears to occur when there are unmatched characters in the text run
1594 aRun->SortGlyphRuns();
1596 // Trigger layout so that our callback fires. We don't actually care about
1597 // the result of this call.
1598 ATSTrapezoid trap;
1599 ItemCount trapCount;
1600 ATSUGetGlyphBounds(layout, 0, 0, aLayoutStart, aLengthInTextRun,
1601 kATSUseFractionalOrigins, 1, &trap, &trapCount);
1603 ATSUDisposeTextLayout(layout);
1605 aRun->AdjustAdvancesForSyntheticBold(aOffsetInTextRun, aLengthInTextRun);
1607 PRUint32 i;
1608 for (i = 0; i < stylesToDispose.Length(); ++i) {
1609 ATSUDisposeStyle(stylesToDispose[i]);
1611 gCallbackClosure = nsnull;
1612 return !closure.mOverrunningGlyphs;