/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { static constexpr float referenceFontSize = 1024.0f; static CTFontRef getCTFontFromTypeface (const Font&); namespace CoreTextTypeLayout { static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform) { auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName()); auto style = font.getTypefaceStyle(); if (! availableStyles.contains (style)) { if (font.isItalic()) // Fake-up an italic font if there isn't a real one. requiredTransform = CGAffineTransformMake (1.0f, 0, 0.1f, 1.0f, 0, 0); return availableStyles[0]; } return style; } static float getFontTotalHeight (CTFontRef font) { return std::abs ((float) CTFontGetAscent (font)) + std::abs ((float) CTFontGetDescent (font)); } static float getHeightToPointsFactor (CTFontRef font) { return referenceFontSize / getFontTotalHeight (font); } static CFUniquePtr getFontWithPointSize (CTFontRef font, float pointSize) { return CFUniquePtr (CTFontCreateCopyWithAttributes (font, pointSize, nullptr, nullptr)); } static CFUniquePtr createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired) { CFUniquePtr cfFontFamily (FontStyleHelpers::getConcreteFamilyName (font).toCFString()); CFUniquePtr cfFontStyle (findBestAvailableStyle (font, transformRequired).toCFString()); CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute }; CFTypeRef values[] = { cfFontFamily.get(), cfFontStyle.get() }; CFUniquePtr fontDescAttributes (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); CFUniquePtr ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get())); return CFUniquePtr (CTFontCreateWithFontDescriptor (ctFontDescRef.get(), fontSizePoints, nullptr)); } //============================================================================== struct Advances { Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run)) { if (advances == nullptr) { local.malloc (numGlyphs); CTRunGetAdvances (run, CFRangeMake (0, 0), local); advances = local; } } const CGSize* advances; HeapBlock local; }; struct Glyphs { Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run)) { if (glyphs == nullptr) { local.malloc (numGlyphs); CTRunGetGlyphs (run, CFRangeMake (0, 0), local); glyphs = local; } } const CGGlyph* glyphs; HeapBlock local; }; struct Positions { Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run)) { if (points == nullptr) { local.malloc (numGlyphs); CTRunGetPositions (run, CFRangeMake (0, 0), local); points = local; } } const CGPoint* points; HeapBlock local; }; struct LineInfo { LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex) { CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin); CTLineGetTypographicBounds (line, &ascent, &descent, &leading); } CGPoint origin; CGFloat ascent, descent, leading; }; static CFUniquePtr getOrCreateFont (const Font& f) { if (auto ctf = getCTFontFromTypeface (f)) { CFRetain (ctf); return CFUniquePtr (ctf); } CGAffineTransform transform; return createCTFont (f, referenceFontSize, transform); } //============================================================================== static CTTextAlignment getTextAlignment (const AttributedString& text) { const auto flags = text.getJustification().getOnlyHorizontalFlags(); if (@available (macOS 10.8, *)) { switch (flags) { case Justification::right: return kCTTextAlignmentRight; case Justification::horizontallyCentred: return kCTTextAlignmentCenter; case Justification::horizontallyJustified: return kCTTextAlignmentJustified; default: return kCTTextAlignmentLeft; } } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") switch (flags) { case Justification::right: return kCTRightTextAlignment; case Justification::horizontallyCentred: return kCTCenterTextAlignment; case Justification::horizontallyJustified: return kCTJustifiedTextAlignment; default: return kCTLeftTextAlignment; } JUCE_END_IGNORE_WARNINGS_GCC_LIKE } static CTLineBreakMode getLineBreakMode (const AttributedString& text) { switch (text.getWordWrap()) { case AttributedString::none: return kCTLineBreakByClipping; case AttributedString::byChar: return kCTLineBreakByCharWrapping; case AttributedString::byWord: default: return kCTLineBreakByWordWrapping; } } static CTWritingDirection getWritingDirection (const AttributedString& text) { switch (text.getReadingDirection()) { case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft; case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight; case AttributedString::natural: default: return kCTWritingDirectionNatural; } } //============================================================================== // A flatmap that properly retains/releases font refs class FontMap { public: void emplace (CTFontRef ctFontRef, Font value) { pairs.emplace (std::lower_bound (pairs.begin(), pairs.end(), ctFontRef), ctFontRef, std::move (value)); } const Font* find (CTFontRef ctFontRef) const { const auto iter = std::lower_bound (pairs.begin(), pairs.end(), ctFontRef); if (iter == pairs.end()) return nullptr; if (iter->key.get() != ctFontRef) return nullptr; return &iter->value; } private: struct Pair { Pair (CTFontRef ref, Font font) : key (ref), value (std::move (font)) { CFRetain (ref); } bool operator< (CTFontRef other) const { return key.get() < other; } CFUniquePtr key; Font value; }; std::vector pairs; }; struct AttributedStringAndFontMap { CFUniquePtr string; FontMap fontMap; }; static AttributedStringAndFontMap createCFAttributedString (const AttributedString& text) { FontMap fontMap; const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); CFUniquePtr cfText (text.getText().toCFString()); CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get()); const auto numCharacterAttributes = text.getNumAttributes(); const auto attribStringLen = CFAttributedStringGetLength (attribString); const auto beginPtr = text.getText().toUTF16(); auto currentPosition = beginPtr; for (int i = 0; i < numCharacterAttributes; currentPosition += text.getAttribute (i).range.getLength(), ++i) { const auto& attr = text.getAttribute (i); const auto wordBegin = currentPosition.getAddress() - beginPtr.getAddress(); if (attribStringLen <= wordBegin) continue; const auto wordEndAddress = (currentPosition + attr.range.getLength()).getAddress(); const auto wordEnd = jmin (attribStringLen, (CFIndex) (wordEndAddress - beginPtr.getAddress())); const auto range = CFRangeMake (wordBegin, wordEnd - wordBegin); if (auto ctFontRef = getOrCreateFont (attr.font)) { ctFontRef = getFontWithPointSize (ctFontRef.get(), attr.font.getHeight() * getHeightToPointsFactor (ctFontRef.get())); fontMap.emplace (ctFontRef.get(), attr.font); CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef.get()); if (attr.font.isUnderlined()) { auto underline = kCTUnderlineStyleSingle; CFUniquePtr numberRef (CFNumberCreate (nullptr, kCFNumberIntType, &underline)); CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef.get()); } auto extraKerning = attr.font.getExtraKerningFactor(); if (extraKerning != 0) { extraKerning *= attr.font.getHeight(); CFUniquePtr numberRef (CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning)); CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef.get()); } } { auto col = attr.colour; const CGFloat components[] = { col.getFloatRed(), col.getFloatGreen(), col.getFloatBlue(), col.getFloatAlpha() }; auto colour = CGColorCreate (rgbColourSpace.get(), components); CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour); CGColorRelease (colour); } } // Paragraph Attributes auto ctTextAlignment = getTextAlignment (text); auto ctLineBreakMode = getLineBreakMode (text); auto ctWritingDirection = getWritingDirection (text); CGFloat ctLineSpacing = text.getLineSpacing(); CTParagraphStyleSetting settings[] = { { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment }, { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode }, { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection}, { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing } }; CFUniquePtr ctParagraphStyleRef (CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings))); CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), kCTParagraphStyleAttributeName, ctParagraphStyleRef.get()); return { CFUniquePtr (attribString), std::move (fontMap) }; } struct FramesetterAndFontMap { CFUniquePtr framesetter; FontMap fontMap; }; static FramesetterAndFontMap createCTFramesetter (const AttributedString& text) { auto attribStringAndMap = createCFAttributedString (text); return { CFUniquePtr (CTFramesetterCreateWithAttributedString (attribStringAndMap.string.get())), std::move (attribStringAndMap.fontMap) }; } static CFUniquePtr createCTFrame (CTFramesetterRef framesetter, CGRect bounds) { auto path = CGPathCreateMutable(); CGPathAddRect (path, nullptr, bounds); CFUniquePtr frame (CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr)); CGPathRelease (path); return frame; } struct FrameAndFontMap { CFUniquePtr frame; FontMap fontMap; }; static FrameAndFontMap createCTFrame (const AttributedString& text, CGRect bounds) { auto framesetterAndMap = createCTFramesetter (text); return { createCTFrame (framesetterAndMap.framesetter.get(), bounds), std::move (framesetterAndMap.fontMap) }; } static Range getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex) { LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex); return { (float) (info.origin.y - info.descent), (float) (info.origin.y + info.ascent) }; } static float findCTFrameHeight (CTFrameRef frame) { auto lines = CTFrameGetLines (frame); auto numLines = CFArrayGetCount (lines); if (numLines == 0) return 0; auto range = getLineVerticalRange (frame, lines, 0); if (numLines > 1) range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1)); return range.getLength(); } static bool areAllFontsDefaultWidth (const AttributedString& text) { auto numCharacterAttributes = text.getNumAttributes(); for (int i = 0; i < numCharacterAttributes; ++i) if (text.getAttribute (i).font.getHorizontalScale() != 1.0f) return false; return true; } static bool drawToCGContext (const AttributedString& text, const Rectangle& area, const CGContextRef& context, float flipHeight) { if (! areAllFontsDefaultWidth (text)) return false; auto framesetter = createCTFramesetter (text).framesetter; // Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly // larger than the font height - otherwise the CTFrame will be invalid CFRange fitrange; auto suggestedSingleLineFrameSize = CTFramesetterSuggestFrameSizeWithConstraints (framesetter.get(), CFRangeMake (0, 0), nullptr, CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange); auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height; auto verticalJustification = text.getJustification().getOnlyVerticalFlags(); auto ctFrameArea = [area, minCTFrameHeight, verticalJustification] { if (minCTFrameHeight < area.getHeight()) return area; if (verticalJustification == Justification::verticallyCentred) return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight); auto frameArea = area.withHeight (minCTFrameHeight); if (verticalJustification == Justification::bottom) return frameArea.withBottomY (area.getBottom()); return frameArea; }(); auto frame = createCTFrame (framesetter.get(), CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight())); auto textMatrix = CGContextGetTextMatrix (context); CGContextSaveGState (context); if (verticalJustification == Justification::verticallyCentred || verticalJustification == Justification::bottom) { auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame.get()); if (verticalJustification == Justification::verticallyCentred) adjust *= 0.5f; CGContextTranslateCTM (context, 0, -adjust); } CTFrameDraw (frame.get(), context); CGContextRestoreGState (context); CGContextSetTextMatrix (context, textMatrix); return true; } static void createLayout (TextLayout& glyphLayout, const AttributedString& text) { auto boundsHeight = glyphLayout.getHeight(); auto frameAndMap = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); auto lines = CTFrameGetLines (frameAndMap.frame.get()); auto numLines = CFArrayGetCount (lines); glyphLayout.ensureStorageAllocated ((int) numLines); for (CFIndex i = 0; i < numLines; ++i) { auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i); auto runs = CTLineGetGlyphRuns (line); auto numRuns = CFArrayGetCount (runs); auto cfrlineStringRange = CTLineGetStringRange (line); auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length; Range lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); LineInfo lineInfo (frameAndMap.frame.get(), line, i); auto glyphLine = std::make_unique (lineStringRange, Point ((float) lineInfo.origin.x, (float) (boundsHeight - lineInfo.origin.y)), (float) lineInfo.ascent, (float) lineInfo.descent, (float) lineInfo.leading, (int) numRuns); for (CFIndex j = 0; j < numRuns; ++j) { auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j); auto numGlyphs = CTRunGetGlyphCount (run); auto runStringRange = CTRunGetStringRange (run); auto glyphRun = new TextLayout::Run (Range ((int) runStringRange.location, (int) (runStringRange.location + runStringRange.length - 1)), (int) numGlyphs); glyphLine->runs.add (glyphRun); CFDictionaryRef runAttributes = CTRunGetAttributes (run); CTFontRef ctRunFont; if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont)) { glyphRun->font = [&] { if (auto* it = frameAndMap.fontMap.find (ctRunFont)) return *it; CFUniquePtr cfsFontName (CTFontCopyPostScriptName (ctRunFont)); CFUniquePtr ctFontRef (CTFontCreateWithName (cfsFontName.get(), referenceFontSize, nullptr)); auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef.get()); CFUniquePtr cfsFontFamily ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute)); CFUniquePtr cfsFontStyle ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute)); Font result (String::fromCFString (cfsFontFamily.get()), String::fromCFString (cfsFontStyle.get()), (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); auto isUnderlined = [&] { CFNumberRef underlineStyle; if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle)) { if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID()) { int value = 0; CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value); return value != 0; } } return false; }(); result.setUnderline (isUnderlined); return result; }(); } CGColorRef cgRunColor; if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor) && CGColorGetNumberOfComponents (cgRunColor) == 4) { auto* components = CGColorGetComponents (cgRunColor); glyphRun->colour = Colour::fromFloatRGBA ((float) components[0], (float) components[1], (float) components[2], (float) components[3]); } const Glyphs glyphs (run, (size_t) numGlyphs); const Advances advances (run, numGlyphs); const Positions positions (run, (size_t) numGlyphs); for (CFIndex k = 0; k < numGlyphs; ++k) glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], convertToPointFloat (positions.points[k]), (float) advances.advances[k].width)); } glyphLayout.addLine (std::move (glyphLine)); } } } //============================================================================== class OSXTypeface : public Typeface { public: OSXTypeface (const Font& font) : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true) { ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform); if (ctFontRef != nullptr) { fontRef = CTFontCopyGraphicsFont (ctFontRef.get(), nullptr); initialiseMetrics(); } } OSXTypeface (const void* data, size_t dataSize) : Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize) { // We can't use CFDataCreate here as this triggers a false positive in ASAN // so copy the data manually and use CFDataCreateWithBytesNoCopy CFUniquePtr cfData (CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(), (CFIndex) dataCopy.getSize(), kCFAllocatorNull)); auto provider = CGDataProviderCreateWithCFData (cfData.get()); #if JUCE_IOS // Workaround for a an obscure iOS bug which can cause the app to dead-lock // when loading custom type faces. See: http://www.openradar.me/18778790 and // http://stackoverflow.com/questions/40242370/app-hangs-in-simulator [UIFont systemFontOfSize: 12]; #endif fontRef = CGFontCreateWithDataProvider (provider); CGDataProviderRelease (provider); if (fontRef != nullptr) { if (@available (macOS 10.11, *)) canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr); ctFontRef.reset (CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr)); if (ctFontRef != nullptr) { if (auto fontName = CFUniquePtr (CTFontCopyName (ctFontRef.get(), kCTFontFamilyNameKey))) name = String::fromCFString (fontName.get()); if (auto fontStyle = CFUniquePtr (CTFontCopyName (ctFontRef.get(), kCTFontStyleNameKey))) style = String::fromCFString (fontStyle.get()); initialiseMetrics(); } } } void initialiseMetrics() { auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef.get())); auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef.get())); auto ctTotalHeight = ctAscent + ctDescent; ascent = ctAscent / ctTotalHeight; unitsToHeightScaleFactor = 1.0f / ctTotalHeight; pathTransform = AffineTransform::scale (unitsToHeightScaleFactor); fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; const short zero = 0; CFUniquePtr numberRef (CFNumberCreate (nullptr, kCFNumberShortType, &zero)); CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; CFTypeRef values[] = { ctFontRef.get(), numberRef.get() }; attributedStringAtts.reset (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); } ~OSXTypeface() override { if (fontRef != nullptr) { if (@available (macOS 10.8, *)) if (dataCopy.getSize() != 0) CTFontManagerUnregisterGraphicsFont (fontRef, nullptr); CGFontRelease (fontRef); } } float getAscent() const override { return ascent; } float getDescent() const override { return 1.0f - ascent; } float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; } float getStringWidth (const String& text) override { float x = 0; if (ctFontRef != nullptr && text.isNotEmpty()) { CFUniquePtr cfText (text.toCFString()); CFUniquePtr attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get())); CFUniquePtr line (CTLineCreateWithAttributedString (attribString.get())); auto runArray = CTLineGetGlyphRuns (line.get()); for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) { auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); auto length = CTRunGetGlyphCount (run); const CoreTextTypeLayout::Advances advances (run, length); for (int j = 0; j < length; ++j) x += (float) advances.advances[j].width; } x *= unitsToHeightScaleFactor; } return x; } void getGlyphPositions (const String& text, Array& resultGlyphs, Array& xOffsets) override { xOffsets.add (0); if (ctFontRef != nullptr && text.isNotEmpty()) { float x = 0; CFUniquePtr cfText (text.toCFString()); CFUniquePtr attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get())); CFUniquePtr line (CTLineCreateWithAttributedString (attribString.get())); auto runArray = CTLineGetGlyphRuns (line.get()); for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) { auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); auto length = CTRunGetGlyphCount (run); const CoreTextTypeLayout::Advances advances (run, length); const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length); for (int j = 0; j < length; ++j) { x += (float) advances.advances[j].width; xOffsets.add (x * unitsToHeightScaleFactor); resultGlyphs.add (glyphs.glyphs[j]); } } } } bool getOutlineForGlyph (int glyphNumber, Path& path) override { jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty if (auto pathRef = CFUniquePtr (CTFontCreatePathForGlyph (ctFontRef.get(), (CGGlyph) glyphNumber, &renderingTransform))) { CGPathApply (pathRef.get(), &path, pathApplier); if (! pathTransform.isIdentity()) path.applyTransform (pathTransform); return true; } return false; } //============================================================================== CGFontRef fontRef = {}; CFUniquePtr ctFontRef; float fontHeightToPointsFactor = 1.0f; CGAffineTransform renderingTransform = CGAffineTransformIdentity; bool canBeUsedForLayout; private: MemoryBlock dataCopy; CFUniquePtr attributedStringAtts; float ascent = 0, unitsToHeightScaleFactor = 0; AffineTransform pathTransform; static void pathApplier (void* info, const CGPathElement* element) { auto& path = *static_cast (info); auto* p = element->points; switch (element->type) { case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, (float) p[1].x, (float) -p[1].y); break; case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, (float) p[1].x, (float) -p[1].y, (float) p[2].x, (float) -p[2].y); break; case kCGPathElementCloseSubpath: path.closeSubPath(); break; default: jassertfalse; break; } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface) }; CTFontRef getCTFontFromTypeface (const Font& f) { const auto typeface = f.getTypefacePtr(); if (auto* tf = dynamic_cast (typeface.get())) return tf->ctFontRef.get(); return {}; } StringArray Font::findAllTypefaceNames() { StringArray names; #if JUCE_MAC // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS CFUniquePtr fontFamilyArray (CTFontManagerCopyAvailableFontFamilyNames()); for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray.get()); ++i) { auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray.get(), i)); if (! family.startsWithChar ('.')) // ignore fonts that start with a '.' names.addIfNotAlreadyThere (family); } #else CFUniquePtr fontCollectionRef (CTFontCollectionCreateFromAvailableFonts (nullptr)); CFUniquePtr fontDescriptorArray (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get())); for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i) { auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i); CFUniquePtr cfsFontFamily ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute)); names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily.get())); } #endif names.sort (true); return names; } StringArray Font::findAllTypefaceStyles (const String& family) { if (FontStyleHelpers::isPlaceholderFamilyName (family)) return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); StringArray results; CFUniquePtr cfsFontFamily (family.toCFString()); CFStringRef keys[] = { kCTFontFamilyNameAttribute }; CFTypeRef values[] = { cfsFontFamily.get() }; CFUniquePtr fontDescAttributes (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); CFUniquePtr ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get())); CFUniquePtr fontFamilyArray (CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks)); CFUniquePtr fontCollectionRef (CTFontCollectionCreateWithFontDescriptors (fontFamilyArray.get(), nullptr)); if (auto fontDescriptorArray = CFUniquePtr (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get()))) { for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i) { auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i); CFUniquePtr cfsFontStyle ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute)); results.add (String::fromCFString (cfsFontStyle.get())); } } return results; } //============================================================================== Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return *new OSXTypeface (font); } Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) { return *new OSXTypeface (data, size); } void Typeface::scanFolderForFonts (const File& folder) { for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf")) if (auto urlref = CFUniquePtr (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true))) CTFontManagerRegisterFontsForURL (urlref.get(), kCTFontManagerScopeProcess, nullptr); } struct DefaultFontNames { #if JUCE_IOS String defaultSans { "Helvetica" }, defaultSerif { "Times New Roman" }, defaultFixed { "Courier New" }; #else String defaultSans { "Lucida Grande" }, defaultSerif { "Times New Roman" }, defaultFixed { "Menlo" }; #endif }; Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) { static DefaultFontNames defaultNames; auto newFont = font; auto faceName = font.getTypefaceName(); if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans); else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif); else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed); if (font.getTypefaceStyle() == getDefaultStyle()) newFont.setTypefaceStyle ("Regular"); return Typeface::createSystemTypefaceFor (newFont); } static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) { auto numCharacterAttributes = text.getNumAttributes(); for (int i = 0; i < numCharacterAttributes; ++i) { auto typeface = text.getAttribute (i).font.getTypefacePtr(); if (auto tf = dynamic_cast (typeface.get())) if (tf->canBeUsedForLayout) continue; return false; } return true; } bool TextLayout::createNativeLayout (const AttributedString& text) { if (canAllTypefacesBeUsedInLayout (text) && CoreTextTypeLayout::areAllFontsDefaultWidth (text)) { CoreTextTypeLayout::createLayout (*this, text); return true; } return false; } } // namespace juce