зеркало из https://github.com/mozilla/gecko-dev.git
bug 502906 - part 2 - split out gfxCoreTextShaper and gfxMacFont. r=jdaggett
--HG-- rename : gfx/thebes/src/gfxCoreTextFonts.cpp => gfx/thebes/src/gfxCoreTextShaper.cpp rename : gfx/thebes/public/gfxCoreTextFonts.h => gfx/thebes/src/gfxCoreTextShaper.h
This commit is contained in:
Родитель
5058eb564c
Коммит
d56e0f4d7b
|
@ -112,7 +112,6 @@ EXPORTS += gfxPlatformMac.h \
|
|||
gfxQuartzImageSurface.h \
|
||||
gfxQuartzPDFSurface.h \
|
||||
gfxQuartzNativeDrawing.h \
|
||||
gfxCoreTextFonts.h \
|
||||
$(NULL)
|
||||
|
||||
endif
|
||||
|
|
|
@ -634,6 +634,42 @@ private:
|
|||
PRUint32 mAppUnitsPerDevUnit;
|
||||
};
|
||||
|
||||
/**
|
||||
* gfxFontShaper
|
||||
*
|
||||
* This class implements text shaping (character to glyph mapping and
|
||||
* glyph layout). There is a gfxFontShaper subclass for each text layout
|
||||
* technology (uniscribe, core text, harfbuzz,....) we support.
|
||||
*
|
||||
* The shaper is responsible for setting up glyph data in gfxTextRuns.
|
||||
*
|
||||
* A generic, platform-independent shaper relies only on the standard
|
||||
* gfxFont interface and can work with any concrete subclass of gfxFont.
|
||||
*
|
||||
* Platform-specific implementations designed to interface to platform
|
||||
* shaping APIs such as Uniscribe or CoreText may rely on features of a
|
||||
* specific font subclass to access native font references
|
||||
* (such as CTFont, HFONT, DWriteFont, etc).
|
||||
*/
|
||||
|
||||
class gfxFontShaper {
|
||||
public:
|
||||
gfxFontShaper(gfxFont *aFont)
|
||||
: mFont(aFont) { }
|
||||
|
||||
virtual ~gfxFontShaper() { }
|
||||
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) = 0;
|
||||
|
||||
protected:
|
||||
// the font this shaper is working with
|
||||
gfxFont * mFont;
|
||||
};
|
||||
|
||||
/* a SPECIFIC single font family */
|
||||
class THEBES_API gfxFont {
|
||||
public:
|
||||
|
@ -680,7 +716,7 @@ protected:
|
|||
public:
|
||||
virtual ~gfxFont();
|
||||
|
||||
PRBool Valid() {
|
||||
PRBool Valid() const {
|
||||
return mIsValid;
|
||||
}
|
||||
|
||||
|
@ -714,7 +750,7 @@ public:
|
|||
const nsString& GetName() const { return mFontEntry->Name(); }
|
||||
const gfxFontStyle *GetStyle() const { return &mStyle; }
|
||||
|
||||
virtual nsString GetUniqueName() = 0;
|
||||
virtual nsString GetUniqueName() { return GetName(); }
|
||||
|
||||
// Font metrics
|
||||
struct Metrics {
|
||||
|
@ -873,11 +909,16 @@ public:
|
|||
return mFontEntry->HasCharacter(ch);
|
||||
}
|
||||
|
||||
virtual void InitTextRun(gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) {
|
||||
NS_NOTREACHED("oops, somebody didn't override InitTextRun");
|
||||
void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength) {
|
||||
NS_ASSERTION(mShaper != nsnull, "no shaper?!");
|
||||
if (!mShaper) {
|
||||
return;
|
||||
}
|
||||
mShaper->InitTextRun(aContext, aTextRun, aString, aRunStart, aRunLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -891,6 +932,8 @@ protected:
|
|||
// synthetic bolding for environments where this is not supported by the platform
|
||||
PRUint32 mSyntheticBoldOffset; // number of devunit pixels to offset double-strike, 0 ==> no bolding
|
||||
|
||||
nsAutoPtr<gfxFontShaper> mShaper;
|
||||
|
||||
// some fonts have bad metrics, this method sanitize them.
|
||||
// if this font has bad underline offset, aIsBadUnderlineFont should be true.
|
||||
void SanitizeMetrics(gfxFont::Metrics *aMetrics, PRBool aIsBadUnderlineFont);
|
||||
|
@ -1866,7 +1909,8 @@ protected:
|
|||
// you should call this with the *first* bad font.
|
||||
void InitMetricsForBadFont(gfxFont* aBadFont);
|
||||
|
||||
void InitTextRun(gfxTextRun *aTextRun,
|
||||
void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aLength);
|
||||
|
||||
|
|
|
@ -98,9 +98,6 @@ public:
|
|||
// lower threshold on font anti-aliasing
|
||||
PRUint32 GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
|
||||
|
||||
// record Unicode cluster boundaries in the text run
|
||||
virtual void SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString);
|
||||
|
||||
private:
|
||||
virtual qcms_profile* GetPlatformCMSOutputProfile();
|
||||
|
||||
|
|
|
@ -170,7 +170,8 @@ CPPSRCS += \
|
|||
gfxQuartzImageSurface.cpp \
|
||||
gfxQuartzPDFSurface.cpp \
|
||||
gfxPlatformMac.cpp \
|
||||
gfxCoreTextFonts.cpp \
|
||||
gfxMacFont.cpp \
|
||||
gfxCoreTextShaper.cpp \
|
||||
$(NULL)
|
||||
#CPPSRCS += gfxPDFSurface.cpp
|
||||
CPPSRCS += nsUnicodeRange.cpp
|
||||
|
|
|
@ -50,13 +50,12 @@
|
|||
#include "gfxContext.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxPlatformMac.h"
|
||||
#include "gfxCoreTextFonts.h"
|
||||
#include "gfxCoreTextShaper.h"
|
||||
#include "gfxMacFont.h"
|
||||
|
||||
#include "gfxFontTest.h"
|
||||
#include "gfxFontUtils.h"
|
||||
|
||||
#include "cairo-quartz.h"
|
||||
|
||||
#include "gfxQuartzSurface.h"
|
||||
#include "gfxMacPlatformFontList.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
|
@ -70,360 +69,45 @@
|
|||
static PRLogModuleInfo *gCoreTextTextRunLog = PR_NewLogModule("coreTextTextRun");
|
||||
#endif
|
||||
|
||||
#define ROUND(x) (floor((x) + 0.5))
|
||||
|
||||
|
||||
// standard font descriptors that we construct the first time they're needed
|
||||
CTFontDescriptorRef gfxCoreTextFont::sDefaultFeaturesDescriptor = NULL;
|
||||
CTFontDescriptorRef gfxCoreTextFont::sDisableLigaturesDescriptor = NULL;
|
||||
CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = NULL;
|
||||
CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = NULL;
|
||||
|
||||
#ifdef DEBUG_jonathan
|
||||
static void dumpFontDescCallback(const void *key, const void *value, void *context)
|
||||
gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont)
|
||||
: gfxFontShaper(aFont)
|
||||
{
|
||||
CFStringRef attribute = (CFStringRef)key;
|
||||
CFTypeRef setting = (CFTypeRef)value;
|
||||
fprintf(stderr, "attr: "); CFShow(attribute);
|
||||
fprintf(stderr, " = "); CFShow(setting);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void
|
||||
dumpFontDescriptor(CTFontRef font)
|
||||
{
|
||||
CTFontDescriptorRef desc = CTFontCopyFontDescriptor(font);
|
||||
CFDictionaryRef dict = CTFontDescriptorCopyAttributes(desc);
|
||||
CFRelease(desc);
|
||||
CFDictionaryApplyFunction(dict, &dumpFontDescCallback, 0);
|
||||
CFRelease(dict);
|
||||
}
|
||||
#endif
|
||||
|
||||
gfxCoreTextFont::gfxCoreTextFont(MacOSFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
PRBool aNeedsBold)
|
||||
: gfxFont(aFontEntry, aFontStyle),
|
||||
mFontStyle(aFontStyle),
|
||||
mCTFont(nsnull),
|
||||
mAttributesDict(nsnull),
|
||||
mHasMetrics(PR_FALSE),
|
||||
mFontFace(nsnull),
|
||||
mScaledFont(nsnull),
|
||||
mAdjustedSize(0.0f)
|
||||
{
|
||||
mATSFont = aFontEntry->GetFontRef();
|
||||
|
||||
// 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 will create the mCTFont (possibly taking account of sizeAdjust)
|
||||
InitMetrics();
|
||||
if (!mIsValid) {
|
||||
return;
|
||||
}
|
||||
// Create our CTFontRef
|
||||
mCTFont = ::CTFontCreateWithPlatformFont(aFont->GetATSFontRef(),
|
||||
aFont->GetAdjustedSize(),
|
||||
NULL,
|
||||
GetDefaultFeaturesDescriptor());
|
||||
|
||||
// Set up the default attribute dictionary that we will need each time we create a CFAttributedString
|
||||
mAttributesDict =
|
||||
::CFDictionaryCreate(kCFAllocatorDefault,
|
||||
(const void**) &kCTFontAttributeName,
|
||||
(const void**) &mCTFont,
|
||||
1, // count of attributes
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
// Remaining initialization is largely based on CommonInit() in the gfxAtsuiFont code
|
||||
CGFontRef cgFont = ::CGFontCreateWithPlatformFont(&mATSFont);
|
||||
mFontFace = cairo_quartz_font_face_create_for_cgfont(cgFont);
|
||||
::CGFontRelease(cgFont);
|
||||
|
||||
cairo_status_t cairoerr = cairo_font_face_status(mFontFace);
|
||||
if (cairoerr != CAIRO_STATUS_SUCCESS) {
|
||||
mIsValid = PR_FALSE;
|
||||
#ifdef DEBUG
|
||||
char warnBuf[1024];
|
||||
sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d",
|
||||
NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
|
||||
NS_WARNING(warnBuf);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
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 =
|
||||
(mFontEntry != NULL) &&
|
||||
(!mFontEntry->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();
|
||||
// if this fails (out of memory), the pointer is still safe to use
|
||||
// although we're almost certainly going to fail below anyway
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
}
|
||||
mAttributesDict = ::CFDictionaryCreate(kCFAllocatorDefault,
|
||||
(const void**) &kCTFontAttributeName,
|
||||
(const void**) &mCTFont,
|
||||
1, // count of attributes
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
}
|
||||
|
||||
static double
|
||||
RoundToNearestMultiple(double aValue, double aFraction)
|
||||
gfxCoreTextShaper::~gfxCoreTextShaper()
|
||||
{
|
||||
return floor(aValue/aFraction + 0.5)*aFraction;
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxCoreTextFont::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;
|
||||
}
|
||||
|
||||
float
|
||||
gfxCoreTextFont::GetCharWidth(PRUnichar aUniChar, PRUint32 *aGlyphID)
|
||||
{
|
||||
UniChar c = aUniChar;
|
||||
CGGlyph glyph;
|
||||
if (::CTFontGetGlyphsForCharacters(mCTFont, &c, &glyph, 1)) {
|
||||
CGSize advance;
|
||||
::CTFontGetAdvancesForGlyphs(mCTFont,
|
||||
kCTFontHorizontalOrientation,
|
||||
&glyph,
|
||||
&advance,
|
||||
1);
|
||||
if (aGlyphID != nsnull)
|
||||
*aGlyphID = glyph;
|
||||
return advance.width;
|
||||
}
|
||||
|
||||
// couldn't get glyph for the char
|
||||
if (aGlyphID != nsnull)
|
||||
*aGlyphID = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
float
|
||||
gfxCoreTextFont::GetCharHeight(PRUnichar aUniChar)
|
||||
{
|
||||
UniChar c = aUniChar;
|
||||
CGGlyph glyph;
|
||||
if (::CTFontGetGlyphsForCharacters(mCTFont, &c, &glyph, 1)) {
|
||||
CGRect boundingRect;
|
||||
::CTFontGetBoundingRectsForGlyphs(mCTFont,
|
||||
kCTFontHorizontalOrientation,
|
||||
&glyph,
|
||||
&boundingRect,
|
||||
1);
|
||||
return boundingRect.size.height;
|
||||
}
|
||||
|
||||
// couldn't get glyph for the char
|
||||
return 0;
|
||||
}
|
||||
|
||||
gfxCoreTextFont::~gfxCoreTextFont()
|
||||
{
|
||||
if (mScaledFont)
|
||||
cairo_scaled_font_destroy(mScaledFont);
|
||||
if (mFontFace)
|
||||
cairo_font_face_destroy(mFontFace);
|
||||
|
||||
if (mAttributesDict)
|
||||
if (mAttributesDict) {
|
||||
::CFRelease(mAttributesDict);
|
||||
if (mCTFont)
|
||||
}
|
||||
if (mCTFont) {
|
||||
::CFRelease(mCTFont);
|
||||
}
|
||||
|
||||
MacOSFontEntry*
|
||||
gfxCoreTextFont::GetFontEntry()
|
||||
{
|
||||
return static_cast<MacOSFontEntry*>(mFontEntry.get());
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxCoreTextFont::TestCharacterMap(PRUint32 aCh)
|
||||
{
|
||||
return mIsValid && GetFontEntry()->TestCharacterMap(aCh);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gfxCoreTextFont::InitMetrics()
|
||||
{
|
||||
if (mHasMetrics)
|
||||
return;
|
||||
|
||||
gfxFloat size =
|
||||
PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : GetStyle()->size), 1.0f);
|
||||
|
||||
if (mCTFont != NULL) {
|
||||
::CFRelease(mCTFont);
|
||||
mCTFont = NULL;
|
||||
}
|
||||
|
||||
ATSFontMetrics atsMetrics;
|
||||
OSStatus err;
|
||||
|
||||
err = ::ATSFontGetHorizontalMetrics(mATSFont, 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;
|
||||
}
|
||||
|
||||
// prefer to get xHeight from ATS metrics (unhinted) rather than Core Text (hinted),
|
||||
// see bug 429605.
|
||||
if (atsMetrics.xHeight > 0) {
|
||||
mMetrics.xHeight = atsMetrics.xHeight * size;
|
||||
} else {
|
||||
mCTFont = ::CTFontCreateWithPlatformFont(mATSFont, size, NULL, GetDefaultFeaturesDescriptor());
|
||||
mMetrics.xHeight = ::CTFontGetXHeight(mCTFont);
|
||||
}
|
||||
|
||||
if (mAdjustedSize == 0.0f) {
|
||||
if (mMetrics.xHeight != 0.0f && GetStyle()->sizeAdjust != 0.0f) {
|
||||
gfxFloat aspect = mMetrics.xHeight / size;
|
||||
mAdjustedSize = GetStyle()->GetAdjustedSize(aspect);
|
||||
|
||||
// the recursive call to InitMetrics will re-create mCTFont, with adjusted size,
|
||||
// and then continue to set up the rest of the metrics fields
|
||||
InitMetrics();
|
||||
return;
|
||||
}
|
||||
mAdjustedSize = size;
|
||||
}
|
||||
|
||||
// create the CTFontRef if we didn't already do so above
|
||||
if (mCTFont == NULL)
|
||||
mCTFont = ::CTFontCreateWithPlatformFont(mATSFont, size, NULL, GetDefaultFeaturesDescriptor());
|
||||
|
||||
mMetrics.superscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.subscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.underlineSize = ::CTFontGetUnderlineThickness(mCTFont);
|
||||
mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(mCTFont);
|
||||
mMetrics.strikeoutSize = mMetrics.underlineSize;
|
||||
mMetrics.strikeoutOffset = mMetrics.xHeight / 2;
|
||||
|
||||
mMetrics.externalLeading = ::CTFontGetLeading(mCTFont);
|
||||
mMetrics.emHeight = size;
|
||||
// mMetrics.maxAscent = CTFontGetAscent(mCTFont);
|
||||
// mMetrics.maxDescent = CTFontGetDescent(mCTFont);
|
||||
// using the ATS metrics rather than CT gives us results more consistent with the ATSUI path
|
||||
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.0)
|
||||
mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
|
||||
else
|
||||
mMetrics.internalLeading = 0.0;
|
||||
|
||||
mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
|
||||
|
||||
mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
|
||||
mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
|
||||
|
||||
PRUint32 glyphID;
|
||||
float xWidth = GetCharWidth('x', &glyphID);
|
||||
if (atsMetrics.avgAdvanceWidth != 0.0)
|
||||
mMetrics.aveCharWidth = PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
|
||||
else if (glyphID != 0)
|
||||
mMetrics.aveCharWidth = xWidth;
|
||||
else
|
||||
mMetrics.aveCharWidth = mMetrics.maxAdvance;
|
||||
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.spaceWidth = GetCharWidth(' ', &glyphID);
|
||||
mSpaceGlyph = glyphID;
|
||||
|
||||
mMetrics.zeroOrAveCharWidth = GetCharWidth('0', &glyphID);
|
||||
if (glyphID == 0)
|
||||
mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
|
||||
|
||||
mHasMetrics = PR_TRUE;
|
||||
|
||||
SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
|
||||
|
||||
#if 0
|
||||
fprintf (stderr, "Font: %p (%s) size: %f\n", this,
|
||||
NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
|
||||
// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height);
|
||||
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
|
||||
}
|
||||
|
||||
void
|
||||
gfxCoreTextFont::InitTextRun(gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
gfxCoreTextShaper::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength)
|
||||
{
|
||||
// aRunStart and aRunLength define the section of the textRun and of aString
|
||||
// that is to be drawn with this particular font
|
||||
|
@ -483,9 +167,10 @@ gfxCoreTextFont::InitTextRun(gfxTextRun *aTextRun,
|
|||
if (disableLigatures) {
|
||||
// For letterspacing (or maybe other situations) we need to make a copy of the CTFont
|
||||
// with the ligature feature disabled
|
||||
gfxMacFont *font = static_cast<gfxMacFont*>(mFont);
|
||||
CTFontRef ctFont =
|
||||
gfxCoreTextFont::CreateCTFontWithDisabledLigatures(GetATSFont(),
|
||||
::CTFontGetSize(GetCTFont()));
|
||||
CreateCTFontWithDisabledLigatures(font->GetATSFontRef(),
|
||||
::CTFontGetSize(mCTFont));
|
||||
|
||||
attrObj =
|
||||
::CFDictionaryCreate(kCFAllocatorDefault,
|
||||
|
@ -497,7 +182,7 @@ gfxCoreTextFont::InitTextRun(gfxTextRun *aTextRun,
|
|||
// Having created the dict, we're finished with our ligature-disabled CTFontRef
|
||||
::CFRelease(ctFont);
|
||||
} else {
|
||||
attrObj = GetAttributesDictionary();
|
||||
attrObj = mAttributesDict;
|
||||
::CFRetain(attrObj);
|
||||
}
|
||||
|
||||
|
@ -532,11 +217,11 @@ gfxCoreTextFont::InitTextRun(gfxTextRun *aTextRun,
|
|||
// without requiring a separate allocation
|
||||
|
||||
nsresult
|
||||
gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
||||
CTRunRef aCTRun,
|
||||
PRInt32 aStringOffset, // offset in the string used to build the CTLine
|
||||
PRInt32 aRunStart, // starting offset of this font run in the gfxTextRun
|
||||
PRInt32 aRunLength) // length of this font run in characters
|
||||
gfxCoreTextShaper::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
||||
CTRunRef aCTRun,
|
||||
PRInt32 aStringOffset, // offset in the string used to build the CTLine
|
||||
PRInt32 aRunStart, // starting offset of this font run in the gfxTextRun
|
||||
PRInt32 aRunLength) // length of this font run in characters
|
||||
{
|
||||
// The textRun has been bidi-wrapped; aStringOffset is the number
|
||||
// of chars at the beginning of the CTLine that we should skip.
|
||||
|
@ -548,8 +233,9 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
PRInt32 direction = isLTR ? 1 : -1;
|
||||
|
||||
PRInt32 numGlyphs = ::CTRunGetGlyphCount(aCTRun);
|
||||
if (numGlyphs == 0)
|
||||
if (numGlyphs == 0) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// character offsets get really confusing here, as we have to keep track of
|
||||
// (a) the text in the actual textRun we're constructing
|
||||
|
@ -565,8 +251,9 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
CFRange stringRange = ::CTRunGetStringRange(aCTRun);
|
||||
// skip the run if it is entirely outside the actual range of the font run
|
||||
if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
|
||||
stringRange.location - aStringOffset >= aRunLength)
|
||||
stringRange.location - aStringOffset >= aRunLength) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// retrieve the laid-out glyph data from the CTRun
|
||||
nsAutoArrayPtr<CGGlyph> glyphsArray;
|
||||
|
@ -587,8 +274,9 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
glyphs = ::CTRunGetGlyphsPtr(aCTRun);
|
||||
if (!glyphs) {
|
||||
glyphsArray = new (std::nothrow) CGGlyph[numGlyphs];
|
||||
if (!glyphsArray)
|
||||
if (!glyphsArray) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get());
|
||||
glyphs = glyphsArray.get();
|
||||
}
|
||||
|
@ -596,8 +284,9 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
positions = ::CTRunGetPositionsPtr(aCTRun);
|
||||
if (!positions) {
|
||||
positionsArray = new (std::nothrow) CGPoint[numGlyphs];
|
||||
if (!positionsArray)
|
||||
if (!positionsArray) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get());
|
||||
positions = positionsArray.get();
|
||||
}
|
||||
|
@ -607,8 +296,9 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun);
|
||||
if (!glyphToChar) {
|
||||
glyphToCharArray = new (std::nothrow) CFIndex[numGlyphs];
|
||||
if (!glyphToCharArray)
|
||||
if (!glyphToCharArray) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
|
||||
glyphToChar = glyphToCharArray.get();
|
||||
}
|
||||
|
@ -631,11 +321,13 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
|
||||
static const PRInt32 NO_GLYPH = -1;
|
||||
nsAutoTArray<PRInt32,SMALL_GLYPH_RUN> charToGlyphArray;
|
||||
if (!charToGlyphArray.SetLength(stringRange.length))
|
||||
if (!charToGlyphArray.SetLength(stringRange.length)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
PRInt32 *charToGlyph = charToGlyphArray.Elements();
|
||||
for (PRInt32 offset = 0; offset < stringRange.length; ++offset)
|
||||
for (PRInt32 offset = 0; offset < stringRange.length; ++offset) {
|
||||
charToGlyph[offset] = NO_GLYPH;
|
||||
}
|
||||
for (PRInt32 i = 0; i < numGlyphs; ++i) {
|
||||
PRInt32 loc = glyphToChar[i] - stringRange.location;
|
||||
if (loc >= 0 && loc < stringRange.length) {
|
||||
|
@ -742,13 +434,15 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
// adjusting for the offset of the stringRange relative to the textRun.
|
||||
PRInt32 baseCharIndex, endCharIndex;
|
||||
if (isLTR) {
|
||||
while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH)
|
||||
while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
|
||||
charEnd++;
|
||||
}
|
||||
baseCharIndex = charStart + stringRange.location - aStringOffset + aRunStart;
|
||||
endCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart;
|
||||
} else {
|
||||
while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH)
|
||||
while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
|
||||
charEnd--;
|
||||
}
|
||||
baseCharIndex = charEnd + stringRange.location - aStringOffset + aRunStart + 1;
|
||||
endCharIndex = charStart + stringRange.location - aStringOffset + aRunStart + 1;
|
||||
}
|
||||
|
@ -766,10 +460,11 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
// Now we're ready to set the glyph info in the textRun; measure the glyph width
|
||||
// of the first (perhaps only) glyph, to see if it is "Simple"
|
||||
double toNextGlyph;
|
||||
if (glyphStart < numGlyphs-1)
|
||||
if (glyphStart < numGlyphs-1) {
|
||||
toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
|
||||
else
|
||||
} else {
|
||||
toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
|
||||
}
|
||||
PRInt32 advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
|
||||
|
||||
// Check if it's a simple one-to-one mapping
|
||||
|
@ -792,12 +487,14 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
details->mXOffset = 0;
|
||||
details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit;
|
||||
details->mAdvance = advance;
|
||||
if (++glyphStart >= glyphEnd)
|
||||
if (++glyphStart >= glyphEnd) {
|
||||
break;
|
||||
if (glyphStart < numGlyphs-1)
|
||||
}
|
||||
if (glyphStart < numGlyphs-1) {
|
||||
toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
|
||||
else
|
||||
} else {
|
||||
toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
|
||||
}
|
||||
advance = PRInt32(toNextGlyph * appUnitsPerDevUnit);
|
||||
}
|
||||
|
||||
|
@ -827,10 +524,11 @@ gfxCoreTextFont::SetGlyphsFromRun(gfxTextRun *aTextRun,
|
|||
// This will turn off line-edge swashes by default, because we don't know the actual line breaks
|
||||
// when doing glyph shaping.
|
||||
void
|
||||
gfxCoreTextFont::CreateDefaultFeaturesDescriptor()
|
||||
gfxCoreTextShaper::CreateDefaultFeaturesDescriptor()
|
||||
{
|
||||
if (sDefaultFeaturesDescriptor != NULL)
|
||||
if (sDefaultFeaturesDescriptor != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
SInt16 val = kSmartSwashType;
|
||||
CFNumberRef swashesType =
|
||||
|
@ -899,7 +597,7 @@ gfxCoreTextFont::CreateDefaultFeaturesDescriptor()
|
|||
|
||||
// Create a CTFontRef, with the Common Ligatures feature disabled [static]
|
||||
CTFontRef
|
||||
gfxCoreTextFont::CreateCTFontWithDisabledLigatures(ATSFontRef aFontRef, CGFloat aSize)
|
||||
gfxCoreTextShaper::CreateCTFontWithDisabledLigatures(ATSFontRef aFontRef, CGFloat aSize)
|
||||
{
|
||||
if (sDisableLigaturesDescriptor == NULL) {
|
||||
// initialize cached descriptor to turn off the Common Ligatures feature
|
||||
|
@ -953,7 +651,7 @@ gfxCoreTextFont::CreateCTFontWithDisabledLigatures(ATSFontRef aFontRef, CGFloat
|
|||
}
|
||||
|
||||
void
|
||||
gfxCoreTextFont::Shutdown() // [static]
|
||||
gfxCoreTextShaper::Shutdown() // [static]
|
||||
{
|
||||
if (sDisableLigaturesDescriptor != NULL) {
|
||||
::CFRelease(sDisableLigaturesDescriptor);
|
|
@ -38,118 +38,60 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef GFX_CORETEXTFONTS_H
|
||||
#define GFX_CORETEXTFONTS_H
|
||||
#ifndef GFX_CORETEXTSHAPER_H
|
||||
#define GFX_CORETEXTSHAPER_H
|
||||
|
||||
#include "cairo.h"
|
||||
#include "gfxTypes.h"
|
||||
#include "gfxFont.h"
|
||||
#include "gfxFontUtils.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxMacPlatformFontList.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
class MacOSFontEntry;
|
||||
class gfxMacFont;
|
||||
|
||||
class gfxCoreTextFont : public gfxFont {
|
||||
class gfxCoreTextShaper : public gfxFontShaper {
|
||||
public:
|
||||
gfxCoreTextShaper(gfxMacFont *aFont);
|
||||
|
||||
gfxCoreTextFont(MacOSFontEntry *aFontEntry,
|
||||
const gfxFontStyle *fontStyle, PRBool aNeedsBold);
|
||||
virtual ~gfxCoreTextShaper();
|
||||
|
||||
virtual ~gfxCoreTextFont();
|
||||
|
||||
virtual const gfxFont::Metrics& GetMetrics() {
|
||||
NS_ASSERTION(mHasMetrics == PR_TRUE, "metrics not initialized");
|
||||
return mMetrics;
|
||||
}
|
||||
|
||||
float GetCharWidth(PRUnichar c, PRUint32 *aGlyphID = nsnull);
|
||||
float GetCharHeight(PRUnichar c);
|
||||
|
||||
ATSFontRef GetATSFont() {
|
||||
return mATSFont;
|
||||
}
|
||||
|
||||
CTFontRef GetCTFont() {
|
||||
return mCTFont;
|
||||
}
|
||||
|
||||
CFDictionaryRef GetAttributesDictionary() {
|
||||
return mAttributesDict;
|
||||
}
|
||||
|
||||
cairo_font_face_t *CairoFontFace() {
|
||||
return mFontFace;
|
||||
}
|
||||
|
||||
cairo_scaled_font_t *CairoScaledFont() {
|
||||
return mScaledFont;
|
||||
}
|
||||
|
||||
virtual nsString GetUniqueName() {
|
||||
return GetName();
|
||||
}
|
||||
|
||||
virtual PRUint32 GetSpaceGlyph() {
|
||||
return mSpaceGlyph;
|
||||
}
|
||||
|
||||
PRBool TestCharacterMap(PRUint32 aCh);
|
||||
|
||||
MacOSFontEntry* GetFontEntry();
|
||||
virtual void InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
// clean up static objects that may have been cached
|
||||
static void Shutdown();
|
||||
|
||||
static CTFontRef CreateCTFontWithDisabledLigatures(ATSFontRef aFontRef, CGFloat aSize);
|
||||
|
||||
protected:
|
||||
const gfxFontStyle *mFontStyle;
|
||||
|
||||
ATSFontRef mATSFont;
|
||||
CTFontRef mCTFont;
|
||||
CFDictionaryRef mAttributesDict;
|
||||
|
||||
PRBool mHasMetrics;
|
||||
|
||||
nsString mUniqueName;
|
||||
|
||||
cairo_font_face_t *mFontFace;
|
||||
cairo_scaled_font_t *mScaledFont;
|
||||
|
||||
gfxFont::Metrics mMetrics;
|
||||
|
||||
gfxFloat mAdjustedSize;
|
||||
PRUint32 mSpaceGlyph;
|
||||
|
||||
void InitMetrics();
|
||||
|
||||
virtual void InitTextRun(gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aRunStart,
|
||||
PRUint32 aRunLength);
|
||||
|
||||
nsresult SetGlyphsFromRun(gfxTextRun *aTextRun,
|
||||
CTRunRef aCTRun,
|
||||
PRInt32 aStringOffset,
|
||||
PRInt32 aLayoutStart,
|
||||
PRInt32 aLayoutLength);
|
||||
|
||||
virtual PRBool SetupCairoFont(gfxContext *aContext);
|
||||
|
||||
static void CreateDefaultFeaturesDescriptor();
|
||||
|
||||
static CTFontDescriptorRef GetDefaultFeaturesDescriptor() {
|
||||
if (sDefaultFeaturesDescriptor == NULL)
|
||||
if (sDefaultFeaturesDescriptor == NULL) {
|
||||
CreateDefaultFeaturesDescriptor();
|
||||
}
|
||||
return sDefaultFeaturesDescriptor;
|
||||
}
|
||||
|
||||
static CTFontRef CreateCTFontWithDisabledLigatures(ATSFontRef aFontRef, CGFloat aSize);
|
||||
|
||||
// cached font descriptor, created the first time it's needed
|
||||
static CTFontDescriptorRef sDefaultFeaturesDescriptor;
|
||||
|
||||
// cached descriptor for adding disable-ligatures setting to a font
|
||||
static CTFontDescriptorRef sDisableLigaturesDescriptor;
|
||||
};
|
||||
|
||||
#endif /* GFX_CORETEXTFONTS_H */
|
||||
#endif /* GFX_CORETEXTSHAPER_H */
|
|
@ -796,7 +796,9 @@ gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, PRBool aOtherIsOnLeft
|
|||
}
|
||||
|
||||
gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle) :
|
||||
mFontEntry(aFontEntry), mIsValid(PR_TRUE), mStyle(*aFontStyle), mSyntheticBoldOffset(0)
|
||||
mFontEntry(aFontEntry), mIsValid(PR_TRUE),
|
||||
mStyle(*aFontStyle), mSyntheticBoldOffset(0),
|
||||
mShaper(nsnull)
|
||||
{
|
||||
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
||||
++gFontCount;
|
||||
|
@ -1792,7 +1794,7 @@ gfxFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
|
|||
nsAutoString utf16;
|
||||
AppendASCIItoUTF16(cString, utf16);
|
||||
|
||||
InitTextRun(textRun, utf16.get(), utf16.Length());
|
||||
InitTextRun(aParams->mContext, textRun, utf16.get(), utf16.Length());
|
||||
|
||||
textRun->FetchGlyphExtents(aParams->mContext);
|
||||
|
||||
|
@ -1810,7 +1812,7 @@ gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
|
|||
|
||||
gfxPlatform::GetPlatform()->SetupClusterBoundaries(textRun, aString);
|
||||
|
||||
InitTextRun(textRun, aString, aLength);
|
||||
InitTextRun(aParams->mContext, textRun, aString, aLength);
|
||||
|
||||
textRun->FetchGlyphExtents(aParams->mContext);
|
||||
|
||||
|
@ -1822,7 +1824,8 @@ gfxFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
|
|||
// without requiring a separate allocation
|
||||
|
||||
void
|
||||
gfxFontGroup::InitTextRun(gfxTextRun *aTextRun,
|
||||
gfxFontGroup::InitTextRun(gfxContext *aContext,
|
||||
gfxTextRun *aTextRun,
|
||||
const PRUnichar *aString,
|
||||
PRUint32 aLength)
|
||||
{
|
||||
|
@ -1845,8 +1848,9 @@ gfxFontGroup::InitTextRun(gfxTextRun *aTextRun,
|
|||
// create the glyph run for this range
|
||||
aTextRun->AddGlyphRun(matchedFont, runStart, (matchedLength > 0));
|
||||
|
||||
// do glyph layout and record the resulting positioned glyphs in the run
|
||||
matchedFont->InitTextRun(aTextRun, aString, runStart, matchedLength);
|
||||
// do glyph layout and record the resulting positioned glyphs
|
||||
matchedFont->InitTextRun(aContext, aTextRun, aString,
|
||||
runStart, matchedLength);
|
||||
} else {
|
||||
// no font available, so record missing glyph info instead
|
||||
if (unmatched == NULL) {
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
/* -*- 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 Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2006-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Vladimir Vukicevic <vladimir@pobox.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.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 ***** */
|
||||
|
||||
#include "gfxMacFont.h"
|
||||
#include "gfxCoreTextShaper.h"
|
||||
#include "gfxPlatformMac.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
#include "cairo-quartz.h"
|
||||
|
||||
gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
PRBool aNeedsBold)
|
||||
: gfxFont(aFontEntry, aFontStyle),
|
||||
mATSFont(aFontEntry->GetFontRef()),
|
||||
mFontFace(nsnull),
|
||||
mScaledFont(nsnull),
|
||||
mAdjustedSize(0.0)
|
||||
{
|
||||
// determine whether synthetic bolding is needed
|
||||
PRInt8 baseWeight, weightDistance;
|
||||
mStyle.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 will handle the sizeAdjust factor and set mAdjustedSize
|
||||
InitMetrics();
|
||||
if (!mIsValid)
|
||||
return;
|
||||
|
||||
CGFontRef cgFont = ::CGFontCreateWithPlatformFont(&mATSFont);
|
||||
mFontFace = cairo_quartz_font_face_create_for_cgfont(cgFont);
|
||||
::CGFontRelease(cgFont);
|
||||
|
||||
cairo_status_t cairoerr = cairo_font_face_status(mFontFace);
|
||||
if (cairoerr != CAIRO_STATUS_SUCCESS) {
|
||||
mIsValid = PR_FALSE;
|
||||
#ifdef DEBUG
|
||||
char warnBuf[1024];
|
||||
sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d",
|
||||
NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
|
||||
NS_WARNING(warnBuf);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
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 =
|
||||
(mFontEntry != NULL) &&
|
||||
(!mFontEntry->IsItalic() && (mStyle.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);
|
||||
}
|
||||
|
||||
mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, fontOptions);
|
||||
cairo_font_options_destroy(fontOptions);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
mShaper = new gfxCoreTextShaper(this);
|
||||
}
|
||||
|
||||
gfxMacFont::~gfxMacFont()
|
||||
{
|
||||
if (mScaledFont) {
|
||||
cairo_scaled_font_destroy(mScaledFont);
|
||||
}
|
||||
if (mFontFace) {
|
||||
cairo_font_face_destroy(mFontFace);
|
||||
}
|
||||
}
|
||||
|
||||
PRBool
|
||||
gfxMacFont::SetupCairoFont(gfxContext *aContext)
|
||||
{
|
||||
if (cairo_scaled_font_status(mScaledFont) != 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(), mScaledFont);
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
static double
|
||||
RoundToNearestMultiple(double aValue, double aFraction)
|
||||
{
|
||||
return floor(aValue/aFraction + 0.5) * aFraction;
|
||||
}
|
||||
|
||||
void
|
||||
gfxMacFont::InitMetrics()
|
||||
{
|
||||
gfxFloat size =
|
||||
PR_MAX(((mAdjustedSize != 0.0f) ? mAdjustedSize : mStyle.size), 1.0f);
|
||||
|
||||
ATSFontMetrics atsMetrics;
|
||||
OSStatus err;
|
||||
|
||||
err = ::ATSFontGetHorizontalMetrics(mATSFont, 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(mFontEntry->Name()).get(), PRUint32(err));
|
||||
NS_WARNING(warnBuf);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// create a temporary local CTFont for glyph measurement
|
||||
CTFontRef aCTFont =
|
||||
::CTFontCreateWithPlatformFont(mATSFont, size, NULL, NULL);
|
||||
|
||||
// prefer to get xHeight from ATS metrics (unhinted) rather than Core Text (hinted),
|
||||
// see bug 429605.
|
||||
if (atsMetrics.xHeight > 0)
|
||||
mMetrics.xHeight = atsMetrics.xHeight * size;
|
||||
else
|
||||
mMetrics.xHeight = GetCharHeight(aCTFont, 'x');
|
||||
|
||||
if (mAdjustedSize == 0.0f) {
|
||||
if (mMetrics.xHeight != 0.0f && mStyle.sizeAdjust != 0.0f) {
|
||||
gfxFloat aspect = mMetrics.xHeight / size;
|
||||
mAdjustedSize = mStyle.GetAdjustedSize(aspect);
|
||||
|
||||
// the recursive call to InitMetrics will see the adjusted size,
|
||||
// and set up the rest of the metrics fields accordingly
|
||||
InitMetrics();
|
||||
|
||||
// release our temporary CTFont
|
||||
::CFRelease(aCTFont);
|
||||
return;
|
||||
}
|
||||
mAdjustedSize = size;
|
||||
}
|
||||
|
||||
mMetrics.superscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.subscriptOffset = mMetrics.xHeight;
|
||||
mMetrics.underlineOffset = atsMetrics.underlinePosition * size;
|
||||
mMetrics.underlineSize = atsMetrics.underlineThickness * size;
|
||||
mMetrics.strikeoutSize = mMetrics.underlineSize;
|
||||
mMetrics.strikeoutOffset = mMetrics.xHeight / 2;
|
||||
|
||||
mMetrics.externalLeading = atsMetrics.leading * 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.0)
|
||||
mMetrics.internalLeading = mMetrics.maxHeight - mMetrics.emHeight;
|
||||
else
|
||||
mMetrics.internalLeading = 0.0;
|
||||
|
||||
mMetrics.maxAdvance = atsMetrics.maxAdvanceWidth * size + mSyntheticBoldOffset;
|
||||
|
||||
mMetrics.emAscent = mMetrics.maxAscent * mMetrics.emHeight / mMetrics.maxHeight;
|
||||
mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
|
||||
|
||||
PRUint32 glyphID;
|
||||
float xWidth = GetCharWidth(aCTFont, 'x', &glyphID);
|
||||
if (atsMetrics.avgAdvanceWidth != 0.0)
|
||||
mMetrics.aveCharWidth = PR_MIN(atsMetrics.avgAdvanceWidth * size, xWidth);
|
||||
else if (glyphID != 0)
|
||||
mMetrics.aveCharWidth = xWidth;
|
||||
else
|
||||
mMetrics.aveCharWidth = mMetrics.maxAdvance;
|
||||
mMetrics.aveCharWidth += mSyntheticBoldOffset;
|
||||
|
||||
if (mFontEntry->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.spaceWidth = GetCharWidth(aCTFont, ' ', &glyphID);
|
||||
mSpaceGlyph = glyphID;
|
||||
|
||||
mMetrics.zeroOrAveCharWidth = GetCharWidth(aCTFont, '0', &glyphID);
|
||||
if (glyphID == 0)
|
||||
mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
|
||||
|
||||
::CFRelease(aCTFont);
|
||||
|
||||
SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
|
||||
|
||||
mIsValid = PR_TRUE;
|
||||
|
||||
#if 0
|
||||
fprintf (stderr, "Font: %p (%s) size: %f\n", this,
|
||||
NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
|
||||
// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height);
|
||||
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
|
||||
}
|
||||
|
||||
float
|
||||
gfxMacFont::GetCharWidth(CTFontRef aCTFont, PRUnichar aUniChar,
|
||||
PRUint32 *aGlyphID)
|
||||
{
|
||||
UniChar c = aUniChar;
|
||||
CGGlyph glyph;
|
||||
if (::CTFontGetGlyphsForCharacters(aCTFont, &c, &glyph, 1)) {
|
||||
CGSize advance;
|
||||
::CTFontGetAdvancesForGlyphs(aCTFont,
|
||||
kCTFontHorizontalOrientation,
|
||||
&glyph,
|
||||
&advance,
|
||||
1);
|
||||
if (aGlyphID != nsnull)
|
||||
*aGlyphID = glyph;
|
||||
return advance.width;
|
||||
}
|
||||
|
||||
// couldn't get glyph for the char
|
||||
if (aGlyphID != nsnull)
|
||||
*aGlyphID = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
float
|
||||
gfxMacFont::GetCharHeight(CTFontRef aCTFont, PRUnichar aUniChar)
|
||||
{
|
||||
UniChar c = aUniChar;
|
||||
CGGlyph glyph;
|
||||
if (::CTFontGetGlyphsForCharacters(aCTFont, &c, &glyph, 1)) {
|
||||
CGRect boundingRect;
|
||||
::CTFontGetBoundingRectsForGlyphs(aCTFont,
|
||||
kCTFontHorizontalOrientation,
|
||||
&glyph,
|
||||
&boundingRect,
|
||||
1);
|
||||
return boundingRect.size.height;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* -*- 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 Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2006-2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Vladimir Vukicevic <vladimir@pobox.com>
|
||||
* Masayuki Nakano <masayuki@d-toybox.com>
|
||||
* John Daggett <jdaggett@mozilla.com>
|
||||
* Jonathan Kew <jfkthame@gmail.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 GFX_MACFONT_H
|
||||
#define GFX_MACFONT_H
|
||||
|
||||
#include "gfxFont.h"
|
||||
#include "gfxMacPlatformFontList.h"
|
||||
|
||||
#include "cairo.h"
|
||||
|
||||
class gfxMacFont : public gfxFont
|
||||
{
|
||||
public:
|
||||
gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
PRBool aNeedsBold);
|
||||
|
||||
virtual ~gfxMacFont();
|
||||
|
||||
ATSFontRef GetATSFontRef() const { return mATSFont; }
|
||||
|
||||
// TODO: probably should move this up to gfxFont
|
||||
// and ensure it is handled uniformly across all platforms
|
||||
float GetAdjustedSize() const { return mAdjustedSize; }
|
||||
|
||||
/* overrides for the pure virtual methods in gfxFont */
|
||||
virtual const gfxFont::Metrics& GetMetrics() {
|
||||
return mMetrics;
|
||||
}
|
||||
|
||||
virtual PRUint32 GetSpaceGlyph() {
|
||||
return mSpaceGlyph;
|
||||
}
|
||||
|
||||
virtual PRBool SetupCairoFont(gfxContext *aContext);
|
||||
|
||||
protected:
|
||||
void InitMetrics();
|
||||
|
||||
float GetCharWidth(CTFontRef aCTFont, PRUnichar aUniChar,
|
||||
PRUint32 *aGlyphID);
|
||||
float GetCharHeight(CTFontRef aCTFont, PRUnichar aUniChar);
|
||||
|
||||
ATSFontRef mATSFont;
|
||||
|
||||
cairo_font_face_t *mFontFace;
|
||||
cairo_scaled_font_t *mScaledFont;
|
||||
|
||||
Metrics mMetrics;
|
||||
PRUint32 mSpaceGlyph;
|
||||
float mAdjustedSize;
|
||||
};
|
||||
|
||||
#endif /* GFX_MACFONT_H */
|
|
@ -45,7 +45,6 @@
|
|||
#include "nsRefPtrHashtable.h"
|
||||
|
||||
#include "gfxPlatformFontList.h"
|
||||
#include "gfxCoreTextFonts.h"
|
||||
#include "gfxPlatform.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
#include "gfxPlatformMac.h"
|
||||
#include "gfxMacPlatformFontList.h"
|
||||
#include "gfxMacFont.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
@ -311,7 +312,7 @@ MacOSFontEntry::GetFontTable(PRUint32 aTableTag, nsTArray<PRUint8>& aBuffer)
|
|||
gfxFont*
|
||||
MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, PRBool aNeedsBold)
|
||||
{
|
||||
return new gfxCoreTextFont(this, aFontStyle, aNeedsBold);
|
||||
return new gfxMacFont(this, aFontStyle, aNeedsBold);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1003,7 +1003,7 @@ static nsIUGenCategory*
|
|||
GetGenCategory()
|
||||
{
|
||||
if (!gGenCategory) {
|
||||
nsresult rv = CallGetService(NS_UNICHARUTIL_CONTRACTID, &gGenCategory);
|
||||
nsresult rv = CallGetService(NS_UNICHARCATEGORY_CONTRACTID, &gGenCategory);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Failed to get the Unicode character category service!");
|
||||
gGenCategory = nsnull;
|
||||
|
|
|
@ -43,8 +43,9 @@
|
|||
#include "gfxQuartzImageSurface.h"
|
||||
|
||||
#include "gfxMacPlatformFontList.h"
|
||||
#include "gfxMacFont.h"
|
||||
#include "gfxCoreTextShaper.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
#include "gfxCoreTextFonts.h"
|
||||
|
||||
#include "nsIPrefBranch.h"
|
||||
#include "nsIPrefService.h"
|
||||
|
@ -64,7 +65,7 @@ gfxPlatformMac::gfxPlatformMac()
|
|||
|
||||
gfxPlatformMac::~gfxPlatformMac()
|
||||
{
|
||||
gfxCoreTextFont::Shutdown();
|
||||
gfxCoreTextShaper::Shutdown();
|
||||
}
|
||||
|
||||
gfxPlatformFontList*
|
||||
|
@ -327,35 +328,3 @@ fail_close:
|
|||
CMCloseProfile(cmProfile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatformMac::SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
|
||||
{
|
||||
TextBreakLocatorRef locator;
|
||||
OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask,
|
||||
&locator);
|
||||
if (status != noErr)
|
||||
return;
|
||||
UniCharArrayOffset breakOffset = 0;
|
||||
UCTextBreakOptions options = kUCTextBreakLeadingEdgeMask;
|
||||
PRUint32 length = aTextRun->GetLength();
|
||||
while (breakOffset < length) {
|
||||
UniCharArrayOffset next;
|
||||
status = UCFindTextBreak(locator, kUCTextBreakClusterMask, options,
|
||||
aString, length, breakOffset, &next);
|
||||
if (status != noErr)
|
||||
break;
|
||||
options |= kUCTextBreakIterateMask;
|
||||
PRUint32 i;
|
||||
for (i = breakOffset + 1; i < next; ++i) {
|
||||
gfxTextRun::CompressedGlyph g;
|
||||
// Remember that this character is not the start of a cluster by
|
||||
// setting its glyph data to "not a cluster start", "is a
|
||||
// ligature start", with no glyphs.
|
||||
aTextRun->SetGlyphs(i, g.SetComplex(PR_FALSE, PR_TRUE, 0), nsnull);
|
||||
}
|
||||
breakOffset = next;
|
||||
}
|
||||
UCDisposeTextBreakLocator(&locator);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче