Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

941 lines
38KB

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