Audio plugin host https://kx.studio/carla
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.

1204 lines
46KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #if (! defined (JUCE_CORETEXT_AVAILABLE)) \
  18. && (JUCE_IOS || (JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4))
  19. #define JUCE_CORETEXT_AVAILABLE 1
  20. #endif
  21. const float referenceFontSize = 1024.0f;
  22. #if JUCE_CORETEXT_AVAILABLE
  23. //#if JUCE_MAC && MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5
  24. extern "C"
  25. {
  26. void CTRunGetAdvances (CTRunRef, CFRange, CGSize buffer[]);
  27. const CGSize* CTRunGetAdvancesPtr (CTRunRef);
  28. }
  29. //#endif
  30. static CTFontRef getCTFontFromTypeface (const Font& f);
  31. namespace CoreTextTypeLayout
  32. {
  33. static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform)
  34. {
  35. const StringArray availableStyles (Font::findAllTypefaceStyles (font.getTypefaceName()));
  36. const String style (font.getTypefaceStyle());
  37. if (! availableStyles.contains (style))
  38. {
  39. if (font.isItalic()) // Fake-up an italic font if there isn't a real one.
  40. requiredTransform = CGAffineTransformMake (1.0f, 0, 0.25f, 1.0f, 0, 0);
  41. return availableStyles[0];
  42. }
  43. return style;
  44. }
  45. // Workaround for Apple bug in CTFontCreateWithFontDescriptor in Garageband/Logic on 10.6
  46. #if JUCE_MAC && ((! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
  47. static CTFontRef getFontWithTrait (CTFontRef ctFontRef, CTFontSymbolicTraits trait)
  48. {
  49. if (CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr, trait, trait))
  50. {
  51. CFRelease (ctFontRef);
  52. return newFont;
  53. }
  54. return ctFontRef;
  55. }
  56. static CTFontRef useStyleFallbackIfNecessary (CTFontRef ctFontRef, CFStringRef cfFontFamily,
  57. const float fontSizePoints, const Font& font)
  58. {
  59. CFStringRef cfActualFontFamily = (CFStringRef) CTFontCopyAttribute (ctFontRef, kCTFontFamilyNameAttribute);
  60. if (CFStringCompare (cfFontFamily, cfActualFontFamily, 0) != kCFCompareEqualTo)
  61. {
  62. CFRelease (ctFontRef);
  63. ctFontRef = CTFontCreateWithName (cfFontFamily, fontSizePoints, nullptr);
  64. if (font.isItalic()) ctFontRef = getFontWithTrait (ctFontRef, kCTFontItalicTrait);
  65. if (font.isBold()) ctFontRef = getFontWithTrait (ctFontRef, kCTFontBoldTrait);
  66. }
  67. CFRelease (cfActualFontFamily);
  68. return ctFontRef;
  69. }
  70. #endif
  71. static float getFontTotalHeight (CTFontRef font)
  72. {
  73. return std::abs ((float) CTFontGetAscent (font)) + std::abs ((float) CTFontGetDescent (font));
  74. }
  75. static float getHeightToPointsFactor (CTFontRef font)
  76. {
  77. return referenceFontSize / getFontTotalHeight (font);
  78. }
  79. static CTFontRef getFontWithPointSize (CTFontRef font, float size)
  80. {
  81. CTFontRef newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr);
  82. CFRelease (font);
  83. return newFont;
  84. }
  85. static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
  86. {
  87. CFStringRef cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString();
  88. CFStringRef cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString();
  89. CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
  90. CFTypeRef values[] = { cfFontFamily, cfFontStyle };
  91. CFDictionaryRef fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys,
  92. (const void**) &values,
  93. numElementsInArray (keys),
  94. &kCFTypeDictionaryKeyCallBacks,
  95. &kCFTypeDictionaryValueCallBacks);
  96. CFRelease (cfFontStyle);
  97. CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
  98. CFRelease (fontDescAttributes);
  99. CTFontRef ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr);
  100. CFRelease (ctFontDescRef);
  101. #if JUCE_MAC && ((! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7)
  102. ctFontRef = useStyleFallbackIfNecessary (ctFontRef, cfFontFamily, fontSizePoints, font);
  103. #endif
  104. CFRelease (cfFontFamily);
  105. return ctFontRef;
  106. }
  107. //==============================================================================
  108. struct Advances
  109. {
  110. Advances (CTRunRef run, const CFIndex numGlyphs)
  111. : advances (CTRunGetAdvancesPtr (run))
  112. {
  113. if (advances == nullptr)
  114. {
  115. local.malloc ((size_t) numGlyphs);
  116. CTRunGetAdvances (run, CFRangeMake (0, 0), local);
  117. advances = local;
  118. }
  119. }
  120. const CGSize* advances;
  121. HeapBlock<CGSize> local;
  122. };
  123. struct Glyphs
  124. {
  125. Glyphs (CTRunRef run, const size_t numGlyphs)
  126. : glyphs (CTRunGetGlyphsPtr (run))
  127. {
  128. if (glyphs == nullptr)
  129. {
  130. local.malloc (numGlyphs);
  131. CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
  132. glyphs = local;
  133. }
  134. }
  135. const CGGlyph* glyphs;
  136. HeapBlock<CGGlyph> local;
  137. };
  138. struct Positions
  139. {
  140. Positions (CTRunRef run, const size_t numGlyphs)
  141. : points (CTRunGetPositionsPtr (run))
  142. {
  143. if (points == nullptr)
  144. {
  145. local.malloc (numGlyphs);
  146. CTRunGetPositions (run, CFRangeMake (0, 0), local);
  147. points = local;
  148. }
  149. }
  150. const CGPoint* points;
  151. HeapBlock<CGPoint> local;
  152. };
  153. static CTFontRef getOrCreateFont (const Font& f)
  154. {
  155. if (CTFontRef ctf = getCTFontFromTypeface (f))
  156. {
  157. CFRetain (ctf);
  158. return ctf;
  159. }
  160. CGAffineTransform transform;
  161. return createCTFont (f, referenceFontSize, transform);
  162. }
  163. //==============================================================================
  164. static CFAttributedStringRef createCFAttributedString (const AttributedString& text)
  165. {
  166. #if JUCE_IOS
  167. CGColorSpaceRef rgbColourSpace = CGColorSpaceCreateDeviceRGB();
  168. #endif
  169. CFStringRef cfText = text.getText().toCFString();
  170. CFMutableAttributedStringRef attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
  171. CFAttributedStringReplaceString (attribString, CFRangeMake(0, 0), cfText);
  172. CFRelease (cfText);
  173. const int numCharacterAttributes = text.getNumAttributes();
  174. for (int i = 0; i < numCharacterAttributes; ++i)
  175. {
  176. const AttributedString::Attribute* const attr = text.getAttribute (i);
  177. if (attr->range.getStart() > CFAttributedStringGetLength (attribString))
  178. continue;
  179. Range<int> range (attr->range);
  180. range.setEnd (jmin (range.getEnd(), (int) CFAttributedStringGetLength (attribString)));
  181. if (const Font* const f = attr->getFont())
  182. {
  183. if (CTFontRef ctFontRef = getOrCreateFont (*f))
  184. {
  185. ctFontRef = getFontWithPointSize (ctFontRef, f->getHeight() * getHeightToPointsFactor (ctFontRef));
  186. CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()),
  187. kCTFontAttributeName, ctFontRef);
  188. CFRelease (ctFontRef);
  189. }
  190. }
  191. if (const Colour* const col = attr->getColour())
  192. {
  193. #if JUCE_IOS
  194. const CGFloat components[] = { col->getFloatRed(),
  195. col->getFloatGreen(),
  196. col->getFloatBlue(),
  197. col->getFloatAlpha() };
  198. CGColorRef colour = CGColorCreate (rgbColourSpace, components);
  199. #else
  200. CGColorRef colour = CGColorCreateGenericRGB (col->getFloatRed(),
  201. col->getFloatGreen(),
  202. col->getFloatBlue(),
  203. col->getFloatAlpha());
  204. #endif
  205. CFAttributedStringSetAttribute (attribString,
  206. CFRangeMake (range.getStart(), range.getLength()),
  207. kCTForegroundColorAttributeName, colour);
  208. CGColorRelease (colour);
  209. }
  210. }
  211. // Paragraph Attributes
  212. CTTextAlignment ctTextAlignment = kCTLeftTextAlignment;
  213. CTLineBreakMode ctLineBreakMode = kCTLineBreakByWordWrapping;
  214. const CGFloat ctLineSpacing = text.getLineSpacing();
  215. switch (text.getJustification().getOnlyHorizontalFlags())
  216. {
  217. case Justification::left: break;
  218. case Justification::right: ctTextAlignment = kCTRightTextAlignment; break;
  219. case Justification::horizontallyCentred: ctTextAlignment = kCTCenterTextAlignment; break;
  220. case Justification::horizontallyJustified: ctTextAlignment = kCTJustifiedTextAlignment; break;
  221. default: jassertfalse; break; // Illegal justification flags
  222. }
  223. switch (text.getWordWrap())
  224. {
  225. case AttributedString::byWord: break;
  226. case AttributedString::none: ctLineBreakMode = kCTLineBreakByClipping; break;
  227. case AttributedString::byChar: ctLineBreakMode = kCTLineBreakByCharWrapping; break;
  228. default: break;
  229. }
  230. CTParagraphStyleSetting settings[] =
  231. {
  232. { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment },
  233. { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode },
  234. #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
  235. { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing }
  236. #else
  237. { kCTParagraphStyleSpecifierLineSpacing, sizeof (CGFloat), &ctLineSpacing }
  238. #endif
  239. };
  240. CTParagraphStyleRef ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings));
  241. CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
  242. kCTParagraphStyleAttributeName, ctParagraphStyleRef);
  243. CFRelease (ctParagraphStyleRef);
  244. #if JUCE_IOS
  245. CGColorSpaceRelease (rgbColourSpace);
  246. #endif
  247. return attribString;
  248. }
  249. static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
  250. const CGContextRef& context, const float flipHeight)
  251. {
  252. CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text);
  253. CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString);
  254. CFRelease (attribString);
  255. CGMutablePathRef path = CGPathCreateMutable();
  256. CGRect bounds = CGRectMake ((CGFloat) area.getX(), flipHeight - (CGFloat) area.getBottom(),
  257. (CGFloat) area.getWidth(), (CGFloat) area.getHeight());
  258. CGPathAddRect (path, nullptr, bounds);
  259. CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr);
  260. CFRelease (framesetter);
  261. CGPathRelease (path);
  262. CTFrameDraw (frame, context);
  263. CFRelease (frame);
  264. }
  265. static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
  266. {
  267. CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text);
  268. CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString);
  269. CFRelease (attribString);
  270. CGMutablePathRef path = CGPathCreateMutable();
  271. const CGRect bounds = CGRectMake (0, 0, glyphLayout.getWidth(), 1.0e6f);
  272. CGPathAddRect (path, nullptr, bounds);
  273. CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake(0, 0), path, nullptr);
  274. CFRelease (framesetter);
  275. CGPathRelease (path);
  276. CFArrayRef lines = CTFrameGetLines (frame);
  277. const CFIndex numLines = CFArrayGetCount (lines);
  278. glyphLayout.ensureStorageAllocated ((int) numLines);
  279. for (CFIndex i = 0; i < numLines; ++i)
  280. {
  281. CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
  282. CFArrayRef runs = CTLineGetGlyphRuns (line);
  283. const CFIndex numRuns = CFArrayGetCount (runs);
  284. const CFRange cfrlineStringRange = CTLineGetStringRange (line);
  285. const CFIndex lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1;
  286. const Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
  287. CGPoint cgpLineOrigin;
  288. CTFrameGetLineOrigins (frame, CFRangeMake(i, 1), &cgpLineOrigin);
  289. Point<float> lineOrigin ((float) cgpLineOrigin.x, bounds.size.height - (float) cgpLineOrigin.y);
  290. CGFloat ascent, descent, leading;
  291. CTLineGetTypographicBounds (line, &ascent, &descent, &leading);
  292. TextLayout::Line* const glyphLine = new TextLayout::Line (lineStringRange, lineOrigin,
  293. (float) ascent, (float) descent, (float) leading,
  294. (int) numRuns);
  295. glyphLayout.addLine (glyphLine);
  296. for (CFIndex j = 0; j < numRuns; ++j)
  297. {
  298. CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
  299. const CFIndex numGlyphs = CTRunGetGlyphCount (run);
  300. const CFRange runStringRange = CTRunGetStringRange (run);
  301. TextLayout::Run* const glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
  302. (int) (runStringRange.location + runStringRange.length - 1)),
  303. (int) numGlyphs);
  304. glyphLine->runs.add (glyphRun);
  305. CFDictionaryRef runAttributes = CTRunGetAttributes (run);
  306. CTFontRef ctRunFont;
  307. if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void **) &ctRunFont))
  308. {
  309. CFStringRef cfsFontName = CTFontCopyPostScriptName (ctRunFont);
  310. CTFontRef ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr);
  311. CFRelease (cfsFontName);
  312. const float fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef);
  313. CFRelease (ctFontRef);
  314. CFStringRef cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute);
  315. CFStringRef cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute);
  316. glyphRun->font = Font (String::fromCFString (cfsFontFamily),
  317. String::fromCFString (cfsFontStyle),
  318. CTFontGetSize (ctRunFont) / fontHeightToPointsFactor);
  319. CFRelease (cfsFontStyle);
  320. CFRelease (cfsFontFamily);
  321. }
  322. CGColorRef cgRunColor;
  323. if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
  324. && CGColorGetNumberOfComponents (cgRunColor) == 4)
  325. {
  326. const CGFloat* const components = CGColorGetComponents (cgRunColor);
  327. glyphRun->colour = Colour::fromFloatRGBA (components[0], components[1], components[2], components[3]);
  328. }
  329. const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) numGlyphs);
  330. const CoreTextTypeLayout::Advances advances (run, numGlyphs);
  331. const CoreTextTypeLayout::Positions positions (run, (size_t) numGlyphs);
  332. for (CFIndex k = 0; k < numGlyphs; ++k)
  333. glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> (positions.points[k].x,
  334. positions.points[k].y),
  335. advances.advances[k].width));
  336. }
  337. }
  338. CFRelease (frame);
  339. }
  340. }
  341. //==============================================================================
  342. class OSXTypeface : public Typeface
  343. {
  344. public:
  345. OSXTypeface (const Font& font)
  346. : Typeface (font.getTypefaceName(),
  347. font.getTypefaceStyle()),
  348. fontRef (nullptr),
  349. ctFontRef (nullptr),
  350. fontHeightToPointsFactor (1.0f),
  351. renderingTransform (CGAffineTransformIdentity),
  352. attributedStringAtts (nullptr),
  353. ascent (0.0f),
  354. unitsToHeightScaleFactor (0.0f)
  355. {
  356. ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);
  357. if (ctFontRef != nullptr)
  358. {
  359. const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef));
  360. const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef));
  361. const float ctTotalHeight = ctAscent + ctDescent;
  362. ascent = ctAscent / ctTotalHeight;
  363. unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
  364. pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor);
  365. fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
  366. fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
  367. const short zero = 0;
  368. CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero);
  369. CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
  370. CFTypeRef values[] = { ctFontRef, numberRef };
  371. attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
  372. &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  373. CFRelease (numberRef);
  374. }
  375. }
  376. ~OSXTypeface()
  377. {
  378. if (attributedStringAtts != nullptr)
  379. CFRelease (attributedStringAtts);
  380. if (fontRef != nullptr)
  381. CGFontRelease (fontRef);
  382. if (ctFontRef != nullptr)
  383. CFRelease (ctFontRef);
  384. }
  385. float getAscent() const { return ascent; }
  386. float getDescent() const { return 1.0f - ascent; }
  387. float getHeightToPointsFactor() const { return fontHeightToPointsFactor; }
  388. float getStringWidth (const String& text)
  389. {
  390. float x = 0;
  391. if (ctFontRef != nullptr && text.isNotEmpty())
  392. {
  393. CFStringRef cfText = text.toCFString();
  394. CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
  395. CFRelease (cfText);
  396. CTLineRef line = CTLineCreateWithAttributedString (attribString);
  397. CFArrayRef runArray = CTLineGetGlyphRuns (line);
  398. for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
  399. {
  400. CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
  401. CFIndex length = CTRunGetGlyphCount (run);
  402. const CoreTextTypeLayout::Advances advances (run, length);
  403. for (int j = 0; j < length; ++j)
  404. x += (float) advances.advances[j].width;
  405. }
  406. CFRelease (line);
  407. CFRelease (attribString);
  408. x *= unitsToHeightScaleFactor;
  409. }
  410. return x;
  411. }
  412. void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
  413. {
  414. xOffsets.add (0);
  415. if (ctFontRef != nullptr && text.isNotEmpty())
  416. {
  417. float x = 0;
  418. CFStringRef cfText = text.toCFString();
  419. CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
  420. CFRelease (cfText);
  421. CTLineRef line = CTLineCreateWithAttributedString (attribString);
  422. CFArrayRef runArray = CTLineGetGlyphRuns (line);
  423. for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
  424. {
  425. CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
  426. CFIndex length = CTRunGetGlyphCount (run);
  427. const CoreTextTypeLayout::Advances advances (run, length);
  428. const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
  429. for (int j = 0; j < length; ++j)
  430. {
  431. x += (float) advances.advances[j].width;
  432. xOffsets.add (x * unitsToHeightScaleFactor);
  433. resultGlyphs.add (glyphs.glyphs[j]);
  434. }
  435. }
  436. CFRelease (line);
  437. CFRelease (attribString);
  438. }
  439. }
  440. EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
  441. {
  442. Path path;
  443. if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
  444. return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
  445. path, transform);
  446. return nullptr;
  447. }
  448. bool getOutlineForGlyph (int glyphNumber, Path& path)
  449. {
  450. jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
  451. CGPathRef pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform);
  452. if (pathRef == 0)
  453. return false;
  454. CGPathApply (pathRef, &path, pathApplier);
  455. CFRelease (pathRef);
  456. if (! pathTransform.isIdentity())
  457. path.applyTransform (pathTransform);
  458. return true;
  459. }
  460. //==============================================================================
  461. CGFontRef fontRef;
  462. CTFontRef ctFontRef;
  463. float fontHeightToPointsFactor;
  464. CGAffineTransform renderingTransform;
  465. private:
  466. CFDictionaryRef attributedStringAtts;
  467. float ascent, unitsToHeightScaleFactor;
  468. AffineTransform pathTransform;
  469. static void pathApplier (void* info, const CGPathElement* const element)
  470. {
  471. Path& path = *static_cast<Path*> (info);
  472. const CGPoint* const p = element->points;
  473. switch (element->type)
  474. {
  475. case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
  476. case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break;
  477. case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y,
  478. (float) p[1].x, (float) -p[1].y); break;
  479. case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y,
  480. (float) p[1].x, (float) -p[1].y,
  481. (float) p[2].x, (float) -p[2].y); break;
  482. case kCGPathElementCloseSubpath: path.closeSubPath(); break;
  483. default: jassertfalse; break;
  484. }
  485. }
  486. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
  487. };
  488. CTFontRef getCTFontFromTypeface (const Font& f)
  489. {
  490. if (OSXTypeface* tf = dynamic_cast <OSXTypeface*> (f.getTypeface()))
  491. return tf->ctFontRef;
  492. return 0;
  493. }
  494. StringArray Font::findAllTypefaceNames()
  495. {
  496. StringArray names;
  497. #if 0 //MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5 && ! JUCE_IOS
  498. // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
  499. CFArrayRef fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames();
  500. for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i)
  501. {
  502. const String family (String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i)));
  503. if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
  504. names.addIfNotAlreadyThere (family);
  505. }
  506. CFRelease (fontFamilyArray);
  507. #else
  508. CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr);
  509. CFArrayRef fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
  510. CFRelease (fontCollectionRef);
  511. for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
  512. {
  513. CTFontDescriptorRef ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
  514. CFStringRef cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute);
  515. names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily));
  516. CFRelease (cfsFontFamily);
  517. }
  518. CFRelease (fontDescriptorArray);
  519. #endif
  520. names.sort (true);
  521. return names;
  522. }
  523. StringArray Font::findAllTypefaceStyles (const String& family)
  524. {
  525. if (FontStyleHelpers::isPlaceholderFamilyName (family))
  526. return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
  527. StringArray results;
  528. CFStringRef cfsFontFamily = family.toCFString();
  529. CFStringRef keys[] = { kCTFontFamilyNameAttribute };
  530. CFTypeRef values[] = { cfsFontFamily };
  531. CFDictionaryRef fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  532. CFRelease (cfsFontFamily);
  533. CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
  534. CFRelease (fontDescAttributes);
  535. CFArrayRef fontFamilyArray = CFArrayCreate(kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks);
  536. CFRelease (ctFontDescRef);
  537. CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr);
  538. CFRelease (fontFamilyArray);
  539. CFArrayRef fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
  540. CFRelease (fontCollectionRef);
  541. if (fontDescriptorArray != nullptr)
  542. {
  543. for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
  544. {
  545. CTFontDescriptorRef ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
  546. CFStringRef cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute);
  547. results.add (String::fromCFString (cfsFontStyle));
  548. CFRelease (cfsFontStyle);
  549. }
  550. CFRelease (fontDescriptorArray);
  551. }
  552. return results;
  553. }
  554. #else
  555. //==============================================================================
  556. // The stuff that follows is a mash-up that supports pre-OSX 10.5 APIs.
  557. // (Hopefully all of this can be ditched at some point in the future).
  558. //==============================================================================
  559. #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
  560. #define SUPPORT_10_4_FONTS 1
  561. #define NEW_CGFONT_FUNCTIONS_UNAVAILABLE (CGFontCreateWithFontName == 0)
  562. #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
  563. #define SUPPORT_ONLY_10_4_FONTS 1
  564. #endif
  565. } // (juce namespace)
  566. @interface NSFont (PrivateHack)
  567. - (NSGlyph) _defaultGlyphForChar: (unichar) theChar;
  568. @end
  569. namespace juce
  570. {
  571. #endif
  572. //==============================================================================
  573. class OSXTypeface : public Typeface
  574. {
  575. public:
  576. OSXTypeface (const Font& font)
  577. : Typeface (font.getTypefaceName(), font.getTypefaceStyle())
  578. {
  579. JUCE_AUTORELEASEPOOL
  580. {
  581. renderingTransform = CGAffineTransformIdentity;
  582. NSDictionary* nsDict = [NSDictionary dictionaryWithObjectsAndKeys:
  583. juceStringToNS (name), NSFontFamilyAttribute,
  584. juceStringToNS (style), NSFontFaceAttribute, nil];
  585. NSFontDescriptor* nsFontDesc = [NSFontDescriptor fontDescriptorWithFontAttributes: nsDict];
  586. nsFont = [NSFont fontWithDescriptor: nsFontDesc size: referenceFontSize];
  587. [nsFont retain];
  588. #if SUPPORT_ONLY_10_4_FONTS
  589. initWithATSFont();
  590. #else
  591. #if SUPPORT_10_4_FONTS
  592. if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE)
  593. {
  594. initWithATSFont();
  595. }
  596. else
  597. #endif
  598. {
  599. fontRef = CGFontCreateWithFontName ((CFStringRef) [nsFont fontName]);
  600. const float absAscent = std::abs ((float) CGFontGetAscent (fontRef));
  601. const float totalHeight = absAscent + std::abs ((float) CGFontGetDescent (fontRef));
  602. ascent = absAscent / totalHeight;
  603. unitsToHeightScaleFactor = 1.0f / totalHeight;
  604. const float nsFontAscent = std::abs ([nsFont ascender]);
  605. const float nsFontDescent = std::abs ([nsFont descender]);
  606. fontHeightToPointsFactor = referenceFontSize / (nsFontAscent + nsFontDescent);
  607. }
  608. #endif
  609. pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor);
  610. }
  611. }
  612. ~OSXTypeface()
  613. {
  614. #if ! JUCE_IOS
  615. [nsFont release];
  616. #endif
  617. if (fontRef != 0)
  618. CGFontRelease (fontRef);
  619. }
  620. #if SUPPORT_10_4_FONTS
  621. void initWithATSFont()
  622. {
  623. ATSFontRef atsFont = ATSFontFindFromName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault);
  624. if (atsFont == 0)
  625. atsFont = ATSFontFindFromPostScriptName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault);
  626. fontRef = CGFontCreateWithPlatformFont (&atsFont);
  627. const float absAscent = std::abs ([nsFont ascender]);
  628. const float absDescent = std::abs ([nsFont descender]);
  629. const float totalHeight = absAscent + absDescent;
  630. unitsToHeightScaleFactor = 1.0f / totalHeight;
  631. fontHeightToPointsFactor = referenceFontSize / totalHeight;
  632. ascent = absAscent / totalHeight;
  633. }
  634. #endif
  635. float getAscent() const { return ascent; }
  636. float getDescent() const { return 1.0f - ascent; }
  637. float getHeightToPointsFactor() const { return fontHeightToPointsFactor; }
  638. float getStringWidth (const String& text)
  639. {
  640. if (fontRef == 0 || text.isEmpty())
  641. return 0;
  642. const int length = text.length();
  643. HeapBlock <CGGlyph> glyphs;
  644. createGlyphsForString (text.getCharPointer(), length, glyphs);
  645. float x = 0;
  646. #if SUPPORT_ONLY_10_4_FONTS
  647. HeapBlock <NSSize> advances (length);
  648. [nsFont getAdvancements: advances forGlyphs: reinterpret_cast <NSGlyph*> (glyphs.getData()) count: length];
  649. for (int i = 0; i < length; ++i)
  650. x += advances[i].width;
  651. #else
  652. #if SUPPORT_10_4_FONTS
  653. if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE)
  654. {
  655. HeapBlock <NSSize> advances (length);
  656. [nsFont getAdvancements: advances forGlyphs: reinterpret_cast<NSGlyph*> (glyphs.getData()) count: length];
  657. for (int i = 0; i < length; ++i)
  658. x += advances[i].width;
  659. }
  660. else
  661. #endif
  662. {
  663. HeapBlock <int> advances (length);
  664. if (CGFontGetGlyphAdvances (fontRef, glyphs, length, advances))
  665. for (int i = 0; i < length; ++i)
  666. x += advances[i];
  667. }
  668. #endif
  669. return x * unitsToHeightScaleFactor;
  670. }
  671. void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
  672. {
  673. xOffsets.add (0);
  674. if (fontRef == 0 || text.isEmpty())
  675. return;
  676. const int length = text.length();
  677. HeapBlock <CGGlyph> glyphs;
  678. createGlyphsForString (text.getCharPointer(), length, glyphs);
  679. #if SUPPORT_ONLY_10_4_FONTS
  680. HeapBlock <NSSize> advances (length);
  681. [nsFont getAdvancements: advances forGlyphs: reinterpret_cast <NSGlyph*> (glyphs.getData()) count: length];
  682. int x = 0;
  683. for (int i = 0; i < length; ++i)
  684. {
  685. x += advances[i].width;
  686. xOffsets.add (x * unitsToHeightScaleFactor);
  687. resultGlyphs.add (reinterpret_cast <NSGlyph*> (glyphs.getData())[i]);
  688. }
  689. #else
  690. #if SUPPORT_10_4_FONTS
  691. if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE)
  692. {
  693. HeapBlock <NSSize> advances (length);
  694. NSGlyph* const nsGlyphs = reinterpret_cast<NSGlyph*> (glyphs.getData());
  695. [nsFont getAdvancements: advances forGlyphs: nsGlyphs count: length];
  696. float x = 0;
  697. for (int i = 0; i < length; ++i)
  698. {
  699. x += advances[i].width;
  700. xOffsets.add (x * unitsToHeightScaleFactor);
  701. resultGlyphs.add (nsGlyphs[i]);
  702. }
  703. }
  704. else
  705. #endif
  706. {
  707. HeapBlock <int> advances (length);
  708. if (CGFontGetGlyphAdvances (fontRef, glyphs, length, advances))
  709. {
  710. int x = 0;
  711. for (int i = 0; i < length; ++i)
  712. {
  713. x += advances [i];
  714. xOffsets.add (x * unitsToHeightScaleFactor);
  715. resultGlyphs.add (glyphs[i]);
  716. }
  717. }
  718. }
  719. #endif
  720. }
  721. EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
  722. {
  723. Path path;
  724. if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
  725. return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
  726. path, transform);
  727. return nullptr;
  728. }
  729. bool getOutlineForGlyph (int glyphNumber, Path& path)
  730. {
  731. #if JUCE_IOS
  732. return false;
  733. #else
  734. if (nsFont == nil)
  735. return false;
  736. // we might need to apply a transform to the path, so it mustn't have anything else in it
  737. jassert (path.isEmpty());
  738. JUCE_AUTORELEASEPOOL
  739. {
  740. NSBezierPath* bez = [NSBezierPath bezierPath];
  741. [bez moveToPoint: NSMakePoint (0, 0)];
  742. [bez appendBezierPathWithGlyph: (NSGlyph) glyphNumber
  743. inFont: nsFont];
  744. for (int i = 0; i < [bez elementCount]; ++i)
  745. {
  746. NSPoint p[3];
  747. switch ([bez elementAtIndex: i associatedPoints: p])
  748. {
  749. case NSMoveToBezierPathElement: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
  750. case NSLineToBezierPathElement: path.lineTo ((float) p[0].x, (float) -p[0].y); break;
  751. case NSCurveToBezierPathElement: path.cubicTo ((float) p[0].x, (float) -p[0].y,
  752. (float) p[1].x, (float) -p[1].y,
  753. (float) p[2].x, (float) -p[2].y); break;
  754. case NSClosePathBezierPathElement: path.closeSubPath(); break;
  755. default: jassertfalse; break;
  756. }
  757. }
  758. path.applyTransform (pathTransform);
  759. }
  760. return true;
  761. #endif
  762. }
  763. //==============================================================================
  764. CGFontRef fontRef;
  765. float fontHeightToPointsFactor;
  766. CGAffineTransform renderingTransform;
  767. private:
  768. float ascent, unitsToHeightScaleFactor;
  769. #if ! JUCE_IOS
  770. NSFont* nsFont;
  771. AffineTransform pathTransform;
  772. #endif
  773. void createGlyphsForString (String::CharPointerType text, const int length, HeapBlock <CGGlyph>& glyphs)
  774. {
  775. #if SUPPORT_10_4_FONTS
  776. #if ! SUPPORT_ONLY_10_4_FONTS
  777. if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE)
  778. #endif
  779. {
  780. glyphs.malloc (sizeof (NSGlyph) * length, 1);
  781. NSGlyph* const nsGlyphs = reinterpret_cast<NSGlyph*> (glyphs.getData());
  782. for (int i = 0; i < length; ++i)
  783. nsGlyphs[i] = (NSGlyph) [nsFont _defaultGlyphForChar: text.getAndAdvance()];
  784. return;
  785. }
  786. #endif
  787. #if ! SUPPORT_ONLY_10_4_FONTS
  788. if (charToGlyphMapper == nullptr)
  789. charToGlyphMapper = new CharToGlyphMapper (fontRef);
  790. glyphs.malloc (length);
  791. for (int i = 0; i < length; ++i)
  792. glyphs[i] = (CGGlyph) charToGlyphMapper->getGlyphForCharacter (text.getAndAdvance());
  793. #endif
  794. }
  795. #if ! SUPPORT_ONLY_10_4_FONTS
  796. // Reads a CGFontRef's character map table to convert unicode into glyph numbers
  797. class CharToGlyphMapper
  798. {
  799. public:
  800. CharToGlyphMapper (CGFontRef cgFontRef)
  801. : segCount (0), endCode (0), startCode (0), idDelta (0),
  802. idRangeOffset (0), glyphIndexes (0)
  803. {
  804. CFDataRef cmapTable = CGFontCopyTableForTag (cgFontRef, 'cmap');
  805. if (cmapTable != 0)
  806. {
  807. const int numSubtables = getValue16 (cmapTable, 2);
  808. for (int i = 0; i < numSubtables; ++i)
  809. {
  810. if (getValue16 (cmapTable, i * 8 + 4) == 0) // check for platform ID of 0
  811. {
  812. const int offset = getValue32 (cmapTable, i * 8 + 8);
  813. if (getValue16 (cmapTable, offset) == 4) // check that it's format 4..
  814. {
  815. const int length = getValue16 (cmapTable, offset + 2);
  816. const int segCountX2 = getValue16 (cmapTable, offset + 6);
  817. segCount = segCountX2 / 2;
  818. const int endCodeOffset = offset + 14;
  819. const int startCodeOffset = endCodeOffset + 2 + segCountX2;
  820. const int idDeltaOffset = startCodeOffset + segCountX2;
  821. const int idRangeOffsetOffset = idDeltaOffset + segCountX2;
  822. const int glyphIndexesOffset = idRangeOffsetOffset + segCountX2;
  823. endCode = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + endCodeOffset, segCountX2);
  824. startCode = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + startCodeOffset, segCountX2);
  825. idDelta = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + idDeltaOffset, segCountX2);
  826. idRangeOffset = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + idRangeOffsetOffset, segCountX2);
  827. glyphIndexes = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + glyphIndexesOffset, offset + length - glyphIndexesOffset);
  828. }
  829. break;
  830. }
  831. }
  832. CFRelease (cmapTable);
  833. }
  834. }
  835. ~CharToGlyphMapper()
  836. {
  837. if (endCode != 0)
  838. {
  839. CFRelease (endCode);
  840. CFRelease (startCode);
  841. CFRelease (idDelta);
  842. CFRelease (idRangeOffset);
  843. CFRelease (glyphIndexes);
  844. }
  845. }
  846. int getGlyphForCharacter (const juce_wchar c) const
  847. {
  848. for (int i = 0; i < segCount; ++i)
  849. {
  850. if (getValue16 (endCode, i * 2) >= c)
  851. {
  852. const int start = getValue16 (startCode, i * 2);
  853. if (start > c)
  854. break;
  855. const int delta = getValue16 (idDelta, i * 2);
  856. const int rangeOffset = getValue16 (idRangeOffset, i * 2);
  857. if (rangeOffset == 0)
  858. return delta + c;
  859. else
  860. return getValue16 (glyphIndexes, 2 * ((rangeOffset / 2) + (c - start) - (segCount - i)));
  861. }
  862. }
  863. // If we failed to find it "properly", this dodgy fall-back seems to do the trick for most fonts!
  864. return jmax (-1, (int) c - 29);
  865. }
  866. private:
  867. int segCount;
  868. CFDataRef endCode, startCode, idDelta, idRangeOffset, glyphIndexes;
  869. static uint16 getValue16 (CFDataRef data, const int index)
  870. {
  871. return CFSwapInt16BigToHost (*(UInt16*) (CFDataGetBytePtr (data) + index));
  872. }
  873. static uint32 getValue32 (CFDataRef data, const int index)
  874. {
  875. return CFSwapInt32BigToHost (*(UInt32*) (CFDataGetBytePtr (data) + index));
  876. }
  877. };
  878. ScopedPointer <CharToGlyphMapper> charToGlyphMapper;
  879. #endif
  880. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
  881. };
  882. StringArray Font::findAllTypefaceNames()
  883. {
  884. StringArray names;
  885. JUCE_AUTORELEASEPOOL
  886. {
  887. #if JUCE_IOS
  888. for (NSString* name in [UIFont familyNames])
  889. #else
  890. for (NSString* name in [[NSFontManager sharedFontManager] availableFontFamilies])
  891. #endif
  892. names.add (nsStringToJuce (name));
  893. names.sort (true);
  894. }
  895. return names;
  896. }
  897. StringArray Font::findAllTypefaceStyles (const String& family)
  898. {
  899. if (FontStyleHelpers::isPlaceholderFamilyName (family))
  900. return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
  901. StringArray results;
  902. JUCE_AUTORELEASEPOOL
  903. {
  904. for (NSArray* style in [[NSFontManager sharedFontManager] availableMembersOfFontFamily: juceStringToNS (family)])
  905. results.add (nsStringToJuce ((NSString*) [style objectAtIndex: 1]));
  906. }
  907. return results;
  908. }
  909. #endif
  910. //==============================================================================
  911. Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
  912. {
  913. return new OSXTypeface (font);
  914. }
  915. void Typeface::scanFolderForFonts (const File&)
  916. {
  917. jassertfalse; // not implemented on this platform
  918. }
  919. struct DefaultFontNames
  920. {
  921. DefaultFontNames()
  922. #if JUCE_IOS
  923. : defaultSans ("Helvetica"),
  924. defaultSerif ("Times New Roman"),
  925. defaultFixed ("Courier New"),
  926. #else
  927. : defaultSans ("Lucida Grande"),
  928. defaultSerif ("Times New Roman"),
  929. defaultFixed ("Menlo"),
  930. #endif
  931. defaultFallback ("Arial Unicode MS")
  932. {
  933. }
  934. String defaultSans, defaultSerif, defaultFixed, defaultFallback;
  935. };
  936. Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
  937. {
  938. static DefaultFontNames defaultNames;
  939. Font newFont (font);
  940. const String& faceName = font.getTypefaceName();
  941. if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
  942. else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
  943. else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
  944. if (font.getTypefaceStyle() == getDefaultStyle())
  945. newFont.setTypefaceStyle ("Regular");
  946. return Typeface::createSystemTypefaceFor (newFont);
  947. }
  948. bool TextLayout::createNativeLayout (const AttributedString& text)
  949. {
  950. #if JUCE_CORETEXT_AVAILABLE
  951. CoreTextTypeLayout::createLayout (*this, text);
  952. return true;
  953. #else
  954. (void) text;
  955. return false;
  956. #endif
  957. }