The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

902 lines
37KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. struct AttributedStringAndFontMap
  173. {
  174. CFUniquePtr<CFAttributedStringRef> string;
  175. std::map<CTFontRef, Font> fontMap;
  176. };
  177. static AttributedStringAndFontMap createCFAttributedString (const AttributedString& text)
  178. {
  179. std::map<CTFontRef, Font> fontMap;
  180. const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) };
  181. auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
  182. CFUniquePtr<CFStringRef> cfText (text.getText().toCFString());
  183. CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get());
  184. auto numCharacterAttributes = text.getNumAttributes();
  185. auto attribStringLen = CFAttributedStringGetLength (attribString);
  186. for (int i = 0; i < numCharacterAttributes; ++i)
  187. {
  188. auto& attr = text.getAttribute (i);
  189. auto rangeStart = attr.range.getStart();
  190. if (rangeStart >= attribStringLen)
  191. continue;
  192. auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
  193. if (auto ctFontRef = getOrCreateFont (attr.font))
  194. {
  195. ctFontRef = getFontWithPointSize (ctFontRef.get(), attr.font.getHeight() * getHeightToPointsFactor (ctFontRef.get()));
  196. fontMap.emplace (ctFontRef.get(), attr.font);
  197. CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef.get());
  198. if (attr.font.isUnderlined())
  199. {
  200. auto underline = kCTUnderlineStyleSingle;
  201. CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberIntType, &underline));
  202. CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef.get());
  203. }
  204. auto extraKerning = attr.font.getExtraKerningFactor();
  205. if (extraKerning != 0)
  206. {
  207. extraKerning *= attr.font.getHeight();
  208. CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning));
  209. CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef.get());
  210. }
  211. }
  212. {
  213. auto col = attr.colour;
  214. const CGFloat components[] = { col.getFloatRed(),
  215. col.getFloatGreen(),
  216. col.getFloatBlue(),
  217. col.getFloatAlpha() };
  218. auto colour = CGColorCreate (rgbColourSpace.get(), components);
  219. CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
  220. CGColorRelease (colour);
  221. }
  222. }
  223. // Paragraph Attributes
  224. auto ctTextAlignment = getTextAlignment (text);
  225. auto ctLineBreakMode = getLineBreakMode (text);
  226. auto ctWritingDirection = getWritingDirection (text);
  227. CGFloat ctLineSpacing = text.getLineSpacing();
  228. CTParagraphStyleSetting settings[] =
  229. {
  230. { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment },
  231. { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode },
  232. { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection},
  233. { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing }
  234. };
  235. CFUniquePtr<CTParagraphStyleRef> ctParagraphStyleRef (CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)));
  236. CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
  237. kCTParagraphStyleAttributeName, ctParagraphStyleRef.get());
  238. return { CFUniquePtr<CFAttributedStringRef> (attribString), std::move (fontMap) };
  239. }
  240. struct FramesetterAndFontMap
  241. {
  242. CFUniquePtr<CTFramesetterRef> framesetter;
  243. std::map<CTFontRef, Font> fontMap;
  244. };
  245. static FramesetterAndFontMap createCTFramesetter (const AttributedString& text)
  246. {
  247. auto attribStringAndMap = createCFAttributedString (text);
  248. return { CFUniquePtr<CTFramesetterRef> (CTFramesetterCreateWithAttributedString (attribStringAndMap.string.get())),
  249. std::move (attribStringAndMap.fontMap) };
  250. }
  251. static CFUniquePtr<CTFrameRef> createCTFrame (CTFramesetterRef framesetter, CGRect bounds)
  252. {
  253. auto path = CGPathCreateMutable();
  254. CGPathAddRect (path, nullptr, bounds);
  255. CFUniquePtr<CTFrameRef> frame (CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr));
  256. CGPathRelease (path);
  257. return frame;
  258. }
  259. struct FrameAndFontMap
  260. {
  261. CFUniquePtr<CTFrameRef> frame;
  262. std::map<CTFontRef, Font> fontMap;
  263. };
  264. static FrameAndFontMap createCTFrame (const AttributedString& text, CGRect bounds)
  265. {
  266. auto framesetterAndMap = createCTFramesetter (text);
  267. return { createCTFrame (framesetterAndMap.framesetter.get(), bounds),
  268. std::move (framesetterAndMap.fontMap) };
  269. }
  270. static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex)
  271. {
  272. LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex);
  273. return { (float) (info.origin.y - info.descent),
  274. (float) (info.origin.y + info.ascent) };
  275. }
  276. static float findCTFrameHeight (CTFrameRef frame)
  277. {
  278. auto lines = CTFrameGetLines (frame);
  279. auto numLines = CFArrayGetCount (lines);
  280. if (numLines == 0)
  281. return 0;
  282. auto range = getLineVerticalRange (frame, lines, 0);
  283. if (numLines > 1)
  284. range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1));
  285. return range.getLength();
  286. }
  287. static bool areAllFontsDefaultWidth (const AttributedString& text)
  288. {
  289. auto numCharacterAttributes = text.getNumAttributes();
  290. for (int i = 0; i < numCharacterAttributes; ++i)
  291. if (text.getAttribute (i).font.getHorizontalScale() != 1.0f)
  292. return false;
  293. return true;
  294. }
  295. static bool drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
  296. const CGContextRef& context, float flipHeight)
  297. {
  298. if (! areAllFontsDefaultWidth (text))
  299. return false;
  300. auto framesetter = createCTFramesetter (text).framesetter;
  301. // Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly
  302. // larger than the font height - otherwise the CTFrame will be invalid
  303. CFRange fitrange;
  304. auto suggestedSingleLineFrameSize =
  305. CTFramesetterSuggestFrameSizeWithConstraints (framesetter.get(), CFRangeMake (0, 0), nullptr,
  306. CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange);
  307. auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height;
  308. auto verticalJustification = text.getJustification().getOnlyVerticalFlags();
  309. auto ctFrameArea = [area, minCTFrameHeight, verticalJustification]
  310. {
  311. if (minCTFrameHeight < area.getHeight())
  312. return area;
  313. if (verticalJustification == Justification::verticallyCentred)
  314. return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight);
  315. auto frameArea = area.withHeight (minCTFrameHeight);
  316. if (verticalJustification == Justification::bottom)
  317. return frameArea.withBottomY (area.getBottom());
  318. return frameArea;
  319. }();
  320. auto frame = createCTFrame (framesetter.get(), CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
  321. (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
  322. auto textMatrix = CGContextGetTextMatrix (context);
  323. CGContextSaveGState (context);
  324. if (verticalJustification == Justification::verticallyCentred
  325. || verticalJustification == Justification::bottom)
  326. {
  327. auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame.get());
  328. if (verticalJustification == Justification::verticallyCentred)
  329. adjust *= 0.5f;
  330. CGContextTranslateCTM (context, 0, -adjust);
  331. }
  332. CTFrameDraw (frame.get(), context);
  333. CGContextRestoreGState (context);
  334. CGContextSetTextMatrix (context, textMatrix);
  335. return true;
  336. }
  337. static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
  338. {
  339. auto boundsHeight = glyphLayout.getHeight();
  340. auto frameAndMap = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
  341. auto lines = CTFrameGetLines (frameAndMap.frame.get());
  342. auto numLines = CFArrayGetCount (lines);
  343. glyphLayout.ensureStorageAllocated ((int) numLines);
  344. for (CFIndex i = 0; i < numLines; ++i)
  345. {
  346. auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
  347. auto runs = CTLineGetGlyphRuns (line);
  348. auto numRuns = CFArrayGetCount (runs);
  349. auto cfrlineStringRange = CTLineGetStringRange (line);
  350. auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length;
  351. Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
  352. LineInfo lineInfo (frameAndMap.frame.get(), line, i);
  353. auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange,
  354. Point<float> ((float) lineInfo.origin.x,
  355. (float) (boundsHeight - lineInfo.origin.y)),
  356. (float) lineInfo.ascent,
  357. (float) lineInfo.descent,
  358. (float) lineInfo.leading,
  359. (int) numRuns);
  360. for (CFIndex j = 0; j < numRuns; ++j)
  361. {
  362. auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
  363. auto numGlyphs = CTRunGetGlyphCount (run);
  364. auto runStringRange = CTRunGetStringRange (run);
  365. auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
  366. (int) (runStringRange.location + runStringRange.length - 1)),
  367. (int) numGlyphs);
  368. glyphLine->runs.add (glyphRun);
  369. CFDictionaryRef runAttributes = CTRunGetAttributes (run);
  370. CTFontRef ctRunFont;
  371. if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont))
  372. {
  373. glyphRun->font = [&]
  374. {
  375. auto it = frameAndMap.fontMap.find (ctRunFont);
  376. if (it != frameAndMap.fontMap.end())
  377. return it->second;
  378. CFUniquePtr<CFStringRef> cfsFontName (CTFontCopyPostScriptName (ctRunFont));
  379. CFUniquePtr<CTFontRef> ctFontRef (CTFontCreateWithName (cfsFontName.get(), referenceFontSize, nullptr));
  380. auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef.get());
  381. CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute));
  382. CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute));
  383. Font result (String::fromCFString (cfsFontFamily.get()),
  384. String::fromCFString (cfsFontStyle.get()),
  385. (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));
  386. auto isUnderlined = [&]
  387. {
  388. CFNumberRef underlineStyle;
  389. if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle))
  390. {
  391. if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID())
  392. {
  393. int value = 0;
  394. CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value);
  395. return value != 0;
  396. }
  397. }
  398. return false;
  399. }();
  400. result.setUnderline (isUnderlined);
  401. return result;
  402. }();
  403. }
  404. CGColorRef cgRunColor;
  405. if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
  406. && CGColorGetNumberOfComponents (cgRunColor) == 4)
  407. {
  408. auto* components = CGColorGetComponents (cgRunColor);
  409. glyphRun->colour = Colour::fromFloatRGBA ((float) components[0],
  410. (float) components[1],
  411. (float) components[2],
  412. (float) components[3]);
  413. }
  414. const Glyphs glyphs (run, (size_t) numGlyphs);
  415. const Advances advances (run, numGlyphs);
  416. const Positions positions (run, (size_t) numGlyphs);
  417. for (CFIndex k = 0; k < numGlyphs; ++k)
  418. glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k],
  419. convertToPointFloat (positions.points[k]),
  420. (float) advances.advances[k].width));
  421. }
  422. glyphLayout.addLine (std::move (glyphLine));
  423. }
  424. }
  425. }
  426. //==============================================================================
  427. class OSXTypeface : public Typeface
  428. {
  429. public:
  430. OSXTypeface (const Font& font)
  431. : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true)
  432. {
  433. ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);
  434. if (ctFontRef != nullptr)
  435. {
  436. fontRef = CTFontCopyGraphicsFont (ctFontRef.get(), nullptr);
  437. initialiseMetrics();
  438. }
  439. }
  440. OSXTypeface (const void* data, size_t dataSize)
  441. : Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize)
  442. {
  443. // We can't use CFDataCreate here as this triggers a false positive in ASAN
  444. // so copy the data manually and use CFDataCreateWithBytesNoCopy
  445. CFUniquePtr<CFDataRef> cfData (CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
  446. (CFIndex) dataCopy.getSize(), kCFAllocatorNull));
  447. auto provider = CGDataProviderCreateWithCFData (cfData.get());
  448. #if JUCE_IOS
  449. // Workaround for a an obscure iOS bug which can cause the app to dead-lock
  450. // when loading custom type faces. See: http://www.openradar.me/18778790 and
  451. // http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
  452. [UIFont systemFontOfSize: 12];
  453. #endif
  454. fontRef = CGFontCreateWithDataProvider (provider);
  455. CGDataProviderRelease (provider);
  456. if (fontRef != nullptr)
  457. {
  458. if (@available (macOS 10.11, *))
  459. canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
  460. ctFontRef.reset (CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr));
  461. if (ctFontRef != nullptr)
  462. {
  463. if (auto fontName = CFUniquePtr<CFStringRef> (CTFontCopyName (ctFontRef.get(), kCTFontFamilyNameKey)))
  464. name = String::fromCFString (fontName.get());
  465. if (auto fontStyle = CFUniquePtr<CFStringRef> (CTFontCopyName (ctFontRef.get(), kCTFontStyleNameKey)))
  466. style = String::fromCFString (fontStyle.get());
  467. initialiseMetrics();
  468. }
  469. }
  470. }
  471. void initialiseMetrics()
  472. {
  473. auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef.get()));
  474. auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef.get()));
  475. auto ctTotalHeight = ctAscent + ctDescent;
  476. ascent = ctAscent / ctTotalHeight;
  477. unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
  478. pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);
  479. fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
  480. const short zero = 0;
  481. CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberShortType, &zero));
  482. CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
  483. CFTypeRef values[] = { ctFontRef.get(), numberRef.get() };
  484. attributedStringAtts.reset (CFDictionaryCreate (nullptr, (const void**) &keys,
  485. (const void**) &values, numElementsInArray (keys),
  486. &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  487. }
  488. ~OSXTypeface() override
  489. {
  490. if (fontRef != nullptr)
  491. {
  492. if (@available (macOS 10.8, *))
  493. if (dataCopy.getSize() != 0)
  494. CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
  495. CGFontRelease (fontRef);
  496. }
  497. }
  498. float getAscent() const override { return ascent; }
  499. float getDescent() const override { return 1.0f - ascent; }
  500. float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; }
  501. float getStringWidth (const String& text) override
  502. {
  503. float x = 0;
  504. if (ctFontRef != nullptr && text.isNotEmpty())
  505. {
  506. CFUniquePtr<CFStringRef> cfText (text.toCFString());
  507. CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get()));
  508. CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
  509. auto runArray = CTLineGetGlyphRuns (line.get());
  510. for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
  511. {
  512. auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
  513. auto length = CTRunGetGlyphCount (run);
  514. const CoreTextTypeLayout::Advances advances (run, length);
  515. for (int j = 0; j < length; ++j)
  516. x += (float) advances.advances[j].width;
  517. }
  518. x *= unitsToHeightScaleFactor;
  519. }
  520. return x;
  521. }
  522. void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
  523. {
  524. xOffsets.add (0);
  525. if (ctFontRef != nullptr && text.isNotEmpty())
  526. {
  527. float x = 0;
  528. CFUniquePtr<CFStringRef> cfText (text.toCFString());
  529. CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get()));
  530. CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
  531. auto runArray = CTLineGetGlyphRuns (line.get());
  532. for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
  533. {
  534. auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
  535. auto length = CTRunGetGlyphCount (run);
  536. const CoreTextTypeLayout::Advances advances (run, length);
  537. const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
  538. for (int j = 0; j < length; ++j)
  539. {
  540. x += (float) advances.advances[j].width;
  541. xOffsets.add (x * unitsToHeightScaleFactor);
  542. resultGlyphs.add (glyphs.glyphs[j]);
  543. }
  544. }
  545. }
  546. }
  547. bool getOutlineForGlyph (int glyphNumber, Path& path) override
  548. {
  549. jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
  550. if (auto pathRef = CFUniquePtr<CGPathRef> (CTFontCreatePathForGlyph (ctFontRef.get(), (CGGlyph) glyphNumber, &renderingTransform)))
  551. {
  552. CGPathApply (pathRef.get(), &path, pathApplier);
  553. if (! pathTransform.isIdentity())
  554. path.applyTransform (pathTransform);
  555. return true;
  556. }
  557. return false;
  558. }
  559. //==============================================================================
  560. CGFontRef fontRef = {};
  561. CFUniquePtr<CTFontRef> ctFontRef;
  562. float fontHeightToPointsFactor = 1.0f;
  563. CGAffineTransform renderingTransform = CGAffineTransformIdentity;
  564. bool canBeUsedForLayout;
  565. private:
  566. MemoryBlock dataCopy;
  567. CFUniquePtr<CFDictionaryRef> attributedStringAtts;
  568. float ascent = 0, unitsToHeightScaleFactor = 0;
  569. AffineTransform pathTransform;
  570. static void pathApplier (void* info, const CGPathElement* element)
  571. {
  572. auto& path = *static_cast<Path*> (info);
  573. auto* p = element->points;
  574. switch (element->type)
  575. {
  576. case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
  577. case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break;
  578. case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y,
  579. (float) p[1].x, (float) -p[1].y); break;
  580. case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y,
  581. (float) p[1].x, (float) -p[1].y,
  582. (float) p[2].x, (float) -p[2].y); break;
  583. case kCGPathElementCloseSubpath: path.closeSubPath(); break;
  584. default: jassertfalse; break;
  585. }
  586. }
  587. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
  588. };
  589. CTFontRef getCTFontFromTypeface (const Font& f)
  590. {
  591. const auto typeface = f.getTypefacePtr();
  592. if (auto* tf = dynamic_cast<OSXTypeface*> (typeface.get()))
  593. return tf->ctFontRef.get();
  594. return {};
  595. }
  596. StringArray Font::findAllTypefaceNames()
  597. {
  598. StringArray names;
  599. #if JUCE_MAC
  600. // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
  601. CFUniquePtr<CFArrayRef> fontFamilyArray (CTFontManagerCopyAvailableFontFamilyNames());
  602. for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray.get()); ++i)
  603. {
  604. auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray.get(), i));
  605. if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
  606. names.addIfNotAlreadyThere (family);
  607. }
  608. #else
  609. CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateFromAvailableFonts (nullptr));
  610. CFUniquePtr<CFArrayRef> fontDescriptorArray (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get()));
  611. for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
  612. {
  613. auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
  614. CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute));
  615. names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily.get()));
  616. }
  617. #endif
  618. names.sort (true);
  619. return names;
  620. }
  621. StringArray Font::findAllTypefaceStyles (const String& family)
  622. {
  623. if (FontStyleHelpers::isPlaceholderFamilyName (family))
  624. return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
  625. StringArray results;
  626. CFUniquePtr<CFStringRef> cfsFontFamily (family.toCFString());
  627. CFStringRef keys[] = { kCTFontFamilyNameAttribute };
  628. CFTypeRef values[] = { cfsFontFamily.get() };
  629. CFUniquePtr<CFDictionaryRef> fontDescAttributes (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
  630. &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  631. CFUniquePtr<CTFontDescriptorRef> ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get()));
  632. CFUniquePtr<CFArrayRef> fontFamilyArray (CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks));
  633. CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateWithFontDescriptors (fontFamilyArray.get(), nullptr));
  634. if (auto fontDescriptorArray = CFUniquePtr<CFArrayRef> (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get())))
  635. {
  636. for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
  637. {
  638. auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
  639. CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute));
  640. results.add (String::fromCFString (cfsFontStyle.get()));
  641. }
  642. }
  643. return results;
  644. }
  645. //==============================================================================
  646. Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return *new OSXTypeface (font); }
  647. Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) { return *new OSXTypeface (data, size); }
  648. void Typeface::scanFolderForFonts (const File& folder)
  649. {
  650. for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf"))
  651. if (auto urlref = CFUniquePtr<CFURLRef> (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true)))
  652. CTFontManagerRegisterFontsForURL (urlref.get(), kCTFontManagerScopeProcess, nullptr);
  653. }
  654. struct DefaultFontNames
  655. {
  656. #if JUCE_IOS
  657. String defaultSans { "Helvetica" },
  658. defaultSerif { "Times New Roman" },
  659. defaultFixed { "Courier New" };
  660. #else
  661. String defaultSans { "Lucida Grande" },
  662. defaultSerif { "Times New Roman" },
  663. defaultFixed { "Menlo" };
  664. #endif
  665. };
  666. Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
  667. {
  668. static DefaultFontNames defaultNames;
  669. auto newFont = font;
  670. auto faceName = font.getTypefaceName();
  671. if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
  672. else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
  673. else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
  674. if (font.getTypefaceStyle() == getDefaultStyle())
  675. newFont.setTypefaceStyle ("Regular");
  676. return Typeface::createSystemTypefaceFor (newFont);
  677. }
  678. static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
  679. {
  680. auto numCharacterAttributes = text.getNumAttributes();
  681. for (int i = 0; i < numCharacterAttributes; ++i)
  682. {
  683. auto typeface = text.getAttribute (i).font.getTypefacePtr();
  684. if (auto tf = dynamic_cast<OSXTypeface*> (typeface.get()))
  685. if (tf->canBeUsedForLayout)
  686. continue;
  687. return false;
  688. }
  689. return true;
  690. }
  691. bool TextLayout::createNativeLayout (const AttributedString& text)
  692. {
  693. if (canAllTypefacesBeUsedInLayout (text) && CoreTextTypeLayout::areAllFontsDefaultWidth (text))
  694. {
  695. CoreTextTypeLayout::createLayout (*this, text);
  696. return true;
  697. }
  698. return false;
  699. }
  700. } // namespace juce