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
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.
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 ***** */
43 #include "nsBidiUtils.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
69 static PRLogModuleInfo
*gAtsuiTextRunLog
= PR_NewLogModule("atsuiTextRun");
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 */
77 #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
80 /* We might still need this for fast-pathing, but we'll see */
82 OSStatus
ATSUGetStyleGroup(ATSUStyle style
, void **styleGroup
);
83 OSStatus
ATSUDisposeStyleGroup(void *styleGroup
);
84 OSStatus
ATSUConvertCharToGlyphs(void *styleGroup
,
86 unsigned int bufferLength
,
88 OSStatus
ATSInitializeGlyphVector(int size
, void *glyphVectorPtr
);
89 OSStatus
ATSClearGlyphVector(void *glyphVectorPtr
);
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
)));
130 double skewfactor
= (needsOblique
? Fix2X(kATSItalicQDSkew
) : 0);
132 cairo_matrix_t style
;
133 cairo_matrix_init(&style
,
136 -1 * skewfactor
, //xy
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
) {
160 sprintf(warnBuf
, "Failed to create scaled font: %s status: %d", NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr
);
167 ATSUFontID
gfxAtsuiFont::GetATSUFontID()
169 return GetFontEntry()->GetFontID();
173 DisableUncommonLigaturesAndLineBoundarySwashes(ATSUStyle aStyle
)
175 static const ATSUFontFeatureType types
[] = {
185 static const ATSUFontFeatureType selectors
[NS_ARRAY_LENGTH(types
)] = {
186 kRareLigaturesOffSelector
,
188 kRebusPicturesOffSelector
,
189 kDiphthongLigaturesOffSelector
,
190 kSquaredLigaturesOffSelector
,
191 kAbbrevSquaredLigaturesOffSelector
,
192 kLineInitialSwashesOffSelector
,
193 kLineFinalSwashesOffSelector
195 ATSUSetFontFeatures(aStyle
, NS_ARRAY_LENGTH(types
), types
, selectors
);
199 DisableCommonLigatures(ATSUStyle aStyle
)
201 static const ATSUFontFeatureType types
[] = {
204 static const ATSUFontFeatureType selectors
[NS_ARRAY_LENGTH(types
)] = {
205 kCommonLigaturesOffSelector
207 ATSUSetFontFeatures(aStyle
, NS_ARRAY_LENGTH(types
), types
, selectors
);
211 RoundToNearestMultiple(double aValue
, double aFraction
)
213 return floor(aValue
/aFraction
+ 0.5)*aFraction
;
217 gfxAtsuiFont::InitMetrics(ATSUFontID aFontID
, ATSFontRef aFontRef
)
219 /* Create the ATSUStyle */
222 PR_MAX(((mAdjustedSize
!= 0.0f
) ? mAdjustedSize
: GetStyle()->size
), 1.0f
);
224 //fprintf (stderr, "string: '%s', size: %f\n", NS_ConvertUTF16toUTF8(aString).get(), size);
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
[] = {
240 const ATSUAttributeValuePtr styleArgs
[NS_ARRAY_LENGTH(styleTags
)] = {
245 static const ByteCount styleArgSizes
[NS_ARRAY_LENGTH(styleTags
)] = {
248 sizeof(CGAffineTransform
)
251 ATSUCreateStyle(&mATSUStyle
);
252 ATSUSetAttributes(mATSUStyle
,
253 NS_ARRAY_LENGTH(styleTags
),
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
,
272 if (atsMetrics
.xHeight
)
273 mMetrics
.xHeight
= atsMetrics
.xHeight
* size
;
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
);
284 mAdjustedSize
= size
;
287 mMetrics
.emHeight
= size
;
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
;
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
);
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
;
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
);
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
);
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.
361 cairo_set_scaled_font(aContext
->GetCairo(), scaledFont
);
366 gfxAtsuiFont::GetUniqueName()
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
);
384 ATSUGetGlyphBounds(layout
, FloatToFixed(0.0), FloatToFixed(0.0),
385 0, 1, kATSUseFractionalOrigins
, 1, &trap
, &numBounds
);
388 FixedToFloat(PR_MAX(trap
.upperRight
.x
, trap
.lowerRight
.x
)) -
389 FixedToFloat(PR_MIN(trap
.upperLeft
.x
, trap
.lowerLeft
.x
));
392 ATSUGlyphInfoArray glyphInfo
;
393 ByteCount bytes
= sizeof(glyphInfo
);
394 ATSUGetGlyphInfo(layout
, 0, 1, &bytes
, &glyphInfo
);
395 *aGlyphID
= glyphInfo
.glyphs
[0].glyphID
;
398 ATSUDisposeTextLayout(layout
);
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
);
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()
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,
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
));
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
);
466 gfxAtsuiFont::HasMirroringInfo()
468 if (!mHasMirroringLookedUp
) {
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
);
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
);
504 gfxAtsuiFont
*newFont
= new gfxAtsuiFont(aFontEntry
, aStyle
, aNeedsBold
);
507 if (!newFont
->Valid()) {
512 gfxFontCache::GetCache()->AddNew(font
);
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!
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
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
);
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
);
567 gfxAtsuiFontGroup::FindATSUFont(const nsAString
& aName
,
568 const nsACString
& aGenericName
,
571 gfxAtsuiFontGroup
*fontGroup
= (gfxAtsuiFontGroup
*) closure
;
572 const gfxFontStyle
*fontStyle
= fontGroup
->GetStyle();
576 MacOSFontEntry
*fe
= nsnull
;
578 // first, look up in the user font set
579 gfxUserFontSet
*fs
= fontGroup
->GetUserFontSet();
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
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
);
595 fontGroup
->mFonts
.AppendElement(font
);
603 gfxAtsuiFontGroup::Copy(const gfxFontStyle
*aStyle
)
605 return new gfxAtsuiFontGroup(mFamilies
, aStyle
, mUserFontSet
);
609 SetupClusterBoundaries(gfxTextRun
*aTextRun
, const PRUnichar
*aString
)
611 TextBreakLocatorRef locator
;
612 OSStatus status
= UCCreateTextBreakLocator(NULL
, 0, kUCTextBreakClusterMask
,
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
);
625 options
|= kUCTextBreakIterateMask
;
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
);
636 UCDisposeTextBreakLocator(&locator
);
639 #define UNICODE_LRO 0x202d
640 #define UNICODE_RLO 0x202e
641 #define UNICODE_PDF 0x202c
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
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.
666 // Ensure that none of the whitespace in the run is considered "trailing"
667 // by ATSUI's bidi algorithm
669 aString
.Append(UNICODE_PDF
);
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.
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,
686 return aTextRun
->GetLength() - aOffset
;
689 // Try to end the segment before or after a space, since spaces don't kern
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
;
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
);
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.
755 gfxAtsuiFontGroup::MakeTextRun(const PRUnichar
*aString
, PRUint32 aLength
,
756 const Parameters
*aParams
, PRUint32 aFlags
)
758 gfxTextRun
*textRun
= gfxTextRun::Create(aParams
, aString
, aLength
, this, aFlags
);
762 textRun
->RecordSurrogates(aString
);
763 SetupClusterBoundaries(textRun
, aString
);
767 for (maxLen
= GuessMaximumStringLength(); maxLen
> 0; maxLen
/= 2) {
769 while (start
< aLength
) {
770 PRUint32 len
= FindTextRunSegmentLength(textRun
, start
, maxLen
);
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)
787 if (start
== aLength
)
789 textRun
->ResetGlyphRuns();
792 textRun
->FetchGlyphExtents(aParams
->mContext
);
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
);
808 for (maxLen
= GuessMaximumStringLength(); maxLen
> 0; maxLen
/= 2) {
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
));
816 PRBool wrapBidi
= (aFlags
& TEXT_IS_RTL
) != 0;
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)
831 if (start
== aLength
)
833 textRun
->ResetGlyphRuns();
836 textRun
->FetchGlyphExtents(aParams
->mContext
);
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())
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
);
864 prefFontData
->mPrefFamilies
.AppendElement(family
);
871 already_AddRefed
<gfxFont
>
872 gfxAtsuiFontGroup::WhichPrefFontSupportsChar(PRUint32 aCh
)
876 // FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs
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
;
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
,
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
925 if (family
== mLastPrefFamily
&& mLastPrefFont
->TestCharacterMap(aCh
)) {
926 font
= mLastPrefFont
;
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();
951 already_AddRefed
<gfxFont
>
952 gfxAtsuiFontGroup::WhichSystemFontSupportsChar(PRUint32 aCh
)
956 fe
= gfxQuartzFontCache::SharedFontCache()->FindFontForChar(aCh
, GetFontAt(0));
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();
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
973 ForEachFont(FindATSUFont
, this);
974 mCurrGeneration
= GetGeneration();
980 * Simple wrapper for ATSU "direct data arrays"
982 class AutoLayoutDataArrayPtr
{
984 AutoLayoutDataArrayPtr(ATSULineRef aLineRef
,
985 ATSUDirectDataSelector aSelector
)
986 : mLineRef(aLineRef
), mSelector(aSelector
)
989 ATSUDirectGetLayoutDataArrayPtrFromLineRef(aLineRef
,
990 aSelector
, PR_FALSE
, &mArray
, &mItemCount
);
991 if (status
!= noErr
) {
996 ~AutoLayoutDataArrayPtr() {
998 ATSUDirectReleaseLayoutDataArrayPtr(mLineRef
, mSelector
, &mArray
);
1003 ItemCount mItemCount
;
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
1015 #define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
1018 * Calculate the advance in appunits of a run of ATSUI glyphs
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.
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
;
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
1067 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString
[firstOffset
/2]),
1068 "Invalid char passed in");
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]));
1080 aRun
->SetMissingGlyph(aOffsetInTextRun
+ index
, aString
[index
]);
1086 gfxTextRun::CompressedGlyph g
;
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) {
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
));
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
1139 NS_ERROR("Font change inside character group!");
1140 // Be safe, just throw out this glyph
1145 gfxTextRun::DetailedGlyph
*details
= detailedGlyphs
.AppendElement();
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
);
1166 // The advance width for the whole cluster
1167 PRInt32 clusterAdvance
= GetAdvanceAppUnits(aGlyphs
, aGlyphCount
, aAppUnitsPerDevUnit
);
1168 if (aRun
->IsRightToLeft())
1169 detailedGlyphs
[0].mAdvance
= clusterAdvance
;
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
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");
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");
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.
1214 NS_ASSERTION(glyphRecords
[trailingCharactersToIgnore
- 1].originalOffset
== aLengthInTextRun
*2,
1215 "Couldn't find glyph for trailing marker");
1216 glyphRecords
+= trailingCharactersToIgnore
;
1218 NS_ASSERTION(glyphRecords
[numGlyphs
- trailingCharactersToIgnore
].originalOffset
== aLengthInTextRun
*2,
1219 "Couldn't find glyph for trailing marker");
1221 numGlyphs
-= trailingCharactersToIgnore
;
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
;
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.
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
1264 if (aUnmatched
&& aUnmatched
[glyphOffset
/2]) {
1265 // Next character is ummatched, so definitely stop the group here
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
;
1275 SetGlyphsForCharacterGroup(glyphRecords
+ numGlyphs
- glyphCount
,
1277 baselineDeltas
? baselineDeltas
+ numGlyphs
- glyphCount
: nsnull
,
1278 appUnitsPerDevUnit
, aRun
, aOffsetInTextRun
,
1279 aUnmatched
, aString
, aLengthInTextRun
);
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
;
1313 PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation
,
1314 ATSULineRef iLineRef
,
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
;
1330 // xxx - leaving this here for now, probably belongs in platform code somewhere
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
) {
1395 mirroredStr
= new PRUnichar
[aLength
];
1396 memcpy(mirroredStr
, aString
, sizeof(PRUnichar
) * aLength
);
1398 // adjust the layout
1399 ATSUTextMoved(layout
, mirroredStr
);
1402 mirroredStr
[off
] = mirroredChar
;
1410 SetLayoutRangeToFont(ATSUTextLayout layout
, ATSUStyle mainStyle
, UniCharArrayOffset offset
,
1411 UniCharCount length
, ATSUFontID fontID
)
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
);
1430 gfxAtsuiFontGroup::InitTextRun(gfxTextRun
*aRun
,
1431 const PRUnichar
*aString
, PRUint32 aLength
,
1432 PRUint32 aLayoutStart
, PRUint32 aLayoutLength
,
1433 PRUint32 aOffsetInTextRun
, PRUint32 aLengthInTextRun
)
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
)) );
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
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
[] = {
1506 ATSUSetLayoutControls(layout
,
1507 NS_ARRAY_LENGTH(layoutTags
),
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
));
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
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
);
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.
1592 ItemCount trapCount
;
1593 ATSUGetGlyphBounds(layout
, 0, 0, aLayoutStart
, aLengthInTextRun
,
1594 kATSUseFractionalOrigins
, 1, &trap
, &trapCount
);
1596 ATSUDisposeTextLayout(layout
);
1598 aRun
->AdjustAdvancesForSyntheticBold(aOffsetInTextRun
, aLengthInTextRun
);
1601 for (i
= 0; i
< stylesToDispose
.Length(); ++i
) {
1602 ATSUDisposeStyle(stylesToDispose
[i
]);
1604 gCallbackClosure
= nsnull
;
1605 return !closure
.mOverrunningGlyphs
;