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