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.

857 lines
33KB

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