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