1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qfontengine_coretext_p.h"
41
42#include <qpa/qplatformfontdatabase.h>
43#include <QtCore/qendian.h>
44#if QT_CONFIG(settings)
45#include <QtCore/qsettings.h>
46#endif
47#include <QtCore/qoperatingsystemversion.h>
48#include <QtGui/qpainterpath.h>
49#include <private/qcoregraphics_p.h>
50#include <private/qimage_p.h>
51
52#include <cmath>
53
54#if defined(Q_OS_MACOS)
55#import <AppKit/AppKit.h>
56#endif
57
58#if defined(QT_PLATFORM_UIKIT)
59#import <UIKit/UIKit.h>
60#endif
61
62// These are available cross platform, exported as kCTFontWeightXXX from CoreText.framework,
63// but they are not documented and are not in public headers so are private API and exposed
64// only through the NSFontWeightXXX and UIFontWeightXXX aliases in AppKit and UIKit (rdar://26109857)
65#if defined(Q_OS_MACOS)
66#define kCTFontWeightUltraLight NSFontWeightUltraLight
67#define kCTFontWeightThin NSFontWeightThin
68#define kCTFontWeightLight NSFontWeightLight
69#define kCTFontWeightRegular NSFontWeightRegular
70#define kCTFontWeightMedium NSFontWeightMedium
71#define kCTFontWeightSemibold NSFontWeightSemibold
72#define kCTFontWeightBold NSFontWeightBold
73#define kCTFontWeightHeavy NSFontWeightHeavy
74#define kCTFontWeightBlack NSFontWeightBlack
75#elif defined(QT_PLATFORM_UIKIT)
76#define kCTFontWeightUltraLight UIFontWeightUltraLight
77#define kCTFontWeightThin UIFontWeightThin
78#define kCTFontWeightLight UIFontWeightLight
79#define kCTFontWeightRegular UIFontWeightRegular
80#define kCTFontWeightMedium UIFontWeightMedium
81#define kCTFontWeightSemibold UIFontWeightSemibold
82#define kCTFontWeightBold UIFontWeightBold
83#define kCTFontWeightHeavy UIFontWeightHeavy
84#define kCTFontWeightBlack UIFontWeightBlack
85#endif
86
87QT_BEGIN_NAMESPACE
88
89Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
90
91static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);
92
93bool QCoreTextFontEngine::ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
94{
95    CTFontRef ctfont = *(CTFontRef *)user_data;
96
97    QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0);
98    if (!table)
99        return false;
100
101    CFIndex tableLength = CFDataGetLength(table);
102    if (buffer && int(*length) >= tableLength)
103        CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer);
104    *length = tableLength;
105    Q_ASSERT(int(*length) > 0);
106    return true;
107}
108
109QFont::Weight QCoreTextFontEngine::qtWeightFromCFWeight(float value)
110{
111#define COMPARE_WEIGHT_DISTANCE(ct_weight, qt_weight) \
112    { \
113        float d; \
114        if ((d = qAbs(value - ct_weight)) < distance) { \
115            distance = d; \
116            ret = qt_weight; \
117        } \
118    }
119
120    float distance = qAbs(value - kCTFontWeightBlack);
121    QFont::Weight ret = QFont::Black;
122
123    // Compare distance to system weight to find the closest match.
124    // (Note: Must go from high to low, so that midpoints are rounded up)
125    COMPARE_WEIGHT_DISTANCE(kCTFontWeightHeavy, QFont::ExtraBold);
126    COMPARE_WEIGHT_DISTANCE(kCTFontWeightBold, QFont::Bold);
127    COMPARE_WEIGHT_DISTANCE(kCTFontWeightSemibold, QFont::DemiBold);
128    COMPARE_WEIGHT_DISTANCE(kCTFontWeightMedium, QFont::Medium);
129    COMPARE_WEIGHT_DISTANCE(kCTFontWeightRegular, QFont::Normal);
130    COMPARE_WEIGHT_DISTANCE(kCTFontWeightLight, QFont::Light);
131    COMPARE_WEIGHT_DISTANCE(kCTFontWeightThin, QFont::ExtraLight);
132    COMPARE_WEIGHT_DISTANCE(kCTFontWeightUltraLight, QFont::Thin);
133
134#undef COMPARE_WEIGHT_DISTANCE
135
136    return ret;
137}
138
139CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
140{
141    CGAffineTransform transform = CGAffineTransformIdentity;
142    if (fontDef.stretch && fontDef.stretch != 100)
143        transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1);
144    return transform;
145}
146
147// Keeps font data alive until engine is disposed
148class QCoreTextRawFontEngine : public QCoreTextFontEngine
149{
150public:
151    QCoreTextRawFontEngine(CGFontRef font, const QFontDef &def, const QByteArray &fontData)
152        : QCoreTextFontEngine(font, def)
153        , m_fontData(fontData)
154    {}
155    QFontEngine *cloneWithSize(qreal pixelSize) const
156    {
157        QFontDef newFontDef = fontDef;
158        newFontDef.pixelSize = pixelSize;
159        newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
160
161        return new QCoreTextRawFontEngine(cgFont, newFontDef, m_fontData);
162    }
163    QByteArray m_fontData;
164};
165
166QCoreTextFontEngine *QCoreTextFontEngine::create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
167{
168    Q_UNUSED(hintingPreference);
169
170    QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
171    QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithCFData(fontDataReference);
172
173    // Note: CTFontCreateWithGraphicsFont (which we call from the  QCoreTextFontEngine
174    // constructor) has a bug causing it to retain the CGFontRef but never release it.
175    // The result is that we are leaking the CGFont, CGDataProvider, and CGData, but
176    // as the CGData is created from the raw QByteArray data, which we deref in the
177    // subclass above during destruction, we're at least not leaking the font data,
178    // (unless CoreText copies it internally). http://stackoverflow.com/questions/40805382/
179    QCFType<CGFontRef> cgFont = CGFontCreateWithDataProvider(dataProvider);
180
181    if (!cgFont) {
182        qWarning("QCoreTextFontEngine::create: CGFontCreateWithDataProvider failed");
183        return nullptr;
184    }
185
186    QFontDef def;
187    def.pixelSize = pixelSize;
188    def.pointSize = pixelSize * 72.0 / qt_defaultDpi();
189    return new QCoreTextRawFontEngine(cgFont, def, fontData);
190}
191
192QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
193    : QCoreTextFontEngine(def)
194{
195    ctfont = QCFType<CTFontRef>::constructFromGet(font);
196    cgFont = CTFontCopyGraphicsFont(font, nullptr);
197    init();
198}
199
200QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def)
201    : QCoreTextFontEngine(def)
202{
203    cgFont = QCFType<CGFontRef>::constructFromGet(font);
204    ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, nullptr);
205    init();
206}
207
208QCoreTextFontEngine::QCoreTextFontEngine(const QFontDef &def)
209    : QFontEngine(Mac)
210{
211    fontDef = def;
212    transform = qt_transform_from_fontdef(fontDef);
213}
214
215QCoreTextFontEngine::~QCoreTextFontEngine()
216{
217}
218
219void QCoreTextFontEngine::init()
220{
221    Q_ASSERT(ctfont);
222    Q_ASSERT(cgFont);
223
224    face_id.index = 0;
225    QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
226    face_id.filename = QString::fromCFString(name).toUtf8();
227
228    QCFString family = CTFontCopyFamilyName(ctfont);
229    fontDef.family = family;
230
231    QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
232    fontDef.styleName = styleName;
233
234    synthesisFlags = 0;
235    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
236
237    if (traits & kCTFontColorGlyphsTrait)
238        glyphFormat = QFontEngine::Format_ARGB;
239    else if (shouldSmoothFont() && fontSmoothing() == FontSmoothing::Subpixel)
240        glyphFormat = QFontEngine::Format_A32;
241    else
242        glyphFormat = QFontEngine::Format_A8;
243
244    if (traits & kCTFontItalicTrait)
245        fontDef.style = QFont::StyleItalic;
246
247    static const auto getTraitValue = [](CFDictionaryRef allTraits, CFStringRef trait) -> float {
248        if (CFDictionaryContainsKey(allTraits, trait)) {
249            CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait);
250            float v = 0;
251            CFNumberGetValue(traitNum, kCFNumberFloatType, &v);
252            return v;
253        }
254        return 0;
255    };
256
257    QCFType<CFDictionaryRef> allTraits = CTFontCopyTraits(ctfont);
258    fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));
259    int slant = static_cast<int>(getTraitValue(allTraits, kCTFontSlantTrait) * 500 + 500);
260    if (slant > 500 && !(traits & kCTFontItalicTrait))
261        fontDef.style = QFont::StyleOblique;
262
263    if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD"))
264        synthesisFlags |= SynthesizedBold;
265    // XXX: we probably don't need to synthesis italic for oblique font
266    if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"))
267        synthesisFlags |= SynthesizedItalic;
268
269    avgCharWidth = 0;
270    QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
271    unsigned emSize = CTFontGetUnitsPerEm(ctfont);
272    if (os2Table.size() >= 10) {
273        fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
274        // qAbs is a workaround for weird fonts like Lucida Grande
275        qint16 width = qAbs(qFromBigEndian<qint16>(os2Table.constData() + 2));
276        avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize);
277    } else
278        avgCharWidth = QFontEngine::averageCharWidth();
279
280    underlineThickness = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont));
281    underlinePos = -QFixed::fromReal(CTFontGetUnderlinePosition(ctfont));
282
283    cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;
284
285    // HACK hb_coretext requires both CTFont and CGFont but user_data is only void*
286    Q_ASSERT((void *)(&ctfont + 1) == (void *)&cgFont);
287    faceData.user_data = &ctfont;
288    faceData.get_font_table = ct_getSfntTable;
289
290    kerningPairsLoaded = false;
291}
292
293glyph_t QCoreTextFontEngine::glyphIndex(uint ucs4) const
294{
295    int len = 0;
296
297    QChar str[2];
298    if (Q_UNLIKELY(QChar::requiresSurrogates(ucs4))) {
299        str[len++] = QChar(QChar::highSurrogate(ucs4));
300        str[len++] = QChar(QChar::lowSurrogate(ucs4));
301    } else {
302        str[len++] = QChar(ucs4);
303    }
304
305    CGGlyph glyphIndices[2];
306
307    CTFontGetGlyphsForCharacters(ctfont, (const UniChar *)str, glyphIndices, len);
308
309    return glyphIndices[0];
310}
311
312bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
313                                       int *nglyphs, QFontEngine::ShaperFlags flags) const
314{
315    Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
316    if (*nglyphs < len) {
317        *nglyphs = len;
318        return false;
319    }
320
321    QVarLengthArray<CGGlyph> cgGlyphs(len);
322    CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);
323
324    int glyph_pos = 0;
325    for (int i = 0; i < len; ++i) {
326        glyphs->glyphs[glyph_pos] = cgGlyphs[i];
327        if (glyph_pos < i)
328            cgGlyphs[glyph_pos] = cgGlyphs[i];
329        glyph_pos++;
330
331        // If it's a non-BMP char, skip the lower part of surrogate pair and go
332        // directly to the next char without increasing glyph_pos
333        if (str[i].isHighSurrogate() && i < len-1 && str[i+1].isLowSurrogate())
334            ++i;
335    }
336
337    *nglyphs = glyph_pos;
338    glyphs->numGlyphs = glyph_pos;
339
340    if (!(flags & GlyphIndicesOnly))
341        loadAdvancesForGlyphs(cgGlyphs, glyphs);
342
343    return true;
344}
345
346glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs)
347{
348    QFixed w;
349QT_WARNING_PUSH
350QT_WARNING_DISABLE_DEPRECATED
351    bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics;
352QT_WARNING_POP
353
354    for (int i = 0; i < glyphs.numGlyphs; ++i) {
355        w += round ? glyphs.effectiveAdvance(i).round()
356                   : glyphs.effectiveAdvance(i);
357    }
358    return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0);
359}
360
361glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph)
362{
363    glyph_metrics_t ret;
364    CGGlyph g = glyph;
365    CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, 0, 1);
366    if (synthesisFlags & QFontEngine::SynthesizedItalic) {
367        rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW;
368    }
369    ret.width = QFixed::fromReal(rect.size.width);
370    ret.height = QFixed::fromReal(rect.size.height);
371    ret.x = QFixed::fromReal(rect.origin.x);
372    ret.y = -QFixed::fromReal(rect.origin.y) - ret.height;
373    CGSize advances[1];
374    CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, advances, 1);
375    ret.xoff = QFixed::fromReal(advances[0].width);
376    ret.yoff = QFixed::fromReal(advances[0].height);
377
378QT_WARNING_PUSH
379QT_WARNING_DISABLE_DEPRECATED
380    if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
381QT_WARNING_POP
382        ret.xoff = ret.xoff.round();
383        ret.yoff = ret.yoff.round();
384    }
385
386    return ret;
387}
388
389QFixed QCoreTextFontEngine::ascent() const
390{
391QT_WARNING_PUSH
392QT_WARNING_DISABLE_DEPRECATED
393    return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
394            ? QFixed::fromReal(CTFontGetAscent(ctfont)).round()
395            : QFixed::fromReal(CTFontGetAscent(ctfont));
396QT_WARNING_POP
397}
398
399QFixed QCoreTextFontEngine::capHeight() const
400{
401    QFixed c = QFixed::fromReal(CTFontGetCapHeight(ctfont));
402    if (c <= 0)
403        return calculatedCapHeight();
404
405QT_WARNING_PUSH
406QT_WARNING_DISABLE_DEPRECATED
407    if (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
408QT_WARNING_POP
409        c = c.round();
410
411    return c;
412}
413
414QFixed QCoreTextFontEngine::descent() const
415{
416    QFixed d = QFixed::fromReal(CTFontGetDescent(ctfont));
417QT_WARNING_PUSH
418QT_WARNING_DISABLE_DEPRECATED
419    if (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
420QT_WARNING_POP
421        d = d.round();
422
423    return d;
424}
425QFixed QCoreTextFontEngine::leading() const
426{
427QT_WARNING_PUSH
428QT_WARNING_DISABLE_DEPRECATED
429    return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
430            ? QFixed::fromReal(CTFontGetLeading(ctfont)).round()
431            : QFixed::fromReal(CTFontGetLeading(ctfont));
432QT_WARNING_POP
433}
434QFixed QCoreTextFontEngine::xHeight() const
435{
436QT_WARNING_PUSH
437QT_WARNING_DISABLE_DEPRECATED
438    return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
439            ? QFixed::fromReal(CTFontGetXHeight(ctfont)).round()
440            : QFixed::fromReal(CTFontGetXHeight(ctfont));
441QT_WARNING_POP
442}
443
444QFixed QCoreTextFontEngine::averageCharWidth() const
445{
446QT_WARNING_PUSH
447QT_WARNING_DISABLE_DEPRECATED
448    return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
449            ? avgCharWidth.round() : avgCharWidth;
450QT_WARNING_POP
451}
452
453qreal QCoreTextFontEngine::maxCharWidth() const
454{
455    // ### FIXME: 'W' might not be the widest character, but this is better than nothing
456    const glyph_t glyph = glyphIndex('W');
457    glyph_metrics_t bb = const_cast<QCoreTextFontEngine *>(this)->boundingBox(glyph);
458    return bb.xoff.toReal();
459}
460
461bool QCoreTextFontEngine::hasColorGlyphs() const
462{
463    return glyphFormat == QFontEngine::Format_ARGB;
464}
465
466void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
467{
468    QVarLengthArray<QFixedPoint> positions;
469    QVarLengthArray<glyph_t> glyphs;
470    QTransform matrix;
471    matrix.translate(x, y);
472    getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
473    if (glyphs.size() == 0)
474        return;
475
476    CGContextSetFontSize(ctx, fontDef.pixelSize);
477
478    CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx);
479
480    CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight);
481
482    CGAffineTransformConcat(cgMatrix, oldTextMatrix);
483
484    if (synthesisFlags & QFontEngine::SynthesizedItalic)
485        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
486
487    cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
488
489    CGContextSetTextMatrix(ctx, cgMatrix);
490
491    CGContextSetTextDrawingMode(ctx, kCGTextFill);
492
493    QVarLengthArray<CGPoint> cgPositions(glyphs.size());
494    QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size());
495    const qreal firstX = positions[0].x.toReal();
496    const qreal firstY = positions[0].y.toReal();
497    for (int i = 0; i < glyphs.size(); ++i) {
498        cgPositions[i].x = positions[i].x.toReal() - firstX;
499        cgPositions[i].y = firstY - positions[i].y.toReal();
500        cgGlyphs[i] = glyphs[i];
501    }
502
503    //NSLog(@"Font inDraw %@  ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont));
504
505    CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal());
506    CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
507
508    if (synthesisFlags & QFontEngine::SynthesizedBold) {
509        CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(),
510                                 positions[0].y.toReal());
511        CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
512    }
513
514    CGContextSetTextMatrix(ctx, oldTextMatrix);
515}
516
517struct ConvertPathInfo
518{
519    ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos, qreal newStretch = 1.0) :
520        path(newPath), pos(newPos), stretch(newStretch) {}
521    QPainterPath *path;
522    QPointF pos;
523    qreal stretch;
524};
525
526static void convertCGPathToQPainterPath(void *info, const CGPathElement *element)
527{
528    ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info);
529    switch(element->type) {
530        case kCGPathElementMoveToPoint:
531            myInfo->path->moveTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
532                                 element->points[0].y + myInfo->pos.y());
533            break;
534        case kCGPathElementAddLineToPoint:
535            myInfo->path->lineTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
536                                 element->points[0].y + myInfo->pos.y());
537            break;
538        case kCGPathElementAddQuadCurveToPoint:
539            myInfo->path->quadTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
540                                 element->points[0].y + myInfo->pos.y(),
541                                 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
542                                 element->points[1].y + myInfo->pos.y());
543            break;
544        case kCGPathElementAddCurveToPoint:
545            myInfo->path->cubicTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
546                                  element->points[0].y + myInfo->pos.y(),
547                                  (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
548                                  element->points[1].y + myInfo->pos.y(),
549                                  (element->points[2].x * myInfo->stretch) + myInfo->pos.x(),
550                                  element->points[2].y + myInfo->pos.y());
551            break;
552        case kCGPathElementCloseSubpath:
553            myInfo->path->closeSubpath();
554            break;
555        default:
556            qCWarning(lcQpaFonts) << "Unhandled path transform type: " << element->type;
557    }
558
559}
560
561void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs,
562                                          QPainterPath *path, QTextItem::RenderFlags)
563{
564    if (hasColorGlyphs())
565        return; // We can't convert color-glyphs to path
566
567    CGAffineTransform cgMatrix = CGAffineTransformIdentity;
568    cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);
569
570    if (synthesisFlags & QFontEngine::SynthesizedItalic)
571        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
572
573    qreal stretch = fontDef.stretch ? qreal(fontDef.stretch) / 100 : 1.0;
574    for (int i = 0; i < nGlyphs; ++i) {
575        QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
576        ConvertPathInfo info(path, positions[i].toPointF(), stretch);
577        CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
578    }
579}
580
581static void qcoretextfontengine_scaleMetrics(glyph_metrics_t &br, const QTransform &matrix)
582{
583    if (matrix.isScaling()) {
584        qreal hscale = matrix.m11();
585        qreal vscale = matrix.m22();
586        br.width  = QFixed::fromReal(br.width.toReal() * hscale);
587        br.height = QFixed::fromReal(br.height.toReal() * vscale);
588        br.x      = QFixed::fromReal(br.x.toReal() * hscale);
589        br.y      = QFixed::fromReal(br.y.toReal() * vscale);
590    }
591}
592
593glyph_metrics_t QCoreTextFontEngine::alphaMapBoundingBox(glyph_t glyph, QFixed subPixelPosition, const QTransform &matrix, GlyphFormat format)
594{
595    if (matrix.type() > QTransform::TxScale)
596        return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);
597
598    glyph_metrics_t br = boundingBox(glyph);
599    qcoretextfontengine_scaleMetrics(br, matrix);
600
601    // Normalize width and height
602    if (br.width < 0)
603        br.width = -br.width;
604    if (br.height < 0)
605        br.height = -br.height;
606
607    if (format == QFontEngine::Format_A8 || format == QFontEngine::Format_A32) {
608        // Drawing a glyph at x-position 0 with anti-aliasing enabled
609        // will potentially fill the pixel to the left of 0, as the
610        // coordinates are not aligned to the center of pixels. To
611        // prevent clipping of this pixel we need to shift the glyph
612        // in the bitmap one pixel to the right. The shift needs to
613        // be reflected in the glyph metrics as well, so that the final
614        // position of the glyph is correct, which is why doing the
615        // shift in imageForGlyph() is not enough.
616        br.x -= 1;
617
618        // As we've shifted the glyph one pixel to the right, we need
619        // to expand the width of the alpha map bounding box as well.
620        br.width += 1;
621
622        // But we have the same anti-aliasing problem on the right
623        // hand side of the glyph, eg. if the width of the glyph
624        // results in the bounding rect landing between two pixels.
625        // We pad the bounding rect again to account for the possible
626        // anti-aliased drawing.
627        br.width += 1;
628
629        // We also shift the glyph to right right based on the subpixel
630        // position, so we pad the bounding box to take account for the
631        // subpixel positions that may result in the glyph being drawn
632        // one pixel to the right of the 0-subpixel position.
633        br.width += 1;
634
635        // The same same logic as for the x-position needs to be applied
636        // to the y-position, except we don't need to compensate for
637        // the subpixel positioning.
638        br.y -= 1;
639        br.height += 2;
640    }
641
642    return br;
643}
644
645/*
646    Apple has gone through many iterations of its font smoothing algorithms,
647    and there are many ways to enable or disable certain aspects of it. As
648    keeping up with all the different toggles and behavior differences between
649    macOS versions is tricky, we resort to rendering a single glyph in a few
650    configurations, picking up the font smoothing algorithm from the observed
651    result.
652
653    The possible values are:
654
655     - Disabled: No font smoothing is applied.
656
657       Possibly triggered by the user unchecking the "Use font smoothing when
658       available" checkbox in the system preferences or setting AppleFontSmoothing
659       to 0. Also controlled by the CGContextSetAllowsFontSmoothing() API,
660       which gets its default from the settings above. This API overrides
661       the more granular CGContextSetShouldSmoothFonts(), which we use to
662       enable (request) or disable font smoothing.
663
664       Note that this does not exclude normal antialiasing, controlled by
665       the CGContextSetShouldAntialias() API.
666
667     - Subpixel: Font smoothing is applied, and affects subpixels.
668
669       This was the default mode on macOS versions prior to 10.14 (Mojave).
670       The font dilation (stem darkening) parameters were controlled by the
671       AppleFontSmoothing setting, ranging from 1 to 3 (light to strong).
672
673       On Mojave it is no longer supported, but can be triggered by a legacy
674       override (CGFontRenderingFontSmoothingDisabled=NO), so we need to
675       still account for it, otherwise users will have a bad time.
676
677     - Grayscale: Font smoothing is applied, but does not affect subpixels.
678
679       This is the default mode on macOS 10.14 (Mojave). The font dilation
680       (stem darkening) parameters are not affected by the AppleFontSmoothing
681       setting, but are instead computed based on the fill color used when
682       drawing the glyphs (white fill gives a lighter dilation than black
683       fill). This affects how we build our glyph cache, since we produce
684       alpha maps by drawing white on black.
685*/
686QCoreTextFontEngine::FontSmoothing QCoreTextFontEngine::fontSmoothing()
687{
688    static const FontSmoothing cachedFontSmoothing = [] {
689        static const int kSize = 10;
690        QCFType<CTFontRef> font = CTFontCreateWithName(CFSTR("Helvetica"), kSize, nullptr);
691
692        UniChar character('X'); CGGlyph glyph;
693        CTFontGetGlyphsForCharacters(font, &character, &glyph, 1);
694
695        auto drawGlyph = [&](bool smooth) -> QImage {
696            QImage image(kSize, kSize, QImage::Format_RGB32);
697            image.fill(0);
698
699            QMacCGContext ctx(&image);
700            CGContextSetTextDrawingMode(ctx, kCGTextFill);
701            CGContextSetGrayFillColor(ctx, 1, 1);
702
703            // Will be ignored if CGContextSetAllowsFontSmoothing() has been
704            // set to false by CoreGraphics based on user defaults.
705            CGContextSetShouldSmoothFonts(ctx, smooth);
706
707            CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
708            return image;
709        };
710
711        QImage nonSmoothed = drawGlyph(false);
712        QImage smoothed = drawGlyph(true);
713
714        FontSmoothing fontSmoothing = FontSmoothing::Disabled;
715        [&] {
716            for (int x = 0; x < kSize; ++x) {
717                for (int y = 0; y < kSize; ++y) {
718                    QRgb sp = smoothed.pixel(x, y);
719                    if (qRed(sp) != qGreen(sp) || qRed(sp) != qBlue(sp)) {
720                        fontSmoothing = FontSmoothing::Subpixel;
721                        return;
722                    }
723
724                    if (sp != nonSmoothed.pixel(x, y))
725                        fontSmoothing = FontSmoothing::Grayscale;
726                }
727            }
728        }();
729
730        auto defaults = [NSUserDefaults standardUserDefaults];
731        qCDebug(lcQpaFonts) << "Resolved font smoothing algorithm. Defaults ="
732            << [[defaults dictionaryRepresentation] dictionaryWithValuesForKeys:@[
733                @"AppleFontSmoothing",
734                @"CGFontRenderingFontSmoothingDisabled"
735            ]] << "Result =" << fontSmoothing;
736
737        return fontSmoothing;
738    }();
739
740    return cachedFontSmoothing;
741}
742
743bool QCoreTextFontEngine::shouldAntialias() const
744{
745    return !(fontDef.styleStrategy & QFont::NoAntialias);
746}
747
748bool QCoreTextFontEngine::shouldSmoothFont() const
749{
750    if (hasColorGlyphs())
751        return false;
752
753    if (!shouldAntialias())
754        return false;
755
756    switch (fontSmoothing()) {
757    case Disabled: return false;
758    case Subpixel: return !(fontDef.styleStrategy & QFont::NoSubpixelAntialias);
759    case Grayscale: return true;
760    }
761
762    Q_UNREACHABLE();
763}
764
765bool QCoreTextFontEngine::expectsGammaCorrectedBlending() const
766{
767    return shouldSmoothFont() && fontSmoothing() == Subpixel;
768}
769
770qreal QCoreTextFontEngine::fontSmoothingGamma()
771{
772    return 2.0;
773}
774
775QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &matrix, const QColor &color)
776{
777    glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);
778
779    QImage::Format imageFormat = hasColorGlyphs() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
780    QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
781    if (!im.width() || !im.height())
782        return im;
783
784    QCFType<CGColorSpaceRef> colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
785    QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
786                                             8, im.bytesPerLine(), colorspace,
787                                             qt_mac_bitmapInfoForImage(im));
788    Q_ASSERT(ctx);
789
790    CGContextSetShouldAntialias(ctx, shouldAntialias());
791
792    const bool shouldSmooth = shouldSmoothFont();
793    CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
794
795#if defined(Q_OS_MACOS)
796    auto glyphColor = [&] {
797        if (shouldSmooth && fontSmoothing() == Grayscale) {
798            // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
799            // its dilation (stem darkening) parameters based on the fill color. This means our
800            // default approach of drawing white on black to produce the alpha map will result
801            // in non-native looking text when then drawn as black on white during the final blit.
802            // As a workaround we use the application's current appearance to decide whether to
803            // draw with white or black fill, and then invert the glyph image in the latter case,
804            // producing an alpha map. This covers the most common use-cases, but longer term we
805            // should propagate the fill color all the way from the paint engine, and include it
806            //in the key for the glyph cache.
807
808            if (!qt_mac_applicationIsInDarkMode())
809                return kCGColorBlack;
810        }
811        return kCGColorWhite;
812    }();
813
814    const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
815    if (blackOnWhiteGlyphs)
816        im.fill(Qt::white);
817    else
818#endif
819        im.fill(0);
820
821    CGContextSetFontSize(ctx, fontDef.pixelSize);
822
823    CGAffineTransform cgMatrix = CGAffineTransformIdentity;
824
825    if (synthesisFlags & QFontEngine::SynthesizedItalic)
826        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
827
828    if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
829        cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
830
831    if (matrix.isScaling())
832        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
833
834    CGGlyph cgGlyph = glyph;
835    qreal pos_x = -br.x.truncate() + subPixelPosition.toReal();
836    qreal pos_y = im.height() + br.y.toReal();
837
838    if (!hasColorGlyphs()) {
839        CGContextSetTextMatrix(ctx, cgMatrix);
840#if defined(Q_OS_MACOS)
841        CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
842#else
843        CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
844#endif
845        CGContextSetTextDrawingMode(ctx, kCGTextFill);
846        CGContextSetTextPosition(ctx, pos_x, pos_y);
847
848        CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
849
850        if (synthesisFlags & QFontEngine::SynthesizedBold) {
851            CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y);
852            CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
853        }
854    } else {
855        CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
856
857        // CGContextSetTextMatrix does not work with color glyphs, so we use
858        // the CTM instead. This means we must translate the CTM as well, to
859        // set the glyph position, instead of using CGContextSetTextPosition.
860        CGContextTranslateCTM(ctx, pos_x, pos_y);
861        CGContextConcatCTM(ctx, cgMatrix);
862
863        // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
864        // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
865        CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
866    }
867
868    if (expectsGammaCorrectedBlending())
869        qGamma_correct_back_to_linear_cs(&im);
870
871#if defined(Q_OS_MACOS)
872    if (blackOnWhiteGlyphs)
873        im.invertPixels();
874#endif
875
876    return im;
877}
878
879QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition)
880{
881    return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
882}
883
884QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &x)
885{
886    if (x.type() > QTransform::TxScale)
887        return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
888
889    QImage im = imageForGlyph(glyph, subPixelPosition, x);
890
891    QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
892
893    for (int y=0; y<im.height(); ++y) {
894        uint *src = (uint*) im.scanLine(y);
895        uchar *dst = alphaMap.scanLine(y);
896        for (int x=0; x<im.width(); ++x) {
897            *dst = qGray(*src);
898            ++dst;
899            ++src;
900        }
901    }
902
903    return alphaMap;
904}
905
906QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &x)
907{
908    if (x.type() > QTransform::TxScale)
909        return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);
910
911    return imageForGlyph(glyph, subPixelPosition, x);
912}
913
914QImage QCoreTextFontEngine::bitmapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t, const QColor &color)
915{
916    if (t.type() > QTransform::TxScale)
917        return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);
918
919    return imageForGlyph(glyph, subPixelPosition, t, color);
920}
921
922void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
923{
924    Q_UNUSED(flags);
925
926    const int numGlyphs = glyphs->numGlyphs;
927    QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs);
928
929    for (int i = 0; i < numGlyphs; ++i) {
930        Q_ASSERT(!QFontEngineMulti::highByte(glyphs->glyphs[i]));
931        cgGlyphs[i] = glyphs->glyphs[i];
932    }
933
934    loadAdvancesForGlyphs(cgGlyphs, glyphs);
935}
936
937void QCoreTextFontEngine::loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const
938{
939    const int numGlyphs = glyphs->numGlyphs;
940    QVarLengthArray<CGSize> advances(numGlyphs);
941    CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);
942
943    for (int i = 0; i < numGlyphs; ++i) {
944        QFixed advance = QFixed::fromReal(advances[i].width);
945QT_WARNING_PUSH
946QT_WARNING_DISABLE_DEPRECATED
947        glyphs->advances[i] = fontDef.styleStrategy & QFont::ForceIntegerMetrics
948                                    ? advance.round() : advance;
949QT_WARNING_POP
950    }
951}
952
953QFontEngine::FaceId QCoreTextFontEngine::faceId() const
954{
955    return face_id;
956}
957
958bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
959{
960    QVarLengthArray<CGGlyph> cgGlyphs(len);
961    return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
962}
963
964bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const
965{
966    return ct_getSfntTable((void *)&ctfont, tag, buffer, length);
967}
968
969void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metric)
970{
971    CGAffineTransform cgMatrix = CGAffineTransformIdentity;
972
973    qreal emSquare = CTFontGetUnitsPerEm(ctfont);
974    qreal scale = emSquare / CTFontGetSize(ctfont);
975    cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
976
977    QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
978    ConvertPathInfo info(path, QPointF(0,0));
979    CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
980
981    *metric = boundingBox(glyph);
982    // scale the metrics too
983    metric->width  = QFixed::fromReal(metric->width.toReal() * scale);
984    metric->height = QFixed::fromReal(metric->height.toReal() * scale);
985    metric->x      = QFixed::fromReal(metric->x.toReal() * scale);
986    metric->y      = QFixed::fromReal(metric->y.toReal() * scale);
987    metric->xoff   = QFixed::fromReal(metric->xoff.toReal() * scale);
988    metric->yoff   = QFixed::fromReal(metric->yoff.toReal() * scale);
989}
990
991QFixed QCoreTextFontEngine::emSquareSize() const
992{
993    return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
994}
995
996QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
997{
998    QFontDef newFontDef = fontDef;
999    newFontDef.pixelSize = pixelSize;
1000    newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
1001
1002    return new QCoreTextFontEngine(cgFont, newFontDef);
1003}
1004
1005Qt::HANDLE QCoreTextFontEngine::handle() const
1006{
1007    return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
1008}
1009
1010bool QCoreTextFontEngine::supportsTransformation(const QTransform &transform) const
1011{
1012    if (transform.type() < QTransform::TxScale)
1013        return true;
1014    else if (transform.type() == QTransform::TxScale &&
1015             transform.m11() >= 0 && transform.m22() >= 0)
1016        return true;
1017    else
1018        return false;
1019}
1020
1021QFixed QCoreTextFontEngine::lineThickness() const
1022{
1023    return underlineThickness;
1024}
1025
1026QFixed QCoreTextFontEngine::underlinePosition() const
1027{
1028    return underlinePos;
1029}
1030
1031QFontEngine::Properties QCoreTextFontEngine::properties() const
1032{
1033    Properties result;
1034
1035    QCFString psName, copyright;
1036    psName = CTFontCopyPostScriptName(ctfont);
1037    copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
1038    result.postscriptName = QString::fromCFString(psName).toUtf8();
1039    result.copyright = QString::fromCFString(copyright).toUtf8();
1040
1041    qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1042    qreal scale = emSquare / CTFontGetSize(ctfont);
1043
1044    CGRect cgRect = CTFontGetBoundingBox(ctfont);
1045    result.boundingBox = QRectF(cgRect.origin.x * scale,
1046                                -CTFontGetAscent(ctfont) * scale,
1047                                cgRect.size.width * scale,
1048                                cgRect.size.height * scale);
1049
1050    result.emSquare = emSquareSize();
1051    result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
1052    result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
1053    result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
1054    result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
1055    result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
1056    result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);
1057
1058QT_WARNING_PUSH
1059QT_WARNING_DISABLE_DEPRECATED
1060    if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
1061QT_WARNING_POP
1062        result.ascent = result.ascent.round();
1063        result.descent = result.descent.round();
1064        result.leading = result.leading.round();
1065        result.italicAngle = result.italicAngle.round();
1066        result.capHeight = result.capHeight.round();
1067        result.lineWidth = result.lineWidth.round();
1068    }
1069
1070    return result;
1071}
1072
1073void QCoreTextFontEngine::doKerning(QGlyphLayout *g, ShaperFlags flags) const
1074{
1075    if (!kerningPairsLoaded) {
1076        kerningPairsLoaded = true;
1077        qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1078        qreal scale = emSquare / CTFontGetSize(ctfont);
1079
1080        const_cast<QCoreTextFontEngine *>(this)->loadKerningPairs(QFixed::fromReal(scale));
1081    }
1082
1083    QFontEngine::doKerning(g, flags);
1084}
1085
1086QT_END_NAMESPACE
1087