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