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