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#include <Accelerate/Accelerate.h>
31
32// These are available cross platform, exported as kCTFontWeightXXX from CoreText.framework,
33// but they are not documented and are not in public headers so are private API and exposed
34// only through the NSFontWeightXXX and UIFontWeightXXX aliases in AppKit and UIKit (rdar://26109857)
35#if defined(Q_OS_MACOS)
36#define kCTFontWeightUltraLight NSFontWeightUltraLight
37#define kCTFontWeightThin NSFontWeightThin
38#define kCTFontWeightLight NSFontWeightLight
39#define kCTFontWeightRegular NSFontWeightRegular
40#define kCTFontWeightMedium NSFontWeightMedium
41#define kCTFontWeightSemibold NSFontWeightSemibold
42#define kCTFontWeightBold NSFontWeightBold
43#define kCTFontWeightHeavy NSFontWeightHeavy
44#define kCTFontWeightBlack NSFontWeightBlack
45#elif defined(QT_PLATFORM_UIKIT)
46#define kCTFontWeightUltraLight UIFontWeightUltraLight
47#define kCTFontWeightThin UIFontWeightThin
48#define kCTFontWeightLight UIFontWeightLight
49#define kCTFontWeightRegular UIFontWeightRegular
50#define kCTFontWeightMedium UIFontWeightMedium
51#define kCTFontWeightSemibold UIFontWeightSemibold
52#define kCTFontWeightBold UIFontWeightBold
53#define kCTFontWeightHeavy UIFontWeightHeavy
54#define kCTFontWeightBlack UIFontWeightBlack
55#endif
56
58
59static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);
60
61bool QCoreTextFontEngine::ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
62{
63 CTFontRef ctfont = *(CTFontRef *)user_data;
64
65 QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0);
66 if (!table)
67 return false;
68
69 CFIndex tableLength = CFDataGetLength(table);
70 if (buffer && int(*length) >= tableLength)
71 CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer);
72 *length = tableLength;
73 Q_ASSERT(int(*length) > 0);
74 return true;
75}
76
77QFont::Weight QCoreTextFontEngine::qtWeightFromCFWeight(float value)
78{
79#define COMPARE_WEIGHT_DISTANCE(ct_weight, qt_weight)
80 {
81 float d;
82 if ((d = qAbs(value - ct_weight)) < distance) {
83 distance = d;
84 ret = qt_weight;
85 }
86 }
87
88 float distance = qAbs(value - kCTFontWeightBlack);
89 QFont::Weight ret = QFont::Black;
90
91 // Compare distance to system weight to find the closest match.
92 // (Note: Must go from high to low, so that midpoints are rounded up)
93 COMPARE_WEIGHT_DISTANCE(kCTFontWeightHeavy, QFont::ExtraBold);
94 COMPARE_WEIGHT_DISTANCE(kCTFontWeightBold, QFont::Bold);
95 COMPARE_WEIGHT_DISTANCE(kCTFontWeightSemibold, QFont::DemiBold);
96 COMPARE_WEIGHT_DISTANCE(kCTFontWeightMedium, QFont::Medium);
97 COMPARE_WEIGHT_DISTANCE(kCTFontWeightRegular, QFont::Normal);
98 COMPARE_WEIGHT_DISTANCE(kCTFontWeightLight, QFont::Light);
99 COMPARE_WEIGHT_DISTANCE(kCTFontWeightThin, QFont::ExtraLight);
100 COMPARE_WEIGHT_DISTANCE(kCTFontWeightUltraLight, QFont::Thin);
101
102#undef COMPARE_WEIGHT_DISTANCE
103
104 return ret;
105}
106
108{
109 CGAffineTransform transform = CGAffineTransformIdentity;
110 if (fontDef.stretch && fontDef.stretch != 100)
111 transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1);
112 return transform;
113}
114
115// Keeps font data alive until engine is disposed
117{
118public:
119 QCoreTextRawFontEngine(CGFontRef font, const QFontDef &def, const QByteArray &fontData)
122 {}
123 QFontEngine *cloneWithSize(qreal pixelSize) const
124 {
125 QFontDef newFontDef = fontDef;
126 newFontDef.pixelSize = pixelSize;
127 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
128
129 return new QCoreTextRawFontEngine(cgFont, newFontDef, m_fontData);
130 }
132};
133
134QCoreTextFontEngine *QCoreTextFontEngine::create(const QByteArray &fontData,
135 qreal pixelSize,
136 QFont::HintingPreference hintingPreference,
137 const QMap<QFont::Tag, float> &variableAxisValues)
138{
139 Q_UNUSED(hintingPreference);
140 Q_UNUSED(variableAxisValues);
141
142 QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
143 QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithCFData(fontDataReference);
144
145 // Note: CTFontCreateWithGraphicsFont (which we call from the QCoreTextFontEngine
146 // constructor) has a bug causing it to retain the CGFontRef but never release it.
147 // The result is that we are leaking the CGFont, CGDataProvider, and CGData, but
148 // as the CGData is created from the raw QByteArray data, which we deref in the
149 // subclass above during destruction, we're at least not leaking the font data,
150 // (unless CoreText copies it internally). http://stackoverflow.com/questions/40805382/
151 QCFType<CGFontRef> cgFont = CGFontCreateWithDataProvider(dataProvider);
152
153 if (!cgFont) {
154 qWarning("QCoreTextFontEngine::create: CGFontCreateWithDataProvider failed");
155 return nullptr;
156 }
157
158 QFontDef def;
159 def.pixelSize = pixelSize;
160 def.pointSize = pixelSize * 72.0 / qt_defaultDpi();
161 return new QCoreTextRawFontEngine(cgFont, def, fontData);
162}
163
164QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
165 : QCoreTextFontEngine(def)
166{
167 ctfont = QCFType<CTFontRef>::constructFromGet(font);
168 cgFont = CTFontCopyGraphicsFont(font, nullptr);
169 init();
170}
171
172QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def)
173 : QCoreTextFontEngine(def)
174{
175 cgFont = QCFType<CGFontRef>::constructFromGet(font);
176 ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, nullptr);
177 init();
178}
179
180QCoreTextFontEngine::QCoreTextFontEngine(const QFontDef &def)
181 : QFontEngine(Mac)
182{
183 fontDef = def;
184 transform = qt_transform_from_fontdef(fontDef);
185}
186
187QCoreTextFontEngine::~QCoreTextFontEngine()
188{
189}
190
191void QCoreTextFontEngine::init()
192{
193 Q_ASSERT(ctfont);
194 Q_ASSERT(cgFont);
195
196 face_id.index = 0;
197 QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
198 face_id.filename = QString::fromCFString(name).toUtf8();
199 face_id.variableAxes = fontDef.variableAxisValues;
200
201 QCFString family = CTFontCopyFamilyName(ctfont);
202 fontDef.families = QStringList(family);
203
204 QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
205 fontDef.styleName = styleName;
206
207 synthesisFlags = 0;
208 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
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 if (traits & kCTFontItalicTrait)
218 fontDef.style = QFont::StyleItalic;
219
220 static const auto getTraitValue = [](CFDictionaryRef allTraits, CFStringRef trait) -> float {
221 if (CFDictionaryContainsKey(allTraits, trait)) {
222 CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait);
223 float v = 0;
224 CFNumberGetValue(traitNum, kCFNumberFloatType, &v);
225 return v;
226 }
227 return 0;
228 };
229
230 QCFType<CFDictionaryRef> allTraits = CTFontCopyTraits(ctfont);
231 int slant = static_cast<int>(getTraitValue(allTraits, kCTFontSlantTrait) * 500 + 500);
232 if (slant > 500 && !(traits & kCTFontItalicTrait))
233 fontDef.style = QFont::StyleOblique;
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 (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait) && !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() const
762{
763 return shouldSmoothFont() && fontSmoothing() == Subpixel;
764}
765
766qreal QCoreTextFontEngine::fontSmoothingGamma()
767{
768 return 2.0;
769}
770
771QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &matrix, const QColor &color)
772{
773 glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);
774
775 QImage::Format imageFormat = hasColorGlyphs() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
776 QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
777 if (!im.width() || !im.height())
778 return im;
779
780 auto cgImageFormat = qt_mac_cgImageFormatForImage(im);
781 if (!cgImageFormat)
782 return im;
783
784 QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
785 cgImageFormat->bitsPerComponent, im.bytesPerLine(), cgImageFormat->colorSpace,
786 cgImageFormat->bitmapInfo);
787 Q_ASSERT(ctx);
788
789 CGContextSetShouldAntialias(ctx, shouldAntialias());
790
791 const bool shouldSmooth = shouldSmoothFont();
792 CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
793
794#if defined(Q_OS_MACOS)
795 auto glyphColor = [&] {
796 if (shouldSmooth && fontSmoothing() == Grayscale) {
797 // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
798 // its dilation (stem darkening) parameters based on the fill color. This means our
799 // default approach of drawing white on black to produce the alpha map will result
800 // in non-native looking text when then drawn as black on white during the final blit.
801 // As a workaround we use the application's current appearance to decide whether to
802 // draw with white or black fill, and then invert the glyph image in the latter case,
803 // producing an alpha map. This covers the most common use-cases, but longer term we
804 // should propagate the fill color all the way from the paint engine, and include it
805 // in the key for the glyph cache.
806
807 if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
808 if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
809 return kCGColorBlack;
810 }
811 }
812 return kCGColorWhite;
813 }();
814
815 const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
816 if (blackOnWhiteGlyphs)
817 im.fill(Qt::white);
818 else
819#endif
820 im.fill(0);
821
822 CGContextSetFontSize(ctx, fontDef.pixelSize);
823
824 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
825
826 if (synthesisFlags & QFontEngine::SynthesizedItalic)
827 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
828
829 if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
830 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
831
832 if (matrix.isScaling())
833 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
834
835 CGGlyph cgGlyph = glyph;
836
837 qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
838 qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();
839
840 if (!hasColorGlyphs()) {
841 CGContextSetTextMatrix(ctx, cgMatrix);
842#if defined(Q_OS_MACOS)
843 CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
844#else
845 CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
846#endif
847 CGContextSetTextDrawingMode(ctx, kCGTextFill);
848 CGContextSetTextPosition(ctx, pos_x, pos_y);
849
850 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
851
852 if (synthesisFlags & QFontEngine::SynthesizedBold) {
853 qreal boldOffset = 0.5 * lineThickness().toReal();
854
855 qreal scale;
856 qt_scaleForTransform(matrix, &scale);
857 boldOffset *= scale;
858
859 CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
860 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
861 }
862 } else {
863 CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
864
865 // CGContextSetTextMatrix does not work with color glyphs, so we use
866 // the CTM instead. This means we must translate the CTM as well, to
867 // set the glyph position, instead of using CGContextSetTextPosition.
868 CGContextTranslateCTM(ctx, pos_x, pos_y);
869 CGContextConcatCTM(ctx, cgMatrix);
870
871 // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
872 // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
873 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
874 }
875
876 if (expectsGammaCorrectedBlending())
877 qGamma_correct_back_to_linear_cs(&im);
878
879#if defined(Q_OS_MACOS)
880 if (blackOnWhiteGlyphs)
881 im.invertPixels();
882#endif
883
884 return im;
885}
886
887QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition)
888{
889 return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
890}
891
892QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
893{
894 if (x.type() > QTransform::TxScale)
895 return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
896
897 QImage im = imageForGlyph(glyph, subPixelPosition, x);
898
899 QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
900
901 for (int y=0; y<im.height(); ++y) {
902 uint *src = (uint*) im.scanLine(y);
903 uchar *dst = alphaMap.scanLine(y);
904 for (int x=0; x<im.width(); ++x) {
905 *dst = qGray(*src);
906 ++dst;
907 ++src;
908 }
909 }
910
911 return alphaMap;
912}
913
914QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
915{
916 if (x.type() > QTransform::TxScale)
917 return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);
918
919 return imageForGlyph(glyph, subPixelPosition, x);
920}
921
922QImage QCoreTextFontEngine::bitmapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color)
923{
924 if (t.type() > QTransform::TxScale)
925 return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);
926
927 return imageForGlyph(glyph, subPixelPosition, t, color);
928}
929
930void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
931{
932 Q_UNUSED(flags);
933
934 const int numGlyphs = glyphs->numGlyphs;
935 QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs);
936
937 for (int i = 0; i < numGlyphs; ++i) {
938 Q_ASSERT(!QFontEngineMulti::highByte(glyphs->glyphs[i]));
939 cgGlyphs[i] = glyphs->glyphs[i];
940 }
941
942 loadAdvancesForGlyphs(cgGlyphs, glyphs);
943}
944
945void QCoreTextFontEngine::loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const
946{
947 const int numGlyphs = glyphs->numGlyphs;
948 QVarLengthArray<CGSize> advances(numGlyphs);
949 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);
950
951 qreal stretch = fontDef.stretch != QFont::AnyStretch ? fontDef.stretch / 100.0 : 1.0;
952 for (int i = 0; i < numGlyphs; ++i)
953 glyphs->advances[i] = QFixed::fromReal(advances[i].width * stretch);
954}
955
956QFontEngine::FaceId QCoreTextFontEngine::faceId() const
957{
958 return face_id;
959}
960
961bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
962{
963 QVarLengthArray<CGGlyph> cgGlyphs(len);
964 return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
965}
966
967bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const
968{
969 return ct_getSfntTable((void *)&ctfont, tag, buffer, length);
970}
971
972void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metric)
973{
974 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
975
976 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
977 qreal scale = emSquare / CTFontGetSize(ctfont);
978 cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
979
980 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
981 ConvertPathInfo info(path, QPointF(0,0));
982 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
983
984 *metric = boundingBox(glyph);
985 // scale the metrics too
986 metric->width = QFixed::fromReal(metric->width.toReal() * scale);
987 metric->height = QFixed::fromReal(metric->height.toReal() * scale);
988 metric->x = QFixed::fromReal(metric->x.toReal() * scale);
989 metric->y = QFixed::fromReal(metric->y.toReal() * scale);
990 metric->xoff = QFixed::fromReal(metric->xoff.toReal() * scale);
991 metric->yoff = QFixed::fromReal(metric->yoff.toReal() * scale);
992}
993
994QFixed QCoreTextFontEngine::emSquareSize() const
995{
996 return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
997}
998
999QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
1000{
1001 QFontDef newFontDef = fontDef;
1002 newFontDef.pixelSize = pixelSize;
1003 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
1004
1005 return new QCoreTextFontEngine(cgFont, newFontDef);
1006}
1007
1008Qt::HANDLE QCoreTextFontEngine::handle() const
1009{
1010 return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
1011}
1012
1013bool QCoreTextFontEngine::supportsTransformation(const QTransform &transform) const
1014{
1015 if (transform.type() < QTransform::TxScale)
1016 return true;
1017 else if (transform.type() == QTransform::TxScale &&
1018 transform.m11() >= 0 && transform.m22() >= 0)
1019 return true;
1020 else
1021 return false;
1022}
1023
1024QFixed QCoreTextFontEngine::lineThickness() const
1025{
1026 return underlineThickness;
1027}
1028
1029QFixed QCoreTextFontEngine::underlinePosition() const
1030{
1031 return underlinePos;
1032}
1033
1034QFontEngine::Properties QCoreTextFontEngine::properties() const
1035{
1036 Properties result;
1037
1038 QCFString psName, copyright;
1039 psName = CTFontCopyPostScriptName(ctfont);
1040 copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
1041 result.postscriptName = QString::fromCFString(psName).toUtf8();
1042 result.copyright = QString::fromCFString(copyright).toUtf8();
1043
1044 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1045 qreal scale = emSquare / CTFontGetSize(ctfont);
1046
1047 CGRect cgRect = CTFontGetBoundingBox(ctfont);
1048 result.boundingBox = QRectF(cgRect.origin.x * scale,
1049 -CTFontGetAscent(ctfont) * scale,
1050 cgRect.size.width * scale,
1051 cgRect.size.height * scale);
1052
1053 result.emSquare = emSquareSize();
1054 result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
1055 result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
1056 result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
1057 result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
1058 result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
1059 result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);
1060
1061 return result;
1062}
1063
1064void QCoreTextFontEngine::doKerning(QGlyphLayout *g, ShaperFlags flags) const
1065{
1066 if (!kerningPairsLoaded) {
1067 kerningPairsLoaded = true;
1068 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1069 qreal scale = emSquare / CTFontGetSize(ctfont);
1070
1071 const_cast<QCoreTextFontEngine *>(this)->loadKerningPairs(QFixed::fromReal(scale));
1072 }
1073
1074 QFontEngine::doKerning(g, flags);
1075}
1076
1077QList<QFontVariableAxis> QCoreTextFontEngine::variableAxes() const
1078{
1079 return variableAxisList;
1080}
1081
1082QT_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)