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.

917 lines
36KB

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