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.

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