/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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 CTFontRef getFontWithPointSize (CTFontRef font, float size) { auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr); CFRelease (font); return newFont; } static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired) { auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString(); auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString(); CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute }; CFTypeRef values[] = { cfFontFamily, cfFontStyle }; auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFRelease (cfFontStyle); auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); CFRelease (fontDescAttributes); auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr); CFRelease (ctFontDescRef); CFRelease (cfFontFamily); return ctFontRef; } //============================================================================== 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 CTFontRef getOrCreateFont (const Font& f) { if (auto ctf = getCTFontFromTypeface (f)) { CFRetain (ctf); return ctf; } CGAffineTransform transform; return createCTFont (f, referenceFontSize, transform); } //============================================================================== static CTTextAlignment getTextAlignment (const AttributedString& text) { switch (text.getJustification().getOnlyHorizontalFlags()) { #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 case Justification::right: return kCTTextAlignmentRight; case Justification::horizontallyCentred: return kCTTextAlignmentCenter; case Justification::horizontallyJustified: return kCTTextAlignmentJustified; default: return kCTTextAlignmentLeft; #else case Justification::right: return kCTRightTextAlignment; case Justification::horizontallyCentred: return kCTCenterTextAlignment; case Justification::horizontallyJustified: return kCTJustifiedTextAlignment; default: return kCTLeftTextAlignment; #endif } } 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; } } //============================================================================== static CFAttributedStringRef createCFAttributedString (const AttributedString& text) { #if JUCE_IOS auto rgbColourSpace = CGColorSpaceCreateDeviceRGB(); #endif auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); auto cfText = text.getText().toCFString(); CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText); CFRelease (cfText); auto numCharacterAttributes = text.getNumAttributes(); auto attribStringLen = CFAttributedStringGetLength (attribString); for (int i = 0; i < numCharacterAttributes; ++i) { auto& attr = text.getAttribute (i); auto rangeStart = attr.range.getStart(); if (rangeStart >= attribStringLen) continue; auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart); if (auto ctFontRef = getOrCreateFont (attr.font)) { ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef)); CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef); auto extraKerning = attr.font.getExtraKerningFactor(); if (extraKerning != 0) { extraKerning *= attr.font.getHeight(); auto numberRef = CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning); CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef); CFRelease (numberRef); } CFRelease (ctFontRef); } { auto col = attr.colour; #if JUCE_IOS const CGFloat components[] = { col.getFloatRed(), col.getFloatGreen(), col.getFloatBlue(), col.getFloatAlpha() }; auto colour = CGColorCreate (rgbColourSpace, components); #else auto colour = CGColorCreateGenericRGB (col.getFloatRed(), col.getFloatGreen(), col.getFloatBlue(), col.getFloatAlpha()); #endif 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 } }; auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)); CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), kCTParagraphStyleAttributeName, ctParagraphStyleRef); CFRelease (ctParagraphStyleRef); #if JUCE_IOS CGColorSpaceRelease (rgbColourSpace); #endif return attribString; } static CTFramesetterRef createCTFramesetter (const AttributedString& text) { auto attribString = createCFAttributedString (text); auto framesetter = CTFramesetterCreateWithAttributedString (attribString); CFRelease (attribString); return framesetter; } static CTFrameRef createCTFrame (CTFramesetterRef framesetter, CGRect bounds) { auto path = CGPathCreateMutable(); CGPathAddRect (path, nullptr, bounds); auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr); CGPathRelease (path); return frame; } static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds) { auto framesetter = createCTFramesetter (text); auto frame = createCTFrame (framesetter, bounds); CFRelease (framesetter); return frame; } 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 void drawToCGContext (const AttributedString& text, const Rectangle& area, const CGContextRef& context, float flipHeight) { auto framesetter = createCTFramesetter (text); // 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, 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, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight())); CFRelease (framesetter); auto textMatrix = CGContextGetTextMatrix (context); CGContextSaveGState (context); if (verticalJustification == Justification::verticallyCentred || verticalJustification == Justification::bottom) { auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame); if (verticalJustification == Justification::verticallyCentred) adjust *= 0.5f; CGContextTranslateCTM (context, 0, -adjust); } CTFrameDraw (frame, context); CGContextRestoreGState (context); CGContextSetTextMatrix (context, textMatrix); CFRelease (frame); } static void createLayout (TextLayout& glyphLayout, const AttributedString& text) { auto boundsHeight = glyphLayout.getHeight(); auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); auto lines = CTFrameGetLines (frame); 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 (frame, 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)) { auto cfsFontName = CTFontCopyPostScriptName (ctRunFont); auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr); CFRelease (cfsFontName); auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef); CFRelease (ctFontRef); auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute); auto cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute); glyphRun->font = Font (String::fromCFString (cfsFontFamily), String::fromCFString (cfsFontStyle), (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); CFRelease (cfsFontStyle); CFRelease (cfsFontFamily); } 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], Point ((float) positions.points[k].x, (float) positions.points[k].y), (float) advances.advances[k].width)); } glyphLayout.addLine (std::move (glyphLine)); } CFRelease (frame); } } //============================================================================== 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, 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 auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(), (CFIndex) dataCopy.getSize(), kCFAllocatorNull); auto provider = CGDataProviderCreateWithCFData (cfData); CFRelease (cfData); #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 JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11) canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr); #endif ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr); if (ctFontRef != nullptr) { if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey)) { name = String::fromCFString (fontName); CFRelease (fontName); } if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey)) { style = String::fromCFString (fontStyle); CFRelease (fontStyle); } initialiseMetrics(); } } } void initialiseMetrics() { auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); auto ctTotalHeight = ctAscent + ctDescent; ascent = ctAscent / ctTotalHeight; unitsToHeightScaleFactor = 1.0f / ctTotalHeight; pathTransform = AffineTransform::scale (unitsToHeightScaleFactor); fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; const short zero = 0; auto numberRef = CFNumberCreate (nullptr, kCFNumberShortType, &zero); CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; CFTypeRef values[] = { ctFontRef, numberRef }; attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFRelease (numberRef); } ~OSXTypeface() override { if (attributedStringAtts != nullptr) CFRelease (attributedStringAtts); if (fontRef != nullptr) { #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8 if (dataCopy.getSize() != 0) CTFontManagerUnregisterGraphicsFont (fontRef, nullptr); #endif CGFontRelease (fontRef); } if (ctFontRef != nullptr) CFRelease (ctFontRef); } 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()) { auto cfText = text.toCFString(); auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); CFRelease (cfText); auto line = CTLineCreateWithAttributedString (attribString); auto runArray = CTLineGetGlyphRuns (line); 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; } CFRelease (line); CFRelease (attribString); 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; auto cfText = text.toCFString(); auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); CFRelease (cfText); auto line = CTLineCreateWithAttributedString (attribString); auto runArray = CTLineGetGlyphRuns (line); 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]); } } CFRelease (line); CFRelease (attribString); } } 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 = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform)) { CGPathApply (pathRef, &path, pathApplier); CFRelease (pathRef); if (! pathTransform.isIdentity()) path.applyTransform (pathTransform); return true; } return false; } //============================================================================== CGFontRef fontRef = {}; CTFontRef ctFontRef = {}; float fontHeightToPointsFactor = 1.0f; CGAffineTransform renderingTransform = CGAffineTransformIdentity; bool canBeUsedForLayout; private: MemoryBlock dataCopy; CFDictionaryRef 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) { if (auto* tf = dynamic_cast (f.getTypeface())) return tf->ctFontRef; 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 auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames(); for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i) { auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i)); if (! family.startsWithChar ('.')) // ignore fonts that start with a '.' names.addIfNotAlreadyThere (family); } CFRelease (fontFamilyArray); #else auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr); auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); CFRelease (fontCollectionRef); for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) { auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute); names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily)); CFRelease (cfsFontFamily); } CFRelease (fontDescriptorArray); #endif names.sort (true); return names; } StringArray Font::findAllTypefaceStyles (const String& family) { if (FontStyleHelpers::isPlaceholderFamilyName (family)) return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); StringArray results; auto cfsFontFamily = family.toCFString(); CFStringRef keys[] = { kCTFontFamilyNameAttribute }; CFTypeRef values[] = { cfsFontFamily }; auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFRelease (cfsFontFamily); auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); CFRelease (fontDescAttributes); auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); CFRelease (ctFontDescRef); auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr); CFRelease (fontFamilyArray); auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); CFRelease (fontCollectionRef); if (fontDescriptorArray != nullptr) { for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) { auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute); results.add (String::fromCFString (cfsFontStyle)); CFRelease (cfsFontStyle); } CFRelease (fontDescriptorArray); } 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 = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true)) { CTFontManagerRegisterFontsForURL (urlref, kCTFontManagerScopeProcess, nullptr); CFRelease (urlref); } } } 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) { if (auto tf = dynamic_cast (text.getAttribute(i).font.getTypeface())) if (tf->canBeUsedForLayout) continue; return false; } return true; } bool TextLayout::createNativeLayout (const AttributedString& text) { if (canAllTypefacesBeUsedInLayout (text)) { CoreTextTypeLayout::createLayout (*this, text); return true; } return false; } } // namespace juce