/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE /***************************************************************************** QFontEngine debug facilities *****************************************************************************/ //#define DEBUG_ADVANCES extern int qt_antialiasing_threshold; // QApplication.cpp #ifndef FixedToQFixed #define FixedToQFixed(a) QFixed::fromFixed((a) >> 10) #define QFixedToFixed(x) ((x).value() << 10) #endif class QMacFontPath { float x, y; QPainterPath *path; public: inline QMacFontPath(float _x, float _y, QPainterPath *_path) : x(_x), y(_y), path(_path) { } inline void setPosition(float _x, float _y) { x = _x; y = _y; } inline void advance(float _x) { x += _x; } static OSStatus lineTo(const Float32Point *, void *); static OSStatus cubicTo(const Float32Point *, const Float32Point *, const Float32Point *, void *); static OSStatus moveTo(const Float32Point *, void *); static OSStatus closePath(void *); }; OSStatus QMacFontPath::lineTo(const Float32Point *pt, void *data) { QMacFontPath *p = static_cast(data); p->path->lineTo(p->x + pt->x, p->y + pt->y); return noErr; } OSStatus QMacFontPath::cubicTo(const Float32Point *cp1, const Float32Point *cp2, const Float32Point *ep, void *data) { QMacFontPath *p = static_cast(data); p->path->cubicTo(p->x + cp1->x, p->y + cp1->y, p->x + cp2->x, p->y + cp2->y, p->x + ep->x, p->y + ep->y); return noErr; } OSStatus QMacFontPath::moveTo(const Float32Point *pt, void *data) { QMacFontPath *p = static_cast(data); p->path->moveTo(p->x + pt->x, p->y + pt->y); return noErr; } OSStatus QMacFontPath::closePath(void *data) { static_cast(data)->path->closeSubpath(); return noErr; } void qmacfontengine_gamma_correct(QImage *image) { extern uchar qt_pow_rgb_gamma[256]; // gamma correct the pixels back to linear color space... int h = image->height(); int w = image->width(); for (int y=0; yscanLine(y); for (int x=0; x= MAC_OS_X_VERSION_10_5 QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const ATSFontFamilyRef &, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning) : QFontEngineMulti(0) { this->fontDef = fontDef; CTFontSymbolicTraits symbolicTraits = 0; if (fontDef.weight >= QFont::Bold) symbolicTraits |= kCTFontBoldTrait; switch (fontDef.style) { case QFont::StyleNormal: break; case QFont::StyleItalic: case QFont::StyleOblique: symbolicTraits |= kCTFontItalicTrait; break; } QCFString name; ATSFontGetName(atsFontRef, kATSOptionFlagsDefault, &name); QCFType descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize); QCFType baseFont = CTFontCreateWithFontDescriptor(descriptor, fontDef.pixelSize, 0); ctfont = CTFontCreateCopyWithSymbolicTraits(baseFont, fontDef.pixelSize, 0, symbolicTraits, symbolicTraits); // CTFontCreateCopyWithSymbolicTraits returns NULL if we ask for a trait that does // not exist for the given font. (for example italic) if (ctfont == 0) { ctfont = baseFont; CFRetain(ctfont); } attributeDict = CFDictionaryCreateMutable(0, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(attributeDict, NSFontAttributeName, ctfont); if (!kerning) { float zero = 0.0; QCFType noKern = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &zero); CFDictionaryAddValue(attributeDict, kCTKernAttributeName, noKern); } QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef, this); fe->ref.ref(); engines.append(fe); } QCoreTextFontEngineMulti::~QCoreTextFontEngineMulti() { CFRelease(ctfont); } uint QCoreTextFontEngineMulti::fontIndexForFont(CTFontRef id) const { for (int i = 0; i < engines.count(); ++i) { if (CFEqual(engineAt(i)->ctfont, id)) return i; } QCoreTextFontEngineMulti *that = const_cast(this); QCoreTextFontEngine *fe = new QCoreTextFontEngine(id, fontDef, that); fe->ref.ref(); that->engines.append(fe); return engines.count() - 1; } bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags, unsigned short *logClusters, const HB_CharAttributes *) const { QCFType cfstring = CFStringCreateWithCharactersNoCopy(0, reinterpret_cast(str), len, kCFAllocatorNull); QCFType attributedString = CFAttributedStringCreate(0, cfstring, attributeDict); QCFType typeSetter = CTTypesetterCreateWithAttributedString(attributedString); CFRange range = {0, 0}; QCFType line = CTTypesetterCreateLine(typeSetter, range); CFArrayRef array = CTLineGetGlyphRuns(line); uint arraySize = CFArrayGetCount(array); glyph_t *outGlyphs = glyphs->glyphs; HB_GlyphAttributes *outAttributes = glyphs->attributes; QFixed *outAdvances_x = glyphs->advances_x; QFixed *outAdvances_y = glyphs->advances_y; glyph_t *initialGlyph = outGlyphs; if (arraySize == 0) return false; const bool rtl = (CTRunGetStatus(static_cast(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft); bool outOBounds = false; for (uint i = 0; i < arraySize; ++i) { CTRunRef run = static_cast(CFArrayGetValueAtIndex(array, rtl ? (arraySize - 1 - i) : i)); CFIndex glyphCount = CTRunGetGlyphCount(run); if (glyphCount == 0) continue; Q_ASSERT((CTRunGetStatus(run) & kCTRunStatusRightToLeft) == rtl); if (!outOBounds && outGlyphs + glyphCount - initialGlyph > *nglyphs) { outOBounds = true; } if (!outOBounds) { CFDictionaryRef runAttribs = CTRunGetAttributes(run); //NSLog(@"Dictionary %@", runAttribs); if (!runAttribs) runAttribs = attributeDict; CTFontRef runFont = static_cast(CFDictionaryGetValue(runAttribs, NSFontAttributeName)); const uint fontIndex = (fontIndexForFont(runFont) << 24); //NSLog(@"Run Font Name = %@", CTFontCopyFamilyName(runFont)); QVarLengthArray cgglyphs(0); const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run); if (!tmpGlyphs) { cgglyphs.resize(glyphCount); CTRunGetGlyphs(run, range, cgglyphs.data()); tmpGlyphs = cgglyphs.constData(); } QVarLengthArray cgpoints(0); const CGPoint *tmpPoints = CTRunGetPositionsPtr(run); if (!tmpPoints) { cgpoints.resize(glyphCount); CTRunGetPositions(run, range, cgpoints.data()); tmpPoints = cgpoints.constData(); } const int rtlOffset = rtl ? (glyphCount - 1) : 0; const int rtlSign = rtl ? -1 : 1; if (logClusters) { CFRange stringRange = CTRunGetStringRange(run); QVarLengthArray stringIndices(0); const CFIndex *tmpIndices = CTRunGetStringIndicesPtr(run); if (!tmpIndices) { stringIndices.resize(glyphCount); CTRunGetStringIndices(run, range, stringIndices.data()); tmpIndices = stringIndices.constData(); } const int firstGlyphIndex = outGlyphs - initialGlyph; outAttributes[0].clusterStart = true; CFIndex k = 0; CFIndex i = 0; for (i = stringRange.location; (i < stringRange.location + stringRange.length) && (k < glyphCount); ++i) { if (tmpIndices[k * rtlSign + rtlOffset] == i || i == stringRange.location) { logClusters[i] = k + firstGlyphIndex; outAttributes[k].clusterStart = true; ++k; } else { logClusters[i] = k + firstGlyphIndex - 1; } } // in case of a ligature at the end, fill the remaining logcluster entries for (;i < stringRange.location + stringRange.length; i++) { logClusters[i] = k + firstGlyphIndex - 1; } } for (CFIndex i = 0; i < glyphCount - 1; ++i) { int idx = rtlOffset + rtlSign * i; outGlyphs[idx] = tmpGlyphs[i] | fontIndex; outAdvances_x[idx] = QFixed::fromReal(tmpPoints[i + 1].x - tmpPoints[i].x); outAdvances_y[idx] = QFixed::fromReal(tmpPoints[i + 1].y - tmpPoints[i].y); } CGSize lastGlyphAdvance; CTFontGetAdvancesForGlyphs(runFont, kCTFontHorizontalOrientation, tmpGlyphs + glyphCount - 1, &lastGlyphAdvance, 1); outGlyphs[rtl ? 0 : (glyphCount - 1)] = tmpGlyphs[glyphCount - 1] | fontIndex; outAdvances_x[rtl ? 0 : (glyphCount - 1)] = QFixed::fromReal(lastGlyphAdvance.width).ceil(); } outGlyphs += glyphCount; outAttributes += glyphCount; outAdvances_x += glyphCount; outAdvances_y += glyphCount; } *nglyphs = (outGlyphs - initialGlyph); return !outOBounds; } bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const { return stringToCMap(str, len, glyphs, nglyphs, flags, 0, 0); } void QCoreTextFontEngineMulti::recalcAdvances(int , QGlyphLayout *, QTextEngine::ShaperFlags) const { } void QCoreTextFontEngineMulti::doKerning(int , QGlyphLayout *, QTextEngine::ShaperFlags) const { } void QCoreTextFontEngineMulti::loadEngine(int) { // Do nothing Q_ASSERT(false); } QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def, QCoreTextFontEngineMulti *multiEngine) { fontDef = def; parentEngine = multiEngine; synthesisFlags = 0; ctfont = font; CFRetain(ctfont); ATSFontRef atsfont = CTFontGetPlatformFont(ctfont, 0); cgFont = CGFontCreateWithPlatformFont(&atsfont); CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont); if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait)) { synthesisFlags |= SynthesizedBold; } if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait)) { synthesisFlags |= SynthesizedItalic; } QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2')); if (os2Table.size() >= 10) fsType = qFromBigEndian(reinterpret_cast(os2Table.constData() + 8)); } QCoreTextFontEngine::~QCoreTextFontEngine() { CFRelease(ctfont); CFRelease(cgFont); } bool QCoreTextFontEngine::stringToCMap(const QChar *, int, QGlyphLayout *, int *, QTextEngine::ShaperFlags) const { return false; } glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs) { QFixed w; for (int i = 0; i < glyphs.numGlyphs; ++i) w += glyphs.effectiveAdvance(i); return glyph_metrics_t(0, -(ascent()), w, ascent()+descent(), w, 0); } glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph) { glyph_metrics_t ret; CGGlyph g = glyph; CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, 0, 1); ret.width = QFixed::fromReal(rect.size.width); ret.height = QFixed::fromReal(rect.size.height); ret.x = QFixed::fromReal(rect.origin.x); ret.y = -QFixed::fromReal(rect.origin.y) - ret.height; CGSize advances[1]; CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, advances, 1); ret.xoff = QFixed::fromReal(advances[0].width).ceil(); ret.yoff = QFixed::fromReal(advances[0].height).ceil(); return ret; } QFixed QCoreTextFontEngine::ascent() const { return QFixed::fromReal(CTFontGetAscent(ctfont)).ceil(); } QFixed QCoreTextFontEngine::descent() const { // subtract a pixel to even out the historical +1 in QFontMetrics::height(). // Fix in Qt 5. return QFixed::fromReal(CTFontGetDescent(ctfont)).ceil() - 1; } QFixed QCoreTextFontEngine::leading() const { return QFixed::fromReal(CTFontGetLeading(ctfont)).ceil(); } QFixed QCoreTextFontEngine::xHeight() const { return QFixed::fromReal(CTFontGetXHeight(ctfont)).ceil(); } QFixed QCoreTextFontEngine::averageCharWidth() const { // ### Need to implement properly and get the information from the OS/2 Table. return QFontEngine::averageCharWidth(); } qreal QCoreTextFontEngine::maxCharWidth() const { // ### Max Help! return 0; } qreal QCoreTextFontEngine::minLeftBearing() const { // ### Min Help! return 0; } qreal QCoreTextFontEngine::minRightBearing() const { // ### Max Help! (even thought it's right) return 0; } void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) { QVarLengthArray positions; QVarLengthArray glyphs; QTransform matrix; matrix.translate(x, y); getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); if (glyphs.size() == 0) return; CGContextSetFontSize(ctx, fontDef.pixelSize); CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); CGAffineTransformConcat(cgMatrix, oldTextMatrix); if (synthesisFlags & QFontEngine::SynthesizedItalic) cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); // ### cgMatrix = CGAffineTransformConcat(cgMatrix, transform); CGContextSetTextMatrix(ctx, cgMatrix); CGContextSetTextDrawingMode(ctx, kCGTextFill); QVarLengthArray advances(glyphs.size()); QVarLengthArray cgGlyphs(glyphs.size()); for (int i = 0; i < glyphs.size() - 1; ++i) { advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); cgGlyphs[i] = glyphs[i]; } advances[glyphs.size() - 1].width = 0; advances[glyphs.size() - 1].height = 0; cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; CGContextSetFont(ctx, cgFont); //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont)); CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); if (synthesisFlags & QFontEngine::SynthesizedBold) { CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), positions[0].y.toReal()); CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); } CGContextSetTextMatrix(ctx, oldTextMatrix); } struct ConvertPathInfo { ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos) : path(newPath), pos(newPos) {} QPainterPath *path; QPointF pos; }; static void convertCGPathToQPainterPath(void *info, const CGPathElement *element) { ConvertPathInfo *myInfo = static_cast(info); switch(element->type) { case kCGPathElementMoveToPoint: myInfo->path->moveTo(element->points[0].x + myInfo->pos.x(), element->points[0].y + myInfo->pos.y()); break; case kCGPathElementAddLineToPoint: myInfo->path->lineTo(element->points[0].x + myInfo->pos.x(), element->points[0].y + myInfo->pos.y()); break; case kCGPathElementAddQuadCurveToPoint: myInfo->path->quadTo(element->points[0].x + myInfo->pos.x(), element->points[0].y + myInfo->pos.y(), element->points[1].x + myInfo->pos.x(), element->points[1].y + myInfo->pos.y()); break; case kCGPathElementAddCurveToPoint: myInfo->path->cubicTo(element->points[0].x + myInfo->pos.x(), element->points[0].y + myInfo->pos.y(), element->points[1].x + myInfo->pos.x(), element->points[1].y + myInfo->pos.y(), element->points[2].x + myInfo->pos.x(), element->points[2].y + myInfo->pos.y()); break; case kCGPathElementCloseSubpath: myInfo->path->closeSubpath(); break; default: qDebug() << "Unhandled path transform type: " << element->type; } } void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, QPainterPath *path, QTextItem::RenderFlags) { CGAffineTransform cgMatrix = CGAffineTransformIdentity; cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1); if (synthesisFlags & QFontEngine::SynthesizedItalic) cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); for (int i = 0; i < nGlyphs; ++i) { QCFType cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix); ConvertPathInfo info(path, positions[i].toPointF()); CGPathApply(cgpath, &info, convertCGPathToQPainterPath); } } QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, int margin, bool aa) { const glyph_metrics_t br = boundingBox(glyph); QImage im(qRound(br.width)+2, qRound(br.height)+2, QImage::Format_RGB32); im.fill(0); CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); uint cgflags = kCGImageAlphaNoneSkipFirst; #ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version cgflags |= kCGBitmapByteOrder32Host; #endif CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), 8, im.bytesPerLine(), colorspace, cgflags); CGContextSetFontSize(ctx, fontDef.pixelSize); CGContextSetShouldAntialias(ctx, aa || (fontDef.pointSize > qt_antialiasing_threshold && !(fontDef.styleStrategy & QFont::NoAntialias))); CGContextSetShouldSmoothFonts(ctx, aa); CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); CGAffineTransformConcat(cgMatrix, oldTextMatrix); if (synthesisFlags & QFontEngine::SynthesizedItalic) cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, tanf(14 * acosf(0) / 90), 1, 0, 0)); // ### cgMatrix = CGAffineTransformConcat(cgMatrix, transform); CGContextSetTextMatrix(ctx, cgMatrix); CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); CGContextSetTextDrawingMode(ctx, kCGTextFill); ATSFontRef atsfont = CTFontGetPlatformFont(ctfont, 0); QCFType cgFont = CGFontCreateWithPlatformFont(&atsfont); CGContextSetFont(ctx, cgFont); qreal pos_x = -br.x.toReal()+1, pos_y = im.height()+br.y.toReal(); CGContextSetTextPosition(ctx, pos_x, pos_y); CGSize advance; advance.width = 0; advance.height = 0; CGGlyph cgGlyph = glyph; CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); if (synthesisFlags & QFontEngine::SynthesizedBold) { CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); } CGContextRelease(ctx); return im; } QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph) { QImage im = imageForGlyph(glyph, 0, false); QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); QVector colors(256); for (int i=0; i<256; ++i) colors[i] = qRgba(0, 0, 0, i); indexed.setColorTable(colors); for (int y=0; y= QTransform::TxScale) return QFontEngine::alphaRGBMapForGlyph(glyph, margin, x); QImage im = imageForGlyph(glyph, margin, true); qmacfontengine_gamma_correct(&im); return im; } void QCoreTextFontEngine::recalcAdvances(int numGlyphs, QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const { Q_ASSERT(false); Q_UNUSED(numGlyphs); Q_UNUSED(glyphs); Q_UNUSED(flags); } QFontEngine::FaceId QCoreTextFontEngine::faceId() const { return QFontEngine::FaceId(); } bool QCoreTextFontEngine::canRender(const QChar *string, int len) { QCFType retFont = CTFontCreateForString(ctfont, QCFType(CFStringCreateWithCharactersNoCopy(0, reinterpret_cast(string), len, kCFAllocatorNull)), CFRangeMake(0, len)); return retFont != 0; return false; } bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const { QCFType table = CTFontCopyTable(ctfont, tag, 0); if (!table || !length) return false; CFIndex tableLength = CFDataGetLength(table); int availableLength = *length; *length = tableLength; if (buffer) { if (tableLength > availableLength) return false; CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer); } return true; } void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metrics_t *) { // ### } #endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 #ifndef QT_MAC_USE_COCOA QFontEngineMacMulti::QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning) : QFontEngineMulti(0) { this->fontDef = fontDef; this->kerning = kerning; // hopefully (CTFontCreateWithName or CTFontCreateWithFontDescriptor) + CTFontCreateCopyWithSymbolicTraits // (or CTFontCreateWithQuickdrawInstance) FMFontFamily fmFamily; FMFontStyle fntStyle = 0; fmFamily = FMGetFontFamilyFromATSFontFamilyRef(atsFamily); if (fmFamily == kInvalidFontFamily) { // Use the ATSFont then... fontID = FMGetFontFromATSFontRef(atsFontRef); } else { if (fontDef.weight >= QFont::Bold) fntStyle |= ::bold; if (fontDef.style != QFont::StyleNormal) fntStyle |= ::italic; FMFontStyle intrinsicStyle; FMFont fnt = 0; if (FMGetFontFromFontFamilyInstance(fmFamily, fntStyle, &fnt, &intrinsicStyle) == noErr) fontID = FMGetATSFontRefFromFont(fnt); } // CFDictionaryRef, OSStatus status; status = ATSUCreateTextLayout(&textLayout); Q_ASSERT(status == noErr); const int maxAttributeCount = 5; ATSUAttributeTag tags[maxAttributeCount + 1]; ByteCount sizes[maxAttributeCount + 1]; ATSUAttributeValuePtr values[maxAttributeCount + 1]; int attributeCount = 0; Fixed size = FixRatio(fontDef.pixelSize, 1); tags[attributeCount] = kATSUSizeTag; sizes[attributeCount] = sizeof(size); values[attributeCount] = &size; ++attributeCount; tags[attributeCount] = kATSUFontTag; sizes[attributeCount] = sizeof(fontID); values[attributeCount] = &this->fontID; ++attributeCount; transform = CGAffineTransformIdentity; if (fontDef.stretch != 100) { transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); tags[attributeCount] = kATSUFontMatrixTag; sizes[attributeCount] = sizeof(transform); values[attributeCount] = &transform; ++attributeCount; } status = ATSUCreateStyle(&style); Q_ASSERT(status == noErr); Q_ASSERT(attributeCount < maxAttributeCount + 1); status = ATSUSetAttributes(style, attributeCount, tags, sizes, values); Q_ASSERT(status == noErr); QFontEngineMac *fe = new QFontEngineMac(style, fontID, fontDef, this); fe->ref.ref(); engines.append(fe); } QFontEngineMacMulti::~QFontEngineMacMulti() { ATSUDisposeTextLayout(textLayout); ATSUDisposeStyle(style); for (int i = 0; i < engines.count(); ++i) { QFontEngineMac *fe = const_cast(static_cast(engines.at(i))); fe->multiEngine = 0; if (!fe->ref.deref()) delete fe; } engines.clear(); } struct QGlyphLayoutInfo { QGlyphLayout *glyphs; int *numGlyphs; bool callbackCalled; int *mappedFonts; QTextEngine::ShaperFlags flags; QFontEngineMacMulti::ShaperItem *shaperItem; }; static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon, void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus) { Q_UNUSED(selector); Q_UNUSED(operationExtraParameter); QGlyphLayoutInfo *nfo = reinterpret_cast(refCon); nfo->callbackCalled = true; ATSLayoutRecord *layoutData = 0; ItemCount itemCount = 0; OSStatus e = noErr; e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, /*iCreate =*/ false, (void **) &layoutData, &itemCount); if (e != noErr) return e; *nfo->numGlyphs = itemCount - 1; Fixed *baselineDeltas = 0; e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataBaselineDeltaFixedArray, /*iCreate =*/ true, (void **) &baselineDeltas, &itemCount); if (e != noErr) return e; int nextCharStop = -1; int currentClusterGlyph = -1; // first glyph in log cluster QFontEngineMacMulti::ShaperItem *item = nfo->shaperItem; if (item->charAttributes) { item = nfo->shaperItem; #if !defined(QT_NO_DEBUG) int surrogates = 0; const QChar *str = item->string; for (int i = item->from; i < item->from + item->length - 1; ++i) { surrogates += (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); } #endif for (nextCharStop = item->from; nextCharStop < item->from + item->length; ++nextCharStop) if (item->charAttributes[nextCharStop].charStop) break; nextCharStop -= item->from; } nfo->glyphs->attributes[0].clusterStart = true; int glyphIdx = 0; int glyphIncrement = 1; if (nfo->flags & QTextEngine::RightToLeft) { glyphIdx = itemCount - 2; glyphIncrement = -1; } for (int i = 0; i < *nfo->numGlyphs; ++i, glyphIdx += glyphIncrement) { int charOffset = layoutData[glyphIdx].originalOffset / sizeof(UniChar); const int fontIdx = nfo->mappedFonts[charOffset]; ATSGlyphRef glyphId = layoutData[glyphIdx].glyphID; QFixed yAdvance = FixedToQFixed(baselineDeltas[glyphIdx]); QFixed xAdvance = FixedToQFixed(layoutData[glyphIdx + 1].realPos - layoutData[glyphIdx].realPos); if (glyphId != 0xffff || i == 0) { if (i < nfo->glyphs->numGlyphs) { nfo->glyphs->glyphs[i] = (glyphId & 0x00ffffff) | (fontIdx << 24); nfo->glyphs->advances_y[i] = yAdvance; nfo->glyphs->advances_x[i] = xAdvance; } } else { // ATSUI gives us 0xffff as glyph id at the index in the glyph array for // a character position that maps to a ligtature. Such a glyph id does not // result in any visual glyph, but it may have an advance, which is why we // sum up the glyph advances. --i; nfo->glyphs->advances_y[i] += yAdvance; nfo->glyphs->advances_x[i] += xAdvance; *nfo->numGlyphs -= 1; } if (item->log_clusters) { if (charOffset >= nextCharStop) { nfo->glyphs->attributes[i].clusterStart = true; currentClusterGlyph = i; ++nextCharStop; for (; nextCharStop < item->length; ++nextCharStop) if (item->charAttributes[item->from + nextCharStop].charStop) break; } else { if (currentClusterGlyph == -1) currentClusterGlyph = i; } item->log_clusters[charOffset] = currentClusterGlyph; // surrogate handling if (charOffset < item->length - 1) { QChar current = item->string[item->from + charOffset]; QChar next = item->string[item->from + charOffset + 1]; if (current.unicode() >= 0xd800 && current.unicode() < 0xdc00 && next.unicode() >= 0xdc00 && next.unicode() < 0xe000) { item->log_clusters[charOffset + 1] = currentClusterGlyph; } } } } /* if (item) { qDebug() << "resulting logclusters:"; for (int i = 0; i < item->length; ++i) qDebug() << "logClusters[" << i << "] =" << item->log_clusters[i]; qDebug() << "clusterstarts:"; for (int i = 0; i < *nfo->numGlyphs; ++i) qDebug() << "clusterStart[" << i << "] =" << nfo->glyphs[i].attributes.clusterStart; } */ ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataBaselineDeltaFixedArray, (void **) &baselineDeltas); ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **) &layoutData); *callbackStatus = kATSULayoutOperationCallbackStatusHandled; return noErr; } int QFontEngineMacMulti::fontIndexForFontID(ATSUFontID id) const { for (int i = 0; i < engines.count(); ++i) { if (engineAt(i)->fontID == id) return i; } QFontEngineMacMulti *that = const_cast(this); QFontEngineMac *fe = new QFontEngineMac(style, id, fontDef, that); fe->ref.ref(); that->engines.append(fe); return engines.count() - 1; } bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const { return stringToCMap(str, len, glyphs, nglyphs, flags, /*logClusters=*/0, /*charAttributes=*/0); } bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, unsigned short *logClusters, const HB_CharAttributes *charAttributes) const { if (*nglyphs < len) { *nglyphs = len; return false; } ShaperItem shaperItem; shaperItem.string = str; shaperItem.from = 0; shaperItem.length = len; shaperItem.glyphs = *glyphs; shaperItem.glyphs.numGlyphs = *nglyphs; shaperItem.flags = flags; shaperItem.log_clusters = logClusters; shaperItem.charAttributes = charAttributes; const int maxChars = qMax(1, int(SHRT_MAX / maxCharWidth()) - 10 // subtract a few to be on the safe side ); if (len < maxChars || !charAttributes) return stringToCMapInternal(str, len, glyphs, nglyphs, flags, &shaperItem); int charIdx = 0; int glyphIdx = 0; ShaperItem tmpItem = shaperItem; do { tmpItem.from = shaperItem.from + charIdx; int charCount = qMin(maxChars, len - charIdx); int lastWhitespace = tmpItem.from + charCount - 1; int lastSoftBreak = lastWhitespace; int lastCharStop = lastSoftBreak; for (int i = lastCharStop; i >= tmpItem.from; --i) { if (tmpItem.charAttributes[i].whiteSpace) { lastWhitespace = i; break; } if (tmpItem.charAttributes[i].lineBreakType != HB_NoBreak) { lastSoftBreak = i; } if (tmpItem.charAttributes[i].charStop) { lastCharStop = i; } } charCount = qMin(lastWhitespace, qMin(lastSoftBreak, lastCharStop)) - tmpItem.from + 1; int glyphCount = shaperItem.glyphs.numGlyphs - glyphIdx; if (glyphCount <= 0) return false; tmpItem.length = charCount; tmpItem.glyphs = shaperItem.glyphs.mid(glyphIdx, glyphCount); tmpItem.log_clusters = shaperItem.log_clusters + charIdx; if (!stringToCMapInternal(tmpItem.string + tmpItem.from, tmpItem.length, &tmpItem.glyphs, &glyphCount, flags, &tmpItem)) { *nglyphs = glyphIdx + glyphCount; return false; } for (int i = 0; i < charCount; ++i) tmpItem.log_clusters[i] += glyphIdx; glyphIdx += glyphCount; charIdx += charCount; } while (charIdx < len); *nglyphs = glyphIdx; glyphs->numGlyphs = glyphIdx; return true; } bool QFontEngineMacMulti::stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags,ShaperItem *shaperItem) const { //qDebug() << "stringToCMap" << QString(str, len); OSStatus e = noErr; e = ATSUSetTextPointerLocation(textLayout, (UniChar *)(str), 0, len, len); if (e != noErr) { qWarning("Qt: internal: %ld: Error ATSUSetTextPointerLocation %s: %d", long(e), __FILE__, __LINE__); return false; } QGlyphLayoutInfo nfo; nfo.glyphs = glyphs; nfo.numGlyphs = nglyphs; nfo.callbackCalled = false; nfo.flags = flags; nfo.shaperItem = shaperItem; int prevNumGlyphs = *nglyphs; QVarLengthArray mappedFonts(len); for (int i = 0; i < len; ++i) mappedFonts[i] = 0; nfo.mappedFonts = mappedFonts.data(); Q_ASSERT(sizeof(void *) <= sizeof(URefCon)); e = ATSUSetTextLayoutRefCon(textLayout, (URefCon)&nfo); if (e != noErr) { qWarning("Qt: internal: %ld: Error ATSUSetTextLayoutRefCon %s: %d", long(e), __FILE__, __LINE__); return false; } { const int maxAttributeCount = 3; ATSUAttributeTag tags[maxAttributeCount + 1]; ByteCount sizes[maxAttributeCount + 1]; ATSUAttributeValuePtr values[maxAttributeCount + 1]; int attributeCount = 0; tags[attributeCount] = kATSULineLayoutOptionsTag; ATSLineLayoutOptions layopts = kATSLineHasNoOpticalAlignment | kATSLineIgnoreFontLeading | kATSLineNoSpecialJustification // we do kashidas ourselves | kATSLineDisableAllJustification ; layopts |= kATSLineUseDeviceMetrics; if (fontDef.styleStrategy & QFont::NoAntialias) layopts |= kATSLineNoAntiAliasing; if (!kerning) layopts |= kATSLineDisableAllKerningAdjustments; values[attributeCount] = &layopts; sizes[attributeCount] = sizeof(layopts); ++attributeCount; tags[attributeCount] = kATSULayoutOperationOverrideTag; ATSULayoutOperationOverrideSpecifier spec; spec.operationSelector = kATSULayoutOperationPostLayoutAdjustment; spec.overrideUPP = atsuPostLayoutCallback; values[attributeCount] = &spec; sizes[attributeCount] = sizeof(spec); ++attributeCount; // CTWritingDirection Boolean direction; if (flags & QTextEngine::RightToLeft) direction = kATSURightToLeftBaseDirection; else direction = kATSULeftToRightBaseDirection; tags[attributeCount] = kATSULineDirectionTag; values[attributeCount] = &direction; sizes[attributeCount] = sizeof(direction); ++attributeCount; Q_ASSERT(attributeCount < maxAttributeCount + 1); e = ATSUSetLayoutControls(textLayout, attributeCount, tags, sizes, values); if (e != noErr) { qWarning("Qt: internal: %ld: Error ATSUSetLayoutControls %s: %d", long(e), __FILE__, __LINE__); return false; } } e = ATSUSetRunStyle(textLayout, style, 0, len); if (e != noErr) { qWarning("Qt: internal: %ld: Error ATSUSetRunStyle %s: %d", long(e), __FILE__, __LINE__); return false; } if (!(fontDef.styleStrategy & QFont::NoFontMerging)) { int pos = 0; do { ATSUFontID substFont = 0; UniCharArrayOffset changedOffset = 0; UniCharCount changeCount = 0; e = ATSUMatchFontsToText(textLayout, pos, len - pos, &substFont, &changedOffset, &changeCount); if (e == kATSUFontsMatched) { int fontIdx = fontIndexForFontID(substFont); for (uint i = 0; i < changeCount; ++i) mappedFonts[changedOffset + i] = fontIdx; pos = changedOffset + changeCount; ATSUSetRunStyle(textLayout, engineAt(fontIdx)->style, changedOffset, changeCount); } else if (e == kATSUFontsNotMatched) { pos = changedOffset + changeCount; } } while (pos < len && e != noErr); } { // trigger the a layout // CFAttributedStringCreate, CTFramesetterCreateWithAttributedString (or perhaps Typesetter) Rect rect; e = ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning, kATSUToTextEnd, /*iLocationX =*/ 0, /*iLocationY =*/ 0, &rect); if (e != noErr) { qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage %s: %d", long(e), __FILE__, __LINE__); return false; } } if (!nfo.callbackCalled) { qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage did not trigger callback %s: %d", long(e), __FILE__, __LINE__); return false; } ATSUClearLayoutCache(textLayout, kATSUFromTextBeginning); if (prevNumGlyphs < *nfo.numGlyphs) return false; return true; } void QFontEngineMacMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const { Q_ASSERT(false); Q_UNUSED(glyphs); Q_UNUSED(flags); } void QFontEngineMacMulti::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const { //Q_ASSERT(false); } void QFontEngineMacMulti::loadEngine(int /*at*/) { // should never be called! Q_ASSERT(false); } bool QFontEngineMacMulti::canRender(const QChar *string, int len) { ATSUSetTextPointerLocation(textLayout, reinterpret_cast(string), 0, len, len); ATSUSetRunStyle(textLayout, style, 0, len); OSStatus e = noErr; int pos = 0; do { FMFont substFont = 0; UniCharArrayOffset changedOffset = 0; UniCharCount changeCount = 0; // CTFontCreateForString e = ATSUMatchFontsToText(textLayout, pos, len - pos, &substFont, &changedOffset, &changeCount); if (e == kATSUFontsMatched) { pos = changedOffset + changeCount; } else if (e == kATSUFontsNotMatched) { break; } } while (pos < len && e != noErr); return e == noErr || e == kATSUFontsMatched; } QFontEngineMac::QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine) : fontID(fontID), multiEngine(multiEngine), cmap(0), symbolCMap(false) { fontDef = def; ATSUCreateAndCopyStyle(baseStyle, &style); ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); cgFont = CGFontCreateWithPlatformFont(&atsFont); const int maxAttributeCount = 4; ATSUAttributeTag tags[maxAttributeCount + 1]; ByteCount sizes[maxAttributeCount + 1]; ATSUAttributeValuePtr values[maxAttributeCount + 1]; int attributeCount = 0; synthesisFlags = 0; // synthesizing using CG is not recommended quint16 macStyle = 0; { uchar data[4]; ByteCount len = 4; if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) macStyle = qFromBigEndian(data); } Boolean atsuBold = false; Boolean atsuItalic = false; if (fontDef.weight >= QFont::Bold) { if (!(macStyle & 1)) { synthesisFlags |= SynthesizedBold; atsuBold = true; tags[attributeCount] = kATSUQDBoldfaceTag; sizes[attributeCount] = sizeof(atsuBold); values[attributeCount] = &atsuBold; ++attributeCount; } } if (fontDef.style != QFont::StyleNormal) { if (!(macStyle & 2)) { synthesisFlags |= SynthesizedItalic; atsuItalic = true; tags[attributeCount] = kATSUQDItalicTag; sizes[attributeCount] = sizeof(atsuItalic); values[attributeCount] = &atsuItalic; ++attributeCount; } } tags[attributeCount] = kATSUFontTag; values[attributeCount] = &fontID; sizes[attributeCount] = sizeof(fontID); ++attributeCount; Q_ASSERT(attributeCount < maxAttributeCount + 1); OSStatus err = ATSUSetAttributes(style, attributeCount, tags, sizes, values); Q_ASSERT(err == noErr); Q_UNUSED(err); // CTFontCopyTable quint16 tmpFsType; if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 8, 2, &tmpFsType, 0) == noErr) fsType = qFromBigEndian(tmpFsType); else fsType = 0; if (multiEngine) transform = multiEngine->transform; else transform = CGAffineTransformIdentity; ATSUTextMeasurement metric; ATSUGetAttribute(style, kATSUAscentTag, sizeof(metric), &metric, 0); m_ascent = FixRound(metric); ATSUGetAttribute(style, kATSUDescentTag, sizeof(metric), &metric, 0); m_descent = FixRound(metric); ATSUGetAttribute(style, kATSULeadingTag, sizeof(metric), &metric, 0); m_leading = FixRound(metric); ATSFontMetrics metrics; ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); m_maxCharWidth = metrics.maxAdvanceWidth * fontDef.pointSize; ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); m_xHeight = QFixed::fromReal(metrics.xHeight * fontDef.pointSize); ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); m_averageCharWidth = QFixed::fromReal(metrics.avgAdvanceWidth * fontDef.pointSize); // Use width of 'X' if ATSFontGetHorizontalMetrics returns 0 for avgAdvanceWidth. if (m_averageCharWidth == QFixed(0)) { QChar c('X'); QGlyphLayoutArray<1> glyphs; int nglyphs = 1; stringToCMap(&c, 1, &glyphs, &nglyphs, 0); glyph_metrics_t metrics = boundingBox(glyphs); m_averageCharWidth = metrics.width; } } QFontEngineMac::~QFontEngineMac() { ATSUDisposeStyle(style); } static inline unsigned int getChar(const QChar *str, int &i, const int len) { unsigned int uc = str[i].unicode(); if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { uint low = str[i+1].unicode(); if (low >= 0xdc00 && low < 0xe000) { uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; ++i; } } return uc; } bool QFontEngineMac::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const { if (!cmap) { cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); int size = 0; cmap = getCMap(reinterpret_cast(cmapTable.constData()), cmapTable.size(), &symbolCMap, &size); if (!cmap) return false; } if (symbolCMap) { for (int i = 0; i < len; ++i) { unsigned int uc = getChar(str, i, len); glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); if(!glyphs->glyphs[i] && uc < 0x100) glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); } } else { for (int i = 0; i < len; ++i) { unsigned int uc = getChar(str, i, len); glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); } } *nglyphs = len; glyphs->numGlyphs = *nglyphs; if (!(flags & QTextEngine::GlyphIndicesOnly)) recalcAdvances(glyphs, flags); return true; } void QFontEngineMac::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const { Q_UNUSED(flags) QVarLengthArray atsuGlyphs(glyphs->numGlyphs); for (int i = 0; i < glyphs->numGlyphs; ++i) atsuGlyphs[i] = glyphs->glyphs[i]; QVarLengthArray metrics(glyphs->numGlyphs); ATSUGlyphGetScreenMetrics(style, glyphs->numGlyphs, atsuGlyphs.data(), sizeof(GlyphID), /* iForcingAntiAlias =*/ false, /* iAntiAliasSwitch =*/true, metrics.data()); for (int i = 0; i < glyphs->numGlyphs; ++i) { glyphs->advances_x[i] = QFixed::fromReal(metrics[i].deviceAdvance.x); glyphs->advances_y[i] = QFixed::fromReal(metrics[i].deviceAdvance.y); } } glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs) { QFixed w; for (int i = 0; i < glyphs.numGlyphs; ++i) w += glyphs.effectiveAdvance(i); return glyph_metrics_t(0, -(ascent()), w, ascent()+descent(), w, 0); } glyph_metrics_t QFontEngineMac::boundingBox(glyph_t glyph) { GlyphID atsuGlyph = glyph; ATSGlyphScreenMetrics metrics; ATSUGlyphGetScreenMetrics(style, 1, &atsuGlyph, 0, /* iForcingAntiAlias =*/ false, /* iAntiAliasSwitch =*/true, &metrics); // ### check again glyph_metrics_t gm; gm.width = int(metrics.width); gm.height = int(metrics.height); gm.x = QFixed::fromReal(metrics.topLeft.x); gm.y = -QFixed::fromReal(metrics.topLeft.y); gm.xoff = QFixed::fromReal(metrics.deviceAdvance.x); gm.yoff = QFixed::fromReal(metrics.deviceAdvance.y); return gm; } QFixed QFontEngineMac::ascent() const { return m_ascent; } QFixed QFontEngineMac::descent() const { // subtract a pixel to even out the historical +1 in QFontMetrics::height(). // Fix in Qt 5. return m_descent - 1; } QFixed QFontEngineMac::leading() const { return m_leading; } qreal QFontEngineMac::maxCharWidth() const { return m_maxCharWidth; } QFixed QFontEngineMac::xHeight() const { return m_xHeight; } QFixed QFontEngineMac::averageCharWidth() const { return m_averageCharWidth; } static void addGlyphsToPathHelper(ATSUStyle style, glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path) { if (!numGlyphs) return; OSStatus e; QMacFontPath fontpath(0, 0, path); ATSCubicMoveToUPP moveTo = NewATSCubicMoveToUPP(QMacFontPath::moveTo); ATSCubicLineToUPP lineTo = NewATSCubicLineToUPP(QMacFontPath::lineTo); ATSCubicCurveToUPP cubicTo = NewATSCubicCurveToUPP(QMacFontPath::cubicTo); ATSCubicClosePathUPP closePath = NewATSCubicClosePathUPP(QMacFontPath::closePath); // CTFontCreatePathForGlyph for (int i = 0; i < numGlyphs; ++i) { GlyphID glyph = glyphs[i]; fontpath.setPosition(positions[i].x.toReal(), positions[i].y.toReal()); ATSUGlyphGetCubicPaths(style, glyph, moveTo, lineTo, cubicTo, closePath, &fontpath, &e); } DisposeATSCubicMoveToUPP(moveTo); DisposeATSCubicLineToUPP(lineTo); DisposeATSCubicCurveToUPP(cubicTo); DisposeATSCubicClosePathUPP(closePath); } void QFontEngineMac::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path, QTextItem::RenderFlags) { addGlyphsToPathHelper(style, glyphs, positions, numGlyphs, path); } /*! Helper function for alphaMapForGlyph and alphaRGBMapForGlyph. The two are identical, except for the subpixel antialiasing... */ QImage QFontEngineMac::imageForGlyph(glyph_t glyph, int margin, bool colorful) { const glyph_metrics_t br = boundingBox(glyph); QImage im(qRound(br.width)+2, qRound(br.height)+4, QImage::Format_RGB32); im.fill(0xff000000); CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); uint cgflags = kCGImageAlphaNoneSkipFirst; #ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version cgflags |= kCGBitmapByteOrder32Host; #endif CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), 8, im.bytesPerLine(), colorspace, cgflags); CGContextSetFontSize(ctx, fontDef.pixelSize); CGContextSetShouldAntialias(ctx, fontDef.pointSize > qt_antialiasing_threshold && !(fontDef.styleStrategy & QFont::NoAntialias)); // turn off sub-pixel hinting - no support for that in OpenGL CGContextSetShouldSmoothFonts(ctx, colorful); CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); CGAffineTransformConcat(cgMatrix, oldTextMatrix); if (synthesisFlags & QFontEngine::SynthesizedItalic) cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, tanf(14 * acosf(0) / 90), 1, 0, 0)); cgMatrix = CGAffineTransformConcat(cgMatrix, transform); CGContextSetTextMatrix(ctx, cgMatrix); CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); CGContextSetTextDrawingMode(ctx, kCGTextFill); CGContextSetFont(ctx, cgFont); qreal pos_x = -br.x.toReal() + 1; qreal pos_y = im.height() + br.y.toReal() - 2; CGContextSetTextPosition(ctx, pos_x, pos_y); CGSize advance; advance.width = 0; advance.height = 0; CGGlyph cgGlyph = glyph; CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); if (synthesisFlags & QFontEngine::SynthesizedBold) { CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); } CGContextRelease(ctx); return im; } QImage QFontEngineMac::alphaMapForGlyph(glyph_t glyph) { QImage im = imageForGlyph(glyph, 2, false); QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); QVector colors(256); for (int i=0; i<256; ++i) colors[i] = qRgba(0, 0, 0, i); indexed.setColorTable(colors); for (int y=0; y= QTransform::TxScale) { im = im.transformed(t); } qmacfontengine_gamma_correct(&im); return im; } bool QFontEngineMac::canRender(const QChar *string, int len) { Q_ASSERT(false); Q_UNUSED(string); Q_UNUSED(len); return false; } void QFontEngineMac::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) { QVarLengthArray positions; QVarLengthArray glyphs; QTransform matrix; matrix.translate(x, y); getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); if (glyphs.size() == 0) return; CGContextSetFontSize(ctx, fontDef.pixelSize); CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); CGAffineTransformConcat(cgMatrix, oldTextMatrix); if (synthesisFlags & QFontEngine::SynthesizedItalic) cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); cgMatrix = CGAffineTransformConcat(cgMatrix, transform); CGContextSetTextMatrix(ctx, cgMatrix); CGContextSetTextDrawingMode(ctx, kCGTextFill); QVarLengthArray advances(glyphs.size()); QVarLengthArray cgGlyphs(glyphs.size()); for (int i = 0; i < glyphs.size() - 1; ++i) { advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); cgGlyphs[i] = glyphs[i]; } advances[glyphs.size() - 1].width = 0; advances[glyphs.size() - 1].height = 0; cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; CGContextSetFont(ctx, cgFont); CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); if (synthesisFlags & QFontEngine::SynthesizedBold) { CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), positions[0].y.toReal()); CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); } CGContextSetTextMatrix(ctx, oldTextMatrix); } QFontEngine::FaceId QFontEngineMac::faceId() const { FaceId ret; #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { // CTFontGetPlatformFont FSRef ref; if (ATSFontGetFileReference(FMGetATSFontRefFromFont(fontID), &ref) != noErr) return ret; ret.filename = QByteArray(128, 0); ret.index = fontID; FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); }else #endif { FSSpec spec; if (ATSFontGetFileSpecification(FMGetATSFontRefFromFont(fontID), &spec) != noErr) return ret; FSRef ref; FSpMakeFSRef(&spec, &ref); ret.filename = QByteArray(128, 0); ret.index = fontID; FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); } return ret; } QByteArray QFontEngineMac::getSfntTable(uint tag) const { ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); ByteCount length; OSStatus status = ATSFontGetTable(atsFont, tag, 0, 0, 0, &length); if (status != noErr) return QByteArray(); QByteArray table(length, 0); // CTFontCopyTable status = ATSFontGetTable(atsFont, tag, 0, table.length(), table.data(), &length); if (status != noErr) return QByteArray(); return table; } QFontEngine::Properties QFontEngineMac::properties() const { QFontEngine::Properties props; ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); quint16 tmp; // CTFontGetUnitsPerEm if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 18, 2, &tmp, 0) == noErr) props.emSquare = qFromBigEndian(tmp); struct { qint16 xMin; qint16 yMin; qint16 xMax; qint16 yMax; } bbox; bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; // CTFontGetBoundingBox if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 36, 8, &bbox, 0) == noErr) { bbox.xMin = qFromBigEndian(bbox.xMin); bbox.yMin = qFromBigEndian(bbox.yMin); bbox.xMax = qFromBigEndian(bbox.xMax); bbox.yMax = qFromBigEndian(bbox.yMax); } struct { qint16 ascender; qint16 descender; qint16 linegap; } metrics; metrics.ascender = metrics.descender = metrics.linegap = 0; // CTFontGetAscent, etc. if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'h', 'e', 'a'), 4, 6, &metrics, 0) == noErr) { metrics.ascender = qFromBigEndian(metrics.ascender); metrics.descender = qFromBigEndian(metrics.descender); metrics.linegap = qFromBigEndian(metrics.linegap); } props.ascent = metrics.ascender; props.descent = -metrics.descender; props.leading = metrics.linegap; props.boundingBox = QRectF(bbox.xMin, -bbox.yMax, bbox.xMax - bbox.xMin, bbox.yMax - bbox.yMin); props.italicAngle = 0; props.capHeight = props.ascent; qint16 lw = 0; // fonts lie if (ATSFontGetTable(atsFont, MAKE_TAG('p', 'o', 's', 't'), 10, 2, &lw, 0) == noErr) lw = qFromBigEndian(lw); props.lineWidth = lw; // CTFontCopyPostScriptName QCFString psName; if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr) props.postscriptName = QString(psName).toUtf8(); props.postscriptName = QPdf::stripSpecialCharacters(props.postscriptName); return props; } void QFontEngineMac::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) { ATSUStyle unscaledStyle; ATSUCreateAndCopyStyle(style, &unscaledStyle); int emSquare = properties().emSquare.toInt(); const int maxAttributeCount = 4; ATSUAttributeTag tags[maxAttributeCount + 1]; ByteCount sizes[maxAttributeCount + 1]; ATSUAttributeValuePtr values[maxAttributeCount + 1]; int attributeCount = 0; Fixed size = FixRatio(emSquare, 1); tags[attributeCount] = kATSUSizeTag; sizes[attributeCount] = sizeof(size); values[attributeCount] = &size; ++attributeCount; Q_ASSERT(attributeCount < maxAttributeCount + 1); OSStatus err = ATSUSetAttributes(unscaledStyle, attributeCount, tags, sizes, values); Q_ASSERT(err == noErr); Q_UNUSED(err); // various CTFont metrics functions: CTFontGetBoundingRectsForGlyphs, CTFontGetAdvancesForGlyphs GlyphID atsuGlyph = glyph; ATSGlyphScreenMetrics atsuMetrics; ATSUGlyphGetScreenMetrics(unscaledStyle, 1, &atsuGlyph, 0, /* iForcingAntiAlias =*/ false, /* iAntiAliasSwitch =*/true, &atsuMetrics); metrics->width = int(atsuMetrics.width); metrics->height = int(atsuMetrics.height); metrics->x = QFixed::fromReal(atsuMetrics.topLeft.x); metrics->y = -QFixed::fromReal(atsuMetrics.topLeft.y); metrics->xoff = QFixed::fromReal(atsuMetrics.deviceAdvance.x); metrics->yoff = QFixed::fromReal(atsuMetrics.deviceAdvance.y); QFixedPoint p; addGlyphsToPathHelper(unscaledStyle, &glyph, &p, 1, path); ATSUDisposeStyle(unscaledStyle); } #endif // !QT_MAC_USE_COCOA QT_END_NAMESPACE