1/* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24*/ 25 26namespace juce 27{ 28 29static constexpr float referenceFontSize = 1024.0f; 30 31static CTFontRef getCTFontFromTypeface (const Font&); 32 33namespace CoreTextTypeLayout 34{ 35 static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform) 36 { 37 auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName()); 38 auto style = font.getTypefaceStyle(); 39 40 if (! availableStyles.contains (style)) 41 { 42 if (font.isItalic()) // Fake-up an italic font if there isn't a real one. 43 requiredTransform = CGAffineTransformMake (1.0f, 0, 0.1f, 1.0f, 0, 0); 44 45 return availableStyles[0]; 46 } 47 48 return style; 49 } 50 51 static float getFontTotalHeight (CTFontRef font) 52 { 53 return std::abs ((float) CTFontGetAscent (font)) 54 + std::abs ((float) CTFontGetDescent (font)); 55 } 56 57 static float getHeightToPointsFactor (CTFontRef font) 58 { 59 return referenceFontSize / getFontTotalHeight (font); 60 } 61 62 static CTFontRef getFontWithPointSize (CTFontRef font, float size) 63 { 64 auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr); 65 CFRelease (font); 66 return newFont; 67 } 68 69 static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired) 70 { 71 auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString(); 72 auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString(); 73 CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute }; 74 CFTypeRef values[] = { cfFontFamily, cfFontStyle }; 75 76 auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, 77 (const void**) &values, 78 numElementsInArray (keys), 79 &kCFTypeDictionaryKeyCallBacks, 80 &kCFTypeDictionaryValueCallBacks); 81 CFRelease (cfFontStyle); 82 83 auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); 84 CFRelease (fontDescAttributes); 85 86 auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr); 87 CFRelease (ctFontDescRef); 88 CFRelease (cfFontFamily); 89 90 return ctFontRef; 91 } 92 93 //============================================================================== 94 struct Advances 95 { 96 Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run)) 97 { 98 if (advances == nullptr) 99 { 100 local.malloc (numGlyphs); 101 CTRunGetAdvances (run, CFRangeMake (0, 0), local); 102 advances = local; 103 } 104 } 105 106 const CGSize* advances; 107 HeapBlock<CGSize> local; 108 }; 109 110 struct Glyphs 111 { 112 Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run)) 113 { 114 if (glyphs == nullptr) 115 { 116 local.malloc (numGlyphs); 117 CTRunGetGlyphs (run, CFRangeMake (0, 0), local); 118 glyphs = local; 119 } 120 } 121 122 const CGGlyph* glyphs; 123 HeapBlock<CGGlyph> local; 124 }; 125 126 struct Positions 127 { 128 Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run)) 129 { 130 if (points == nullptr) 131 { 132 local.malloc (numGlyphs); 133 CTRunGetPositions (run, CFRangeMake (0, 0), local); 134 points = local; 135 } 136 } 137 138 const CGPoint* points; 139 HeapBlock<CGPoint> local; 140 }; 141 142 struct LineInfo 143 { 144 LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex) 145 { 146 CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin); 147 CTLineGetTypographicBounds (line, &ascent, &descent, &leading); 148 } 149 150 CGPoint origin; 151 CGFloat ascent, descent, leading; 152 }; 153 154 static CTFontRef getOrCreateFont (const Font& f) 155 { 156 if (auto ctf = getCTFontFromTypeface (f)) 157 { 158 CFRetain (ctf); 159 return ctf; 160 } 161 162 CGAffineTransform transform; 163 return createCTFont (f, referenceFontSize, transform); 164 } 165 166 //============================================================================== 167 static CTTextAlignment getTextAlignment (const AttributedString& text) 168 { 169 switch (text.getJustification().getOnlyHorizontalFlags()) 170 { 171 #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 172 case Justification::right: return kCTTextAlignmentRight; 173 case Justification::horizontallyCentred: return kCTTextAlignmentCenter; 174 case Justification::horizontallyJustified: return kCTTextAlignmentJustified; 175 default: return kCTTextAlignmentLeft; 176 #else 177 case Justification::right: return kCTRightTextAlignment; 178 case Justification::horizontallyCentred: return kCTCenterTextAlignment; 179 case Justification::horizontallyJustified: return kCTJustifiedTextAlignment; 180 default: return kCTLeftTextAlignment; 181 #endif 182 } 183 } 184 185 static CTLineBreakMode getLineBreakMode (const AttributedString& text) 186 { 187 switch (text.getWordWrap()) 188 { 189 case AttributedString::none: return kCTLineBreakByClipping; 190 case AttributedString::byChar: return kCTLineBreakByCharWrapping; 191 case AttributedString::byWord: 192 default: return kCTLineBreakByWordWrapping; 193 } 194 } 195 196 static CTWritingDirection getWritingDirection (const AttributedString& text) 197 { 198 switch (text.getReadingDirection()) 199 { 200 case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft; 201 case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight; 202 case AttributedString::natural: 203 default: return kCTWritingDirectionNatural; 204 } 205 } 206 207 //============================================================================== 208 static CFAttributedStringRef createCFAttributedString (const AttributedString& text) 209 { 210 #if JUCE_IOS 211 auto rgbColourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB); 212 #endif 213 214 auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); 215 auto cfText = text.getText().toCFString(); 216 CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText); 217 CFRelease (cfText); 218 219 auto numCharacterAttributes = text.getNumAttributes(); 220 auto attribStringLen = CFAttributedStringGetLength (attribString); 221 222 for (int i = 0; i < numCharacterAttributes; ++i) 223 { 224 auto& attr = text.getAttribute (i); 225 auto rangeStart = attr.range.getStart(); 226 227 if (rangeStart >= attribStringLen) 228 continue; 229 230 auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart); 231 232 if (auto ctFontRef = getOrCreateFont (attr.font)) 233 { 234 ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef)); 235 CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef); 236 237 if (attr.font.isUnderlined()) 238 { 239 auto underline = kCTUnderlineStyleSingle; 240 241 auto numberRef = CFNumberCreate (nullptr, kCFNumberIntType, &underline); 242 CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef); 243 CFRelease (numberRef); 244 } 245 246 auto extraKerning = attr.font.getExtraKerningFactor(); 247 248 if (extraKerning != 0) 249 { 250 extraKerning *= attr.font.getHeight(); 251 252 auto numberRef = CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning); 253 CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef); 254 CFRelease (numberRef); 255 } 256 257 CFRelease (ctFontRef); 258 } 259 260 { 261 auto col = attr.colour; 262 263 #if JUCE_IOS 264 const CGFloat components[] = { col.getFloatRed(), 265 col.getFloatGreen(), 266 col.getFloatBlue(), 267 col.getFloatAlpha() }; 268 auto colour = CGColorCreate (rgbColourSpace, components); 269 #else 270 auto colour = CGColorCreateGenericRGB (col.getFloatRed(), 271 col.getFloatGreen(), 272 col.getFloatBlue(), 273 col.getFloatAlpha()); 274 #endif 275 276 CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour); 277 CGColorRelease (colour); 278 } 279 } 280 281 // Paragraph Attributes 282 auto ctTextAlignment = getTextAlignment (text); 283 auto ctLineBreakMode = getLineBreakMode (text); 284 auto ctWritingDirection = getWritingDirection (text); 285 CGFloat ctLineSpacing = text.getLineSpacing(); 286 287 CTParagraphStyleSetting settings[] = 288 { 289 { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment }, 290 { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode }, 291 { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection}, 292 { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing } 293 }; 294 295 auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)); 296 CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), 297 kCTParagraphStyleAttributeName, ctParagraphStyleRef); 298 CFRelease (ctParagraphStyleRef); 299 #if JUCE_IOS 300 CGColorSpaceRelease (rgbColourSpace); 301 #endif 302 return attribString; 303 } 304 305 static CTFramesetterRef createCTFramesetter (const AttributedString& text) 306 { 307 auto attribString = createCFAttributedString (text); 308 auto framesetter = CTFramesetterCreateWithAttributedString (attribString); 309 CFRelease (attribString); 310 311 return framesetter; 312 } 313 314 static CTFrameRef createCTFrame (CTFramesetterRef framesetter, CGRect bounds) 315 { 316 auto path = CGPathCreateMutable(); 317 CGPathAddRect (path, nullptr, bounds); 318 319 auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr); 320 CGPathRelease (path); 321 322 return frame; 323 } 324 325 static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds) 326 { 327 auto framesetter = createCTFramesetter (text); 328 auto frame = createCTFrame (framesetter, bounds); 329 CFRelease (framesetter); 330 331 return frame; 332 } 333 334 static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex) 335 { 336 LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex); 337 338 return { (float) (info.origin.y - info.descent), 339 (float) (info.origin.y + info.ascent) }; 340 } 341 342 static float findCTFrameHeight (CTFrameRef frame) 343 { 344 auto lines = CTFrameGetLines (frame); 345 auto numLines = CFArrayGetCount (lines); 346 347 if (numLines == 0) 348 return 0; 349 350 auto range = getLineVerticalRange (frame, lines, 0); 351 352 if (numLines > 1) 353 range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1)); 354 355 return range.getLength(); 356 } 357 358 static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area, 359 const CGContextRef& context, float flipHeight) 360 { 361 auto framesetter = createCTFramesetter (text); 362 363 // Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly 364 // larger than the font height - otherwise the CTFrame will be invalid 365 366 CFRange fitrange; 367 auto suggestedSingleLineFrameSize = 368 CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake (0, 0), nullptr, 369 CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange); 370 auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height; 371 372 auto verticalJustification = text.getJustification().getOnlyVerticalFlags(); 373 374 const Rectangle<float> ctFrameArea = [area, minCTFrameHeight, verticalJustification] 375 { 376 if (minCTFrameHeight < area.getHeight()) 377 return Rectangle<float> (area); 378 379 if (verticalJustification == Justification::verticallyCentred) 380 return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight); 381 382 const Rectangle<float> frameArea = area.withHeight (minCTFrameHeight); 383 384 if (verticalJustification == Justification::bottom) 385 return frameArea.withBottomY (area.getBottom()); 386 387 return Rectangle<float> (frameArea); 388 }(); 389 390 auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), 391 (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight())); 392 CFRelease (framesetter); 393 394 auto textMatrix = CGContextGetTextMatrix (context); 395 CGContextSaveGState (context); 396 397 if (verticalJustification == Justification::verticallyCentred 398 || verticalJustification == Justification::bottom) 399 { 400 auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame); 401 402 if (verticalJustification == Justification::verticallyCentred) 403 adjust *= 0.5f; 404 405 CGContextTranslateCTM (context, 0, -adjust); 406 } 407 408 CTFrameDraw (frame, context); 409 410 CGContextRestoreGState (context); 411 CGContextSetTextMatrix (context, textMatrix); 412 413 CFRelease (frame); 414 } 415 416 static void createLayout (TextLayout& glyphLayout, const AttributedString& text) 417 { 418 auto boundsHeight = glyphLayout.getHeight(); 419 auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); 420 auto lines = CTFrameGetLines (frame); 421 auto numLines = CFArrayGetCount (lines); 422 423 glyphLayout.ensureStorageAllocated ((int) numLines); 424 425 for (CFIndex i = 0; i < numLines; ++i) 426 { 427 auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i); 428 auto runs = CTLineGetGlyphRuns (line); 429 auto numRuns = CFArrayGetCount (runs); 430 431 auto cfrlineStringRange = CTLineGetStringRange (line); 432 auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length; 433 Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); 434 435 LineInfo lineInfo (frame, line, i); 436 437 auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange, 438 Point<float> ((float) lineInfo.origin.x, 439 (float) (boundsHeight - lineInfo.origin.y)), 440 (float) lineInfo.ascent, 441 (float) lineInfo.descent, 442 (float) lineInfo.leading, 443 (int) numRuns); 444 445 for (CFIndex j = 0; j < numRuns; ++j) 446 { 447 auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j); 448 auto numGlyphs = CTRunGetGlyphCount (run); 449 auto runStringRange = CTRunGetStringRange (run); 450 451 auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location, 452 (int) (runStringRange.location + runStringRange.length - 1)), 453 (int) numGlyphs); 454 glyphLine->runs.add (glyphRun); 455 456 CFDictionaryRef runAttributes = CTRunGetAttributes (run); 457 458 CTFontRef ctRunFont; 459 if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont)) 460 { 461 auto cfsFontName = CTFontCopyPostScriptName (ctRunFont); 462 auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr); 463 CFRelease (cfsFontName); 464 465 auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef); 466 CFRelease (ctFontRef); 467 468 auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute); 469 auto cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute); 470 471 glyphRun->font = Font (String::fromCFString (cfsFontFamily), 472 String::fromCFString (cfsFontStyle), 473 (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); 474 475 auto isUnderlined = [&] 476 { 477 CFNumberRef underlineStyle; 478 479 if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle)) 480 { 481 if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID()) 482 { 483 int value = 0; 484 CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value); 485 486 return value != 0; 487 } 488 } 489 490 return false; 491 }(); 492 493 glyphRun->font.setUnderline (isUnderlined); 494 495 CFRelease (cfsFontStyle); 496 CFRelease (cfsFontFamily); 497 } 498 499 CGColorRef cgRunColor; 500 501 if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor) 502 && CGColorGetNumberOfComponents (cgRunColor) == 4) 503 { 504 auto* components = CGColorGetComponents (cgRunColor); 505 506 glyphRun->colour = Colour::fromFloatRGBA ((float) components[0], 507 (float) components[1], 508 (float) components[2], 509 (float) components[3]); 510 } 511 512 const Glyphs glyphs (run, (size_t) numGlyphs); 513 const Advances advances (run, numGlyphs); 514 const Positions positions (run, (size_t) numGlyphs); 515 516 for (CFIndex k = 0; k < numGlyphs; ++k) 517 glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> ((float) positions.points[k].x, 518 (float) positions.points[k].y), 519 (float) advances.advances[k].width)); 520 } 521 522 glyphLayout.addLine (std::move (glyphLine)); 523 } 524 525 CFRelease (frame); 526 } 527} 528 529 530//============================================================================== 531class OSXTypeface : public Typeface 532{ 533public: 534 OSXTypeface (const Font& font) 535 : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true) 536 { 537 ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform); 538 539 if (ctFontRef != nullptr) 540 { 541 fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); 542 initialiseMetrics(); 543 } 544 } 545 546 OSXTypeface (const void* data, size_t dataSize) 547 : Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize) 548 { 549 // We can't use CFDataCreate here as this triggers a false positive in ASAN 550 // so copy the data manually and use CFDataCreateWithBytesNoCopy 551 auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(), 552 (CFIndex) dataCopy.getSize(), kCFAllocatorNull); 553 auto provider = CGDataProviderCreateWithCFData (cfData); 554 CFRelease (cfData); 555 556 #if JUCE_IOS 557 // Workaround for a an obscure iOS bug which can cause the app to dead-lock 558 // when loading custom type faces. See: http://www.openradar.me/18778790 and 559 // http://stackoverflow.com/questions/40242370/app-hangs-in-simulator 560 [UIFont systemFontOfSize: 12]; 561 #endif 562 563 fontRef = CGFontCreateWithDataProvider (provider); 564 CGDataProviderRelease (provider); 565 566 if (fontRef != nullptr) 567 { 568 #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 569 if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11) 570 canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr); 571 #endif 572 573 ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr); 574 575 if (ctFontRef != nullptr) 576 { 577 if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey)) 578 { 579 name = String::fromCFString (fontName); 580 CFRelease (fontName); 581 } 582 583 if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey)) 584 { 585 style = String::fromCFString (fontStyle); 586 CFRelease (fontStyle); 587 } 588 589 initialiseMetrics(); 590 } 591 } 592 } 593 594 void initialiseMetrics() 595 { 596 auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); 597 auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); 598 auto ctTotalHeight = ctAscent + ctDescent; 599 600 ascent = ctAscent / ctTotalHeight; 601 unitsToHeightScaleFactor = 1.0f / ctTotalHeight; 602 pathTransform = AffineTransform::scale (unitsToHeightScaleFactor); 603 604 fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; 605 606 const short zero = 0; 607 auto numberRef = CFNumberCreate (nullptr, kCFNumberShortType, &zero); 608 609 CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; 610 CFTypeRef values[] = { ctFontRef, numberRef }; 611 attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, 612 (const void**) &values, numElementsInArray (keys), 613 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 614 CFRelease (numberRef); 615 } 616 617 ~OSXTypeface() override 618 { 619 if (attributedStringAtts != nullptr) 620 CFRelease (attributedStringAtts); 621 622 if (fontRef != nullptr) 623 { 624 #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 625 if (dataCopy.getSize() != 0) 626 CTFontManagerUnregisterGraphicsFont (fontRef, nullptr); 627 #endif 628 629 CGFontRelease (fontRef); 630 } 631 632 if (ctFontRef != nullptr) 633 CFRelease (ctFontRef); 634 } 635 636 float getAscent() const override { return ascent; } 637 float getDescent() const override { return 1.0f - ascent; } 638 float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; } 639 640 float getStringWidth (const String& text) override 641 { 642 float x = 0; 643 644 if (ctFontRef != nullptr && text.isNotEmpty()) 645 { 646 auto cfText = text.toCFString(); 647 auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); 648 CFRelease (cfText); 649 650 auto line = CTLineCreateWithAttributedString (attribString); 651 auto runArray = CTLineGetGlyphRuns (line); 652 653 for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) 654 { 655 auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); 656 auto length = CTRunGetGlyphCount (run); 657 658 const CoreTextTypeLayout::Advances advances (run, length); 659 660 for (int j = 0; j < length; ++j) 661 x += (float) advances.advances[j].width; 662 } 663 664 CFRelease (line); 665 CFRelease (attribString); 666 667 x *= unitsToHeightScaleFactor; 668 } 669 670 return x; 671 } 672 673 void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override 674 { 675 xOffsets.add (0); 676 677 if (ctFontRef != nullptr && text.isNotEmpty()) 678 { 679 float x = 0; 680 681 auto cfText = text.toCFString(); 682 auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); 683 CFRelease (cfText); 684 685 auto line = CTLineCreateWithAttributedString (attribString); 686 auto runArray = CTLineGetGlyphRuns (line); 687 688 for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) 689 { 690 auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); 691 auto length = CTRunGetGlyphCount (run); 692 693 const CoreTextTypeLayout::Advances advances (run, length); 694 const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length); 695 696 for (int j = 0; j < length; ++j) 697 { 698 x += (float) advances.advances[j].width; 699 xOffsets.add (x * unitsToHeightScaleFactor); 700 resultGlyphs.add (glyphs.glyphs[j]); 701 } 702 } 703 704 CFRelease (line); 705 CFRelease (attribString); 706 } 707 } 708 709 bool getOutlineForGlyph (int glyphNumber, Path& path) override 710 { 711 jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty 712 713 if (auto pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform)) 714 { 715 CGPathApply (pathRef, &path, pathApplier); 716 CFRelease (pathRef); 717 718 if (! pathTransform.isIdentity()) 719 path.applyTransform (pathTransform); 720 721 return true; 722 } 723 724 return false; 725 } 726 727 //============================================================================== 728 CGFontRef fontRef = {}; 729 CTFontRef ctFontRef = {}; 730 731 float fontHeightToPointsFactor = 1.0f; 732 CGAffineTransform renderingTransform = CGAffineTransformIdentity; 733 734 bool canBeUsedForLayout; 735 736private: 737 MemoryBlock dataCopy; 738 CFDictionaryRef attributedStringAtts = {}; 739 float ascent = 0, unitsToHeightScaleFactor = 0; 740 AffineTransform pathTransform; 741 742 static void pathApplier (void* info, const CGPathElement* element) 743 { 744 auto& path = *static_cast<Path*> (info); 745 auto* p = element->points; 746 747 switch (element->type) 748 { 749 case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; 750 case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; 751 case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, 752 (float) p[1].x, (float) -p[1].y); break; 753 case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, 754 (float) p[1].x, (float) -p[1].y, 755 (float) p[2].x, (float) -p[2].y); break; 756 case kCGPathElementCloseSubpath: path.closeSubPath(); break; 757 default: jassertfalse; break; 758 } 759 } 760 761 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface) 762}; 763 764CTFontRef getCTFontFromTypeface (const Font& f) 765{ 766 if (auto* tf = dynamic_cast<OSXTypeface*> (f.getTypeface())) 767 return tf->ctFontRef; 768 769 return {}; 770} 771 772StringArray Font::findAllTypefaceNames() 773{ 774 StringArray names; 775 776 #if JUCE_MAC 777 // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS 778 auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames(); 779 780 for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i) 781 { 782 auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i)); 783 784 if (! family.startsWithChar ('.')) // ignore fonts that start with a '.' 785 names.addIfNotAlreadyThere (family); 786 } 787 788 CFRelease (fontFamilyArray); 789 #else 790 auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr); 791 auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); 792 CFRelease (fontCollectionRef); 793 794 for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) 795 { 796 auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); 797 auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute); 798 799 names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily)); 800 801 CFRelease (cfsFontFamily); 802 } 803 804 CFRelease (fontDescriptorArray); 805 #endif 806 807 names.sort (true); 808 return names; 809} 810 811StringArray Font::findAllTypefaceStyles (const String& family) 812{ 813 if (FontStyleHelpers::isPlaceholderFamilyName (family)) 814 return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); 815 816 StringArray results; 817 818 auto cfsFontFamily = family.toCFString(); 819 CFStringRef keys[] = { kCTFontFamilyNameAttribute }; 820 CFTypeRef values[] = { cfsFontFamily }; 821 822 auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), 823 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 824 CFRelease (cfsFontFamily); 825 826 auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); 827 CFRelease (fontDescAttributes); 828 829 auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); 830 CFRelease (ctFontDescRef); 831 832 auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr); 833 CFRelease (fontFamilyArray); 834 835 auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); 836 CFRelease (fontCollectionRef); 837 838 if (fontDescriptorArray != nullptr) 839 { 840 for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) 841 { 842 auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); 843 auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute); 844 results.add (String::fromCFString (cfsFontStyle)); 845 CFRelease (cfsFontStyle); 846 } 847 848 CFRelease (fontDescriptorArray); 849 } 850 851 return results; 852} 853 854 855//============================================================================== 856Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return *new OSXTypeface (font); } 857Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) { return *new OSXTypeface (data, size); } 858 859void Typeface::scanFolderForFonts (const File& folder) 860{ 861 for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf")) 862 { 863 if (auto urlref = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true)) 864 { 865 CTFontManagerRegisterFontsForURL (urlref, kCTFontManagerScopeProcess, nullptr); 866 CFRelease (urlref); 867 } 868 } 869} 870 871struct DefaultFontNames 872{ 873 #if JUCE_IOS 874 String defaultSans { "Helvetica" }, 875 defaultSerif { "Times New Roman" }, 876 defaultFixed { "Courier New" }; 877 #else 878 String defaultSans { "Lucida Grande" }, 879 defaultSerif { "Times New Roman" }, 880 defaultFixed { "Menlo" }; 881 #endif 882}; 883 884Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) 885{ 886 static DefaultFontNames defaultNames; 887 888 auto newFont = font; 889 auto& faceName = font.getTypefaceName(); 890 891 if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans); 892 else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif); 893 else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed); 894 895 if (font.getTypefaceStyle() == getDefaultStyle()) 896 newFont.setTypefaceStyle ("Regular"); 897 898 return Typeface::createSystemTypefaceFor (newFont); 899} 900 901static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) 902{ 903 auto numCharacterAttributes = text.getNumAttributes(); 904 905 for (int i = 0; i < numCharacterAttributes; ++i) 906 { 907 if (auto tf = dynamic_cast<OSXTypeface*> (text.getAttribute(i).font.getTypeface())) 908 if (tf->canBeUsedForLayout) 909 continue; 910 911 return false; 912 } 913 914 return true; 915} 916 917bool TextLayout::createNativeLayout (const AttributedString& text) 918{ 919 if (canAllTypefacesBeUsedInLayout (text)) 920 { 921 CoreTextTypeLayout::createLayout (*this, text); 922 return true; 923 } 924 925 return false; 926} 927 928} // namespace juce 929