Qt 6.x
The Qt SDK
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
16#include <cmath>
17
18#if defined(Q_OS_MACOS)
19#import <AppKit/AppKit.h>
20#endif
21
22#if defined(QT_PLATFORM_UIKIT)
23#import <UIKit/UIKit.h>
24#endif
25
26// These are available cross platform, exported as kCTFontWeightXXX from CoreText.framework,
27// but they are not documented and are not in public headers so are private API and exposed
28// only through the NSFontWeightXXX and UIFontWeightXXX aliases in AppKit and UIKit (rdar://26109857)
29#if defined(Q_OS_MACOS)
30#define kCTFontWeightUltraLight NSFontWeightUltraLight
31#define kCTFontWeightThin NSFontWeightThin
32#define kCTFontWeightLight NSFontWeightLight
33#define kCTFontWeightRegular NSFontWeightRegular
34#define kCTFontWeightMedium NSFontWeightMedium
35#define kCTFontWeightSemibold NSFontWeightSemibold
36#define kCTFontWeightBold NSFontWeightBold
37#define kCTFontWeightHeavy NSFontWeightHeavy
38#define kCTFontWeightBlack NSFontWeightBlack
39#elif defined(QT_PLATFORM_UIKIT)
40#define kCTFontWeightUltraLight UIFontWeightUltraLight
41#define kCTFontWeightThin UIFontWeightThin
42#define kCTFontWeightLight UIFontWeightLight
43#define kCTFontWeightRegular UIFontWeightRegular
44#define kCTFontWeightMedium UIFontWeightMedium
45#define kCTFontWeightSemibold UIFontWeightSemibold
46#define kCTFontWeightBold UIFontWeightBold
47#define kCTFontWeightHeavy UIFontWeightHeavy
48#define kCTFontWeightBlack UIFontWeightBlack
49#endif
50
52
53static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);
54
56{
57 CTFontRef ctfont = *(CTFontRef *)user_data;
58
59 QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0);
60 if (!table)
61 return false;
62
63 CFIndex tableLength = CFDataGetLength(table);
64 if (buffer && int(*length) >= tableLength)
65 CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer);
66 *length = tableLength;
67 Q_ASSERT(int(*length) > 0);
68 return true;
69}
70
72{
73#define COMPARE_WEIGHT_DISTANCE(ct_weight, qt_weight) \
74 { \
75 float d; \
76 if ((d = qAbs(value - ct_weight)) < distance) { \
77 distance = d; \
78 ret = qt_weight; \
79 } \
80 }
81
82 float distance = qAbs(value - kCTFontWeightBlack);
84
85 // Compare distance to system weight to find the closest match.
86 // (Note: Must go from high to low, so that midpoints are rounded up)
87 COMPARE_WEIGHT_DISTANCE(kCTFontWeightHeavy, QFont::ExtraBold);
88 COMPARE_WEIGHT_DISTANCE(kCTFontWeightBold, QFont::Bold);
89 COMPARE_WEIGHT_DISTANCE(kCTFontWeightSemibold, QFont::DemiBold);
90 COMPARE_WEIGHT_DISTANCE(kCTFontWeightMedium, QFont::Medium);
91 COMPARE_WEIGHT_DISTANCE(kCTFontWeightRegular, QFont::Normal);
92 COMPARE_WEIGHT_DISTANCE(kCTFontWeightLight, QFont::Light);
94 COMPARE_WEIGHT_DISTANCE(kCTFontWeightUltraLight, QFont::Thin);
95
96#undef COMPARE_WEIGHT_DISTANCE
97
98 return ret;
99}
100
101CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
102{
103 CGAffineTransform transform = CGAffineTransformIdentity;
104 if (fontDef.stretch && fontDef.stretch != 100)
105 transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1);
106 return transform;
107}
108
109// Keeps font data alive until engine is disposed
111{
112public:
116 {}
118 {
119 QFontDef newFontDef = fontDef;
120 newFontDef.pixelSize = pixelSize;
121 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
122
123 return new QCoreTextRawFontEngine(cgFont, newFontDef, m_fontData);
124 }
126};
127
129{
130 Q_UNUSED(hintingPreference);
131
132 QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
133 QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithCFData(fontDataReference);
134
135 // Note: CTFontCreateWithGraphicsFont (which we call from the QCoreTextFontEngine
136 // constructor) has a bug causing it to retain the CGFontRef but never release it.
137 // The result is that we are leaking the CGFont, CGDataProvider, and CGData, but
138 // as the CGData is created from the raw QByteArray data, which we deref in the
139 // subclass above during destruction, we're at least not leaking the font data,
140 // (unless CoreText copies it internally). http://stackoverflow.com/questions/40805382/
141 QCFType<CGFontRef> cgFont = CGFontCreateWithDataProvider(dataProvider);
142
143 if (!cgFont) {
144 qWarning("QCoreTextFontEngine::create: CGFontCreateWithDataProvider failed");
145 return nullptr;
146 }
147
148 QFontDef def;
149 def.pixelSize = pixelSize;
150 def.pointSize = pixelSize * 72.0 / qt_defaultDpi();
151 return new QCoreTextRawFontEngine(cgFont, def, fontData);
152}
153
156{
158 cgFont = CTFontCopyGraphicsFont(font, nullptr);
159 init();
160}
161
164{
166 ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, nullptr);
167 init();
168}
169
171 : QFontEngine(Mac)
172{
173 fontDef = def;
175}
176
178{
179}
180
182{
185
186 face_id.index = 0;
187 QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
188 face_id.filename = QString::fromCFString(name).toUtf8();
189
190 QCFString family = CTFontCopyFamilyName(ctfont);
191 fontDef.families = QStringList(family);
192
193 QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
194 fontDef.styleName = styleName;
195
196 synthesisFlags = 0;
197 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
198
199 if (traits & kCTFontColorGlyphsTrait)
203 else
205
206 if (traits & kCTFontItalicTrait)
208
209 static const auto getTraitValue = [](CFDictionaryRef allTraits, CFStringRef trait) -> float {
210 if (CFDictionaryContainsKey(allTraits, trait)) {
211 CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait);
212 float v = 0;
213 CFNumberGetValue(traitNum, kCFNumberFloatType, &v);
214 return v;
215 }
216 return 0;
217 };
218
219 QCFType<CFDictionaryRef> allTraits = CTFontCopyTraits(ctfont);
220 int slant = static_cast<int>(getTraitValue(allTraits, kCTFontSlantTrait) * 500 + 500);
221 if (slant > 500 && !(traits & kCTFontItalicTrait))
223
224 if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD"))
226 else
227 fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));
228
229 if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"))
231
232 avgCharWidth = 0;
233 QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
234 unsigned emSize = CTFontGetUnitsPerEm(ctfont);
235 if (os2Table.size() >= 10) {
236 fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
237 // qAbs is a workaround for weird fonts like Lucida Grande
238 qint16 width = qAbs(qFromBigEndian<qint16>(os2Table.constData() + 2));
240 } else
242
243 underlineThickness = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont));
244 underlinePos = -QFixed::fromReal(CTFontGetUnderlinePosition(ctfont));
245
246 cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;
247
248 kerningPairsLoaded = false;
249}
250
252{
253 int len = 0;
254
255 QChar str[2];
257 str[len++] = QChar(QChar::highSurrogate(ucs4));
258 str[len++] = QChar(QChar::lowSurrogate(ucs4));
259 } else {
260 str[len++] = QChar(ucs4);
261 }
262
263 CGGlyph glyphIndices[2];
264
265 CTFontGetGlyphsForCharacters(ctfont, (const UniChar *)str, glyphIndices, len);
266
267 return glyphIndices[0];
268}
269
271 int *nglyphs, QFontEngine::ShaperFlags flags) const
272{
273 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
274 if (*nglyphs < len) {
275 *nglyphs = len;
276 return false;
277 }
278
280 CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);
281
282 int glyph_pos = 0;
283 for (int i = 0; i < len; ++i) {
284 glyphs->glyphs[glyph_pos] = cgGlyphs[i];
285 if (glyph_pos < i)
286 cgGlyphs[glyph_pos] = cgGlyphs[i];
287 glyph_pos++;
288
289 // If it's a non-BMP char, skip the lower part of surrogate pair and go
290 // directly to the next char without increasing glyph_pos
291 if (str[i].isHighSurrogate() && i < len-1 && str[i+1].isLowSurrogate())
292 ++i;
293 }
294
295 *nglyphs = glyph_pos;
296 glyphs->numGlyphs = glyph_pos;
297
298 if (!(flags & GlyphIndicesOnly))
299 loadAdvancesForGlyphs(cgGlyphs, glyphs);
300
301 return true;
302}
303
305{
307 CGGlyph g = glyph;
308 CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, 0, 1);
310 rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW;
311 }
312 ret.width = QFixed::fromReal(rect.size.width);
313 ret.height = QFixed::fromReal(rect.size.height);
314 ret.x = QFixed::fromReal(rect.origin.x);
315 ret.y = -QFixed::fromReal(rect.origin.y) - ret.height;
316 CGSize advances[1];
317 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, advances, 1);
318 ret.xoff = QFixed::fromReal(advances[0].width);
319 ret.yoff = QFixed::fromReal(advances[0].height);
320
321 return ret;
322}
323
325{
326 m_ascent = QFixed::fromReal(CTFontGetAscent(ctfont));
327 m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
328 m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
329
331}
332
334{
335 QFixed c = QFixed::fromReal(CTFontGetCapHeight(ctfont));
336 if (c <= 0)
337 return calculatedCapHeight();
338
339 return c;
340}
341
343{
344 return QFixed::fromReal(CTFontGetXHeight(ctfont));
345}
346
348{
349 return avgCharWidth;
350}
351
353{
354 // ### FIXME: 'W' might not be the widest character, but this is better than nothing
355 const glyph_t glyph = glyphIndex('W');
356 glyph_metrics_t bb = const_cast<QCoreTextFontEngine *>(this)->boundingBox(glyph);
357 return bb.xoff.toReal();
358}
359
361{
363}
364
365Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
366void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
367{
371 matrix.translate(x, y);
373 if (glyphs.size() == 0)
374 return;
375
376 CGContextSetFontSize(ctx, fontDef.pixelSize);
377
378 CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx);
379
380 CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight);
381
382 // FIXME: Should we include the old matrix here? If so we need to assign it.
383 Q_UNUSED(CGAffineTransformConcat(cgMatrix, oldTextMatrix));
384
386 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
387
388 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
389
390 CGContextSetTextMatrix(ctx, cgMatrix);
391
392 CGContextSetTextDrawingMode(ctx, kCGTextFill);
393
394 QVarLengthArray<CGPoint> cgPositions(glyphs.size());
395 QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size());
396 const qreal firstX = positions[0].x.toReal();
397 const qreal firstY = positions[0].y.toReal();
398 for (int i = 0; i < glyphs.size(); ++i) {
399 cgPositions[i].x = positions[i].x.toReal() - firstX;
400 cgPositions[i].y = firstY - positions[i].y.toReal();
401 cgGlyphs[i] = glyphs[i];
402 }
403
404 //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont));
405
406 CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal());
407 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
408
410 QTransform matrix(cgMatrix.a, cgMatrix.b, cgMatrix.c, cgMatrix.d, cgMatrix.tx, cgMatrix.ty);
411
412 qreal boldOffset = 0.5 * lineThickness().toReal();
413 qreal scale;
415 boldOffset *= scale;
416
417 CGContextSetTextPosition(ctx,
418 positions[0].x.toReal() + boldOffset,
419 positions[0].y.toReal());
420 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
421 }
422
423 CGContextSetTextMatrix(ctx, oldTextMatrix);
424}
425
427{
428 ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos, qreal newStretch = 1.0) :
429 path(newPath), pos(newPos), stretch(newStretch) {}
433};
434
435static void convertCGPathToQPainterPath(void *info, const CGPathElement *element)
436{
437 ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info);
438 switch(element->type) {
439 case kCGPathElementMoveToPoint:
440 myInfo->path->moveTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
441 element->points[0].y + myInfo->pos.y());
442 break;
443 case kCGPathElementAddLineToPoint:
444 myInfo->path->lineTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
445 element->points[0].y + myInfo->pos.y());
446 break;
447 case kCGPathElementAddQuadCurveToPoint:
448 myInfo->path->quadTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
449 element->points[0].y + myInfo->pos.y(),
450 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
451 element->points[1].y + myInfo->pos.y());
452 break;
453 case kCGPathElementAddCurveToPoint:
454 myInfo->path->cubicTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
455 element->points[0].y + myInfo->pos.y(),
456 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
457 element->points[1].y + myInfo->pos.y(),
458 (element->points[2].x * myInfo->stretch) + myInfo->pos.x(),
459 element->points[2].y + myInfo->pos.y());
460 break;
461 case kCGPathElementCloseSubpath:
462 myInfo->path->closeSubpath();
463 break;
464 default:
465 qCWarning(lcQpaFonts) << "Unhandled path transform type: " << element->type;
466 }
467
468}
469
471 QPainterPath *path, QTextItem::RenderFlags)
472{
473 if (hasColorGlyphs())
474 return; // We can't convert color-glyphs to path
475
476 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
477 cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);
478
480 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
481
482 qreal stretch = fontDef.stretch ? qreal(fontDef.stretch) / 100 : 1.0;
483 for (int i = 0; i < nGlyphs; ++i) {
484 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
485 ConvertPathInfo info(path, positions[i].toPointF(), stretch);
486 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
487 }
488}
489
491{
492 if (matrix.isScaling()) {
493 qreal hscale = matrix.m11();
494 qreal vscale = matrix.m22();
495 br.width = QFixed::fromReal(br.width.toReal() * hscale);
496 br.height = QFixed::fromReal(br.height.toReal() * vscale);
497 br.x = QFixed::fromReal(br.x.toReal() * hscale);
498 br.y = QFixed::fromReal(br.y.toReal() * vscale);
499 }
500}
501
503{
504 if (matrix.type() > QTransform::TxScale)
505 return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);
506
507 glyph_metrics_t br = boundingBox(glyph);
508
511 xform.scale(fontDef.stretch / 100.0, 1.0);
513
514 // Normalize width and height
515 if (br.width < 0)
516 br.width = -br.width;
517 if (br.height < 0)
518 br.height = -br.height;
519
521 // Drawing a glyph at x-position 0 with anti-aliasing enabled
522 // will potentially fill the pixel to the left of 0, as the
523 // coordinates are not aligned to the center of pixels. To
524 // prevent clipping of this pixel we need to shift the glyph
525 // in the bitmap one pixel to the right. The shift needs to
526 // be reflected in the glyph metrics as well, so that the final
527 // position of the glyph is correct, which is why doing the
528 // shift in imageForGlyph() is not enough.
529 br.x -= 1;
530
531 // As we've shifted the glyph one pixel to the right, we need
532 // to expand the width of the alpha map bounding box as well.
533 br.width += 1;
534
535 // But we have the same anti-aliasing problem on the right
536 // hand side of the glyph, eg. if the width of the glyph
537 // results in the bounding rect landing between two pixels.
538 // We pad the bounding rect again to account for the possible
539 // anti-aliased drawing.
540 br.width += 1;
541
542 // We also shift the glyph to right right based on the subpixel
543 // position, so we pad the bounding box to take account for the
544 // subpixel positions that may result in the glyph being drawn
545 // one pixel to the right of the 0-subpixel position.
546 br.width += 1;
547
548 // The same same logic as for the x-position needs to be applied
549 // to the y-position, except we don't need to compensate for
550 // the subpixel positioning.
551 br.y -= 1;
552 br.height += 2;
553 }
554
555 return br;
556}
557
558/*
559 Apple has gone through many iterations of its font smoothing algorithms,
560 and there are many ways to enable or disable certain aspects of it. As
561 keeping up with all the different toggles and behavior differences between
562 macOS versions is tricky, we resort to rendering a single glyph in a few
563 configurations, picking up the font smoothing algorithm from the observed
564 result.
565
566 The possible values are:
567
568 - Disabled: No font smoothing is applied.
569
570 Possibly triggered by the user unchecking the "Use font smoothing when
571 available" checkbox in the system preferences or setting AppleFontSmoothing
572 to 0. Also controlled by the CGContextSetAllowsFontSmoothing() API,
573 which gets its default from the settings above. This API overrides
574 the more granular CGContextSetShouldSmoothFonts(), which we use to
575 enable (request) or disable font smoothing.
576
577 Note that this does not exclude normal antialiasing, controlled by
578 the CGContextSetShouldAntialias() API.
579
580 - Subpixel: Font smoothing is applied, and affects subpixels.
581
582 This was the default mode on macOS versions prior to 10.14 (Mojave).
583 The font dilation (stem darkening) parameters were controlled by the
584 AppleFontSmoothing setting, ranging from 1 to 3 (light to strong).
585
586 On Mojave it is no longer supported, but can be triggered by a legacy
587 override (CGFontRenderingFontSmoothingDisabled=NO), so we need to
588 still account for it, otherwise users will have a bad time.
589
590 - Grayscale: Font smoothing is applied, but does not affect subpixels.
591
592 This is the default mode on macOS 10.14 (Mojave). The font dilation
593 (stem darkening) parameters are not affected by the AppleFontSmoothing
594 setting, but are instead computed based on the fill color used when
595 drawing the glyphs (white fill gives a lighter dilation than black
596 fill). This affects how we build our glyph cache, since we produce
597 alpha maps by drawing white on black.
598*/
600{
601 static const FontSmoothing cachedFontSmoothing = [] {
602 static const int kSize = 10;
603 QCFType<CTFontRef> font = CTFontCreateWithName(CFSTR("Helvetica"), kSize, nullptr);
604
605 UniChar character('X'); CGGlyph glyph;
606 CTFontGetGlyphsForCharacters(font, &character, &glyph, 1);
607
608 auto drawGlyph = [&](bool smooth) -> QImage {
609 QImage image(kSize, kSize, QImage::Format_RGB32);
610 image.fill(0);
611
613 CGContextSetTextDrawingMode(ctx, kCGTextFill);
614 CGContextSetGrayFillColor(ctx, 1, 1);
615
616 // Will be ignored if CGContextSetAllowsFontSmoothing() has been
617 // set to false by CoreGraphics based on user defaults.
618 CGContextSetShouldSmoothFonts(ctx, smooth);
619
620 CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
621 return image;
622 };
623
624 QImage nonSmoothed = drawGlyph(false);
625 QImage smoothed = drawGlyph(true);
626
628 [&] {
629 for (int x = 0; x < kSize; ++x) {
630 for (int y = 0; y < kSize; ++y) {
631 QRgb sp = smoothed.pixel(x, y);
632 if (qRed(sp) != qGreen(sp) || qRed(sp) != qBlue(sp)) {
634 return;
635 }
636
637 if (sp != nonSmoothed.pixel(x, y))
639 }
640 }
641 }();
642
643 auto defaults = [NSUserDefaults standardUserDefaults];
644 qCDebug(lcQpaFonts) << "Resolved font smoothing algorithm. Defaults ="
645 << [[defaults dictionaryRepresentation] dictionaryWithValuesForKeys:@[
646 @"AppleFontSmoothing",
647 @"CGFontRenderingFontSmoothingDisabled"
648 ]] << "Result =" << fontSmoothing;
649
650 return fontSmoothing;
651 }();
652
653 return cachedFontSmoothing;
654}
655
657{
659}
660
662{
663 if (hasColorGlyphs())
664 return false;
665
666 if (!shouldAntialias())
667 return false;
668
669 switch (fontSmoothing()) {
670 case Disabled: return false;
672 case Grayscale: return true;
673 }
674
675 Q_UNREACHABLE();
676}
677
679{
680 return shouldSmoothFont() && fontSmoothing() == Subpixel;
681}
682
684{
685 return 2.0;
686}
687
689{
690 glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);
691
693 QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
694 if (!im.width() || !im.height())
695 return im;
696
697 QCFType<CGColorSpaceRef> colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
698 QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
699 8, im.bytesPerLine(), colorspace,
701 Q_ASSERT(ctx);
702
703 CGContextSetShouldAntialias(ctx, shouldAntialias());
704
705 const bool shouldSmooth = shouldSmoothFont();
706 CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
707
708#if defined(Q_OS_MACOS)
709 auto glyphColor = [&] {
710 if (shouldSmooth && fontSmoothing() == Grayscale) {
711 // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
712 // its dilation (stem darkening) parameters based on the fill color. This means our
713 // default approach of drawing white on black to produce the alpha map will result
714 // in non-native looking text when then drawn as black on white during the final blit.
715 // As a workaround we use the application's current appearance to decide whether to
716 // draw with white or black fill, and then invert the glyph image in the latter case,
717 // producing an alpha map. This covers the most common use-cases, but longer term we
718 // should propagate the fill color all the way from the paint engine, and include it
719 //in the key for the glyph cache.
720
721 if (!qt_mac_applicationIsInDarkMode())
722 return kCGColorBlack;
723 }
724 return kCGColorWhite;
725 }();
726
727 const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
728 if (blackOnWhiteGlyphs)
729 im.fill(Qt::white);
730 else
731#endif
732 im.fill(0);
733
734 CGContextSetFontSize(ctx, fontDef.pixelSize);
735
736 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
737
739 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
740
741 if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
742 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
743
744 if (matrix.isScaling())
745 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
746
747 CGGlyph cgGlyph = glyph;
748
749 qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
750 qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();
751
752 if (!hasColorGlyphs()) {
753 CGContextSetTextMatrix(ctx, cgMatrix);
754#if defined(Q_OS_MACOS)
755 CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
756#else
757 CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
758#endif
759 CGContextSetTextDrawingMode(ctx, kCGTextFill);
760 CGContextSetTextPosition(ctx, pos_x, pos_y);
761
762 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
763
765 qreal boldOffset = 0.5 * lineThickness().toReal();
766
767 qreal scale;
769 boldOffset *= scale;
770
771 CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
772 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
773 }
774 } else {
775 CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
776
777 // CGContextSetTextMatrix does not work with color glyphs, so we use
778 // the CTM instead. This means we must translate the CTM as well, to
779 // set the glyph position, instead of using CGContextSetTextPosition.
780 CGContextTranslateCTM(ctx, pos_x, pos_y);
781 CGContextConcatCTM(ctx, cgMatrix);
782
783 // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
784 // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
785 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
786 }
787
790
791#if defined(Q_OS_MACOS)
792 if (blackOnWhiteGlyphs)
793 im.invertPixels();
794#endif
795
796 return im;
797}
798
800{
801 return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
802}
803
805{
806 if (x.type() > QTransform::TxScale)
807 return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
808
809 QImage im = imageForGlyph(glyph, subPixelPosition, x);
810
811 QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
812
813 for (int y=0; y<im.height(); ++y) {
814 uint *src = (uint*) im.scanLine(y);
815 uchar *dst = alphaMap.scanLine(y);
816 for (int x=0; x<im.width(); ++x) {
817 *dst = qGray(*src);
818 ++dst;
819 ++src;
820 }
821 }
822
823 return alphaMap;
824}
825
827{
828 if (x.type() > QTransform::TxScale)
829 return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);
830
831 return imageForGlyph(glyph, subPixelPosition, x);
832}
833
835{
836 if (t.type() > QTransform::TxScale)
837 return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);
838
839 return imageForGlyph(glyph, subPixelPosition, t, color);
840}
841
842void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
843{
845
846 const int numGlyphs = glyphs->numGlyphs;
848
849 for (int i = 0; i < numGlyphs; ++i) {
851 cgGlyphs[i] = glyphs->glyphs[i];
852 }
853
854 loadAdvancesForGlyphs(cgGlyphs, glyphs);
855}
856
858{
859 const int numGlyphs = glyphs->numGlyphs;
861 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);
862
863 qreal stretch = fontDef.stretch != QFont::AnyStretch ? fontDef.stretch / 100.0 : 1.0;
864 for (int i = 0; i < numGlyphs; ++i)
865 glyphs->advances[i] = QFixed::fromReal(advances[i].width * stretch);
866}
867
869{
870 return face_id;
871}
872
873bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
874{
876 return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
877}
878
880{
881 return ct_getSfntTable((void *)&ctfont, tag, buffer, length);
882}
883
885{
886 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
887
888 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
889 qreal scale = emSquare / CTFontGetSize(ctfont);
890 cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
891
892 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
894 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
895
896 *metric = boundingBox(glyph);
897 // scale the metrics too
898 metric->width = QFixed::fromReal(metric->width.toReal() * scale);
899 metric->height = QFixed::fromReal(metric->height.toReal() * scale);
900 metric->x = QFixed::fromReal(metric->x.toReal() * scale);
901 metric->y = QFixed::fromReal(metric->y.toReal() * scale);
902 metric->xoff = QFixed::fromReal(metric->xoff.toReal() * scale);
903 metric->yoff = QFixed::fromReal(metric->yoff.toReal() * scale);
904}
905
907{
908 return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
909}
910
912{
913 QFontDef newFontDef = fontDef;
914 newFontDef.pixelSize = pixelSize;
915 newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
916
917 return new QCoreTextFontEngine(cgFont, newFontDef);
918}
919
921{
922 return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
923}
924
926{
927 if (transform.type() < QTransform::TxScale)
928 return true;
929 else if (transform.type() == QTransform::TxScale &&
930 transform.m11() >= 0 && transform.m22() >= 0)
931 return true;
932 else
933 return false;
934}
935
937{
938 return underlineThickness;
939}
940
942{
943 return underlinePos;
944}
945
947{
949
950 QCFString psName, copyright;
951 psName = CTFontCopyPostScriptName(ctfont);
952 copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
953 result.postscriptName = QString::fromCFString(psName).toUtf8();
954 result.copyright = QString::fromCFString(copyright).toUtf8();
955
956 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
957 qreal scale = emSquare / CTFontGetSize(ctfont);
958
959 CGRect cgRect = CTFontGetBoundingBox(ctfont);
960 result.boundingBox = QRectF(cgRect.origin.x * scale,
961 -CTFontGetAscent(ctfont) * scale,
962 cgRect.size.width * scale,
963 cgRect.size.height * scale);
964
965 result.emSquare = emSquareSize();
966 result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
967 result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
968 result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
969 result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
970 result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
971 result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);
972
973 return result;
974}
975
977{
978 if (!kerningPairsLoaded) {
979 kerningPairsLoaded = true;
980 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
981 qreal scale = emSquare / CTFontGetSize(ctfont);
982
984 }
985
987}
988
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
static QCFType constructFromGet(const T &t)
\inmodule QtCore
Definition qchar.h:48
static constexpr bool requiresSurrogates(char32_t ucs4) noexcept
Returns true if the UCS-4-encoded character specified by ucs4 can be split into the high and low part...
Definition qchar.h:504
static constexpr char16_t highSurrogate(char32_t ucs4) noexcept
Returns the high surrogate part of a UCS-4-encoded code point.
Definition qchar.h:518
static constexpr char16_t lowSurrogate(char32_t ucs4) noexcept
Returns the low surrogate part of a UCS-4-encoded code point.
Definition qchar.h:522
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
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
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)
static QCoreTextFontEngine * create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
QFontEngine::FaceId face_id
QCFType< CGFontRef > cgFont
bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override
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
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:52
@ AnyStretch
Definition qfont.h:81
@ NoSubpixelAntialias
Definition qfont.h:46
@ NoAntialias
Definition qfont.h:45
Weight
Qt uses a weighting scale from 1 to 1000 compatible with OpenType.
Definition qfont.h:60
@ DemiBold
Definition qfont.h:66
@ Thin
Definition qfont.h:61
@ ExtraBold
Definition qfont.h:68
@ Black
Definition qfont.h:69
@ Bold
Definition qfont.h:67
@ ExtraLight
Definition qfont.h:62
@ Normal
Definition qfont.h:64
@ Light
Definition qfont.h:63
@ Medium
Definition qfont.h:65
@ StyleItalic
Definition qfont.h:75
@ StyleNormal
Definition qfont.h:74
@ StyleOblique
Definition qfont.h:76
\inmodule QtGui
Definition qimage.h:37
qsizetype bytesPerLine() const
Returns the number of bytes per image scanline.
Definition qimage.cpp:1538
uchar * scanLine(int)
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1615
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:2478
int width() const
Returns the width of the image.
uchar * bits()
Returns a pointer to the first pixel data.
Definition qimage.cpp:1677
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:1738
void invertPixels(InvertMode=InvertRgb)
Inverts all pixel values in the image.
Definition qimage.cpp:1970
\inmodule QtGui
void quadTo(const QPointF &ctrlPt, const QPointF &endPt)
Adds a quadratic Bezier curve between the current position and the given endPoint with the control po...
void moveTo(const QPointF &p)
Moves the current point to the given point, implicitly starting a new subpath and closing the previou...
void closeSubpath()
Closes the current subpath by drawing a line to the beginning of the subpath, automatically starting ...
void lineTo(const QPointF &p)
Adds a straight line from the current position to the given endPoint.
void cubicTo(const QPointF &ctrlPt1, const QPointF &ctrlPt2, const QPointF &endPt)
Adds a cubic Bezier curve between the current position and the given endPoint using the control point...
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
\inmodule QtCore\reentrant
Definition qrect.h:483
\inmodule QtCore
Internal QTextItem.
RenderFlags flags
QGlyphLayout glyphs
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...
constexpr size_type size() const noexcept
T * data() noexcept
EGLContext ctx
QString str
[2]
rect
[4]
Combined button and popup list for selecting options.
@ white
Definition qnamespace.h:30
void * HANDLE
Definition image.cpp:4
#define Q_UNLIKELY(x)
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:137
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)
#define MAKE_TAG(ch1, ch2, ch3, ch4)
void qGamma_correct_back_to_linear_cs(QImage *image)
#define qWarning
Definition qlogging.h:162
#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
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
GLenum GLsizei len
GLuint GLenum matrix
GLdouble GLdouble t
Definition qopenglext.h:243
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
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:27
short qint16
Definition qtypes.h:42
unsigned int uint
Definition qtypes.h:29
double qreal
Definition qtypes.h:92
QFileInfo info(fileName)
[8]
QFile defaults(defaultsPath)
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:64
uint style
Definition qfont_p.h:65
uint styleStrategy
Definition qfont_p.h:63
qreal pixelSize
Definition qfont_p.h:60
uint weight
Definition qfont_p.h:69
QStringList families
Definition qfont_p.h:54
QString styleName
Definition qfont_p.h:55
qreal pointSize
Definition qfont_p.h:59
glyph_t * glyphs
QFixed * advances