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.

901 lines
35KB

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