| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "rive/rive_types.hpp" |
| #include "utils/rive_utf.hpp" |
| |
| #if defined(RIVE_BUILD_FOR_APPLE) && defined(RIVE_TEXT) |
| #include "renderfont_coretext.hpp" |
| #include "mac_utils.hpp" |
| |
| #include "rive/factory.hpp" |
| #include "rive/render_text.hpp" |
| #include "rive/core/type_conversions.hpp" |
| |
| #if defined(RIVE_BUILD_FOR_OSX) |
| #include <ApplicationServices/ApplicationServices.h> |
| #elif defined(RIVE_BUILD_FOR_IOS) |
| #include <CoreText/CoreText.h> |
| #include <CoreText/CTFontManager.h> |
| #include <CoreGraphics/CoreGraphics.h> |
| #include <CoreFoundation/CoreFoundation.h> |
| #endif |
| |
| constexpr int kStdScale = 2048; |
| constexpr float gInvScale = 1.0f / kStdScale; |
| |
| static std::vector<rive::RenderFont::Axis> compute_axes(CTFontRef font) { |
| std::vector<rive::RenderFont::Axis> axes; |
| |
| AutoCF array = CTFontCopyVariationAxes(font); |
| if (auto count = array.get() ? CFArrayGetCount(array.get()) : 0) { |
| axes.reserve(count); |
| |
| for (auto i = 0; i < count; ++i) { |
| auto axis = (CFDictionaryRef)CFArrayGetValueAtIndex(array, i); |
| |
| auto tag = find_u32(axis, kCTFontVariationAxisIdentifierKey); |
| auto min = find_float(axis, kCTFontVariationAxisMinimumValueKey); |
| auto def = find_float(axis, kCTFontVariationAxisDefaultValueKey); |
| auto max = find_float(axis, kCTFontVariationAxisMaximumValueKey); |
| // printf("%08X %g %g %g\n", tag, min, def, max); |
| |
| axes.push_back({tag, min, def, max}); |
| } |
| } |
| return axes; |
| } |
| |
| static std::vector<rive::RenderFont::Coord> compute_coords(CTFontRef font) { |
| std::vector<rive::RenderFont::Coord> coords(0); |
| AutoCF dict = CTFontCopyVariation(font); |
| if (dict) { |
| int count = CFDictionaryGetCount(dict); |
| if (count > 0) { |
| coords.resize(count); |
| |
| AutoSTArray<100, const void*> ptrs(count * 2); |
| const void** keys = &ptrs[0]; |
| const void** values = &ptrs[count]; |
| CFDictionaryGetKeysAndValues(dict, keys, values); |
| for (int i = 0; i < count; ++i) { |
| uint32_t tag = number_as_u32((CFNumberRef)keys[i]); |
| float value = number_as_float((CFNumberRef)values[i]); |
| // printf("[%d] %08X %s %g\n", i, tag, tag2str(tag).c_str(), value); |
| coords[i] = {tag, value}; |
| } |
| } |
| } |
| return coords; |
| } |
| |
| static rive::RenderFont::LineMetrics make_lmx(CTFontRef font) { |
| return { |
| (float)-CTFontGetAscent(font) * gInvScale, |
| (float)CTFontGetDescent(font) * gInvScale, |
| }; |
| } |
| |
| CoreTextRenderFont::CoreTextRenderFont(CTFontRef font, std::vector<rive::RenderFont::Axis> axes) : |
| rive::RenderFont(make_lmx(font)), |
| m_font(font), // we take ownership of font |
| m_axes(std::move(axes)), |
| m_coords(compute_coords(font)) {} |
| |
| CoreTextRenderFont::~CoreTextRenderFont() { CFRelease(m_font); } |
| |
| rive::rcp<rive::RenderFont> CoreTextRenderFont::makeAtCoords(rive::Span<const Coord> coords) const { |
| AutoCF vars = CFDictionaryCreateMutable(kCFAllocatorDefault, |
| coords.size(), |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| for (const auto& c : coords) { |
| AutoCF tagNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &c.axis); |
| AutoCF valueNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &c.value); |
| CFDictionaryAddValue(vars.get(), tagNum.get(), valueNum.get()); |
| } |
| |
| AutoCF attrs = CFDictionaryCreateMutable( |
| kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| CFDictionarySetValue(attrs.get(), kCTFontVariationAttribute, vars.get()); |
| |
| AutoCF desc = (CTFontDescriptorRef)CTFontDescriptorCreateWithAttributes(attrs.get()); |
| |
| auto font = CTFontCreateCopyWithAttributes(m_font, 0, nullptr, desc.get()); |
| |
| return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(font, compute_axes(font))); |
| } |
| |
| static CTFontRef font_from_run(CTRunRef run) { |
| auto attr = CTRunGetAttributes(run); |
| assert(attr); |
| CTFontRef ct = (CTFontRef)CFDictionaryGetValue(attr, kCTFontAttributeName); |
| assert(ct); |
| return ct; |
| } |
| |
| static rive::rcp<rive::RenderFont> convert_to_renderfont(CTFontRef ct, |
| rive::rcp<rive::RenderFont> rf) { |
| auto ctrf = static_cast<CoreTextRenderFont*>(rf.get()); |
| if (ctrf->m_font == ct) { |
| return rf; |
| } |
| CFRetain(ct); |
| return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(ct, compute_axes(ct))); |
| } |
| |
| static void apply_element(void* ctx, const CGPathElement* element) { |
| auto path = (rive::RawPath*)ctx; |
| const CGPoint* points = element->points; |
| |
| switch (element->type) { |
| case kCGPathElementMoveToPoint: |
| path->moveTo(points[0].x, points[0].y); |
| break; |
| |
| case kCGPathElementAddLineToPoint: |
| path->lineTo(points[0].x, points[0].y); |
| break; |
| |
| case kCGPathElementAddQuadCurveToPoint: |
| path->quadTo(points[0].x, points[0].y, points[1].x, points[1].y); |
| break; |
| |
| case kCGPathElementAddCurveToPoint: |
| path->cubicTo( |
| points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); |
| break; |
| |
| case kCGPathElementCloseSubpath: |
| path->close(); |
| break; |
| |
| default: |
| assert(false); |
| break; |
| } |
| } |
| |
| rive::RawPath CoreTextRenderFont::getPath(rive::GlyphID glyph) const { |
| rive::RawPath rpath; |
| |
| AutoCF cgPath = CTFontCreatePathForGlyph(m_font, glyph, nullptr); |
| if (!cgPath) { |
| return rpath; |
| } |
| |
| CGPathApply(cgPath.get(), &rpath, apply_element); |
| rpath.transformInPlace(rive::Mat2D::fromScale(gInvScale, -gInvScale)); |
| return rpath; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////// |
| |
| struct AutoUTF16 { |
| std::vector<uint16_t> array; |
| |
| AutoUTF16(const rive::Unichar uni[], int count) { |
| array.reserve(count); |
| for (int i = 0; i < count; ++i) { |
| uint16_t tmp[2]; |
| int n = rive::UTF::ToUTF16(uni[i], tmp); |
| |
| for (int i = 0; i < n; ++i) { |
| array.push_back(tmp[i]); |
| } |
| } |
| } |
| }; |
| |
| static float |
| add_run(rive::RenderGlyphRun* gr, CTRunRef run, uint32_t textStart, float textSize, float startX) { |
| if (auto count = CTRunGetGlyphCount(run)) { |
| const float scale = textSize * gInvScale; |
| |
| gr->glyphs.resize(count); |
| gr->xpos.resize(count + 1); |
| gr->textOffsets.resize(count); |
| |
| CTRunGetGlyphs(run, {0, count}, gr->glyphs.data()); |
| |
| AutoSTArray<1024, CFIndex> indices(count); |
| AutoSTArray<1024, CGSize> advances(count); |
| |
| CTRunGetAdvances(run, {0, count}, advances.data()); |
| CTRunGetStringIndices(run, {0, count}, indices.data()); |
| |
| for (CFIndex i = 0; i < count; ++i) { |
| gr->xpos[i] = startX; |
| gr->textOffsets[i] = textStart + indices[i]; // utf16 offsets, will fix-up later |
| startX += advances[i].width * scale; |
| } |
| gr->xpos[count] = startX; |
| } |
| return startX; |
| } |
| |
| std::vector<rive::RenderGlyphRun> |
| CoreTextRenderFont::onShapeText(rive::Span<const rive::Unichar> text, |
| rive::Span<const rive::RenderTextRun> truns) const { |
| std::vector<rive::RenderGlyphRun> gruns; |
| gruns.reserve(truns.size()); |
| |
| uint32_t textIndex = 0; |
| float startX = 0; |
| for (const auto& tr : truns) { |
| CTFontRef font = ((CoreTextRenderFont*)tr.font.get())->m_font; |
| |
| AutoUTF16 utf16(&text[textIndex], tr.unicharCount); |
| const bool hasSurrogates = utf16.array.size() != tr.unicharCount; |
| assert(!hasSurrogates); |
| |
| AutoCF string = CFStringCreateWithCharactersNoCopy( |
| nullptr, utf16.array.data(), utf16.array.size(), kCFAllocatorNull); |
| |
| AutoCF attr = CFDictionaryCreateMutable(kCFAllocatorDefault, |
| 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| CFDictionaryAddValue(attr.get(), kCTFontAttributeName, font); |
| |
| AutoCF attrString = CFAttributedStringCreate(kCFAllocatorDefault, string.get(), attr.get()); |
| |
| AutoCF typesetter = CTTypesetterCreateWithAttributedString(attrString.get()); |
| |
| AutoCF line = CTTypesetterCreateLine(typesetter.get(), {0, tr.unicharCount}); |
| |
| CFArrayRef run_array = CTLineGetGlyphRuns(line.get()); |
| CFIndex runCount = CFArrayGetCount(run_array); |
| for (CFIndex i = 0; i < runCount; ++i) { |
| CTRunRef runref = (CTRunRef)CFArrayGetValueAtIndex(run_array, i); |
| rive::RenderGlyphRun grun; |
| startX = add_run(&grun, runref, textIndex, tr.size, startX); |
| if (grun.glyphs.size() > 0) { |
| auto ct = font_from_run(runref); |
| grun.font = convert_to_renderfont(ct, tr.font); |
| grun.size = tr.size; |
| gruns.push_back(std::move(grun)); |
| } |
| } |
| textIndex += tr.unicharCount; |
| } |
| |
| return gruns; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| rive::rcp<rive::RenderFont> CoreTextRenderFont::FromCT(CTFontRef ctfont) { |
| if (!ctfont) { |
| return nullptr; |
| } |
| |
| // We always want the ctfont at our magic size |
| if (CTFontGetSize(ctfont) != kStdScale) { |
| ctfont = CTFontCreateCopyWithAttributes(ctfont, kStdScale, nullptr, nullptr); |
| } else { |
| CFRetain(ctfont); |
| } |
| |
| // Apple may have secretly set the opsz axis based on the size. We want to undo this |
| // since our stdsize isn't really the size we'll show it at. |
| auto axes = compute_axes(ctfont); |
| if (axes.size() > 0) { |
| constexpr uint32_t kOPSZ = make_tag('o', 'p', 's', 'z'); |
| for (const auto& ax : axes) { |
| if (ax.tag == kOPSZ) { |
| auto xform = CGAffineTransformMakeScale(kStdScale / ax.def, kStdScale / ax.def); |
| // Recreate the font at this size, but with a balancing transform, |
| // so we get the 'default' shapes w.r.t. the opsz axis |
| auto newfont = CTFontCreateCopyWithAttributes(ctfont, ax.def, &xform, nullptr); |
| CFRelease(ctfont); |
| ctfont = newfont; |
| break; |
| } |
| } |
| } |
| |
| return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(ctfont, std::move(axes))); |
| } |
| |
| rive::rcp<rive::RenderFont> CoreTextRenderFont::Decode(rive::Span<const uint8_t> span) { |
| AutoCF data = CFDataCreate(nullptr, span.data(), span.size()); // makes a copy |
| if (!data) { |
| assert(false); |
| return nullptr; |
| } |
| |
| AutoCF desc = CTFontManagerCreateFontDescriptorFromData(data.get()); |
| if (!desc) { |
| assert(false); |
| return nullptr; |
| } |
| |
| CTFontOptions options = kCTFontOptionsPreventAutoActivation; |
| |
| AutoCF ctfont = |
| CTFontCreateWithFontDescriptorAndOptions(desc.get(), kStdScale, nullptr, options); |
| if (!ctfont) { |
| assert(false); |
| return nullptr; |
| } |
| return FromCT(ctfont.get()); |
| } |
| |
| #endif |