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 if (glyphBoundingBoxExceedsLimit(br.width.toReal(),
778 br.height.toReal(),
779 effectivePixelSizeSquared(matrix))) {
780 return QImage();
781 }
782
783 QImage::Format imageFormat = hasColorGlyphs() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
784 QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
785 if (!im.width() || !im.height())
786 return im;
787
788 auto cgImageFormat = qt_mac_cgImageFormatForImage(im);
789 if (!cgImageFormat)
790 return im;
791
792 QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
793 cgImageFormat->bitsPerComponent, im.bytesPerLine(), cgImageFormat->colorSpace,
794 cgImageFormat->bitmapInfo);
795 Q_ASSERT(ctx);
796
797 CGContextSetShouldAntialias(ctx, shouldAntialias());
798
799 const bool shouldSmooth = shouldSmoothFont();
800 CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
801
802#if defined(Q_OS_MACOS)
803 auto glyphColor = [&] {
804 if (shouldSmooth && fontSmoothing() == Grayscale) {
805 // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
806 // its dilation (stem darkening) parameters based on the fill color. This means our
807 // default approach of drawing white on black to produce the alpha map will result
808 // in non-native looking text when then drawn as black on white during the final blit.
809 // As a workaround we use the application's current appearance to decide whether to
810 // draw with white or black fill, and then invert the glyph image in the latter case,
811 // producing an alpha map. This covers the most common use-cases, but longer term we
812 // should propagate the fill color all the way from the paint engine, and include it
813 // in the key for the glyph cache.
814
815 if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
816 if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
817 return kCGColorBlack;
818 }
819 }
820 return kCGColorWhite;
821 }();
822
823 const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
824 if (blackOnWhiteGlyphs)
825 im.fill(Qt::white);
826 else
827#endif
828 im.fill(0);
829
830 CGContextSetFontSize(ctx, fontDef.pixelSize);
831
832 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
833
834 if (synthesisFlags & QFontEngine::SynthesizedItalic)
835 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
836
837 if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
838 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
839
840 if (matrix.isScaling())
841 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
842
843 CGGlyph cgGlyph = glyph;
844
845 qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
846 qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();
847
848 if (!hasColorGlyphs()) {
849 CGContextSetTextMatrix(ctx, cgMatrix);
850#if defined(Q_OS_MACOS)
851 CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
852#else
853 CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
854#endif
855 CGContextSetTextDrawingMode(ctx, kCGTextFill);
856 CGContextSetTextPosition(ctx, pos_x, pos_y);
857
858 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
859
860 if (synthesisFlags & QFontEngine::SynthesizedBold) {
861 qreal boldOffset = 0.5 * lineThickness().toReal();
862
863 qreal scale;
864 qt_scaleForTransform(matrix, &scale);
865 boldOffset *= scale;
866
867 CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
868 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
869 }
870 } else {
871 CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
872
873 // CGContextSetTextMatrix does not work with color glyphs, so we use
874 // the CTM instead. This means we must translate the CTM as well, to
875 // set the glyph position, instead of using CGContextSetTextPosition.
876 CGContextTranslateCTM(ctx, pos_x, pos_y);
877 CGContextConcatCTM(ctx, cgMatrix);
878
879 // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
880 // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
881 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
882 }
883
884 if (expectsGammaCorrectedBlending())
885 qGamma_correct_back_to_linear_cs(&im);
886
887#if defined(Q_OS_MACOS)
888 if (blackOnWhiteGlyphs)
889 im.invertPixels();
890#endif
891
892 return im;
893}
894
895QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition)
896{
897 return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
898}
899
900QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
901{
902 if (x.type() > QTransform::TxScale)
903 return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
904
905 QImage im = imageForGlyph(glyph, subPixelPosition, x);
906
907 QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
908
909 for (int y=0; y<im.height(); ++y) {
910 uint *src = (uint*) im.scanLine(y);
911 uchar *dst = alphaMap.scanLine(y);
912 for (int x=0; x<im.width(); ++x) {
913 *dst = qGray(*src);
914 ++dst;
915 ++src;
916 }
917 }
918
919 return alphaMap;
920}
921
922QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
923{
924 if (x.type() > QTransform::TxScale)
925 return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);
926
927 return imageForGlyph(glyph, subPixelPosition, x);
928}
929
930QImage QCoreTextFontEngine::bitmapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color)
931{
932 if (t.type() > QTransform::TxScale)
933 return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);
934
935 return imageForGlyph(glyph, subPixelPosition, t, color);
936}
937
938void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
939{
940 Q_UNUSED(flags);
941
942 const int numGlyphs = glyphs->numGlyphs;
943 QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs);
944
945 for (int i = 0; i < numGlyphs; ++i) {
946 Q_ASSERT(!QFontEngineMulti::highByte(glyphs->glyphs[i]));
947 cgGlyphs[i] = glyphs->glyphs[i];
948 }
949
950 loadAdvancesForGlyphs(cgGlyphs, glyphs);
951}
952
953void QCoreTextFontEngine::loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const
954{
955 const int numGlyphs = glyphs->numGlyphs;
956 QVarLengthArray<CGSize> advances(numGlyphs);
957 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);
958
959 qreal stretch = fontDef.stretch != QFont::AnyStretch ? fontDef.stretch / 100.0 : 1.0;
960 for (int i = 0; i < numGlyphs; ++i)
961 glyphs->advances[i] = QFixed::fromReal(advances[i].width * stretch);
962}
963
964QFontEngine::FaceId QCoreTextFontEngine::faceId() const
965{
966 return face_id;
967}
968
969bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
970{
971 QVarLengthArray<CGGlyph> cgGlyphs(len);
972 return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
973}
974
975bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const
976{
977 return ct_getSfntTable((void *)&ctfont, tag, buffer, length);
978}
979
980void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metric)
981{
982 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
983
984 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
985 qreal scale = emSquare / CTFontGetSize(ctfont);
986 cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
987
988 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
989 ConvertPathInfo info(path, QPointF(0,0));
990 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
991
992 *metric = boundingBox(glyph);
993 // scale the metrics too
994 metric->width = QFixed::fromReal(metric->width.toReal() * scale);
995 metric->height = QFixed::fromReal(metric->height.toReal() * scale);
996 metric->x = QFixed::fromReal(metric->x.toReal() * scale);
997 metric->y = QFixed::fromReal(metric->y.toReal() * scale);
998 metric->xoff = QFixed::fromReal(metric->xoff.toReal() * scale);
999 metric->yoff = QFixed::fromReal(metric->yoff.toReal() * scale);
1000}
1001
1002QFixed QCoreTextFontEngine::emSquareSize() const
1003{
1004 return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
1005}
1006
1007QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
1008{
1009 QFontDef newFontDef = fontDef;
1010 newFontDef.pixelSize = pixelSize;
1011 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
1012
1013 return new QCoreTextFontEngine(cgFont, newFontDef);
1014}
1015
1016Qt::HANDLE QCoreTextFontEngine::handle() const
1017{
1018 return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
1019}
1020
1021bool QCoreTextFontEngine::supportsTransformation(const QTransform &transform) const
1022{
1023 if (transform.type() < QTransform::TxScale)
1024 return true;
1025 else if (transform.type() == QTransform::TxScale &&
1026 transform.m11() >= 0 && transform.m22() >= 0)
1027 return true;
1028 else
1029 return false;
1030}
1031
1032QFixed QCoreTextFontEngine::lineThickness() const
1033{
1034 return underlineThickness;
1035}
1036
1037QFixed QCoreTextFontEngine::underlinePosition() const
1038{
1039 return underlinePos;
1040}
1041
1042QFontEngine::Properties QCoreTextFontEngine::properties() const
1043{
1044 Properties result;
1045
1046 QCFString psName, copyright;
1047 psName = CTFontCopyPostScriptName(ctfont);
1048 copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
1049 result.postscriptName = QString::fromCFString(psName).toUtf8();
1050 result.copyright = QString::fromCFString(copyright).toUtf8();
1051
1052 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1053 qreal scale = emSquare / CTFontGetSize(ctfont);
1054
1055 CGRect cgRect = CTFontGetBoundingBox(ctfont);
1056 result.boundingBox = QRectF(cgRect.origin.x * scale,
1057 -CTFontGetAscent(ctfont) * scale,
1058 cgRect.size.width * scale,
1059 cgRect.size.height * scale);
1060
1061 result.emSquare = emSquareSize();
1062 result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
1063 result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
1064 result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
1065 result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
1066 result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
1067 result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);
1068
1069 return result;
1070}
1071
1072void QCoreTextFontEngine::doKerning(QGlyphLayout *g, ShaperFlags flags) const
1073{
1074 if (!kerningPairsLoaded) {
1075 kerningPairsLoaded = true;
1076 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1077 qreal scale = emSquare / CTFontGetSize(ctfont);
1078
1079 const_cast<QCoreTextFontEngine *>(this)->loadKerningPairs(QFixed::fromReal(scale));
1080 }
1081
1082 QFontEngine::doKerning(g, flags);
1083}
1084
1085QList<QFontVariableAxis> QCoreTextFontEngine::variableAxes() const
1086{
1087 return variableAxisList;
1088}
1089
1090QT_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)