mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
d734a6961c
--HG-- extra : rebase_source : dc3dd429395d3219eb1f56e9ec4ecebd86bbfdae
1575 lines
60 KiB
C++
1575 lines
60 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Corporation code.
|
|
*
|
|
* The Initial Developer of the Original Code is Mozilla Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Vladimir Vukicevic <vladimir@pobox.com>
|
|
* Masayuki Nakano <masayuki@d-toybox.com>
|
|
* John Daggett <jdaggett@mozilla.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#ifndef __LP64__ /* don't compile any of this on 64-bit as ATSUI is not available */
|
|
|
|
#include "prtypes.h"
|
|
#include "prmem.h"
|
|
#include "nsString.h"
|
|
#include "nsBidiUtils.h"
|
|
|
|
#include "gfxTypes.h"
|
|
|
|
#include "nsPromiseFlatString.h"
|
|
|
|
#include "gfxContext.h"
|
|
#include "gfxPlatform.h"
|
|
#include "gfxPlatformMac.h"
|
|
#include "gfxAtsuiFonts.h"
|
|
|
|
#include "gfxFontTest.h"
|
|
#include "gfxFontUtils.h"
|
|
|
|
#include "cairo-quartz.h"
|
|
|
|
#include "gfxQuartzSurface.h"
|
|
#include "gfxQuartzFontCache.h"
|
|
#include "gfxUserFontSet.h"
|
|
|
|
#include "nsUnicodeRange.h"
|
|
|
|
// Uncomment this to dump all text runs created to stdout
|
|
// #define DUMP_TEXT_RUNS
|
|
|
|
#ifdef DUMP_TEXT_RUNS
|
|
static PRLogModuleInfo *gAtsuiTextRunLog = PR_NewLogModule("atsuiTextRun");
|
|
#endif
|
|
|
|
#define ROUND(x) (floor((x) + 0.5))
|
|
|
|
/* 10.5 SDK includes a funky new definition of FloatToFixed, so reset to old-style definition */
|
|
#ifdef FloatToFixed
|
|
#undef FloatToFixed
|
|
#define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
|
|
#endif
|
|
|
|
/* We might still need this for fast-pathing, but we'll see */
|
|
#if 0
|
|
OSStatus ATSUGetStyleGroup(ATSUStyle style, void **styleGroup);
|
|
OSStatus ATSUDisposeStyleGroup(void *styleGroup);
|
|
OSStatus ATSUConvertCharToGlyphs(void *styleGroup,
|
|
PRunichar *buffer
|
|
unsigned int bufferLength,
|
|
void *glyphVector);
|
|
OSStatus ATSInitializeGlyphVector(int size, void *glyphVectorPtr);
|
|
OSStatus ATSClearGlyphVector(void *glyphVectorPtr);
|
|
#endif
|
|
|
|
gfxAtsuiFont::gfxAtsuiFont(MacOSFontEntry *aFontEntry,
|
|
const gfxFontStyle *fontStyle, PRBool aNeedsBold)
|
|
: gfxFont(aFontEntry, fontStyle),
|
|
mFontStyle(fontStyle), mATSUStyle(nsnull),
|
|
mHasMirroring(PR_FALSE), mHasMirroringLookedUp(PR_FALSE),
|
|
mFontFace(nsnull), mScaledFont(nsnull), mAdjustedSize(0.0f)
|
|
{
|
|
ATSFontRef fontRef = aFontEntry->GetFontRef();
|
|
ATSUFontID fontID = FMGetFontFromATSFontRef(fontRef);
|
|
|
|
// determine whether synthetic bolding is needed
|
|
PRInt8 baseWeight, weightDistance;
|
|
mFontStyle->ComputeWeightAndOffset(&baseWeight, &weightDistance);
|
|
PRUint16 targetWeight = (baseWeight * 100) + (weightDistance * 100);
|
|
|
|
// synthetic bolding occurs when font itself is not a bold-face and either the absolute weight
|
|
// is at least 600 or the relative weight (e.g. 402) implies a darker face than the ones available.
|
|
// note: this means that (1) lighter styles *never* synthetic bold and (2) synthetic bolding always occurs
|
|
// at the first bolder step beyond available faces, no matter how light the boldest face
|
|
if (!aFontEntry->IsBold()
|
|
&& ((weightDistance == 0 && targetWeight >= 600) || (weightDistance > 0 && aNeedsBold)))
|
|
{
|
|
mSyntheticBoldOffset = 1; // devunit offset when double-striking text to fake boldness
|
|
}
|
|
|
|
InitMetrics(fontID, fontRef);
|
|
if (!mIsValid) {
|
|
return;
|
|
}
|
|
|
|
mFontFace = cairo_quartz_font_face_create_for_atsu_font_id(fontID);
|
|
|
|
cairo_matrix_t sizeMatrix, ctm;
|
|
cairo_matrix_init_identity(&ctm);
|
|
cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
|
|
|
|
// synthetic oblique by skewing via the font matrix
|
|
PRBool needsOblique = (!aFontEntry->IsItalic() && (mFontStyle->style & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)));
|
|
|
|
if (needsOblique) {
|
|
double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0);
|
|
|
|
cairo_matrix_t style;
|
|
cairo_matrix_init(&style,
|
|
1, //xx
|
|
0, //yx
|
|
-1 * skewfactor, //xy
|
|
1, //yy
|
|
0, //x0
|
|
0); //y0
|
|
cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
|
|
}
|
|
|
|
cairo_font_options_t *fontOptions = cairo_font_options_create();
|
|
|
|
// turn off font anti-aliasing based on user pref setting
|
|
if (mAdjustedSize <= (float) gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
|
|
cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
|
|
//printf("font: %s, size: %f, disabling anti-aliasing\n", NS_ConvertUTF16toUTF8(GetName()).get(), mAdjustedSize);
|
|
}
|
|
|
|
mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, fontOptions);
|
|
cairo_font_options_destroy(fontOptions);
|
|
|
|
cairo_status_t cairoerr = cairo_scaled_font_status(mScaledFont);
|
|
if (cairoerr != CAIRO_STATUS_SUCCESS) {
|
|
mIsValid = PR_FALSE;
|
|
|
|
#ifdef DEBUG
|
|
char warnBuf[1024];
|
|
sprintf(warnBuf, "Failed to create scaled font: %s status: %d", NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
|
|
NS_WARNING(warnBuf);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
ATSFontRef gfxAtsuiFont::GetATSFontRef()
|
|
{
|
|
return GetFontEntry()->GetFontRef();
|
|
}
|
|
|
|
static void
|
|
DisableUncommonLigaturesAndLineBoundarySwashes(ATSUStyle aStyle)
|
|
{
|
|
static const ATSUFontFeatureType types[] = {
|
|
kLigaturesType,
|
|
kLigaturesType,
|
|
kLigaturesType,
|
|
kLigaturesType,
|
|
kLigaturesType,
|
|
kLigaturesType,
|
|
kSmartSwashType,
|
|
kSmartSwashType
|
|
};
|
|
static const ATSUFontFeatureType selectors[NS_ARRAY_LENGTH(types)] = {
|
|
kRareLigaturesOffSelector,
|
|
kLogosOffSelector,
|
|
kRebusPicturesOffSelector,
|
|
kDiphthongLigaturesOffSelector,
|
|
kSquaredLigaturesOffSelector,
|
|
kAbbrevSquaredLigaturesOffSelector,
|
|
kLineInitialSwashesOffSelector,
|
|
kLineFinalSwashesOffSelector
|
|
};
|
|
ATSUSetFontFeatures(aStyle, NS_ARRAY_LENGTH(types), types, selectors);
|
|
}
|
|
|
|
static void
|
|
DisableCommonLigatures(ATSUStyle aStyle)
|
|
{
|
|
static const ATSUFontFeatureType types[] = {
|
|
kLigaturesType
|
|
};
|
|
static const ATSUFontFeatureType selectors[NS_ARRAY_LENGTH(types)] = {
|
|
kCommonLigaturesOffSelector
|
|
};
|
|
ATSUSetFontFeatures(aStyle, NS_ARRAY_LENGTH(types), types, selectors);
|
|
}
|
|
|
|
static double
|
|
RoundToNearestMultiple(double aValue, double aFraction)
|
|
{
|
|
return floor(aValue/aFraction + 0.5)*aFraction;
|
|
}
|
|
|
|
void
|
|
gfxAtsuiFont::InitMetrics(ATSUFontID aFontID, ATSFontRef aFontRef)
|
|
{
|
|
/* Create the ATSUStyle */
|
|
|
|
gfxFloat size =
|
|
PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : GetStyle()->size), 1.0f);
|
|
|
|
//fprintf (stderr, "string: '%s', size: %f\n", NS_ConvertUTF16toUTF8(aString).get(), size);
|
|
|
|
if (mATSUStyle)
|
|
ATSUDisposeStyle(mATSUStyle);
|
|
|
|
ATSUFontID fid = aFontID;
|
|
// fSize is in points (72dpi)
|
|
Fixed fSize = FloatToFixed(size);
|
|
// make the font render right-side up
|
|
CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
|
|
|
|
static const ATSUAttributeTag styleTags[] = {
|
|
kATSUFontTag,
|
|
kATSUSizeTag,
|
|
kATSUFontMatrixTag
|
|
};
|
|
const ATSUAttributeValuePtr styleArgs[NS_ARRAY_LENGTH(styleTags)] = {
|
|
&fid,
|
|
&fSize,
|
|
&transform
|
|
};
|
|
static const ByteCount styleArgSizes[NS_ARRAY_LENGTH(styleTags)] = {
|
|
sizeof(ATSUFontID),
|
|
sizeof(Fixed),
|
|
sizeof(CGAffineTransform)
|
|
};
|
|
|
|
ATSUCreateStyle(&mATSUStyle);
|
|
ATSUSetAttributes(mATSUStyle,
|
|
NS_ARRAY_LENGTH(styleTags),
|
|
styleTags,
|
|
styleArgSizes,
|
|
styleArgs);
|
|
// Disable uncommon ligatures, but *don't* enable common ones;
|
|
// the font may have default settings that disable common ligatures
|
|
// and we want to respect that.
|
|
// Also disable line boundary swashes because we can't handle them properly;
|
|
// we don't know where the line-breaks are at the time we're applying shaping,
|
|
// and it would be bad to put words with line-end swashes into the text-run
|
|
// cache until we have a way to distinguish them from mid-line occurrences.
|
|
DisableUncommonLigaturesAndLineBoundarySwashes(mATSUStyle);
|
|
|
|
/* Now pull out the metrics */
|
|
|
|
ATSFontMetrics atsMetrics;
|
|
OSStatus err;
|
|
|
|
err = ATSFontGetHorizontalMetrics(aFontRef, kATSOptionFlagsDefault,
|
|
&atsMetrics);
|
|
|
|
if (err != noErr) {
|
|
mIsValid = PR_FALSE;
|
|
|
|
#ifdef DEBUG
|
|
char warnBuf[1024];
|
|
sprintf(warnBuf, "Bad font metrics for: %s err: %8.8x", NS_ConvertUTF16toUTF8(GetName()).get(), PRUint32(err));
|
|
NS_WARNING(warnBuf);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (atsMetrics.xHeight)
|
|
mMetrics.xHeight = atsMetrics.xHeight * size;
|
|
else
|
|
mMetrics.xHeight = GetCharHeight('x');
|
|
|
|
if (mAdjustedSize == 0.0f) {
|
|
if (mMetrics.xHeight != 0.0f && GetStyle()->sizeAdjust != 0.0f) {
|
|
gfxFloat aspect = mMetrics.xHeight / size;
|
|
mAdjustedSize = GetStyle()->GetAdjustedSize(aspect);
|
|
InitMetrics(aFontID, aFontRef);
|
|
return;
|
|
}
|
|
mAdjustedSize = size;
|
|
}
|
|
|
|
mMetrics.emHeight = size;
|
|
|
|
mMetrics.maxAscent =
|
|
NS_ceil(RoundToNearestMultiple(atsMetrics.ascent*size, 1/1024.0));
|
|
mMetrics.maxDescent =
|
|
NS_ceil(-RoundToNearestMultiple(atsMetrics.descent*size, 1/1024.0));
|
|
|
|
mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
|
|
|
|
if (mMetrics.maxHeight - mMetrics.emHeight > 0)
|
|
mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
|
|
else
|
|
mMetrics.internalLeading = 0.0;
|
|
mMetrics.externalLeading = atsMetrics.leading * size;
|
|
|
|
mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
|
|
mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
|
|
|
|
mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
|
|
|
|
float xWidth = GetCharWidth('x');
|
|
if (atsMetrics.avgAdvanceWidth != 0.0)
|
|
mMetrics.aveCharWidth =
|
|
PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
|
|
else
|
|
mMetrics.aveCharWidth = xWidth;
|
|
|
|
mMetrics.aveCharWidth += mSyntheticBoldOffset;
|
|
|
|
if (GetFontEntry()->IsFixedPitch()) {
|
|
// Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
|
|
// advance than the average character width... this forces
|
|
// those fonts to be recognized like fixed pitch fonts by layout.
|
|
mMetrics.maxAdvance = mMetrics.aveCharWidth;
|
|
}
|
|
|
|
mMetrics.underlineOffset = atsMetrics.underlinePosition * size;
|
|
mMetrics.underlineSize = atsMetrics.underlineThickness * size;
|
|
|
|
mMetrics.subscriptOffset = mMetrics.xHeight;
|
|
mMetrics.superscriptOffset = mMetrics.xHeight;
|
|
|
|
mMetrics.strikeoutOffset = mMetrics.xHeight / 2.0;
|
|
mMetrics.strikeoutSize = mMetrics.underlineSize;
|
|
|
|
PRUint32 glyphID;
|
|
mMetrics.spaceWidth = GetCharWidth(' ', &glyphID);
|
|
mSpaceGlyph = glyphID;
|
|
|
|
mMetrics.zeroOrAveCharWidth = GetCharWidth('0', &glyphID);
|
|
if (glyphID == 0) // no zero in this font
|
|
mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
|
|
|
|
SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
|
|
|
|
#if 0
|
|
fprintf (stderr, "Font: %p (%s) size: %f\n", this,
|
|
NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
|
|
fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
|
|
fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
|
|
fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
|
|
fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
|
|
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);
|
|
#endif
|
|
}
|
|
|
|
PRBool
|
|
gfxAtsuiFont::SetupCairoFont(gfxContext *aContext)
|
|
{
|
|
cairo_scaled_font_t *scaledFont = CairoScaledFont();
|
|
if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
|
|
// Don't cairo_set_scaled_font as that would propagate the error to
|
|
// the cairo_t, precluding any further drawing.
|
|
return PR_FALSE;
|
|
}
|
|
cairo_set_scaled_font(aContext->GetCairo(), scaledFont);
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsString
|
|
gfxAtsuiFont::GetUniqueName()
|
|
{
|
|
return GetName();
|
|
}
|
|
|
|
float
|
|
gfxAtsuiFont::GetCharWidth(PRUnichar c, PRUint32 *aGlyphID)
|
|
{
|
|
// this sucks. There is a faster way to go from a char -> glyphs, but it
|
|
// requires using oodles of apple private interfaces. If we start caching
|
|
// gfxAtsuiFonts, then it might make sense to do that.
|
|
ATSUTextLayout layout;
|
|
|
|
UniCharCount one = 1;
|
|
ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
|
|
|
|
ATSTrapezoid trap;
|
|
ItemCount numBounds;
|
|
ATSUGetGlyphBounds(layout, FloatToFixed(0.0), FloatToFixed(0.0),
|
|
0, 1, kATSUseFractionalOrigins, 1, &trap, &numBounds);
|
|
|
|
float f =
|
|
FixedToFloat(PR_MAX(trap.upperRight.x, trap.lowerRight.x)) -
|
|
FixedToFloat(PR_MIN(trap.upperLeft.x, trap.lowerLeft.x));
|
|
|
|
if (aGlyphID) {
|
|
ATSUGlyphInfoArray glyphInfo;
|
|
ByteCount bytes = sizeof(glyphInfo);
|
|
ATSUGetGlyphInfo(layout, 0, 1, &bytes, &glyphInfo);
|
|
*aGlyphID = glyphInfo.glyphs[0].glyphID;
|
|
}
|
|
|
|
ATSUDisposeTextLayout(layout);
|
|
|
|
return f;
|
|
}
|
|
|
|
float
|
|
gfxAtsuiFont::GetCharHeight(PRUnichar c)
|
|
{
|
|
// this sucks. There is a faster way to go from a char -> glyphs, but it
|
|
// requires using oodles of apple private interfaces. If we start caching
|
|
// gfxAtsuiFonts, then it might make sense to do that.
|
|
ATSUTextLayout layout;
|
|
|
|
UniCharCount one = 1;
|
|
ATSUCreateTextLayoutWithTextPtr(&c, 0, 1, 1, 1, &one, &mATSUStyle, &layout);
|
|
|
|
Rect rect;
|
|
ATSUMeasureTextImage(layout, 0, 1, 0, 0, &rect);
|
|
|
|
ATSUDisposeTextLayout(layout);
|
|
|
|
return rect.bottom - rect.top;
|
|
}
|
|
|
|
gfxAtsuiFont::~gfxAtsuiFont()
|
|
{
|
|
if (mScaledFont)
|
|
cairo_scaled_font_destroy(mScaledFont);
|
|
if (mFontFace)
|
|
cairo_font_face_destroy(mFontFace);
|
|
|
|
if (mATSUStyle)
|
|
ATSUDisposeStyle(mATSUStyle);
|
|
}
|
|
|
|
const gfxFont::Metrics&
|
|
gfxAtsuiFont::GetMetrics()
|
|
{
|
|
return mMetrics;
|
|
}
|
|
|
|
void
|
|
gfxAtsuiFont::SetupGlyphExtents(gfxContext *aContext, PRUint32 aGlyphID,
|
|
PRBool aNeedTight, gfxGlyphExtents *aExtents)
|
|
{
|
|
ATSGlyphScreenMetrics metrics;
|
|
GlyphID glyph = aGlyphID;
|
|
OSStatus err = ATSUGlyphGetScreenMetrics(mATSUStyle, 1, &glyph, 0, false, false,
|
|
&metrics);
|
|
if (err != noErr)
|
|
return;
|
|
PRUint32 appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
|
|
|
|
if (!aNeedTight && metrics.topLeft.x >= 0 &&
|
|
-metrics.topLeft.y + metrics.height <= mMetrics.maxAscent &&
|
|
metrics.topLeft.y <= mMetrics.maxDescent) {
|
|
PRUint32 appUnitsWidth =
|
|
PRUint32(NS_ceil((metrics.topLeft.x + metrics.width)*appUnitsPerDevUnit));
|
|
if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
|
|
aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, PRUint16(appUnitsWidth));
|
|
return;
|
|
}
|
|
}
|
|
|
|
double d2a = appUnitsPerDevUnit;
|
|
gfxRect bounds(metrics.topLeft.x*d2a, (metrics.topLeft.y - metrics.height)*d2a,
|
|
metrics.width*d2a, metrics.height*d2a);
|
|
aExtents->SetTightGlyphExtents(aGlyphID, bounds);
|
|
}
|
|
|
|
PRBool
|
|
gfxAtsuiFont::HasMirroringInfo()
|
|
{
|
|
if (!mHasMirroringLookedUp) {
|
|
OSStatus status;
|
|
ByteCount size;
|
|
|
|
// 361695 - if the font has a 'prop' table, assume that ATSUI will handle glyph mirroring
|
|
status = ATSFontGetTable(GetATSFontRef(), TRUETYPE_TAG('p','r','o','p'), 0, 0, 0, &size);
|
|
mHasMirroring = (status == noErr);
|
|
mHasMirroringLookedUp = PR_TRUE;
|
|
}
|
|
|
|
return mHasMirroring;
|
|
}
|
|
|
|
PRBool gfxAtsuiFont::TestCharacterMap(PRUint32 aCh) {
|
|
if (!mIsValid) return PR_FALSE;
|
|
return GetFontEntry()->TestCharacterMap(aCh);
|
|
}
|
|
|
|
MacOSFontEntry*
|
|
gfxAtsuiFont::GetFontEntry()
|
|
{
|
|
return static_cast< MacOSFontEntry*> (mFontEntry.get());
|
|
}
|
|
|
|
/**
|
|
* Look up the font in the gfxFont cache. If we don't find it, create one.
|
|
* In either case, add a ref and return it ---
|
|
* except for OOM in which case we do nothing and return null.
|
|
*/
|
|
|
|
static already_AddRefed<gfxAtsuiFont>
|
|
GetOrMakeFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aStyle, PRBool aNeedsBold)
|
|
{
|
|
// the font entry name is the psname, not the family name
|
|
nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(aFontEntry->Name(), aStyle);
|
|
if (!font) {
|
|
gfxAtsuiFont *newFont = new gfxAtsuiFont(aFontEntry, aStyle, aNeedsBold);
|
|
if (!newFont)
|
|
return nsnull;
|
|
if (!newFont->Valid()) {
|
|
delete newFont;
|
|
return nsnull;
|
|
}
|
|
font = newFont;
|
|
gfxFontCache::GetCache()->AddNew(font);
|
|
}
|
|
gfxFont *f = nsnull;
|
|
font.swap(f);
|
|
return static_cast<gfxAtsuiFont *>(f);
|
|
}
|
|
|
|
|
|
gfxAtsuiFontGroup::gfxAtsuiFontGroup(const nsAString& families,
|
|
const gfxFontStyle *aStyle,
|
|
gfxUserFontSet *aUserFontSet)
|
|
: gfxFontGroup(families, aStyle, aUserFontSet)
|
|
{
|
|
mPageLang = gfxPlatform::GetFontPrefLangFor(mStyle.langGroup.get());
|
|
|
|
InitFontList();
|
|
}
|
|
|
|
PRBool
|
|
gfxAtsuiFontGroup::FindATSUFont(const nsAString& aName,
|
|
const nsACString& aGenericName,
|
|
void *closure)
|
|
{
|
|
gfxAtsuiFontGroup *fontGroup = (gfxAtsuiFontGroup*) closure;
|
|
const gfxFontStyle *fontStyle = fontGroup->GetStyle();
|
|
|
|
|
|
PRBool needsBold;
|
|
MacOSFontEntry *fe = nsnull;
|
|
|
|
// first, look up in the user font set
|
|
gfxUserFontSet *fs = fontGroup->GetUserFontSet();
|
|
gfxFontEntry *gfe;
|
|
if (fs && (gfe = fs->FindFontEntry(aName, *fontStyle, needsBold))) {
|
|
// assume for now platform font if not SVG
|
|
fe = static_cast<MacOSFontEntry*> (gfe);
|
|
}
|
|
|
|
// nothing in the user font set ==> check system fonts
|
|
if (!fe) {
|
|
gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
|
|
fe = fc->FindFontForFamily(aName, fontStyle, needsBold);
|
|
}
|
|
|
|
if (fe && !fontGroup->HasFont(fe->GetFontRef())) {
|
|
nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(fe, fontStyle, needsBold);
|
|
if (font) {
|
|
fontGroup->mFonts.AppendElement(font);
|
|
}
|
|
}
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
gfxFontGroup *
|
|
gfxAtsuiFontGroup::Copy(const gfxFontStyle *aStyle)
|
|
{
|
|
return new gfxAtsuiFontGroup(mFamilies, aStyle, mUserFontSet);
|
|
}
|
|
|
|
#define UNICODE_LRO 0x202d
|
|
#define UNICODE_RLO 0x202e
|
|
#define UNICODE_PDF 0x202c
|
|
|
|
static void
|
|
AppendDirectionalIndicatorStart(PRUint32 aFlags, nsAString& aString)
|
|
{
|
|
static const PRUnichar overrides[2] = { UNICODE_LRO, UNICODE_RLO };
|
|
aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);
|
|
}
|
|
|
|
// Returns the number of trailing characters that should be part of the
|
|
// layout but should be ignored
|
|
static PRUint32
|
|
AppendDirectionalIndicatorEnd(PRBool aNeedDirection, nsAString& aString)
|
|
{
|
|
// Ensure that we compute the full advance width for the last character
|
|
// in the string --- we don't want that character to form a kerning
|
|
// pair (or a ligature) with the '.' we may append next,
|
|
// so we append a space now.
|
|
// Even if the character is the last character in the layout,
|
|
// we want its width to be determined as if it had a space after it,
|
|
// for consistency with the bidi path and during textrun caching etc.
|
|
aString.Append(' ');
|
|
if (!aNeedDirection)
|
|
return 1;
|
|
|
|
// Ensure that none of the whitespace in the run is considered "trailing"
|
|
// by ATSUI's bidi algorithm
|
|
aString.Append('.');
|
|
aString.Append(UNICODE_PDF);
|
|
return 2;
|
|
}
|
|
|
|
/**
|
|
* Given a textrun and an offset into that textrun, we need to choose a length
|
|
* for the substring of the textrun that we should analyze next. The length
|
|
* should be <= aMaxLength if possible. It must always end at a cluster
|
|
* boundary and it should end at the end of the textrun or at the
|
|
* boundary of a space if possible.
|
|
*/
|
|
static PRUint32
|
|
FindTextRunSegmentLength(gfxTextRun *aTextRun, PRUint32 aOffset, PRUint32 aMaxLength)
|
|
{
|
|
if (aOffset + aMaxLength >= aTextRun->GetLength()) {
|
|
// The remaining part of the textrun fits within the max length,
|
|
// so just use it.
|
|
return aTextRun->GetLength() - aOffset;
|
|
}
|
|
|
|
// Try to end the segment before or after a space, since spaces don't kern
|
|
// or ligate.
|
|
PRUint32 end;
|
|
for (end = aOffset + aMaxLength; end > aOffset; --end) {
|
|
if (aTextRun->IsClusterStart(end) &&
|
|
(aTextRun->GetChar(end) == ' ' || aTextRun->GetChar(end - 1) == ' '))
|
|
return end - aOffset;
|
|
}
|
|
|
|
// Try to end the segment at the last cluster boundary.
|
|
for (end = aOffset + aMaxLength; end > aOffset; --end) {
|
|
if (aTextRun->IsClusterStart(end))
|
|
return end - aOffset;
|
|
}
|
|
|
|
// I guess we just have to return a segment that's the entire cluster
|
|
// starting at aOffset.
|
|
for (end = aOffset + 1; end < aTextRun->GetLength(); ++end) {
|
|
if (aTextRun->IsClusterStart(end))
|
|
return end - aOffset;
|
|
}
|
|
return aTextRun->GetLength() - aOffset;
|
|
}
|
|
|
|
PRUint32
|
|
gfxAtsuiFontGroup::GuessMaximumStringLength()
|
|
{
|
|
// ATSUI can't handle offsets of more than 32K pixels. This function
|
|
// guesses a string length that ATSUI will be able to handle. We want to
|
|
// get the right answer most of the time, but if we're wrong in either
|
|
// direction, we won't break things: if we guess too large, our glyph
|
|
// processing will detect ATSUI's failure and retry with a smaller limit.
|
|
// If we guess too small, we'll just break the string into more pieces
|
|
// than we strictly needed to.
|
|
// The basic calculation is just 32k pixels divided by the font max-advance,
|
|
// but we need to be a bit careful to avoid math errors.
|
|
PRUint32 maxAdvance = PRUint32(GetFontAt(0)->GetMetrics().maxAdvance);
|
|
PRUint32 chars = 0x7FFF/PR_MAX(1, maxAdvance);
|
|
|
|
PRUint32 realGuessMax = PR_MAX(1, chars);
|
|
|
|
// bug 436663 - ATSUI crashes on 10.5.3 with certain character sequences
|
|
// at around 512 characters, so for safety sake max out at 500 characters
|
|
// bug 480134 - Do this for all OSX versions now, because there may be
|
|
// other related bugs on 10.4.
|
|
realGuessMax = PR_MIN(500, realGuessMax);
|
|
|
|
return realGuessMax;
|
|
}
|
|
|
|
/*
|
|
* ATSUI can't handle more than 32K pixels of text. We can easily have
|
|
* textruns longer than that. Our strategy here is to divide the textrun up
|
|
* into pieces each of which is less than 32K pixels wide. We pick a number
|
|
* of characters 'maxLen' such that the first font's max-advance times that
|
|
* number of characters is less than 32K pixels; then we try glyph conversion
|
|
* of the string broken up into chunks each with no more than 'maxLen'
|
|
* characters. That could fail (e.g. if fallback fonts are used); if it does,
|
|
* we retry with a smaller maxLen. When breaking up the string into chunks
|
|
* we prefer to break at space boundaries because spaces don't kern or ligate
|
|
* with other characters, usually. We insist on breaking at cluster boundaries.
|
|
* If the font size is incredibly huge and/or clusters are very large, this
|
|
* could mean that we actually put more than 'maxLen' characters in a chunk.
|
|
*/
|
|
|
|
gfxTextRun *
|
|
gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
|
|
const Parameters *aParams, PRUint32 aFlags)
|
|
{
|
|
NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
|
|
gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
|
|
if (!textRun)
|
|
return nsnull;
|
|
|
|
textRun->RecordSurrogates(aString);
|
|
gfxPlatformMac::SetupClusterBoundaries(textRun, aString);
|
|
|
|
PRUint32 maxLen;
|
|
nsAutoString utf16;
|
|
for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
|
|
PRUint32 start = 0;
|
|
while (start < aLength) {
|
|
PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
|
|
|
|
utf16.Truncate();
|
|
AppendDirectionalIndicatorStart(aFlags, utf16);
|
|
PRUint32 layoutStart = utf16.Length();
|
|
utf16.Append(aString + start, len);
|
|
// Ensure that none of the whitespace in the run is considered "trailing"
|
|
// by ATSUI's bidi algorithm
|
|
PRUint32 trailingCharsToIgnore =
|
|
AppendDirectionalIndicatorEnd(PR_TRUE, utf16);
|
|
PRUint32 layoutLength = len + trailingCharsToIgnore;
|
|
if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
|
|
layoutStart, layoutLength,
|
|
start, len) && maxLen > 1)
|
|
break;
|
|
start += len;
|
|
}
|
|
if (start == aLength)
|
|
break;
|
|
textRun->ResetGlyphRuns();
|
|
}
|
|
|
|
textRun->FetchGlyphExtents(aParams->mContext);
|
|
|
|
return textRun;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
|
|
const Parameters *aParams, PRUint32 aFlags)
|
|
{
|
|
NS_ASSERTION(aLength > 0, "should use MakeEmptyTextRun for zero-length text");
|
|
NS_ASSERTION(aFlags & TEXT_IS_8BIT, "should be marked 8bit");
|
|
gfxTextRun *textRun = gfxTextRun::Create(aParams, aString, aLength, this, aFlags);
|
|
if (!textRun)
|
|
return nsnull;
|
|
|
|
PRUint32 maxLen;
|
|
nsAutoString utf16;
|
|
for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
|
|
PRUint32 start = 0;
|
|
while (start < aLength) {
|
|
PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
|
|
|
|
nsDependentCSubstring cString(reinterpret_cast<const char*>(aString + start),
|
|
reinterpret_cast<const char*>(aString + start + len));
|
|
utf16.Truncate();
|
|
PRBool wrapBidi = (aFlags & TEXT_IS_RTL) != 0;
|
|
if (wrapBidi) {
|
|
AppendDirectionalIndicatorStart(aFlags, utf16);
|
|
}
|
|
PRUint32 layoutStart = utf16.Length();
|
|
AppendASCIItoUTF16(cString, utf16);
|
|
PRUint32 trailingCharsToIgnore =
|
|
AppendDirectionalIndicatorEnd(wrapBidi, utf16);
|
|
PRUint32 layoutLength = len + trailingCharsToIgnore;
|
|
if (!InitTextRun(textRun, utf16.get(), utf16.Length(),
|
|
layoutStart, layoutLength,
|
|
start, len) && maxLen > 1)
|
|
break;
|
|
start += len;
|
|
}
|
|
if (start == aLength)
|
|
break;
|
|
textRun->ResetGlyphRuns();
|
|
}
|
|
|
|
textRun->FetchGlyphExtents(aParams->mContext);
|
|
|
|
return textRun;
|
|
}
|
|
|
|
PRBool
|
|
gfxAtsuiFontGroup::HasFont(ATSFontRef aFontRef)
|
|
{
|
|
for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
|
|
if (aFontRef == static_cast<gfxAtsuiFont *>(mFonts.ElementAt(i).get())->GetATSFontRef())
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
struct PrefFontCallbackData {
|
|
PrefFontCallbackData(nsTArray<nsRefPtr<MacOSFamilyEntry> >& aFamiliesArray)
|
|
: mPrefFamilies(aFamiliesArray)
|
|
{}
|
|
|
|
nsTArray<nsRefPtr<MacOSFamilyEntry> >& mPrefFamilies;
|
|
|
|
static PRBool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure)
|
|
{
|
|
PrefFontCallbackData *prefFontData = (PrefFontCallbackData*) aClosure;
|
|
|
|
MacOSFamilyEntry *family = gfxQuartzFontCache::SharedFontCache()->FindFamily(aName);
|
|
if (family) {
|
|
prefFontData->mPrefFamilies.AppendElement(family);
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
};
|
|
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxAtsuiFontGroup::WhichPrefFontSupportsChar(PRUint32 aCh)
|
|
{
|
|
gfxFont *font;
|
|
|
|
// FindCharUnicodeRange only supports BMP character points and there are no non-BMP fonts in prefs
|
|
if (aCh > 0xFFFF)
|
|
return nsnull;
|
|
|
|
// get the pref font list if it hasn't been set up already
|
|
PRUint32 unicodeRange = FindCharUnicodeRange(aCh);
|
|
eFontPrefLang charLang = gfxPlatformMac::GetFontPrefLangFor(unicodeRange);
|
|
|
|
// if the last pref font was the first family in the pref list, no need to recheck through a list of families
|
|
if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && mLastPrefFont->TestCharacterMap(aCh)) {
|
|
font = mLastPrefFont;
|
|
NS_ADDREF(font);
|
|
return font;
|
|
}
|
|
|
|
// based on char lang and page lang, set up list of pref lang fonts to check
|
|
eFontPrefLang prefLangs[kMaxLenPrefLangList];
|
|
PRUint32 i, numLangs = 0;
|
|
|
|
gfxPlatformMac *macPlatform = gfxPlatformMac::GetPlatform();
|
|
macPlatform->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
|
|
|
|
for (i = 0; i < numLangs; i++) {
|
|
nsAutoTArray<nsRefPtr<MacOSFamilyEntry>, 5> families;
|
|
eFontPrefLang currentLang = prefLangs[i];
|
|
|
|
gfxQuartzFontCache *fc = gfxQuartzFontCache::SharedFontCache();
|
|
|
|
// get the pref families for a single pref lang
|
|
if (!fc->GetPrefFontFamilyEntries(currentLang, &families)) {
|
|
eFontPrefLang prefLangsToSearch[1] = { currentLang };
|
|
PrefFontCallbackData prefFontData(families);
|
|
gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry,
|
|
&prefFontData);
|
|
fc->SetPrefFontFamilyEntries(currentLang, families);
|
|
}
|
|
|
|
// find the first pref font that includes the character
|
|
PRUint32 i, numPrefs;
|
|
numPrefs = families.Length();
|
|
for (i = 0; i < numPrefs; i++) {
|
|
// look up the appropriate face
|
|
MacOSFamilyEntry *family = families[i];
|
|
if (!family) continue;
|
|
|
|
// if a pref font is used, it's likely to be used again in the same text run.
|
|
// the style doesn't change so the face lookup can be cached rather than calling
|
|
// GetOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent
|
|
// pref font lookups
|
|
if (family == mLastPrefFamily && mLastPrefFont->TestCharacterMap(aCh)) {
|
|
font = mLastPrefFont;
|
|
NS_ADDREF(font);
|
|
return font;
|
|
}
|
|
|
|
PRBool needsBold;
|
|
MacOSFontEntry *fe = family->FindFont(&mStyle, needsBold);
|
|
// if ch in cmap, create and return a gfxFont
|
|
if (fe && fe->TestCharacterMap(aCh)) {
|
|
nsRefPtr<gfxAtsuiFont> prefFont = GetOrMakeFont(fe, &mStyle, needsBold);
|
|
if (!prefFont) continue;
|
|
mLastPrefFamily = family;
|
|
mLastPrefFont = prefFont;
|
|
mLastPrefLang = charLang;
|
|
mLastPrefFirstFont = (i == 0);
|
|
nsRefPtr<gfxFont> font2 = (gfxFont*) prefFont;
|
|
return font2.forget();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxAtsuiFontGroup::WhichSystemFontSupportsChar(PRUint32 aCh)
|
|
{
|
|
MacOSFontEntry *fe;
|
|
|
|
fe = gfxQuartzFontCache::SharedFontCache()->FindFontForChar(aCh, GetFontAt(0));
|
|
if (fe) {
|
|
nsRefPtr<gfxAtsuiFont> atsuiFont = GetOrMakeFont(fe, &mStyle, PR_FALSE); // ignore bolder considerations in system fallback case...
|
|
nsRefPtr<gfxFont> font = (gfxFont*) atsuiFont;
|
|
return font.forget();
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
void
|
|
gfxAtsuiFontGroup::UpdateFontList()
|
|
{
|
|
// if user font set is set, check to see if font list needs updating
|
|
if (mUserFontSet && mCurrGeneration != GetGeneration()) {
|
|
// xxx - can probably improve this to detect when all fonts were found, so no need to update list
|
|
mFonts.Clear();
|
|
mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET;
|
|
InitFontList();
|
|
mCurrGeneration = GetGeneration();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Simple wrapper for ATSU "direct data arrays"
|
|
*/
|
|
class AutoLayoutDataArrayPtr {
|
|
public:
|
|
AutoLayoutDataArrayPtr(ATSULineRef aLineRef,
|
|
ATSUDirectDataSelector aSelector)
|
|
: mLineRef(aLineRef), mSelector(aSelector)
|
|
{
|
|
OSStatus status =
|
|
ATSUDirectGetLayoutDataArrayPtrFromLineRef(aLineRef,
|
|
aSelector, PR_FALSE, &mArray, &mItemCount);
|
|
if (status != noErr) {
|
|
mArray = NULL;
|
|
mItemCount = 0;
|
|
}
|
|
}
|
|
~AutoLayoutDataArrayPtr() {
|
|
if (mArray) {
|
|
ATSUDirectReleaseLayoutDataArrayPtr(mLineRef, mSelector, &mArray);
|
|
}
|
|
}
|
|
|
|
void *mArray;
|
|
ItemCount mItemCount;
|
|
|
|
private:
|
|
ATSULineRef mLineRef;
|
|
ATSUDirectDataSelector mSelector;
|
|
};
|
|
|
|
#define ATSUI_SPECIAL_GLYPH_ID 0xFFFF
|
|
/**
|
|
* This flag seems to be set on glyphs that have overrun the 32K pixel
|
|
* limit in ATSUI.
|
|
*/
|
|
#define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
|
|
|
|
/**
|
|
* Calculate the advance in appunits of a run of ATSUI glyphs
|
|
*/
|
|
static PRInt32
|
|
GetAdvanceAppUnits(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
|
|
PRUint32 aAppUnitsPerDevUnit)
|
|
{
|
|
Fixed fixedAdvance = aGlyphs[aGlyphCount].realPos - aGlyphs->realPos;
|
|
return PRInt32((PRInt64(fixedAdvance)*aAppUnitsPerDevUnit + (1 << 15)) >> 16);
|
|
}
|
|
|
|
/**
|
|
* Given a run of ATSUI glyphs that should be treated as a single cluster/ligature,
|
|
* store them in the textrun at the appropriate character and set the
|
|
* other characters involved to be ligature/cluster continuations as appropriate.
|
|
*/
|
|
static void
|
|
SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
|
|
Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit,
|
|
gfxTextRun *aRun, PRUint32 aOffsetInTextRun,
|
|
const PRPackedBool *aUnmatched,
|
|
const PRUnichar *aString,
|
|
const PRUint32 aLength)
|
|
{
|
|
NS_ASSERTION(aGlyphCount > 0, "Must set at least one glyph");
|
|
PRUint32 firstOffset = aGlyphs[0].originalOffset;
|
|
PRUint32 lastOffset = firstOffset;
|
|
PRUint32 i;
|
|
PRUint32 regularGlyphCount = 0;
|
|
ATSLayoutRecord *displayGlyph = nsnull;
|
|
PRBool inOrder = PR_TRUE;
|
|
PRBool allMatched = PR_TRUE;
|
|
|
|
for (i = 0; i < aGlyphCount; ++i) {
|
|
ATSLayoutRecord *glyph = &aGlyphs[i];
|
|
PRUint32 offset = glyph->originalOffset;
|
|
firstOffset = PR_MIN(firstOffset, offset);
|
|
lastOffset = PR_MAX(lastOffset, offset);
|
|
if (aUnmatched && aUnmatched[offset/2]) {
|
|
allMatched = PR_FALSE;
|
|
}
|
|
if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
|
|
++regularGlyphCount;
|
|
displayGlyph = glyph;
|
|
}
|
|
if (i > 0 && aRun->IsRightToLeft() != (offset < aGlyphs[i - 1].originalOffset)) { // XXXkt allow == in RTL
|
|
inOrder = PR_FALSE;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString[firstOffset/2]),
|
|
"Invalid char passed in");
|
|
|
|
if (!allMatched) {
|
|
for (i = firstOffset; i <= lastOffset; ++i) {
|
|
PRUint32 index = i/2;
|
|
if (NS_IS_HIGH_SURROGATE(aString[index]) &&
|
|
index + 1 < aLength &&
|
|
NS_IS_LOW_SURROGATE(aString[index + 1])) {
|
|
aRun->SetMissingGlyph(aOffsetInTextRun + index,
|
|
SURROGATE_TO_UCS4(aString[index],
|
|
aString[index + 1]));
|
|
} else {
|
|
aRun->SetMissingGlyph(aOffsetInTextRun + index, aString[index]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
gfxTextRun::CompressedGlyph g;
|
|
PRUint32 offset;
|
|
// Make all but the first character in the group NOT be a ligature boundary,
|
|
// i.e. fuse the group into a ligature.
|
|
// Also make them not be cluster boundaries, i.e., fuse them into a cluster,
|
|
// if the glyphs are out of character order.
|
|
for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
|
|
PRUint32 charIndex = aOffsetInTextRun + offset/2;
|
|
PRBool makeClusterStart = inOrder && aRun->IsClusterStart(charIndex);
|
|
g.SetComplex(makeClusterStart, PR_FALSE, 0);
|
|
aRun->SetGlyphs(charIndex, g, nsnull);
|
|
}
|
|
|
|
// Grab total advance for all glyphs
|
|
PRInt32 advance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
|
|
PRUint32 charIndex = aOffsetInTextRun + firstOffset/2;
|
|
if (regularGlyphCount == 1) {
|
|
if (advance >= 0 &&
|
|
(!aBaselineDeltas || aBaselineDeltas[displayGlyph - aGlyphs] == 0) &&
|
|
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
|
|
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(displayGlyph->glyphID) &&
|
|
aRun->IsClusterStart(charIndex)) {
|
|
aRun->SetSimpleGlyph(charIndex, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsAutoTArray<gfxTextRun::DetailedGlyph,10> detailedGlyphs;
|
|
ATSLayoutRecord *advanceStart = aGlyphs;
|
|
for (i = 0; i < aGlyphCount; ++i) {
|
|
ATSLayoutRecord *glyph = &aGlyphs[i];
|
|
if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
|
|
if (glyph->originalOffset > firstOffset) {
|
|
PRUint32 glyphCharIndex = aOffsetInTextRun + glyph->originalOffset/2;
|
|
PRUint32 glyphRunIndex = aRun->FindFirstGlyphRunContaining(glyphCharIndex);
|
|
PRUint32 numGlyphRuns;
|
|
const gfxTextRun::GlyphRun *glyphRun = aRun->GetGlyphRuns(&numGlyphRuns) + glyphRunIndex;
|
|
|
|
if (glyphRun->mCharacterOffset > charIndex) {
|
|
// The font has changed inside the character group. This might
|
|
// happen in some weird situations, e.g. if
|
|
// ATSUI decides in LTR text to put the glyph for character
|
|
// 1 before the glyph for character 0, AND decides to
|
|
// give character 1's glyph a different font from character
|
|
// 0. This sucks because we can't then safely move this
|
|
// glyph to be associated with our first character.
|
|
// To handle this we'd have to do some funky hacking with
|
|
// glyph advances and offsets so that the glyphs stay
|
|
// associated with the right characters but they are
|
|
// displayed out of order. Let's not do this for now,
|
|
// in the hope that it doesn't come up. If it does come up,
|
|
// at least we can fix it right here without changing
|
|
// any other code.
|
|
NS_ERROR("Font change inside character group!");
|
|
// Be safe, just throw out this glyph
|
|
continue;
|
|
}
|
|
}
|
|
|
|
gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
|
|
if (!details)
|
|
return;
|
|
details->mAdvance = 0;
|
|
details->mGlyphID = glyph->glyphID;
|
|
details->mXOffset = 0;
|
|
if (detailedGlyphs.Length() > 1) {
|
|
details->mXOffset +=
|
|
GetAdvanceAppUnits(advanceStart, glyph - advanceStart,
|
|
aAppUnitsPerDevUnit);
|
|
}
|
|
details->mYOffset = !aBaselineDeltas ? 0.0f
|
|
: - FixedToFloat(aBaselineDeltas[i])*aAppUnitsPerDevUnit;
|
|
}
|
|
}
|
|
if (detailedGlyphs.Length() == 0) {
|
|
NS_WARNING("No glyphs visible at all!");
|
|
aRun->SetGlyphs(aOffsetInTextRun + charIndex, g.SetMissing(0), nsnull);
|
|
return;
|
|
}
|
|
|
|
// The advance width for the whole cluster
|
|
PRInt32 clusterAdvance = GetAdvanceAppUnits(aGlyphs, aGlyphCount, aAppUnitsPerDevUnit);
|
|
if (aRun->IsRightToLeft())
|
|
detailedGlyphs[0].mAdvance = clusterAdvance;
|
|
else
|
|
detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance = clusterAdvance;
|
|
g.SetComplex(aRun->IsClusterStart(charIndex), PR_TRUE, detailedGlyphs.Length());
|
|
aRun->SetGlyphs(charIndex, g, detailedGlyphs.Elements());
|
|
}
|
|
|
|
/**
|
|
* Returns true if there are overrunning glyphs
|
|
*/
|
|
static PRBool
|
|
PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
|
|
const PRUnichar *aString, PRUint32 aLayoutLength,
|
|
PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun,
|
|
const PRPackedBool *aUnmatched)
|
|
{
|
|
// AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
|
|
// Fixed *advanceDeltas = static_cast<Fixed *>(advanceDeltasArray.mArray);
|
|
// AutoLayoutDataArrayPtr deviceDeltasArray(aLine, kATSUDirectDataDeviceDeltaSInt16Array);
|
|
AutoLayoutDataArrayPtr baselineDeltasArray(aLine, kATSUDirectDataBaselineDeltaFixedArray);
|
|
Fixed *baselineDeltas = static_cast<Fixed *>(baselineDeltasArray.mArray);
|
|
AutoLayoutDataArrayPtr glyphRecordsArray(aLine, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent);
|
|
|
|
PRUint32 numGlyphs = glyphRecordsArray.mItemCount;
|
|
if (numGlyphs == 0 || !glyphRecordsArray.mArray) {
|
|
NS_WARNING("Failed to retrieve key glyph data");
|
|
return PR_FALSE;
|
|
}
|
|
ATSLayoutRecord *glyphRecords = static_cast<ATSLayoutRecord *>(glyphRecordsArray.mArray);
|
|
NS_ASSERTION(!baselineDeltas || baselineDeltasArray.mItemCount == numGlyphs,
|
|
"Mismatched glyph counts");
|
|
NS_ASSERTION(glyphRecords[numGlyphs - 1].flags & kATSGlyphInfoTerminatorGlyph,
|
|
"Last glyph should be a terminator glyph");
|
|
--numGlyphs;
|
|
if (numGlyphs == 0)
|
|
return PR_FALSE;
|
|
|
|
PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
|
|
PRBool isRTL = aRun->IsRightToLeft();
|
|
|
|
PRUint32 trailingCharactersToIgnore = aLayoutLength - aLengthInTextRun;
|
|
if (trailingCharactersToIgnore > 0) {
|
|
// The glyph array includes a glyph for the artificial trailing
|
|
// non-whitespace character. Strip that glyph from the array now.
|
|
if (isRTL) {
|
|
NS_ASSERTION(glyphRecords[trailingCharactersToIgnore - 1].originalOffset == aLengthInTextRun*2,
|
|
"Couldn't find glyph for trailing marker");
|
|
glyphRecords += trailingCharactersToIgnore;
|
|
} else {
|
|
NS_ASSERTION(glyphRecords[numGlyphs - trailingCharactersToIgnore].originalOffset == aLengthInTextRun*2,
|
|
"Couldn't find glyph for trailing marker");
|
|
}
|
|
numGlyphs -= trailingCharactersToIgnore;
|
|
if (numGlyphs == 0)
|
|
return PR_FALSE;
|
|
}
|
|
|
|
PRUint32 allFlags = 0;
|
|
// Now process the glyphs, which should basically be in
|
|
// the textrun's desired order, so process them in textrun order
|
|
PRInt32 direction = PRInt32(aRun->GetDirection());
|
|
while (numGlyphs > 0) {
|
|
PRUint32 glyphIndex = isRTL ? numGlyphs - 1 : 0;
|
|
PRUint32 lastOffset = glyphRecords[glyphIndex].originalOffset;
|
|
PRUint32 glyphCount = 1;
|
|
// Determine the glyphs for this ligature group
|
|
while (glyphCount < numGlyphs) {
|
|
ATSLayoutRecord *glyph = &glyphRecords[glyphIndex + direction*glyphCount];
|
|
PRUint32 glyphOffset = glyph->originalOffset;
|
|
PRUint32 nextIndex = isRTL ? glyphIndex - 1 : glyphIndex + 1;
|
|
PRUint32 nextOffset;
|
|
if (nextIndex >= 0 && nextIndex < numGlyphs) {
|
|
ATSLayoutRecord *nextGlyph = &glyphRecords[nextIndex + direction*glyphCount];
|
|
nextOffset = nextGlyph->originalOffset;
|
|
}
|
|
else
|
|
nextOffset = glyphOffset;
|
|
allFlags |= glyph->flags;
|
|
if (glyphOffset <= lastOffset || nextOffset <= lastOffset) {
|
|
// Always add the current glyph to the ligature group if it's for the same
|
|
// character as a character whose glyph is already in the group,
|
|
// or an earlier character. The latter can happen because ATSUI
|
|
// sometimes visually reorders glyphs. One case of this is that DEVANAGARI
|
|
// VOWEL I can have its glyph displayed before the glyph for the consonant
|
|
// that it's logically after (even though this is all left-to-right text).
|
|
// Another case is that a sequence of RA; VIRAMA; <consonant> ; <vowel> is
|
|
// reordered to <consonant> ; <vowel> ; RA; VIRAMA.
|
|
// In these cases we need to make sure that the whole sequence of glyphs is
|
|
// processed as a single cluster.
|
|
} else {
|
|
// We could be at the end of a character group
|
|
if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
|
|
// Next character is a normal character, stop the group here
|
|
break;
|
|
}
|
|
if (aUnmatched && aUnmatched[glyphOffset/2]) {
|
|
// Next character is ummatched, so definitely stop the group here
|
|
break;
|
|
}
|
|
// Otherwise the next glyph is, we assume, a ligature continuation.
|
|
// Record that this character too is part of the group
|
|
lastOffset = glyphOffset;
|
|
}
|
|
++glyphCount;
|
|
}
|
|
if (isRTL) {
|
|
SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - glyphCount,
|
|
glyphCount,
|
|
baselineDeltas ? baselineDeltas + numGlyphs - glyphCount : nsnull,
|
|
appUnitsPerDevUnit, aRun, aOffsetInTextRun,
|
|
aUnmatched, aString, aLengthInTextRun);
|
|
} else {
|
|
SetGlyphsForCharacterGroup(glyphRecords,
|
|
glyphCount, baselineDeltas,
|
|
appUnitsPerDevUnit, aRun, aOffsetInTextRun,
|
|
aUnmatched, aString, aLengthInTextRun);
|
|
glyphRecords += glyphCount;
|
|
if (baselineDeltas) {
|
|
baselineDeltas += glyphCount;
|
|
}
|
|
}
|
|
numGlyphs -= glyphCount;
|
|
}
|
|
|
|
return (allFlags & ATSUI_OVERRUNNING_GLYPH_FLAG) != 0;
|
|
}
|
|
|
|
struct PostLayoutCallbackClosure {
|
|
gfxTextRun *mTextRun;
|
|
const PRUnichar *mString;
|
|
PRUint32 mLayoutLength;
|
|
PRUint32 mOffsetInTextRun;
|
|
PRUint32 mLengthInTextRun;
|
|
// Either null or an array of stringlength booleans set to true for
|
|
// each character that did not match any fonts
|
|
nsAutoArrayPtr<PRPackedBool> mUnmatchedChars;
|
|
// The callback *sets* this to indicate whether there were overrunning glyphs
|
|
PRPackedBool mOverrunningGlyphs;
|
|
};
|
|
|
|
// This is really disgusting, but the ATSUI refCon thing is also disgusting
|
|
static PostLayoutCallbackClosure *gCallbackClosure = nsnull;
|
|
|
|
static OSStatus
|
|
PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
|
|
ATSULineRef iLineRef,
|
|
UInt32 iRefCon,
|
|
void *iOperationCallbackParameterPtr,
|
|
ATSULayoutOperationCallbackStatus *oCallbackStatus)
|
|
{
|
|
gCallbackClosure->mOverrunningGlyphs =
|
|
PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
|
|
gCallbackClosure->mString,
|
|
gCallbackClosure->mLayoutLength,
|
|
gCallbackClosure->mOffsetInTextRun,
|
|
gCallbackClosure->mLengthInTextRun,
|
|
gCallbackClosure->mUnmatchedChars);
|
|
*oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
|
|
return noErr;
|
|
}
|
|
|
|
// 361695 - ATSUI only does glyph mirroring when the font contains a 'prop' table
|
|
// with glyph mirroring info, the character mirroring has to be done manually in the
|
|
// fallback case. Only used for RTL text runs. The autoptr for the mirrored copy
|
|
// is owned by the calling routine.
|
|
|
|
// MirrorSubstring - Do Unicode mirroring on characters within a substring. If mirroring
|
|
// needs to be done, copy the original string and change the ATSUI layout to use the mirrored copy.
|
|
//
|
|
// @param layout ATSUI layout for the entire text run
|
|
// @param mirrorStr container used for mirror string, null until a mirrored character is found
|
|
// @param aString original string
|
|
// @param aLength length of the original string
|
|
// @param runStart start offset of substring to be mirrored
|
|
// @param runLength length of substring to be mirrored
|
|
|
|
static void MirrorSubstring(ATSUTextLayout layout, nsAutoArrayPtr<PRUnichar>& mirroredStr,
|
|
const PRUnichar *aString, PRUint32 aLength,
|
|
UniCharArrayOffset runStart, UniCharCount runLength)
|
|
{
|
|
UniCharArrayOffset off;
|
|
|
|
// do the mirroring manually!!
|
|
for (off = runStart; off < runStart + runLength; off++) {
|
|
PRUnichar mirroredChar;
|
|
|
|
mirroredChar = (PRUnichar) SymmSwap(aString[off]);
|
|
if (mirroredChar != aString[off]) {
|
|
// string contains characters that need to be mirrored
|
|
if (mirroredStr == NULL) {
|
|
|
|
// copy the string
|
|
mirroredStr = new PRUnichar[aLength];
|
|
memcpy(mirroredStr, aString, sizeof(PRUnichar) * aLength);
|
|
|
|
// adjust the layout
|
|
ATSUTextMoved(layout, mirroredStr);
|
|
|
|
}
|
|
mirroredStr[off] = mirroredChar;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static ATSUStyle
|
|
SetLayoutRangeToFont(ATSUTextLayout layout, ATSUStyle mainStyle, UniCharArrayOffset offset,
|
|
UniCharCount length, ATSUFontID fontID)
|
|
{
|
|
ATSUStyle subStyle;
|
|
ATSUCreateStyle (&subStyle);
|
|
ATSUCopyAttributes (mainStyle, subStyle);
|
|
|
|
ATSUAttributeTag fontTags[] = { kATSUFontTag };
|
|
ByteCount fontArgSizes[] = { sizeof(ATSUFontID) };
|
|
ATSUAttributeValuePtr fontArgs[] = { &fontID };
|
|
|
|
ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
|
|
|
|
// apply the new style to the layout for the changed substring
|
|
ATSUSetRunStyle (layout, subStyle, offset, length);
|
|
|
|
return subStyle;
|
|
}
|
|
|
|
PRBool
|
|
gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
|
|
const PRUnichar *aString, PRUint32 aLength,
|
|
PRUint32 aLayoutStart, PRUint32 aLayoutLength,
|
|
PRUint32 aOffsetInTextRun, PRUint32 aLengthInTextRun)
|
|
{
|
|
OSStatus status;
|
|
gfxAtsuiFont *firstFont = GetFontAt(0);
|
|
ATSUStyle mainStyle = firstFont->GetATSUStyle();
|
|
nsTArray<ATSUStyle> stylesToDispose;
|
|
const PRUnichar *layoutString = aString + aLayoutStart;
|
|
|
|
#ifdef DUMP_TEXT_RUNS
|
|
NS_ConvertUTF16toUTF8 str(layoutString, aLengthInTextRun);
|
|
NS_ConvertUTF16toUTF8 families(mFamilies);
|
|
PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,\
|
|
("InitTextRun %p fontgroup %p (%s) lang: %s len %d TEXTRUN \"%s\" ENDTEXTRUN\n",
|
|
aRun, this, families.get(), mStyle.langGroup.get(), aLengthInTextRun, str.get()) );
|
|
// PR_LOG(gAtsuiTextRunLog, PR_LOG_DEBUG,
|
|
// ("InitTextRun font: %s user font set: %p (%8.8x)\n",
|
|
// NS_ConvertUTF16toUTF8(firstFont->GetUniqueName()).get(), mUserFontSet, PRUint32(mCurrGeneration)) );
|
|
#endif
|
|
|
|
if (aRun->GetFlags() & TEXT_DISABLE_OPTIONAL_LIGATURES) {
|
|
status = ATSUCreateAndCopyStyle(mainStyle, &mainStyle);
|
|
if (status == noErr) {
|
|
stylesToDispose.AppendElement(mainStyle);
|
|
DisableCommonLigatures(mainStyle);
|
|
}
|
|
}
|
|
|
|
UniCharCount runLengths = aLengthInTextRun;
|
|
ATSUTextLayout layout;
|
|
// Create the text layout for the whole string, but only produce glyphs
|
|
// for the text inside LRO/RLO - PDF, if present. For wrapped strings
|
|
// we do need to produce glyphs for the trailing non-whitespace
|
|
// character to ensure that ATSUI treats all whitespace as non-trailing.
|
|
status = ATSUCreateTextLayoutWithTextPtr
|
|
(aString,
|
|
aLayoutStart,
|
|
aLayoutLength,
|
|
aLength,
|
|
1,
|
|
&runLengths,
|
|
&mainStyle,
|
|
&layout);
|
|
// XXX error check here?
|
|
|
|
PostLayoutCallbackClosure closure;
|
|
closure.mTextRun = aRun;
|
|
closure.mString = layoutString;
|
|
closure.mLayoutLength = aLayoutLength;
|
|
closure.mOffsetInTextRun = aOffsetInTextRun;
|
|
closure.mLengthInTextRun = aLengthInTextRun;
|
|
NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
|
|
gCallbackClosure = &closure;
|
|
|
|
ATSULayoutOperationOverrideSpecifier override;
|
|
override.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
|
|
override.overrideUPP = PostLayoutOperationCallback;
|
|
|
|
// Set up our layout attributes
|
|
ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
|
|
|
|
static ATSUAttributeTag layoutTags[] = {
|
|
kATSULineLayoutOptionsTag,
|
|
kATSULayoutOperationOverrideTag
|
|
};
|
|
static ByteCount layoutArgSizes[] = {
|
|
sizeof(ATSLineLayoutOptions),
|
|
sizeof(ATSULayoutOperationOverrideSpecifier)
|
|
};
|
|
|
|
ATSUAttributeValuePtr layoutArgs[] = {
|
|
&lineLayoutOptions,
|
|
&override
|
|
};
|
|
ATSUSetLayoutControls(layout,
|
|
NS_ARRAY_LENGTH(layoutTags),
|
|
layoutTags,
|
|
layoutArgSizes,
|
|
layoutArgs);
|
|
|
|
/* Now go through and update the styles for the text, based on font matching. */
|
|
|
|
nsAutoArrayPtr<PRUnichar> mirroredStr;
|
|
|
|
UniCharArrayOffset runStart = aLayoutStart;
|
|
UniCharCount runLength = aLengthInTextRun;
|
|
|
|
/// ---- match fonts using cmap info instead of ATSUI ----
|
|
|
|
nsTArray<gfxTextRange> fontRanges;
|
|
|
|
ComputeRanges(fontRanges, aString, runStart, runStart + runLength);
|
|
|
|
PRUint32 r, numRanges = fontRanges.Length();
|
|
|
|
for (r = 0; r < numRanges; r++) {
|
|
const gfxTextRange& range = fontRanges[r];
|
|
|
|
gfxAtsuiFont *matchedFont;
|
|
UniCharCount matchedLength;
|
|
|
|
// match a range of text
|
|
matchedLength = range.Length();
|
|
matchedFont = static_cast<gfxAtsuiFont*> (range.font ? range.font.get() : nsnull);
|
|
|
|
#ifdef DUMP_TEXT_RUNS
|
|
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));
|
|
#endif
|
|
//printf("Matched: %s [%d, %d)\n", (matchedFont ? NS_ConvertUTF16toUTF8(matchedFont->GetUniqueName()).get() : "<null>"), runStart, runStart + matchedLength);
|
|
|
|
// in the RTL case, handle fallback mirroring
|
|
if (aRun->IsRightToLeft() && matchedFont && !matchedFont->HasMirroringInfo()) {
|
|
MirrorSubstring(layout, mirroredStr, aString, aLength, runStart, runLength);
|
|
}
|
|
|
|
// if no matched font, mark as unmatched
|
|
if (!matchedFont) {
|
|
|
|
aRun->AddGlyphRun(firstFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
|
|
|
|
if (!closure.mUnmatchedChars) {
|
|
closure.mUnmatchedChars = new PRPackedBool[aLength];
|
|
if (closure.mUnmatchedChars) {
|
|
//printf("initializing %d\n", aLength);
|
|
memset(closure.mUnmatchedChars.get(), PR_FALSE, aLength);
|
|
}
|
|
}
|
|
|
|
if (closure.mUnmatchedChars) {
|
|
//printf("setting %d unmatched from %d\n", matchedLength, runStart - headerChars);
|
|
memset(closure.mUnmatchedChars.get() + runStart - aLayoutStart,
|
|
PR_TRUE, matchedLength);
|
|
}
|
|
|
|
} else {
|
|
|
|
if (matchedFont != firstFont) {
|
|
// create a new sub-style and add it to the layout
|
|
ATSUStyle subStyle = SetLayoutRangeToFont(layout, mainStyle, runStart, matchedLength,
|
|
FMGetFontFromATSFontRef(matchedFont->GetATSFontRef()));
|
|
stylesToDispose.AppendElement(subStyle);
|
|
}
|
|
|
|
// add a glyph run for the matched substring
|
|
aRun->AddGlyphRun(matchedFont, aOffsetInTextRun + runStart - aLayoutStart, PR_TRUE);
|
|
}
|
|
|
|
runStart += matchedLength;
|
|
runLength -= matchedLength;
|
|
}
|
|
|
|
|
|
/// -------------------------------------------------
|
|
|
|
// xxx - for some reason, this call appears to be needed to avoid assertions about glyph runs not being coalesced properly
|
|
// this appears to occur when there are unmatched characters in the text run
|
|
aRun->SortGlyphRuns();
|
|
|
|
// Trigger layout so that our callback fires. We don't actually care about
|
|
// the result of this call.
|
|
ATSTrapezoid trap;
|
|
ItemCount trapCount;
|
|
ATSUGetGlyphBounds(layout, 0, 0, aLayoutStart, aLengthInTextRun,
|
|
kATSUseFractionalOrigins, 1, &trap, &trapCount);
|
|
|
|
ATSUDisposeTextLayout(layout);
|
|
|
|
aRun->AdjustAdvancesForSyntheticBold(aOffsetInTextRun, aLengthInTextRun);
|
|
|
|
PRUint32 i;
|
|
for (i = 0; i < stylesToDispose.Length(); ++i) {
|
|
ATSUDisposeStyle(stylesToDispose[i]);
|
|
}
|
|
gCallbackClosure = nsnull;
|
|
return !closure.mOverrunningGlyphs;
|
|
}
|
|
|
|
void
|
|
gfxAtsuiFontGroup::InitFontList()
|
|
{
|
|
ForEachFont(FindATSUFont, this);
|
|
|
|
if (mFonts.Length() == 0) {
|
|
// XXX this will generate a list of the lang groups for which we have no
|
|
// default fonts for on the mac; we should fix this!
|
|
// Known:
|
|
// ja x-beng x-devanagari x-tamil x-geor x-ethi x-gujr x-mlym x-armn
|
|
// x-orya x-telu x-knda x-sinh
|
|
|
|
//fprintf (stderr, "gfxAtsuiFontGroup: %s [%s] -> %d fonts found\n", NS_ConvertUTF16toUTF8(families).get(), mStyle.langGroup.get(), mFonts.Length());
|
|
|
|
// If we get here, we most likely didn't have a default font for
|
|
// a specific langGroup. Let's just pick the default OSX
|
|
// user font.
|
|
|
|
PRBool needsBold;
|
|
MacOSFontEntry *defaultFont = gfxQuartzFontCache::SharedFontCache()->GetDefaultFont(&mStyle, needsBold);
|
|
NS_ASSERTION(defaultFont, "invalid default font returned by GetDefaultFont");
|
|
|
|
nsRefPtr<gfxAtsuiFont> font = GetOrMakeFont(defaultFont, &mStyle, needsBold);
|
|
|
|
if (font) {
|
|
mFonts.AppendElement(font);
|
|
}
|
|
}
|
|
|
|
if (!mStyle.systemFont) {
|
|
for (PRUint32 i = 0; i < mFonts.Length(); ++i) {
|
|
gfxAtsuiFont* font = static_cast<gfxAtsuiFont*>(mFonts[i].get());
|
|
if (font->GetFontEntry()->mIsBadUnderlineFont) {
|
|
gfxFloat first = mFonts[0]->GetMetrics().underlineOffset;
|
|
gfxFloat bad = font->GetMetrics().underlineOffset;
|
|
mUnderlineOffset = PR_MIN(first, bad);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* not __LP64__ */
|