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"
62 #include "nsUnicodeRange.h"
64 // Uncomment this to dump all text runs created to stdout
65 // #define DUMP_TEXT_RUNS
68 static PRLogModuleInfo
*gAtsuiTextRunLog
= PR_NewLogModule("atsuiTextRun");
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 */
76 #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
79 /* We might still need this for fast-pathing, but we'll see */
81 OSStatus
ATSUGetStyleGroup(ATSUStyle style
, void **styleGroup
);
82 OSStatus
ATSUDisposeStyleGroup(void *styleGroup
);
83 OSStatus
ATSUConvertCharToGlyphs(void *styleGroup
,
85 unsigned int bufferLength
,
87 OSStatus
ATSInitializeGlyphVector(int size
, void *glyphVectorPtr
);
88 OSStatus
ATSClearGlyphVector(void *glyphVectorPtr
);
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
)));
129 double skewfactor
= (needsOblique
? Fix2X(kATSItalicQDSkew
) : 0);
131 cairo_matrix_t style
;
132 cairo_matrix_init(&style
,
135 -1 * skewfactor
, //xy
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();
163 gfxAtsuiFont::InitMetrics(ATSUFontID aFontID
, ATSFontRef aFontRef
)
165 /* Create the ATSUStyle */
167 ATSUAttributeTag styleTags
[] = {
173 ByteCount styleArgSizes
[] = {
176 sizeof(CGAffineTransform
),
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
[] = {
199 ATSUDisposeStyle(mATSUStyle
);
201 ATSUCreateStyle(&mATSUStyle
);
202 ATSUSetAttributes(mATSUStyle
,
203 sizeof(styleTags
)/sizeof(ATSUAttributeTag
),
208 /* Now pull out the metrics */
210 ATSFontMetrics atsMetrics
;
211 ATSFontGetHorizontalMetrics(aFontRef
, kATSOptionFlagsDefault
,
214 if (atsMetrics
.xHeight
)
215 mMetrics
.xHeight
= atsMetrics
.xHeight
* size
;
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
);
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
;
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
);
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
;
273 mMetrics
.spaceWidth
= GetCharWidth(' ', &glyphID
);
274 mSpaceGlyph
= glyphID
;
276 SanitizeMetrics(&mMetrics
, mFontEntry
->FamilyEntry()->IsBadUnderlineFontFamily());
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
);
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.
297 cairo_set_scaled_font(aContext
->GetCairo(), scaledFont
);
302 gfxAtsuiFont::GetUniqueName()
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
);
320 ATSUGetGlyphBounds(layout
, FloatToFixed(0.0), FloatToFixed(0.0),
321 0, 1, kATSUseFractionalOrigins
, 1, &trap
, &numBounds
);
324 FixedToFloat(PR_MAX(trap
.upperRight
.x
, trap
.lowerRight
.x
)) -
325 FixedToFloat(PR_MIN(trap
.upperLeft
.x
, trap
.lowerLeft
.x
));
328 ATSUGlyphInfoArray glyphInfo
;
329 ByteCount bytes
= sizeof(glyphInfo
);
330 ATSUGetGlyphInfo(layout
, 0, 1, &bytes
, &glyphInfo
);
331 *aGlyphID
= glyphInfo
.glyphs
[0].glyphID
;
334 ATSUDisposeTextLayout(layout
);
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
);
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()
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,
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
));
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
);
402 gfxAtsuiFont::HasMirroringInfo()
404 if (!mHasMirroringLookedUp
) {
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
);
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
);
439 font
= new gfxAtsuiFont(aFontEntry
, aStyle
, aNeedsBold
);
442 gfxFontCache::GetCache()->AddNew(font
);
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!
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
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
);
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
);
495 gfxAtsuiFontGroup::FindATSUFont(const nsAString
& aName
,
496 const nsACString
& aGenericName
,
499 gfxAtsuiFontGroup
*fontGroup
= (gfxAtsuiFontGroup
*) closure
;
500 const gfxFontStyle
*fontStyle
= fontGroup
->GetStyle();
502 gfxQuartzFontCache
*fc
= gfxQuartzFontCache::SharedFontCache();
505 MacOSFontEntry
*fe
= fc
->FindFontForFamily(aName
, fontStyle
, needsBold
);
507 if (fe
&& !fontGroup
->HasFont(fe
->GetFontID())) {
508 nsRefPtr
<gfxAtsuiFont
> font
= GetOrMakeFont(fe
, fontStyle
, needsBold
);
510 fontGroup
->mFonts
.AppendElement(font
);
518 gfxAtsuiFontGroup::Copy(const gfxFontStyle
*aStyle
)
520 return new gfxAtsuiFontGroup(mFamilies
, aStyle
);
524 SetupClusterBoundaries(gfxTextRun
*aTextRun
, const PRUnichar
*aString
)
526 TextBreakLocatorRef locator
;
527 OSStatus status
= UCCreateTextBreakLocator(NULL
, 0, kUCTextBreakClusterMask
,
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
);
540 options
|= kUCTextBreakIterateMask
;
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
);
551 UCDisposeTextBreakLocator(&locator
);
554 #define UNICODE_LRO 0x202d
555 #define UNICODE_RLO 0x202e
556 #define UNICODE_PDF 0x202c
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
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.
581 // Ensure that none of the whitespace in the run is considered "trailing"
582 // by ATSUI's bidi algorithm
584 aString
.Append(UNICODE_PDF
);
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.
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,
601 return aTextRun
->GetLength() - aOffset
;
604 // Try to end the segment before or after a space, since spaces don't kern
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
;
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.
661 gfxAtsuiFontGroup::MakeTextRun(const PRUnichar
*aString
, PRUint32 aLength
,
662 const Parameters
*aParams
, PRUint32 aFlags
)
664 gfxTextRun
*textRun
= gfxTextRun::Create(aParams
, aString
, aLength
, this, aFlags
);
668 textRun
->RecordSurrogates(aString
);
669 SetupClusterBoundaries(textRun
, aString
);
673 for (maxLen
= GuessMaximumStringLength(); maxLen
> 0; maxLen
/= 2) {
675 while (start
< aLength
) {
676 PRUint32 len
= FindTextRunSegmentLength(textRun
, start
, maxLen
);
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)
693 if (start
== aLength
)
695 textRun
->ResetGlyphRuns();
698 textRun
->FetchGlyphExtents(aParams
->mContext
);
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
);
714 for (maxLen
= GuessMaximumStringLength(); maxLen
> 0; maxLen
/= 2) {
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
));
722 PRBool wrapBidi
= (aFlags
& TEXT_IS_RTL
) != 0;
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)
737 if (start
== aLength
)
739 textRun
->ResetGlyphRuns();
742 textRun
->FetchGlyphExtents(aParams
->mContext
);
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())
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
);
770 prefFontData
->mPrefFamilies
.AppendElement(family
);
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
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
,
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
828 if (family
== mLastPrefFamily
&& mLastPrefFont
->TestCharacterMap(aCh
)) {
829 nsRefPtr
<gfxAtsuiFont
> prefFont
= mLastPrefFont
;
830 return prefFont
.forget();
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();
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();
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
892 fe
= gfxQuartzFontCache::SharedFontCache()->FindFontForChar(aCh
, GetFontAt(0));
894 selectedFont
= GetOrMakeFont(fe
, &mStyle
, PR_FALSE
); // ignore bolder considerations in system fallback case...
895 return selectedFont
.forget();
903 * Simple wrapper for ATSU "direct data arrays"
905 class AutoLayoutDataArrayPtr
{
907 AutoLayoutDataArrayPtr(ATSULineRef aLineRef
,
908 ATSUDirectDataSelector aSelector
)
909 : mLineRef(aLineRef
), mSelector(aSelector
)
912 ATSUDirectGetLayoutDataArrayPtrFromLineRef(aLineRef
,
913 aSelector
, PR_FALSE
, &mArray
, &mItemCount
);
914 if (status
!= noErr
) {
919 ~AutoLayoutDataArrayPtr() {
921 ATSUDirectReleaseLayoutDataArrayPtr(mLineRef
, mSelector
, &mArray
);
926 ItemCount mItemCount
;
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
938 #define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
941 * Calculate the advance in appunits of a run of ATSUI glyphs
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.
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
;
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
) {
983 displayGlyph
= glyph
;
985 if (i
> 0 && aRun
->IsRightToLeft() != (offset
< aGlyphs
[i
- 1].originalOffset
)) { // XXXkt allow == in RTL
990 NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString
[firstOffset
/2]),
991 "Invalid char passed in");
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]));
1003 aRun
->SetMissingGlyph(aOffsetInTextRun
+ index
, aString
[index
]);
1009 gfxTextRun::CompressedGlyph g
;
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) {
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
));
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
1062 NS_ERROR("Font change inside character group!");
1063 // Be safe, just throw out this glyph
1068 gfxTextRun::DetailedGlyph
*details
= detailedGlyphs
.AppendElement();
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
);
1089 // The advance width for the whole cluster
1090 PRInt32 clusterAdvance
= GetAdvanceAppUnits(aGlyphs
, aGlyphCount
, aAppUnitsPerDevUnit
);
1091 if (aRun
->IsRightToLeft())
1092 detailedGlyphs
[0].mAdvance
= clusterAdvance
;
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
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");
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");
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.
1137 NS_ASSERTION(glyphRecords
[trailingCharactersToIgnore
- 1].originalOffset
== aLengthInTextRun
*2,
1138 "Couldn't find glyph for trailing marker");
1139 glyphRecords
+= trailingCharactersToIgnore
;
1141 NS_ASSERTION(glyphRecords
[numGlyphs
- trailingCharactersToIgnore
].originalOffset
== aLengthInTextRun
*2,
1142 "Couldn't find glyph for trailing marker");
1144 numGlyphs
-= trailingCharactersToIgnore
;
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.
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
1177 if (aUnmatched
&& aUnmatched
[glyphOffset
/2]) {
1178 // Next character is ummatched, so definitely stop the group here
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
;
1188 SetGlyphsForCharacterGroup(glyphRecords
+ numGlyphs
- glyphCount
,
1190 baselineDeltas
? baselineDeltas
+ numGlyphs
- glyphCount
: nsnull
,
1191 appUnitsPerDevUnit
, aRun
, aOffsetInTextRun
,
1192 aUnmatched
, aString
, aLengthInTextRun
);
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
;
1226 PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation
,
1227 ATSULineRef iLineRef
,
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
;
1243 // xxx - leaving this here for now, probably belongs in platform code somewhere
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
;
1278 DisableOptionalLigaturesInStyle(ATSUStyle aStyle
)
1280 static ATSUFontFeatureType selectors
[] = {
1281 kCommonLigaturesOffSelector
,
1282 kRareLigaturesOffSelector
,
1284 kRebusPicturesOffSelector
,
1285 kDiphthongLigaturesOffSelector
,
1286 kSquaredLigaturesOffSelector
,
1287 kAbbrevSquaredLigaturesOffSelector
,
1288 kSymbolLigaturesOffSelector
1290 static ATSUFontFeatureType types
[NS_ARRAY_LENGTH(selectors
)] = {
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
) {
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
{
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])) {
1372 ch
= SURROGATE_TO_UCS4(ch
, mString
[mOffset
]);
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
);
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(); }
1409 const PRUnichar
*mString
;
1411 PRUint32 mPrevOffset
;
1412 PRUint32 mEndOffset
;
1415 gfxAtsuiFontGroup
*mFontGroup
;
1416 nsRefPtr
<gfxAtsuiFont
> mMatchedFont
;
1417 nsRefPtr
<gfxAtsuiFont
> mNextMatchedFont
;
1422 SetLayoutRangeToFont(ATSUTextLayout layout
, ATSUStyle mainStyle
, UniCharArrayOffset offset
,
1423 UniCharCount length
, ATSUFontID fontID
)
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
);
1442 gfxAtsuiFontGroup::InitTextRun(gfxTextRun
*aRun
,
1443 const PRUnichar
*aString
, PRUint32 aLength
,
1444 PRUint32 aLayoutStart
, PRUint32 aLayoutLength
,
1445 PRUint32 aOffsetInTextRun
, PRUint32 aLengthInTextRun
)
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()) );
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
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
[] = {
1518 ATSUSetLayoutControls(layout
,
1519 NS_ARRAY_LENGTH(layoutTags
),
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
));
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
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
);
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.
1599 ItemCount trapCount
;
1600 ATSUGetGlyphBounds(layout
, 0, 0, aLayoutStart
, aLengthInTextRun
,
1601 kATSUseFractionalOrigins
, 1, &trap
, &trapCount
);
1603 ATSUDisposeTextLayout(layout
);
1605 aRun
->AdjustAdvancesForSyntheticBold(aOffsetInTextRun
, aLengthInTextRun
);
1608 for (i
= 0; i
< stylesToDispose
.Length(); ++i
) {
1609 ATSUDisposeStyle(stylesToDispose
[i
]);
1611 gCallbackClosure
= nsnull
;
1612 return !closure
.mOverrunningGlyphs
;