Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
5
6#include <qpa/qplatformfontdatabase.h>
7#include <QtCore/qendian.h>
8#if QT_CONFIG(settings)
9#include <QtCore/qsettings.h>
10#endif
11#include <QtCore/qoperatingsystemversion.h>
12#include <QtGui/qpainterpath.h>
13#include <private/qcoregraphics_p.h>
14#include <private/qimage_p.h>
15#include <private/qguiapplication_p.h>
16#include <private/qstringiterator_p.h>
17#include <qpa/qplatformtheme.h>
18
19#include <cmath>
20
21#if defined(Q_OS_MACOS)
22#import <AppKit/AppKit.h>
23#endif
24
25#if defined(QT_PLATFORM_UIKIT)
26#import <UIKit/UIKit.h>
27#endif
28
29// These are available cross platform, exported as kCTFontWeightXXX from CoreText.framework,
30// but they are not documented and are not in public headers so are private API and exposed
31// only through the NSFontWeightXXX and UIFontWeightXXX aliases in AppKit and UIKit (rdar://26109857)
32#if defined(Q_OS_MACOS)
33#define kCTFontWeightUltraLight NSFontWeightUltraLight
34#define kCTFontWeightThin NSFontWeightThin
35#define kCTFontWeightLight NSFontWeightLight
36#define kCTFontWeightRegular NSFontWeightRegular
37#define kCTFontWeightMedium NSFontWeightMedium
38#define kCTFontWeightSemibold NSFontWeightSemibold
39#define kCTFontWeightBold NSFontWeightBold
40#define kCTFontWeightHeavy NSFontWeightHeavy
41#define kCTFontWeightBlack NSFontWeightBlack
42#elif defined(QT_PLATFORM_UIKIT)
43#define kCTFontWeightUltraLight UIFontWeightUltraLight
44#define kCTFontWeightThin UIFontWeightThin
45#define kCTFontWeightLight UIFontWeightLight
46#define kCTFontWeightRegular UIFontWeightRegular
47#define kCTFontWeightMedium UIFontWeightMedium
48#define kCTFontWeightSemibold UIFontWeightSemibold
49#define kCTFontWeightBold UIFontWeightBold
50#define kCTFontWeightHeavy UIFontWeightHeavy
51#define kCTFontWeightBlack UIFontWeightBlack
52#endif
53
55
56static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);
57
59{
60 CTFontRef ctfont = *(CTFontRef *)user_data;
61
62 QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0);
63 if (!table)
64 return false;
65
66 CFIndex tableLength = CFDataGetLength(table);
67 if (buffer && int(*length) >= tableLength)
68 CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer);
69 *length = tableLength;
70 Q_ASSERT(int(*length) > 0);
71 return true;
72}
73
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);
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);
97 COMPARE_WEIGHT_DISTANCE(kCTFontWeightUltraLight, QFont::Thin);
98
99#undef COMPARE_WEIGHT_DISTANCE
100
101 return ret;
102}
103
104CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
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:
119 {}
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
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
163{
165 cgFont = CTFontCopyGraphicsFont(font, nullptr);
166 init();
167}
168
171{
173 ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, nullptr);
174 init();
175}
176
183
187
189{
192
193 face_id.index = 0;
194 QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
195 face_id.filename = QString::fromCFString(name).toUtf8();
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 if (traits & kCTFontColorGlyphsTrait)
211 else
213
214 if (traits & kCTFontItalicTrait)
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 (slant > 500 && !(traits & kCTFontItalicTrait))
231
232 if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD"))
234 else
235 fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));
236
237 if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"))
239
240 avgCharWidth = 0;
241 QByteArray os2Table = getSfntTable(QFont::Tag("OS/2").value());
242 unsigned emSize = CTFontGetUnitsPerEm(ctfont);
243 if (os2Table.size() >= 10) {
244 fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
245 // qAbs is a workaround for weird fonts like Lucida Grande
246 qint16 width = qAbs(qFromBigEndian<qint16>(os2Table.constData() + 2));
248 } else
250
251 underlineThickness = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont));
252 underlinePos = -QFixed::fromReal(CTFontGetUnderlinePosition(ctfont));
253
254 cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;
255
256 kerningPairsLoaded = false;
257}
258
260{
261 int len = 0;
262
263 QChar str[2];
264 if (Q_UNLIKELY(QChar::requiresSurrogates(ucs4))) {
265 str[len++] = QChar(QChar::highSurrogate(ucs4));
266 str[len++] = QChar(QChar::lowSurrogate(ucs4));
267 } else {
268 str[len++] = QChar(ucs4);
269 }
270
271 CGGlyph glyphIndices[2];
272
273 CTFontGetGlyphsForCharacters(ctfont, (const UniChar *)str, glyphIndices, len);
274
275 return glyphIndices[0];
276}
277
279 int *nglyphs, QFontEngine::ShaperFlags flags) const
280{
281 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
282 if (*nglyphs < len) {
283 *nglyphs = len;
284 return -1;
285 }
286
287 QVarLengthArray<CGGlyph> cgGlyphs(len);
288 CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);
289
290 int glyph_pos = 0;
291 int mappedGlyphs = 0;
293 while (it.hasNext()) {
294 qsizetype idx = it.index();
295 char32_t ucs4 = it.next();
296 glyphs->glyphs[glyph_pos] = cgGlyphs[idx];
297 if (glyph_pos < idx)
298 cgGlyphs[glyph_pos] = cgGlyphs[idx];
299 if (glyphs->glyphs[glyph_pos] != 0 || isIgnorableChar(ucs4))
300 mappedGlyphs++;
301 glyph_pos++;
302 }
303
304 *nglyphs = glyph_pos;
305 glyphs->numGlyphs = glyph_pos;
306
307 if (!(flags & GlyphIndicesOnly))
308 loadAdvancesForGlyphs(cgGlyphs, glyphs);
309
310 return mappedGlyphs;
311}
312
314{
316 CGGlyph g = glyph;
317 CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, 0, 1);
319 rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW;
320 }
321 ret.width = QFixed::fromReal(rect.size.width);
322 ret.height = QFixed::fromReal(rect.size.height);
323 ret.x = QFixed::fromReal(rect.origin.x);
324 ret.y = -QFixed::fromReal(rect.origin.y) - ret.height;
325 CGSize advances[1];
326 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, advances, 1);
327 ret.xoff = QFixed::fromReal(advances[0].width);
328 ret.yoff = QFixed::fromReal(advances[0].height);
329
330 return ret;
331}
332
334{
335 m_ascent = QFixed::fromReal(CTFontGetAscent(ctfont));
336 m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
337 m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
338
340}
341
343{
344 QFixed c = QFixed::fromReal(CTFontGetCapHeight(ctfont));
345 if (c <= 0)
346 return calculatedCapHeight();
347
348 return c;
349}
350
352{
353 return QFixed::fromReal(CTFontGetXHeight(ctfont));
354}
355
360
362{
363 // ### FIXME: 'W' might not be the widest character, but this is better than nothing
364 const glyph_t glyph = glyphIndex('W');
365 glyph_metrics_t bb = const_cast<QCoreTextFontEngine *>(this)->boundingBox(glyph);
366 return bb.xoff.toReal();
367}
368
373
374Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
375void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
376{
377 QVarLengthArray<QFixedPoint> positions;
378 QVarLengthArray<glyph_t> glyphs;
381 getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
382 if (glyphs.size() == 0)
383 return;
384
385 CGContextSetFontSize(ctx, fontDef.pixelSize);
386
387 CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx);
388
389 CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight);
390
391 // FIXME: Should we include the old matrix here? If so we need to assign it.
392 Q_UNUSED(CGAffineTransformConcat(cgMatrix, oldTextMatrix));
393
395 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
396
397 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
398
399 CGContextSetTextMatrix(ctx, cgMatrix);
400
401 CGContextSetTextDrawingMode(ctx, kCGTextFill);
402
403 QVarLengthArray<CGPoint> cgPositions(glyphs.size());
404 QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size());
405 const qreal firstX = positions[0].x.toReal();
406 const qreal firstY = positions[0].y.toReal();
407 for (int i = 0; i < glyphs.size(); ++i) {
408 cgPositions[i].x = positions[i].x.toReal() - firstX;
409 cgPositions[i].y = firstY - positions[i].y.toReal();
410 cgGlyphs[i] = glyphs[i];
411 }
412
413 //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont));
414
415 CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal());
416 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
417
419 QTransform matrix(cgMatrix.a, cgMatrix.b, cgMatrix.c, cgMatrix.d, cgMatrix.tx, cgMatrix.ty);
420
421 qreal boldOffset = 0.5 * lineThickness().toReal();
422 qreal scale;
424 boldOffset *= scale;
425
426 CGContextSetTextPosition(ctx,
427 positions[0].x.toReal() + boldOffset,
428 positions[0].y.toReal());
429 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
430 }
431
432 CGContextSetTextMatrix(ctx, oldTextMatrix);
433}
434
436{
437 ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos, qreal newStretch = 1.0) :
438 path(newPath), pos(newPos), stretch(newStretch) {}
442};
443
444static void convertCGPathToQPainterPath(void *info, const CGPathElement *element)
445{
446 ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info);
447 switch(element->type) {
448 case kCGPathElementMoveToPoint:
449 myInfo->path->moveTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
450 element->points[0].y + myInfo->pos.y());
451 break;
452 case kCGPathElementAddLineToPoint:
453 myInfo->path->lineTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
454 element->points[0].y + myInfo->pos.y());
455 break;
456 case kCGPathElementAddQuadCurveToPoint:
457 myInfo->path->quadTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
458 element->points[0].y + myInfo->pos.y(),
459 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
460 element->points[1].y + myInfo->pos.y());
461 break;
462 case kCGPathElementAddCurveToPoint:
463 myInfo->path->cubicTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
464 element->points[0].y + myInfo->pos.y(),
465 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
466 element->points[1].y + myInfo->pos.y(),
467 (element->points[2].x * myInfo->stretch) + myInfo->pos.x(),
468 element->points[2].y + myInfo->pos.y());
469 break;
470 case kCGPathElementCloseSubpath:
471 myInfo->path->closeSubpath();
472 break;
473 default:
474 qCWarning(lcQpaFonts) << "Unhandled path transform type: " << element->type;
475 }
476
477}
478
480 QPainterPath *path, QTextItem::RenderFlags)
481{
482 if (hasColorGlyphs())
483 return; // We can't convert color-glyphs to path
484
485 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
486 cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);
487
489 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
490
491 qreal stretch = fontDef.stretch ? qreal(fontDef.stretch) / 100 : 1.0;
492 for (int i = 0; i < nGlyphs; ++i) {
493 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
494 ConvertPathInfo info(path, positions[i].toPointF(), stretch);
495 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
496 }
497}
498
500{
501 if (matrix.isScaling()) {
502 qreal hscale = matrix.m11();
503 qreal vscale = matrix.m22();
504 br.width = QFixed::fromReal(br.width.toReal() * hscale);
505 br.height = QFixed::fromReal(br.height.toReal() * vscale);
506 br.x = QFixed::fromReal(br.x.toReal() * hscale);
507 br.y = QFixed::fromReal(br.y.toReal() * vscale);
508 }
509}
510
512{
513 if (matrix.type() > QTransform::TxScale)
514 return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);
515
516 glyph_metrics_t br = boundingBox(glyph);
517
520 xform.scale(fontDef.stretch / 100.0, 1.0);
522
523 // Normalize width and height
524 if (br.width < 0)
525 br.width = -br.width;
526 if (br.height < 0)
527 br.height = -br.height;
528
530 // Drawing a glyph at x-position 0 with anti-aliasing enabled
531 // will potentially fill the pixel to the left of 0, as the
532 // coordinates are not aligned to the center of pixels. To
533 // prevent clipping of this pixel we need to shift the glyph
534 // in the bitmap one pixel to the right. The shift needs to
535 // be reflected in the glyph metrics as well, so that the final
536 // position of the glyph is correct, which is why doing the
537 // shift in imageForGlyph() is not enough.
538 br.x -= 1;
539
540 // As we've shifted the glyph one pixel to the right, we need
541 // to expand the width of the alpha map bounding box as well.
542 br.width += 1;
543
544 // But we have the same anti-aliasing problem on the right
545 // hand side of the glyph, eg. if the width of the glyph
546 // results in the bounding rect landing between two pixels.
547 // We pad the bounding rect again to account for the possible
548 // anti-aliased drawing.
549 br.width += 1;
550
551 // We also shift the glyph to right right based on the subpixel
552 // position, so we pad the bounding box to take account for the
553 // subpixel positions that may result in the glyph being drawn
554 // one pixel to the right of the 0-subpixel position.
555 br.width += 1;
556
557 // The same same logic as for the x-position needs to be applied
558 // to the y-position, except we don't need to compensate for
559 // the subpixel positioning.
560 br.y -= 1;
561 br.height += 2;
562 }
563
564 return br;
565}
566
567/*
568 Apple has gone through many iterations of its font smoothing algorithms,
569 and there are many ways to enable or disable certain aspects of it. As
570 keeping up with all the different toggles and behavior differences between
571 macOS versions is tricky, we resort to rendering a single glyph in a few
572 configurations, picking up the font smoothing algorithm from the observed
573 result.
574
575 The possible values are:
576
577 - Disabled: No font smoothing is applied.
578
579 Possibly triggered by the user unchecking the "Use font smoothing when
580 available" checkbox in the system preferences or setting AppleFontSmoothing
581 to 0. Also controlled by the CGContextSetAllowsFontSmoothing() API,
582 which gets its default from the settings above. This API overrides
583 the more granular CGContextSetShouldSmoothFonts(), which we use to
584 enable (request) or disable font smoothing.
585
586 Note that this does not exclude normal antialiasing, controlled by
587 the CGContextSetShouldAntialias() API.
588
589 - Subpixel: Font smoothing is applied, and affects subpixels.
590
591 This was the default mode on macOS versions prior to 10.14 (Mojave).
592 The font dilation (stem darkening) parameters were controlled by the
593 AppleFontSmoothing setting, ranging from 1 to 3 (light to strong).
594
595 On Mojave it is no longer supported, but can be triggered by a legacy
596 override (CGFontRenderingFontSmoothingDisabled=NO), so we need to
597 still account for it, otherwise users will have a bad time.
598
599 - Grayscale: Font smoothing is applied, but does not affect subpixels.
600
601 This is the default mode on macOS 10.14 (Mojave). The font dilation
602 (stem darkening) parameters are not affected by the AppleFontSmoothing
603 setting, but are instead computed based on the fill color used when
604 drawing the glyphs (white fill gives a lighter dilation than black
605 fill). This affects how we build our glyph cache, since we produce
606 alpha maps by drawing white on black.
607*/
609{
610 static const FontSmoothing cachedFontSmoothing = [] {
611 static const int kSize = 10;
612 QCFType<CTFontRef> font = CTFontCreateWithName(CFSTR("Helvetica"), kSize, nullptr);
613
614 UniChar character('X'); CGGlyph glyph;
615 CTFontGetGlyphsForCharacters(font, &character, &glyph, 1);
616
617 auto drawGlyph = [&](bool smooth) -> QImage {
618 QImage image(kSize, kSize, QImage::Format_RGB32);
619 image.fill(0);
620
622 CGContextSetTextDrawingMode(ctx, kCGTextFill);
623 CGContextSetGrayFillColor(ctx, 1, 1);
624
625 // Will be ignored if CGContextSetAllowsFontSmoothing() has been
626 // set to false by CoreGraphics based on user defaults.
627 CGContextSetShouldSmoothFonts(ctx, smooth);
628
629 CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
630 return image;
631 };
632
633 QImage nonSmoothed = drawGlyph(false);
634 QImage smoothed = drawGlyph(true);
635
637 [&] {
638 for (int x = 0; x < kSize; ++x) {
639 for (int y = 0; y < kSize; ++y) {
640 QRgb sp = smoothed.pixel(x, y);
641 if (qRed(sp) != qGreen(sp) || qRed(sp) != qBlue(sp)) {
643 return;
644 }
645
646 if (sp != nonSmoothed.pixel(x, y))
648 }
649 }
650 }();
651
652 auto defaults = [NSUserDefaults standardUserDefaults];
653 qCDebug(lcQpaFonts) << "Resolved font smoothing algorithm. Defaults ="
654 << [[defaults dictionaryRepresentation] dictionaryWithValuesForKeys:@[
655 @"AppleFontSmoothing",
656 @"CGFontRenderingFontSmoothingDisabled"
657 ]] << "Result =" << fontSmoothing;
658
659 return fontSmoothing;
660 }();
661
662 return cachedFontSmoothing;
663}
664
669
671{
672 if (hasColorGlyphs())
673 return false;
674
675 if (!shouldAntialias())
676 return false;
677
678 switch (fontSmoothing()) {
679 case Disabled: return false;
681 case Grayscale: return true;
682 }
683
684 Q_UNREACHABLE();
685}
686
691
696
698{
699 glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);
700
702 QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
703 if (!im.width() || !im.height())
704 return im;
705
706 QCFType<CGColorSpaceRef> colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
707 QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
708 8, im.bytesPerLine(), colorspace,
710 Q_ASSERT(ctx);
711
712 CGContextSetShouldAntialias(ctx, shouldAntialias());
713
714 const bool shouldSmooth = shouldSmoothFont();
715 CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
716
717#if defined(Q_OS_MACOS)
718 auto glyphColor = [&] {
719 if (shouldSmooth && fontSmoothing() == Grayscale) {
720 // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
721 // its dilation (stem darkening) parameters based on the fill color. This means our
722 // default approach of drawing white on black to produce the alpha map will result
723 // in non-native looking text when then drawn as black on white during the final blit.
724 // As a workaround we use the application's current appearance to decide whether to
725 // draw with white or black fill, and then invert the glyph image in the latter case,
726 // producing an alpha map. This covers the most common use-cases, but longer term we
727 // should propagate the fill color all the way from the paint engine, and include it
728 // in the key for the glyph cache.
729
730 if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
731 if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
732 return kCGColorBlack;
733 }
734 }
735 return kCGColorWhite;
736 }();
737
738 const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
739 if (blackOnWhiteGlyphs)
740 im.fill(Qt::white);
741 else
742#endif
743 im.fill(0);
744
745 CGContextSetFontSize(ctx, fontDef.pixelSize);
746
747 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
748
750 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
751
752 if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
753 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
754
755 if (matrix.isScaling())
756 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
757
758 CGGlyph cgGlyph = glyph;
759
760 qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
761 qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();
762
763 if (!hasColorGlyphs()) {
764 CGContextSetTextMatrix(ctx, cgMatrix);
765#if defined(Q_OS_MACOS)
766 CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
767#else
768 CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
769#endif
770 CGContextSetTextDrawingMode(ctx, kCGTextFill);
771 CGContextSetTextPosition(ctx, pos_x, pos_y);
772
773 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
774
776 qreal boldOffset = 0.5 * lineThickness().toReal();
777
778 qreal scale;
780 boldOffset *= scale;
781
782 CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
783 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
784 }
785 } else {
786 CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
787
788 // CGContextSetTextMatrix does not work with color glyphs, so we use
789 // the CTM instead. This means we must translate the CTM as well, to
790 // set the glyph position, instead of using CGContextSetTextPosition.
791 CGContextTranslateCTM(ctx, pos_x, pos_y);
792 CGContextConcatCTM(ctx, cgMatrix);
793
794 // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
795 // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
796 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
797 }
798
801
802#if defined(Q_OS_MACOS)
803 if (blackOnWhiteGlyphs)
804 im.invertPixels();
805#endif
806
807 return im;
808}
809
811{
812 return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
813}
814
816{
817 if (x.type() > QTransform::TxScale)
818 return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
819
820 QImage im = imageForGlyph(glyph, subPixelPosition, x);
821
822 QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
823
824 for (int y=0; y<im.height(); ++y) {
825 uint *src = (uint*) im.scanLine(y);
826 uchar *dst = alphaMap.scanLine(y);
827 for (int x=0; x<im.width(); ++x) {
828 *dst = qGray(*src);
829 ++dst;
830 ++src;
831 }
832 }
833
834 return alphaMap;
835}
836
838{
839 if (x.type() > QTransform::TxScale)
840 return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);
841
842 return imageForGlyph(glyph, subPixelPosition, x);
843}
844
846{
847 if (t.type() > QTransform::TxScale)
848 return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);
849
850 return imageForGlyph(glyph, subPixelPosition, t, color);
851}
852
853void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
854{
856
857 const int numGlyphs = glyphs->numGlyphs;
858 QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs);
859
860 for (int i = 0; i < numGlyphs; ++i) {
862 cgGlyphs[i] = glyphs->glyphs[i];
863 }
864
865 loadAdvancesForGlyphs(cgGlyphs, glyphs);
866}
867
868void QCoreTextFontEngine::loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const
869{
870 const int numGlyphs = glyphs->numGlyphs;
871 QVarLengthArray<CGSize> advances(numGlyphs);
872 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);
873
874 qreal stretch = fontDef.stretch != QFont::AnyStretch ? fontDef.stretch / 100.0 : 1.0;
875 for (int i = 0; i < numGlyphs; ++i)
876 glyphs->advances[i] = QFixed::fromReal(advances[i].width * stretch);
877}
878
883
884bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
885{
886 QVarLengthArray<CGGlyph> cgGlyphs(len);
887 return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
888}
889
894
896{
897 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
898
899 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
900 qreal scale = emSquare / CTFontGetSize(ctfont);
901 cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
902
903 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
905 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
906
907 *metric = boundingBox(glyph);
908 // scale the metrics too
909 metric->width = QFixed::fromReal(metric->width.toReal() * scale);
910 metric->height = QFixed::fromReal(metric->height.toReal() * scale);
911 metric->x = QFixed::fromReal(metric->x.toReal() * scale);
912 metric->y = QFixed::fromReal(metric->y.toReal() * scale);
913 metric->xoff = QFixed::fromReal(metric->xoff.toReal() * scale);
914 metric->yoff = QFixed::fromReal(metric->yoff.toReal() * scale);
915}
916
918{
919 return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
920}
921
923{
924 QFontDef newFontDef = fontDef;
925 newFontDef.pixelSize = pixelSize;
926 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
927
928 return new QCoreTextFontEngine(cgFont, newFontDef);
929}
930
932{
933 return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
934}
935
937{
938 if (transform.type() < QTransform::TxScale)
939 return true;
940 else if (transform.type() == QTransform::TxScale &&
941 transform.m11() >= 0 && transform.m22() >= 0)
942 return true;
943 else
944 return false;
945}
946
951
956
958{
960
961 QCFString psName, copyright;
962 psName = CTFontCopyPostScriptName(ctfont);
963 copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
964 result.postscriptName = QString::fromCFString(psName).toUtf8();
965 result.copyright = QString::fromCFString(copyright).toUtf8();
966
967 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
968 qreal scale = emSquare / CTFontGetSize(ctfont);
969
970 CGRect cgRect = CTFontGetBoundingBox(ctfont);
971 result.boundingBox = QRectF(cgRect.origin.x * scale,
972 -CTFontGetAscent(ctfont) * scale,
973 cgRect.size.width * scale,
974 cgRect.size.height * scale);
975
976 result.emSquare = emSquareSize();
977 result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
978 result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
979 result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
980 result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
981 result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
982 result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);
983
984 return result;
985}
986
988{
989 if (!kerningPairsLoaded) {
990 kerningPairsLoaded = true;
991 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
992 qreal scale = emSquare / CTFontGetSize(ctfont);
993
995 }
996
998}
999
\inmodule QtCore
Definition qbytearray.h:57
static QCFType constructFromGet(const T &t)
\inmodule QtCore
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
bool canRender(const QChar *string, int len) const override
Qt::HANDLE handle() const override
glyph_t glyphIndex(uint ucs4) const override
void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path, QTextItem::RenderFlags) override
static qreal fontSmoothingGamma()
QCFType< CTFontRef > ctfont
QFixed emSquareSize() const override
QFixed capHeight() const override
QImage alphaMapForGlyph(glyph_t, const QFixedPoint &subPixelPosition) override
static FontSmoothing fontSmoothing()
void loadAdvancesForGlyphs(QVarLengthArray< CGGlyph > &cgGlyphs, QGlyphLayout *glyphs) const
QFixed averageCharWidth() const override
QImage imageForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &m, const QColor &color=QColor())
glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, const QFixedPoint &, const QTransform &matrix, GlyphFormat) override
static QCoreTextFontEngine * create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference, const QMap< QFont::Tag, float > &variableAxisValue)
bool getSfntTableData(uint, uchar *, uint *) const override
Returns true if the font table idetified by tag exists in the font; returns false otherwise.
QImage alphaRGBMapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t) override
int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override
QFixed lineThickness() const override
void initializeHeightMetrics() const override
QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
static bool ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
QFontEngine::FaceId face_id
QCFType< CGFontRef > cgFont
void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
static QFont::Weight qtWeightFromCFWeight(float value)
qreal maxCharWidth() const override
glyph_metrics_t boundingBox(glyph_t glyph) override
bool supportsTransformation(const QTransform &transform) const override
void recalcAdvances(QGlyphLayout *, ShaperFlags) const override
bool expectsGammaCorrectedBlending() const override
FaceId faceId() const override
QFontEngine::Properties properties() const override
QFixed underlinePosition() const override
void doKerning(QGlyphLayout *g, ShaperFlags flags) const override
void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) override
QFixed xHeight() const override
QFontEngine * cloneWithSize(qreal pixelSize) const override
QImage bitmapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color) override
QCoreTextRawFontEngine(CGFontRef font, const QFontDef &def, const QByteArray &fontData)
QFontEngine * cloneWithSize(qreal pixelSize) const
static uchar highByte(glyph_t glyph)
void loadKerningPairs(QFixed scalingFactor)
QFixed calculatedCapHeight() const
virtual QImage bitmapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color=QColor())
virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, const QFixedPoint &, const QTransform &matrix, GlyphFormat)
bool m_heightMetricsQueried
static bool isIgnorableChar(char32_t ucs4)
QFontDef fontDef
virtual QImage alphaMapForGlyph(glyph_t)
virtual QImage alphaRGBMapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t)
QByteArray getSfntTable(uint tag) const
virtual QFixed averageCharWidth() const
virtual void doKerning(QGlyphLayout *, ShaperFlags) const
void getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, QVarLengthArray< glyph_t > &glyphs_out, QVarLengthArray< QFixedPoint > &positions)
GlyphFormat glyphFormat
HintingPreference
Definition qfont.h:55
@ AnyStretch
Definition qfont.h:84
@ NoSubpixelAntialias
Definition qfont.h:48
@ NoAntialias
Definition qfont.h:47
Weight
Qt uses a weighting scale from 1 to 1000 compatible with OpenType.
Definition qfont.h:63
@ DemiBold
Definition qfont.h:69
@ Thin
Definition qfont.h:64
@ ExtraBold
Definition qfont.h:71
@ Black
Definition qfont.h:72
@ Bold
Definition qfont.h:70
@ ExtraLight
Definition qfont.h:65
@ Normal
Definition qfont.h:67
@ Light
Definition qfont.h:66
@ Medium
Definition qfont.h:68
@ StyleItalic
Definition qfont.h:78
@ StyleNormal
Definition qfont.h:77
@ StyleOblique
Definition qfont.h:79
static QPlatformTheme * platformTheme()
\inmodule QtGui
Definition qimage.h:37
qsizetype bytesPerLine() const
Returns the number of bytes per image scanline.
Definition qimage.cpp:1560
uchar * scanLine(int)
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1637
QRgb pixel(int x, int y) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qimage.cpp:2493
int width() const
Returns the width of the image.
uchar * bits()
Returns a pointer to the first pixel data.
Definition qimage.cpp:1698
int height() const
Returns the height of the image.
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_Alpha8
Definition qimage.h:65
@ Format_RGB32
Definition qimage.h:46
@ Format_ARGB32_Premultiplied
Definition qimage.h:48
void fill(uint pixel)
Fills the entire image with the given pixelValue.
Definition qimage.cpp:1758
void invertPixels(InvertMode=InvertRgb)
Inverts all pixel values in the image.
Definition qimage.cpp:1987
\inmodule QtGui
void moveTo(const QPointF &p)
Moves the current point to the given point, implicitly starting a new subpath and closing the previou...
\inmodule QtCore\reentrant
Definition qpoint.h:217
\inmodule QtCore\reentrant
Definition qrect.h:484
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
Internal QTextItem.
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QTransform & scale(qreal sx, qreal sy)
Scales the coordinate system by sx horizontally and sy vertically, and returns a reference to the mat...
QTransform & translate(qreal dx, qreal dy)
Moves the coordinate system dx along the x axis and dy along the y axis, and returns a reference to t...
EGLContext ctx
QString str
[2]
QSet< QString >::iterator it
rect
[4]
Combined button and popup list for selecting options.
@ white
Definition qnamespace.h:31
void * HANDLE
Definition image.cpp:4
#define Q_UNLIKELY(x)
QList< QString > QStringList
Constructs a string list that contains the given string, str.
AudioChannelLayoutTag tag
QT_USE_NAMESPACE QT_BEGIN_NAMESPACE CGBitmapInfo qt_mac_bitmapInfoForImage(const QImage &image)
static const QCssKnownValue positions[NumKnownPositionModes - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void * user_data
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
Q_GUI_EXPORT int qt_defaultDpi()
Definition qfont.cpp:140
static QT_BEGIN_NAMESPACE float SYNTHETIC_ITALIC_SKEW
CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale)
static void qcoretextfontengine_scaleMetrics(glyph_metrics_t &br, const QTransform &matrix)
static void convertCGPathToQPainterPath(void *info, const CGPathElement *element)
#define COMPARE_WEIGHT_DISTANCE(ct_weight, qt_weight)
CGAffineTransform Q_GUI_EXPORT qt_transform_from_fontdef(const QFontDef &fontDef)
void qGamma_correct_back_to_linear_cs(QImage *image)
#define qWarning
Definition qlogging.h:166
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLsizei const GLfloat * v
[13]
GLint GLint GLint GLint GLint x
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLenum GLsizei length
GLenum const void GLbitfield GLsizei numGlyphs
GLenum src
GLsizei GLsizei GLfloat distance
GLenum GLuint buffer
GLint GLsizei width
GLuint color
[2]
GLenum GLenum dst
GLbitfield flags
GLboolean GLboolean g
GLuint name
GLint GLsizei GLsizei GLenum format
GLenum GLsizeiptr const void * fontData
GLint y
GLuint GLenum GLenum transform
const GLubyte * c
GLuint GLenum matrix
GLdouble GLdouble t
Definition qopenglext.h:243
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLenum GLsizei len
GLenum GLenum GLenum GLenum GLenum scale
GLenum GLenum GLsizei void * table
Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale)
struct CGContext * CGContextRef
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
constexpr int qRed(QRgb rgb)
Definition qrgb.h:18
constexpr int qGreen(QRgb rgb)
Definition qrgb.h:21
constexpr int qGray(int r, int g, int b)
Definition qrgb.h:36
constexpr int qBlue(QRgb rgb)
Definition qrgb.h:24
#define sp
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
unsigned int glyph_t
#define Q_UNUSED(x)
unsigned char uchar
Definition qtypes.h:32
short qint16
Definition qtypes.h:47
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
double qreal
Definition qtypes.h:187
QFile defaults(defaultsPath)
QHostInfo info
[0]
ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos, qreal newStretch=1.0)
QFixed y
Definition qfixed_p.h:163
QFixed x
Definition qfixed_p.h:162
static constexpr QFixed fromReal(qreal r)
Definition qfixed_p.h:35
constexpr int toInt() const
Definition qfixed_p.h:41
constexpr QFixed ceil() const
Definition qfixed_p.h:47
constexpr qreal toReal() const
Definition qfixed_p.h:42
constexpr int truncate() const
Definition qfixed_p.h:44
uint stretch
Definition qfont_p.h:65
uint style
Definition qfont_p.h:66
uint styleStrategy
Definition qfont_p.h:64
qreal pixelSize
Definition qfont_p.h:61
QMap< QFont::Tag, float > variableAxisValues
Definition qfont_p.h:58
uint weight
Definition qfont_p.h:70
QStringList families
Definition qfont_p.h:54
QString styleName
Definition qfont_p.h:55
qreal pointSize
Definition qfont_p.h:60
QMap< QFont::Tag, float > variableAxes
The QFont::Tag type provides access to advanced font features.
Definition qfont.h:215
glyph_t * glyphs
QFixed * advances