189void QCoreTextFontEngine::init()
195 QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
196 face_id.filename = QString::fromCFString(name).toUtf8();
197 face_id.variableAxes = fontDef.variableAxisValues;
199 QCFString family = CTFontCopyFamilyName(ctfont);
200 fontDef.families = QStringList(family);
202 QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
203 fontDef.styleName = styleName;
206 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
208 const auto requestedStyle = fontDef.style;
210 if (traits & kCTFontColorGlyphsTrait)
211 glyphFormat = QFontEngine::Format_ARGB;
212 else if (shouldSmoothFont() && fontSmoothing() == FontSmoothing::Subpixel)
213 glyphFormat = QFontEngine::Format_A32;
215 glyphFormat = QFontEngine::Format_A8;
217 static const auto getTraitValue = [](CFDictionaryRef allTraits, CFStringRef trait) ->
float {
218 if (CFDictionaryContainsKey(allTraits, trait)) {
219 CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait);
221 CFNumberGetValue(traitNum, kCFNumberFloatType, &v);
227 QCFType<CFDictionaryRef> allTraits = CTFontCopyTraits(ctfont);
228 int slant =
static_cast<
int>(getTraitValue(allTraits, kCTFontSlantTrait) * 500 + 500);
229 if (traits & kCTFontItalicTrait)
230 fontDef.style = QFont::StyleItalic;
231 else if (slant > 500)
232 fontDef.style = QFont::StyleOblique;
234 fontDef.style = QFont::StyleNormal;
236 if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait) && !qEnvironmentVariableIsSet(
"QT_NO_SYNTHESIZED_BOLD"))
237 synthesisFlags |= SynthesizedBold;
239 fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));
241 if (requestedStyle != QFont::StyleNormal && fontDef.style == QFont::StyleNormal && !qEnvironmentVariableIsSet(
"QT_NO_SYNTHESIZED_ITALIC"))
242 synthesisFlags |= SynthesizedItalic;
245 QByteArray os2Table = getSfntTable(QFont::Tag(
"OS/2").value());
246 unsigned emSize = CTFontGetUnitsPerEm(ctfont);
247 if (os2Table.size() >= 10) {
248 fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
250 qint16 width = qAbs(qFromBigEndian<qint16>(os2Table.constData() + 2));
251 avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize);
253 avgCharWidth = QFontEngine::averageCharWidth();
255 underlineThickness = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont));
256 underlinePos = -QFixed::fromReal(CTFontGetUnderlinePosition(ctfont));
258 cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;
260 kerningPairsLoaded =
false;
262 if (QCFType<CFArrayRef> variationAxes = CTFontCopyVariationAxes(ctfont)) {
263 CFIndex count = CFArrayGetCount(variationAxes);
264 for (CFIndex i = 0; i < count; ++i) {
265 CFDictionaryRef variationAxis = CFDictionaryRef(CFArrayGetValueAtIndex(variationAxes, i));
267 QFontVariableAxis fontVariableAxis;
268 if (CFNumberRef tagRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
269 kCTFontVariationAxisIdentifierKey)) {
271 CFNumberGetValue(tagRef, kCFNumberIntType, &tag);
272 if (
auto maybeTag = QFont::Tag::fromValue(tag))
273 fontVariableAxis.setTag(*maybeTag);
276 if (CFNumberRef minimumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
277 kCTFontVariationAxisMinimumValueKey)) {
279 CFNumberGetValue(minimumValueRef, kCFNumberFloatType, &minimumValue);
280 fontVariableAxis.setMinimumValue(minimumValue);
283 if (CFNumberRef maximumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
284 kCTFontVariationAxisMaximumValueKey)) {
286 CFNumberGetValue(maximumValueRef, kCFNumberFloatType, &maximumValue);
287 fontVariableAxis.setMaximumValue(maximumValue);
290 if (CFNumberRef defaultValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
291 kCTFontVariationAxisDefaultValueKey)) {
293 CFNumberGetValue(defaultValueRef, kCFNumberFloatType, &defaultValue);
294 fontVariableAxis.setDefaultValue(defaultValue);
297 if (CFStringRef nameRef = (CFStringRef) CFDictionaryGetValue(variationAxis,
298 kCTFontVariationAxisNameKey)) {
299 fontVariableAxis.setName(QString::fromCFString(nameRef));
302 variableAxisList.append(fontVariableAxis);
349int QCoreTextFontEngine::stringToCMap(
const QChar *str,
int len, QGlyphLayout *glyphs,
350 int *nglyphs, QFontEngine::ShaperFlags flags)
const
352 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
353 if (*nglyphs < len) {
358 QVarLengthArray<CGGlyph> cgGlyphs(len);
359 CTFontGetGlyphsForCharacters(ctfont, (
const UniChar*)str, cgGlyphs.data(), len);
362 int mappedGlyphs = 0;
363 QStringIterator it(str, str + len);
364 while (it.hasNext()) {
365 qsizetype idx = it.index();
366 char32_t ucs4 = it.next();
367 glyphs->glyphs[glyph_pos] = cgGlyphs[idx];
369 cgGlyphs[glyph_pos] = cgGlyphs[idx];
370 if (glyphs->glyphs[glyph_pos] != 0 || isIgnorableChar(ucs4))
375 *nglyphs = glyph_pos;
376 glyphs->numGlyphs = glyph_pos;
378 if (!(flags & GlyphIndicesOnly))
379 loadAdvancesForGlyphs(cgGlyphs, glyphs);
384glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph)
388 CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, 0, 1);
389 if (synthesisFlags & QFontEngine::SynthesizedItalic) {
390 rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW;
392 ret.width = QFixed::fromReal(rect.size.width);
393 ret.height = QFixed::fromReal(rect.size.height);
394 ret.x = QFixed::fromReal(rect.origin.x);
395 ret.y = -QFixed::fromReal(rect.origin.y) - ret.height;
397 CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, advances, 1);
398 ret.xoff = QFixed::fromReal(advances[0].width);
399 ret.yoff = QFixed::fromReal(advances[0].height);
449void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y,
const QTextItemInt &ti,
int paintDeviceHeight)
451 QVarLengthArray<QFixedPoint> positions;
452 QVarLengthArray<glyph_t> glyphs;
454 matrix.translate(x, y);
455 getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
456 if (glyphs.size() == 0)
459 CGContextSetFontSize(ctx, fontDef.pixelSize);
461 CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx);
463 CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight);
466 Q_UNUSED(CGAffineTransformConcat(cgMatrix, oldTextMatrix));
468 if (synthesisFlags & QFontEngine::SynthesizedItalic)
469 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
471 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
473 CGContextSetTextMatrix(ctx, cgMatrix);
475 CGContextSetTextDrawingMode(ctx, kCGTextFill);
477 QVarLengthArray<CGPoint> cgPositions(glyphs.size());
478 QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size());
479 const qreal firstX = positions[0].x.toReal();
480 const qreal firstY = positions[0].y.toReal();
481 for (
int i = 0; i < glyphs.size(); ++i) {
482 cgPositions[i].x = positions[i].x.toReal() - firstX;
483 cgPositions[i].y = firstY - positions[i].y.toReal();
484 cgGlyphs[i] = glyphs[i];
489 CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal());
490 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
492 if (synthesisFlags & QFontEngine::SynthesizedBold) {
493 QTransform matrix(cgMatrix.a, cgMatrix.b, cgMatrix.c, cgMatrix.d, cgMatrix.tx, cgMatrix.ty);
495 qreal boldOffset = 0.5 * lineThickness().toReal();
497 qt_scaleForTransform(matrix, &scale);
500 CGContextSetTextPosition(ctx,
501 positions[0].x.toReal() + boldOffset,
502 positions[0].y.toReal());
503 CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
506 CGContextSetTextMatrix(ctx, oldTextMatrix);
521 switch(element->type) {
522 case kCGPathElementMoveToPoint:
523 myInfo->path->moveTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
524 element->points[0].y + myInfo->pos.y());
526 case kCGPathElementAddLineToPoint:
527 myInfo->path->lineTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
528 element->points[0].y + myInfo->pos.y());
530 case kCGPathElementAddQuadCurveToPoint:
531 myInfo->path->quadTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
532 element->points[0].y + myInfo->pos.y(),
533 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
534 element->points[1].y + myInfo->pos.y());
536 case kCGPathElementAddCurveToPoint:
537 myInfo->path->cubicTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
538 element->points[0].y + myInfo->pos.y(),
539 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
540 element->points[1].y + myInfo->pos.y(),
541 (element->points[2].x * myInfo->stretch) + myInfo->pos.x(),
542 element->points[2].y + myInfo->pos.y());
544 case kCGPathElementCloseSubpath:
545 myInfo->path->closeSubpath();
548 qCWarning(lcQpaFonts) <<
"Unhandled path transform type: " << element->type;
553void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions,
int nGlyphs,
554 QPainterPath *path, QTextItem::RenderFlags)
556 if (hasColorGlyphs())
559 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
560 cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);
562 if (synthesisFlags & QFontEngine::SynthesizedItalic)
563 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
565 qreal stretch = fontDef.stretch ? qreal(fontDef.stretch) / 100 : 1.0;
566 for (
int i = 0; i < nGlyphs; ++i) {
567 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
568 ConvertPathInfo info(path, positions[i].toPointF(), stretch);
569 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
585glyph_metrics_t QCoreTextFontEngine::alphaMapBoundingBox(glyph_t glyph,
const QFixedPoint &subPixelPosition,
const QTransform &matrix, GlyphFormat format)
587 if (matrix.type() > QTransform::TxScale)
588 return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);
590 glyph_metrics_t br = boundingBox(glyph);
592 QTransform xform = matrix;
593 if (fontDef.stretch != 100 && fontDef.stretch != QFont::AnyStretch)
594 xform.scale(fontDef.stretch / 100.0, 1.0);
595 qcoretextfontengine_scaleMetrics(br, xform);
599 br.width = -br.width;
601 br.height = -br.height;
603 if (format == QFontEngine::Format_A8 || format == QFontEngine::Format_A32 || format == QFontEngine::Format_ARGB) {
683QCoreTextFontEngine::FontSmoothing QCoreTextFontEngine::fontSmoothing()
685 static const FontSmoothing cachedFontSmoothing = [] {
686 static const int kSize = 10;
687 QCFType<CTFontRef> font = CTFontCreateWithName(CFSTR(
"Helvetica"), kSize,
nullptr);
689 UniChar character(
'X'); CGGlyph glyph;
690 CTFontGetGlyphsForCharacters(font, &character, &glyph, 1);
692 auto drawGlyph = [&](
bool smooth) -> QImage {
693 QImage image(kSize, kSize, QImage::Format_RGB32);
696 QMacCGContext ctx(&image);
697 CGContextSetTextDrawingMode(ctx, kCGTextFill);
698 CGContextSetGrayFillColor(ctx, 1, 1);
702 CGContextSetShouldSmoothFonts(ctx, smooth);
704 CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
708 QImage nonSmoothed = drawGlyph(
false);
709 QImage smoothed = drawGlyph(
true);
711 FontSmoothing fontSmoothing = FontSmoothing::Disabled;
713 for (
int x = 0; x < kSize; ++x) {
714 for (
int y = 0; y < kSize; ++y) {
715 QRgb sp = smoothed.pixel(x, y);
716 if (qRed(sp) != qGreen(sp) || qRed(sp) != qBlue(sp)) {
717 fontSmoothing = FontSmoothing::Subpixel;
721 if (sp != nonSmoothed.pixel(x, y))
722 fontSmoothing = FontSmoothing::Grayscale;
727 auto defaults = [NSUserDefaults standardUserDefaults];
728 qCDebug(lcQpaFonts) <<
"Resolved font smoothing algorithm. Defaults ="
729 << [[defaults dictionaryRepresentation] dictionaryWithValuesForKeys:@[
730 @
"AppleFontSmoothing",
731 @
"CGFontRenderingFontSmoothingDisabled"
732 ]] <<
"Result =" << fontSmoothing;
734 return fontSmoothing;
737 return cachedFontSmoothing;
773QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph,
const QFixedPoint &subPixelPosition,
const QTransform &matrix,
const QColor &color)
775 glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);
777 QImage::Format imageFormat = hasColorGlyphs() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
778 QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
779 if (!im.width() || !im.height())
782 auto cgImageFormat = qt_mac_cgImageFormatForImage(im);
786 QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
787 cgImageFormat->bitsPerComponent, im.bytesPerLine(), cgImageFormat->colorSpace,
788 cgImageFormat->bitmapInfo);
791 CGContextSetShouldAntialias(ctx, shouldAntialias());
793 const bool shouldSmooth = shouldSmoothFont();
794 CGContextSetShouldSmoothFonts(ctx, shouldSmooth);
796#if defined(Q_OS_MACOS)
797 auto glyphColor = [&] {
798 if (shouldSmooth && fontSmoothing() == Grayscale) {
809 if (
auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
810 if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
811 return kCGColorBlack;
814 return kCGColorWhite;
817 const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
818 if (blackOnWhiteGlyphs)
824 CGContextSetFontSize(ctx, fontDef.pixelSize);
826 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
828 if (synthesisFlags & QFontEngine::SynthesizedItalic)
829 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));
831 if (!hasColorGlyphs())
832 cgMatrix = CGAffineTransformConcat(cgMatrix, transform);
834 if (matrix.isScaling())
835 cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));
837 CGGlyph cgGlyph = glyph;
839 qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
840 qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();
842 if (!hasColorGlyphs()) {
843 CGContextSetTextMatrix(ctx, cgMatrix);
844#if defined(Q_OS_MACOS)
845 CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
847 CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
849 CGContextSetTextDrawingMode(ctx, kCGTextFill);
850 CGContextSetTextPosition(ctx, pos_x, pos_y);
852 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
854 if (synthesisFlags & QFontEngine::SynthesizedBold) {
855 qreal boldOffset = 0.5 * lineThickness().toReal();
858 qt_scaleForTransform(matrix, &scale);
861 CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
862 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
865 CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());
870 CGContextTranslateCTM(ctx, pos_x, pos_y);
871 CGContextConcatCTM(ctx, cgMatrix);
875 CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
878 if (expectsGammaCorrectedBlending())
879 qGamma_correct_back_to_linear_cs(&im);
881#if defined(Q_OS_MACOS)
882 if (blackOnWhiteGlyphs)
894QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph,
const QFixedPoint &subPixelPosition,
const QTransform &x)
896 if (x.type() > QTransform::TxScale)
897 return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);
899 QImage im = imageForGlyph(glyph, subPixelPosition, x);
901 QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);
903 for (
int y=0; y<im.height(); ++y) {
904 uint *src = (uint*) im.scanLine(y);
905 uchar *dst = alphaMap.scanLine(y);
906 for (
int x=0; x<im.width(); ++x) {
974void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metric)
976 CGAffineTransform cgMatrix = CGAffineTransformIdentity;
978 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
979 qreal scale = emSquare / CTFontGetSize(ctfont);
980 cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);
982 QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
983 ConvertPathInfo info(path, QPointF(0,0));
984 CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
986 *metric = boundingBox(glyph);
988 metric->width = QFixed::fromReal(metric->width.toReal() * scale);
989 metric->height = QFixed::fromReal(metric->height.toReal() * scale);
990 metric->x = QFixed::fromReal(metric->x.toReal() * scale);
991 metric->y = QFixed::fromReal(metric->y.toReal() * scale);
992 metric->xoff = QFixed::fromReal(metric->xoff.toReal() * scale);
993 metric->yoff = QFixed::fromReal(metric->yoff.toReal() * scale);
1036QFontEngine::Properties QCoreTextFontEngine::properties()
const
1040 QCFString psName, copyright;
1041 psName = CTFontCopyPostScriptName(ctfont);
1042 copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
1043 result.postscriptName = QString::fromCFString(psName).toUtf8();
1044 result.copyright = QString::fromCFString(copyright).toUtf8();
1046 qreal emSquare = CTFontGetUnitsPerEm(ctfont);
1047 qreal scale = emSquare / CTFontGetSize(ctfont);
1049 CGRect cgRect = CTFontGetBoundingBox(ctfont);
1050 result.boundingBox = QRectF(cgRect.origin.x * scale,
1051 -CTFontGetAscent(ctfont) * scale,
1052 cgRect.size.width * scale,
1053 cgRect.size.height * scale);
1055 result.emSquare = emSquareSize();
1056 result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
1057 result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
1058 result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
1059 result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
1060 result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
1061 result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * 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)