mirror of
https://bitbucket.org/smil3y/katie.git
synced 2025-02-26 20:03:13 +00:00
1229 lines
42 KiB
Text
1229 lines
42 KiB
Text
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2015 The Qt Company Ltd.
|
|
** Contact: http://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the QtGui module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://www.qt.io/contact-us.
|
|
**
|
|
** 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 or version 3 as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
** following information to ensure the GNU Lesser General Public License
|
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** As a special exception, The Qt Company gives you certain additional
|
|
** rights. These rights are described in The Qt Company LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 3.0 requirements will be
|
|
** met: http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qfontengine_mac_p.h"
|
|
|
|
#include <qapplication_p.h>
|
|
#include <qfontengine_p.h>
|
|
#include <qpainter_p.h>
|
|
#include <qtextengine_p.h>
|
|
#include <qbitmap.h>
|
|
#include <qpaintengine_mac_p.h>
|
|
#include <qprintengine_mac_p.h>
|
|
#include <qglobal.h>
|
|
#include <qpixmap.h>
|
|
#include <qpixmapcache.h>
|
|
#include <qvarlengtharray.h>
|
|
#include <qdebug.h>
|
|
#include <qendian.h>
|
|
#include <qmath.h>
|
|
#include <qimage_p.h>
|
|
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
#include <AppKit/AppKit.h>
|
|
|
|
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<QMacFontPath*>(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<QMacFontPath*>(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<QMacFontPath*>(data);
|
|
p->path->moveTo(p->x + pt->x, p->y + pt->y);
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus QMacFontPath::closePath(void *data)
|
|
{
|
|
static_cast<QMacFontPath*>(data)->path->closeSubpath();
|
|
return noErr;
|
|
}
|
|
|
|
|
|
#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, <CTStringAttributes.h>
|
|
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<QFontEngineMac *>(static_cast<const QFontEngineMac *>(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;
|
|
unsigned int styleStrategy;
|
|
};
|
|
|
|
static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon,
|
|
void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus)
|
|
{
|
|
Q_UNUSED(selector);
|
|
Q_UNUSED(operationExtraParameter);
|
|
|
|
QGlyphLayoutInfo *nfo = reinterpret_cast<QGlyphLayoutInfo *>(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].isHighSurrogate() && str[i+1].isLowSurrogate());
|
|
#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 (nfo->styleStrategy & QFont::ForceIntegerMetrics) {
|
|
yAdvance = yAdvance.round();
|
|
xAdvance = xAdvance.round();
|
|
}
|
|
|
|
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.isHighSurrogate() && next.isLowSurrogate())
|
|
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<QFontEngineMacMulti *>(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, /*si=*/0);
|
|
}
|
|
|
|
bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags,
|
|
unsigned short *logClusters, const HB_CharAttributes *charAttributes, QScriptItem *) 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;
|
|
nfo.styleStrategy = fontDef.styleStrategy;
|
|
|
|
int prevNumGlyphs = *nglyphs;
|
|
|
|
QVarLengthArray<int> 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
|
|
;
|
|
|
|
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<const UniChar *>(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<quint16>(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<quint16>(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)
|
|
{
|
|
uint ucs4 = str[i].unicode();
|
|
if (str[i].isHighSurrogate() && i < len-1 && str[i+1].isLowSurrogate()) {
|
|
++i;
|
|
ucs4 = QChar::surrogateToUcs4(ucs4, str[i].unicode());
|
|
}
|
|
return ucs4;
|
|
}
|
|
|
|
// Not used directly for shaping, only used to calculate m_averageCharWidth
|
|
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<const uchar *>(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<GlyphID> atsuGlyphs(glyphs->numGlyphs);
|
|
for (int i = 0; i < glyphs->numGlyphs; ++i)
|
|
atsuGlyphs[i] = glyphs->glyphs[i];
|
|
|
|
QVarLengthArray<ATSGlyphScreenMetrics> 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);
|
|
|
|
if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
|
|
glyphs->advances_x[i] = glyphs->advances_x[i].round();
|
|
glyphs->advances_y[i] = glyphs->advances_y[i].round();
|
|
}
|
|
}
|
|
}
|
|
|
|
glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs)
|
|
{
|
|
QFixed w;
|
|
bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics;
|
|
for (int i = 0; i < glyphs.numGlyphs; ++i) {
|
|
w += round ? glyphs.effectiveAdvance(i).round()
|
|
: glyphs.effectiveAdvance(i);
|
|
}
|
|
return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), 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);
|
|
|
|
if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
|
|
gm.x = gm.x.floor();
|
|
gm.y = gm.y.floor();
|
|
gm.xoff = gm.xoff.round();
|
|
gm.yoff = gm.yoff.round();
|
|
}
|
|
|
|
return gm;
|
|
}
|
|
|
|
QFixed QFontEngineMac::ascent() const
|
|
{
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? m_ascent.round()
|
|
: m_ascent;
|
|
}
|
|
|
|
QFixed QFontEngineMac::descent() const
|
|
{
|
|
// subtract a pixel to even out the historical +1 in QFontMetrics::height().
|
|
// Fix in Qt 5.
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? m_descent.round() - 1
|
|
: m_descent;
|
|
}
|
|
|
|
QFixed QFontEngineMac::leading() const
|
|
{
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? m_leading.round()
|
|
: m_leading;
|
|
}
|
|
|
|
qreal QFontEngineMac::maxCharWidth() const
|
|
{
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? qRound(m_maxCharWidth)
|
|
: m_maxCharWidth;
|
|
}
|
|
|
|
QFixed QFontEngineMac::xHeight() const
|
|
{
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? m_xHeight.round()
|
|
: m_xHeight;
|
|
}
|
|
|
|
QFixed QFontEngineMac::averageCharWidth() const
|
|
{
|
|
return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
|
|
? m_averageCharWidth.round()
|
|
: 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<QRgb> colors(256);
|
|
for (int i=0; i<256; ++i)
|
|
colors[i] = qRgba(0, 0, 0, i);
|
|
indexed.setColorTable(colors);
|
|
|
|
for (int y=0; y<im.height(); ++y) {
|
|
uint *src = (uint*) im.scanLine(y);
|
|
uchar *dst = indexed.scanLine(y);
|
|
for (int x=0; x<im.width(); ++x) {
|
|
*dst = qGray(*src);
|
|
++dst;
|
|
++src;
|
|
}
|
|
}
|
|
|
|
return indexed;
|
|
}
|
|
|
|
QImage QFontEngineMac::alphaRGBMapForGlyph(glyph_t glyph, QFixed, int margin, const QTransform &t)
|
|
{
|
|
QImage im = imageForGlyph(glyph, margin, true);
|
|
|
|
if (t.type() >= QTransform::TxScale) {
|
|
im = im.transformed(t);
|
|
}
|
|
|
|
qGamma_correct_back_to_linear_cs(&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<QFixedPoint> positions;
|
|
QVarLengthArray<glyph_t> 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<CGSize> advances(glyphs.size());
|
|
QVarLengthArray<CGGlyph> 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<quint16>(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<quint16>(bbox.xMin);
|
|
bbox.yMin = qFromBigEndian<quint16>(bbox.yMin);
|
|
bbox.xMax = qFromBigEndian<quint16>(bbox.xMax);
|
|
bbox.yMax = qFromBigEndian<quint16>(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<quint16>(metrics.ascender);
|
|
metrics.descender = qFromBigEndian<quint16>(metrics.descender);
|
|
metrics.linegap = qFromBigEndian<quint16>(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<quint16>(lw);
|
|
props.lineWidth = lw;
|
|
|
|
// CTFontCopyPostScriptName
|
|
QCFString psName;
|
|
if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr)
|
|
props.postscriptName = QString(psName).toUtf8();
|
|
props.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(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
|