| /* | |
| * Copyright 2022 Rive | |
| */ | |
| #include "rive/text_engine.hpp" | |
| #ifdef WITH_RIVE_TEXT | |
| #include "rive/text/font_hb.hpp" | |
| #include "rive/factory.hpp" | |
| #include "rive/renderer_utils.hpp" | |
| #include "rive/shapes/paint/color.hpp" | |
| #include "hb.h" | |
| #include "hb-ot.h" | |
| #include <algorithm> | |
| #include <unordered_set> | |
| // harfbuzz's own file loader is compiled out (HB_NO_OPEN/HB_NO_MMAP keep it | |
| // out of the wasm builds), so map the file ourselves and hand harfbuzz a | |
| // borrowed pointer. RIVE_HB_FILE_MAPPING is defined in font_hb.hpp. | |
| #ifdef RIVE_HB_FILE_MAPPING | |
| #include <fcntl.h> | |
| #include <sys/mman.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #endif | |
| extern "C" | |
| { | |
| #include "SheenBidi.h" | |
| } | |
| // Initialized to null. Client can set this to a callback. | |
| rive::Font::FallbackProc rive::Font::gFallbackProc; | |
| bool rive::Font::gFallbackProcEnabled = true; | |
| rive::rcp<rive::Font> HBFont::Decode(rive::Span<const uint8_t> span) | |
| { | |
| auto blob = hb_blob_create_or_fail((const char*)span.data(), | |
| (unsigned)span.size(), | |
| HB_MEMORY_MODE_DUPLICATE, | |
| nullptr, | |
| nullptr); | |
| if (blob) | |
| { | |
| auto face = hb_face_create_or_fail(blob, 0); | |
| hb_blob_destroy(blob); | |
| if (face) | |
| { | |
| auto font = hb_font_create(face); | |
| hb_face_destroy(face); | |
| if (font) | |
| { | |
| return rive::rcp<rive::Font>(new HBFont(font)); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| #ifdef RIVE_HB_FILE_MAPPING | |
| namespace | |
| { | |
| struct HBFileMapping | |
| { | |
| void* data; | |
| size_t size; | |
| }; | |
| void destroyHBFileMapping(void* context) | |
| { | |
| auto* mapping = static_cast<HBFileMapping*>(context); | |
| munmap(mapping->data, mapping->size); | |
| delete mapping; | |
| } | |
| } // namespace | |
| #endif | |
| rive::rcp<rive::Font> HBFont::DecodeFile(const char* path) | |
| { | |
| #ifdef RIVE_HB_FILE_MAPPING | |
| if (path == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| int fd = open(path, O_RDONLY); | |
| if (fd == -1) | |
| { | |
| return nullptr; | |
| } | |
| struct stat info; | |
| // hb_blob_create_or_fail rejects >= 2GB, so bail before mapping. | |
| if (fstat(fd, &info) == -1 || info.st_size <= 0 || | |
| static_cast<uint64_t>(info.st_size) >= (1ull << 31)) | |
| { | |
| close(fd); | |
| return nullptr; | |
| } | |
| size_t size = static_cast<size_t>(info.st_size); | |
| void* data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); | |
| // The mapping holds its own reference to the file. | |
| close(fd); | |
| if (data == MAP_FAILED) | |
| { | |
| return nullptr; | |
| } | |
| // READONLY_MAY_MAKE_WRITABLE: harfbuzz reads our clean, evictable file | |
| // pages and only copies if it ever needs to write. | |
| auto blob = | |
| hb_blob_create_or_fail(static_cast<const char*>(data), | |
| static_cast<unsigned>(size), | |
| HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE, | |
| new HBFileMapping{data, size}, | |
| &destroyHBFileMapping); | |
| // On failure harfbuzz has already invoked the destroy callback. | |
| if (blob == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| auto face = hb_face_create_or_fail(blob, 0); | |
| hb_blob_destroy(blob); | |
| if (face == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| auto font = hb_font_create(face); | |
| hb_face_destroy(face); | |
| if (font == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| return rive::rcp<rive::Font>(new HBFont(font)); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| #if defined(RIVE_NO_CORETEXT) || !defined(__APPLE__) | |
| rive::rcp<rive::Font> HBFont::FromSystem(void* systemFont, | |
| bool useSystemShaper, | |
| uint16_t weight, | |
| uint8_t width) | |
| { | |
| return nullptr; | |
| } | |
| #endif | |
| float HBFont::GetStyle(hb_font_t* font, uint32_t styleTag) | |
| { | |
| return hb_style_get_value(font, (hb_style_tag_t)styleTag); | |
| } | |
| ////////////// | |
| constexpr int kStdScale = 2048; | |
| constexpr float gInvScale = 1.0f / kStdScale; | |
| extern "C" | |
| { | |
| static void rpath_move_to(hb_draw_funcs_t*, | |
| void* rpath, | |
| hb_draw_state_t*, | |
| float x, | |
| float y, | |
| void*) | |
| { | |
| ((rive::RawPath*)rpath)->moveTo(x * gInvScale, -y * gInvScale); | |
| } | |
| static void rpath_line_to(hb_draw_funcs_t*, | |
| void* rpath, | |
| hb_draw_state_t*, | |
| float x1, | |
| float y1, | |
| void*) | |
| { | |
| ((rive::RawPath*)rpath)->lineTo(x1 * gInvScale, -y1 * gInvScale); | |
| } | |
| static void rpath_quad_to(hb_draw_funcs_t*, | |
| void* rpath, | |
| hb_draw_state_t*, | |
| float x1, | |
| float y1, | |
| float x2, | |
| float y2, | |
| void*) | |
| { | |
| ((rive::RawPath*)rpath) | |
| ->quadToCubic(x1 * gInvScale, | |
| -y1 * gInvScale, | |
| x2 * gInvScale, | |
| -y2 * gInvScale); | |
| } | |
| static void rpath_cubic_to(hb_draw_funcs_t*, | |
| void* rpath, | |
| hb_draw_state_t*, | |
| float x1, | |
| float y1, | |
| float x2, | |
| float y2, | |
| float x3, | |
| float y3, | |
| void*) | |
| { | |
| ((rive::RawPath*)rpath) | |
| ->cubicTo(x1 * gInvScale, | |
| -y1 * gInvScale, | |
| x2 * gInvScale, | |
| -y2 * gInvScale, | |
| x3 * gInvScale, | |
| -y3 * gInvScale); | |
| } | |
| static void rpath_close(hb_draw_funcs_t*, | |
| void* rpath, | |
| hb_draw_state_t*, | |
| void*) | |
| { | |
| ((rive::RawPath*)rpath)->close(); | |
| } | |
| } | |
| // Convert HarfBuzz color (BGRA byte order) to Rive ColorInt (ARGB). | |
| static rive::ColorInt hbColorToColorInt(hb_color_t c) | |
| { | |
| uint8_t b = hb_color_get_blue(c); | |
| uint8_t g = hb_color_get_green(c); | |
| uint8_t r = hb_color_get_red(c); | |
| uint8_t a = hb_color_get_alpha(c); | |
| return rive::colorARGB(a, r, g, b); | |
| } | |
| // ── COLRv1 paint callbacks ────────────────────────────────────────────── | |
| // We flatten the COLRv1 paint tree into a list of (path, fill) layers. | |
| struct PaintState | |
| { | |
| hb_font_t* font; | |
| hb_draw_funcs_t* drawFuncs; | |
| std::vector<rive::Font::ColorGlyphLayer>* layers; | |
| rive::RawPath clipPath; | |
| bool hasClip = false; | |
| rive::ColorInt foreground = 0xFF000000; | |
| // HarfBuzz transforms conjugated into Rive glyph space (scaled, Y down). | |
| std::vector<rive::Mat2D> transformStack; | |
| void pushTransform(float xx, | |
| float yx, | |
| float xy, | |
| float yy, | |
| float dx, | |
| float dy) | |
| { | |
| rive::Mat2D m(xx, -yx, -xy, yy, dx * gInvScale, -dy * gInvScale); | |
| transformStack.push_back( | |
| transformStack.empty() ? m : transformStack.back() * m); | |
| } | |
| void popTransform() | |
| { | |
| if (!transformStack.empty()) | |
| { | |
| transformStack.pop_back(); | |
| } | |
| } | |
| rive::Mat2D glyphTransform() const | |
| { | |
| rive::Mat2D scale = rive::Mat2D::fromScale(gInvScale, -gInvScale); | |
| return transformStack.empty() ? scale : transformStack.back() * scale; | |
| } | |
| rive::Vec2D mapPoint(float x, float y) const | |
| { | |
| return glyphTransform() * rive::Vec2D(x, y); | |
| } | |
| float mapRadius(float r) const | |
| { | |
| float scaled = r * gInvScale; | |
| if (transformStack.empty()) | |
| { | |
| return scaled; | |
| } | |
| const auto& m = transformStack.back(); | |
| return scaled * std::sqrt(m[0] * m[0] + m[1] * m[1]); | |
| } | |
| void pushClipGlyph(hb_codepoint_t glyph) | |
| { | |
| clipPath.rewind(); | |
| hb_font_draw_glyph(font, glyph, drawFuncs, &clipPath); | |
| if (!transformStack.empty()) | |
| { | |
| clipPath.transformInPlace(transformStack.back()); | |
| } | |
| hasClip = true; | |
| } | |
| // Helper: extract color stops from hb_color_line_t. | |
| static std::vector<rive::Font::GradientStop> extractStops( | |
| hb_color_line_t* colorLine, | |
| rive::ColorInt foreground) | |
| { | |
| unsigned int count = | |
| hb_color_line_get_color_stops(colorLine, 0, nullptr, nullptr); | |
| std::vector<hb_color_stop_t> hbStops(count); | |
| hb_color_line_get_color_stops(colorLine, 0, &count, hbStops.data()); | |
| std::vector<rive::Font::GradientStop> stops; | |
| stops.reserve(count); | |
| for (auto& s : hbStops) | |
| { | |
| rive::ColorInt c = | |
| s.is_foreground ? foreground : hbColorToColorInt(s.color); | |
| stops.push_back( | |
| {std::clamp(s.offset, 0.0f, 1.0f), c, (bool)s.is_foreground}); | |
| } | |
| // Variations can reorder stops and renderers expect them ascending. | |
| std::stable_sort( | |
| stops.begin(), | |
| stops.end(), | |
| [](const auto& a, const auto& b) { return a.offset < b.offset; }); | |
| return stops; | |
| } | |
| rive::Font::ColorGlyphLayer makeClipLayer() const | |
| { | |
| rive::Font::ColorGlyphLayer layer; | |
| layer.path = clipPath; | |
| return layer; | |
| } | |
| }; | |
| static void paint_push_transform(hb_paint_funcs_t*, | |
| void* paint_data, | |
| float xx, | |
| float yx, | |
| float xy, | |
| float yy, | |
| float dx, | |
| float dy, | |
| void*) | |
| { | |
| ((PaintState*)paint_data)->pushTransform(xx, yx, xy, yy, dx, dy); | |
| } | |
| static void paint_pop_transform(hb_paint_funcs_t*, void* paint_data, void*) | |
| { | |
| ((PaintState*)paint_data)->popTransform(); | |
| } | |
| static void paint_push_clip_glyph(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_codepoint_t glyph, | |
| hb_font_t*, | |
| void*) | |
| { | |
| ((PaintState*)paint_data)->pushClipGlyph(glyph); | |
| } | |
| static void paint_push_clip_rectangle(hb_paint_funcs_t*, | |
| void*, | |
| float, | |
| float, | |
| float, | |
| float, | |
| void*) | |
| {} | |
| static void paint_pop_clip(hb_paint_funcs_t*, void* paint_data, void*) | |
| { | |
| ((PaintState*)paint_data)->hasClip = false; | |
| } | |
| static void paint_solid(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_bool_t is_foreground, | |
| hb_color_t color, | |
| void*) | |
| { | |
| auto* state = (PaintState*)paint_data; | |
| if (!state->hasClip) | |
| return; | |
| auto layer = state->makeClipLayer(); | |
| layer.paintType = rive::Font::ColorGlyphPaintType::solid; | |
| if (is_foreground) | |
| { | |
| layer.useForeground = true; | |
| layer.color = state->foreground; | |
| } | |
| else | |
| { | |
| layer.color = hbColorToColorInt(color); | |
| } | |
| state->layers->push_back(std::move(layer)); | |
| } | |
| static void paint_linear_gradient(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_color_line_t* colorLine, | |
| float x0, | |
| float y0, | |
| float x1, | |
| float y1, | |
| float x2, | |
| float y2, | |
| void*) | |
| { | |
| auto* state = (PaintState*)paint_data; | |
| if (!state->hasClip) | |
| return; | |
| auto layer = state->makeClipLayer(); | |
| layer.paintType = rive::Font::ColorGlyphPaintType::linearGradient; | |
| layer.stops = PaintState::extractStops(colorLine, state->foreground); | |
| // OpenType linear gradient uses 3 points: p0 (start), p1 (end), p2 | |
| // (rotation). The gradient line goes from p0 toward p1, and p2 is used | |
| // to rotate the direction. For Rive we need a 2-point definition. | |
| // The actual gradient direction is: start = p0, end = p1 projected | |
| // along the p0->p2 direction. For simplicity, just use p0 and p1. | |
| auto sp0 = state->mapPoint(x0, y0); | |
| auto sp1 = state->mapPoint(x1, y1); | |
| layer.x0 = sp0.x; | |
| layer.y0 = sp0.y; | |
| layer.x1 = sp1.x; | |
| layer.y1 = sp1.y; | |
| state->layers->push_back(std::move(layer)); | |
| } | |
| static void paint_radial_gradient(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_color_line_t* colorLine, | |
| float x0, | |
| float y0, | |
| float radius0, | |
| float x1, | |
| float y1, | |
| float radius1, | |
| void*) | |
| { | |
| auto* state = (PaintState*)paint_data; | |
| if (!state->hasClip) | |
| return; | |
| auto layer = state->makeClipLayer(); | |
| layer.paintType = rive::Font::ColorGlyphPaintType::radialGradient; | |
| layer.stops = PaintState::extractStops(colorLine, state->foreground); | |
| auto sp0 = state->mapPoint(x0, y0); | |
| auto sp1 = state->mapPoint(x1, y1); | |
| layer.x0 = sp0.x; | |
| layer.y0 = sp0.y; | |
| layer.x1 = sp1.x; | |
| layer.y1 = sp1.y; | |
| layer.r0 = state->mapRadius(radius0); | |
| layer.r1 = state->mapRadius(radius1); | |
| layer.radialTransform = | |
| state->glyphTransform() * | |
| rive::Mat2D::fromScaleAndTranslation(radius1, radius1, x1, y1); | |
| state->layers->push_back(std::move(layer)); | |
| } | |
| static void paint_sweep_gradient(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_color_line_t* colorLine, | |
| float cx, | |
| float cy, | |
| float startAngle, | |
| float endAngle, | |
| void*) | |
| { | |
| auto* state = (PaintState*)paint_data; | |
| if (!state->hasClip) | |
| return; | |
| auto layer = state->makeClipLayer(); | |
| layer.paintType = rive::Font::ColorGlyphPaintType::sweepGradient; | |
| layer.stops = PaintState::extractStops(colorLine, state->foreground); | |
| auto sc = state->mapPoint(cx, cy); | |
| layer.x0 = sc.x; | |
| layer.y0 = sc.y; | |
| layer.startAngle = startAngle; | |
| layer.endAngle = endAngle; | |
| state->layers->push_back(std::move(layer)); | |
| } | |
| static void paint_push_group(hb_paint_funcs_t*, void*, void*) {} | |
| static void paint_pop_group(hb_paint_funcs_t*, | |
| void*, | |
| hb_paint_composite_mode_t, | |
| void*) | |
| {} | |
| static hb_bool_t paint_color_glyph(hb_paint_funcs_t*, | |
| void*, | |
| hb_codepoint_t, | |
| hb_font_t*, | |
| void*) | |
| { | |
| return false; | |
| } | |
| static hb_bool_t paint_image(hb_paint_funcs_t*, | |
| void* paint_data, | |
| hb_blob_t* blob, | |
| unsigned int width, | |
| unsigned int height, | |
| hb_tag_t format, | |
| float slant, | |
| hb_glyph_extents_t* extents, | |
| void*) | |
| { | |
| // We only support PNG images (SBIX / CBDT). | |
| if (format != HB_TAG('p', 'n', 'g', ' ')) | |
| { | |
| return false; | |
| } | |
| unsigned int length; | |
| const char* data = hb_blob_get_data(blob, &length); | |
| if (data == nullptr || length == 0) | |
| { | |
| return false; | |
| } | |
| auto* state = (PaintState*)paint_data; | |
| rive::Font::ColorGlyphLayer layer; | |
| layer.paintType = rive::Font::ColorGlyphPaintType::image; | |
| layer.imageBytes.assign(reinterpret_cast<const uint8_t*>(data), | |
| reinterpret_cast<const uint8_t*>(data) + length); | |
| layer.imageWidth = width; | |
| layer.imageHeight = height; | |
| if (extents != nullptr) | |
| { | |
| layer.imageBearingX = extents->x_bearing * gInvScale; | |
| layer.imageBearingY = -extents->y_bearing * gInvScale; | |
| layer.imageExtentX = extents->width * gInvScale; | |
| layer.imageExtentY = -extents->height * gInvScale; | |
| } | |
| state->layers->push_back(std::move(layer)); | |
| return true; | |
| } | |
| // ──────────────────────────────────────────────────────────────────────── | |
| static rive::Font::LineMetrics make_lmx(hb_font_t* font) | |
| { | |
| // premable on font... | |
| hb_ot_font_set_funcs(font); | |
| hb_font_set_scale(font, kStdScale, kStdScale); | |
| hb_font_extents_t extents; | |
| hb_font_get_h_extents(font, &extents); | |
| // Cap height / x-height: distances from the baseline to the top of capital | |
| // and lowercase letters, stored negative (up is -Y) to match ascent. We | |
| // measure the 'H' and 'x' glyphs directly (their top-side bearing) rather | |
| // than the OS/2 metrics, since the metrics API is compiled out of this | |
| // HarfBuzz build (HB_NO_METRICS). Each falls back to the ascent for fonts | |
| // without the reference glyph so vertical trim becomes a no-op at the top. | |
| float ascent = -extents.ascender * gInvScale; | |
| auto measureGlyphTop = [&](hb_codepoint_t unicode) -> float { | |
| hb_codepoint_t glyph = 0; | |
| hb_glyph_extents_t glyphExtents; | |
| if (hb_font_get_nominal_glyph(font, unicode, &glyph) && | |
| hb_font_get_glyph_extents(font, glyph, &glyphExtents)) | |
| { | |
| return -glyphExtents.y_bearing * gInvScale; | |
| } | |
| return ascent; | |
| }; | |
| float capHeight = measureGlyphTop('H'); | |
| float xHeight = measureGlyphTop('x'); | |
| return {ascent, -extents.descender * gInvScale, capHeight, xHeight}; | |
| } | |
| HBFont::HBFont(hb_font_t* font) : HBFont(font, {}, {}, {}) {} | |
| HBFont::HBFont(hb_font_t* font, | |
| std::unordered_map<hb_tag_t, float> axisValues, | |
| std::unordered_map<hb_tag_t, uint32_t> featureValues, | |
| std::vector<hb_feature_t> features) : | |
| Font(make_lmx(font)), | |
| m_font(font), | |
| m_features(features), | |
| m_featureValues(featureValues), | |
| m_axisValues(axisValues) | |
| { | |
| m_drawFuncs = hb_draw_funcs_create(); | |
| hb_draw_funcs_set_move_to_func(m_drawFuncs, | |
| rpath_move_to, | |
| nullptr, | |
| nullptr); | |
| hb_draw_funcs_set_line_to_func(m_drawFuncs, | |
| rpath_line_to, | |
| nullptr, | |
| nullptr); | |
| hb_draw_funcs_set_quadratic_to_func(m_drawFuncs, | |
| rpath_quad_to, | |
| nullptr, | |
| nullptr); | |
| hb_draw_funcs_set_cubic_to_func(m_drawFuncs, | |
| rpath_cubic_to, | |
| nullptr, | |
| nullptr); | |
| hb_draw_funcs_set_close_path_func(m_drawFuncs, | |
| rpath_close, | |
| nullptr, | |
| nullptr); | |
| hb_draw_funcs_make_immutable(m_drawFuncs); | |
| // Initialize COLRv1 paint funcs. | |
| m_paintFuncs = hb_paint_funcs_create(); | |
| hb_paint_funcs_set_push_transform_func(m_paintFuncs, | |
| paint_push_transform, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_pop_transform_func(m_paintFuncs, | |
| paint_pop_transform, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_push_clip_glyph_func(m_paintFuncs, | |
| paint_push_clip_glyph, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_push_clip_rectangle_func(m_paintFuncs, | |
| paint_push_clip_rectangle, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_pop_clip_func(m_paintFuncs, | |
| paint_pop_clip, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_color_func(m_paintFuncs, paint_solid, nullptr, nullptr); | |
| hb_paint_funcs_set_push_group_func(m_paintFuncs, | |
| paint_push_group, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_pop_group_func(m_paintFuncs, | |
| paint_pop_group, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_color_glyph_func(m_paintFuncs, | |
| paint_color_glyph, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_linear_gradient_func(m_paintFuncs, | |
| paint_linear_gradient, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_radial_gradient_func(m_paintFuncs, | |
| paint_radial_gradient, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_sweep_gradient_func(m_paintFuncs, | |
| paint_sweep_gradient, | |
| nullptr, | |
| nullptr); | |
| hb_paint_funcs_set_image_func(m_paintFuncs, paint_image, nullptr, nullptr); | |
| hb_paint_funcs_make_immutable(m_paintFuncs); | |
| // Initialize color glyph (COLR/CPAL/SBIX/CBDT) support. | |
| hb_face_t* face = hb_font_get_face(font); | |
| m_hasColorLayers = hb_ot_color_has_layers(face); | |
| m_hasColorPaint = hb_ot_color_has_paint(face); | |
| m_hasPNG = hb_ot_color_has_png(face); | |
| if (m_hasColorLayers || m_hasColorPaint) | |
| { | |
| unsigned int colorCount = 0; | |
| hb_ot_color_palette_get_colors(face, 0, 0, &colorCount, nullptr); | |
| if (colorCount > 0) | |
| { | |
| m_paletteColors.resize(colorCount); | |
| hb_ot_color_palette_get_colors(face, | |
| 0, | |
| 0, | |
| &colorCount, | |
| m_paletteColors.data()); | |
| } | |
| } | |
| } | |
| HBFont::~HBFont() | |
| { | |
| hb_draw_funcs_destroy(m_drawFuncs); | |
| hb_paint_funcs_destroy(m_paintFuncs); | |
| hb_font_destroy(m_font); | |
| } | |
| static void fillLanguageFeatures(hb_face_t* face, | |
| hb_tag_t tag, | |
| uint32_t scriptIndex, | |
| uint32_t languageIndex, | |
| std::unordered_set<uint32_t>& features) | |
| { | |
| auto featureCount = hb_ot_layout_language_get_feature_tags(face, | |
| tag, | |
| scriptIndex, | |
| languageIndex, | |
| 0, | |
| nullptr, | |
| nullptr); | |
| auto featureTags = std::vector<hb_tag_t>(featureCount); | |
| hb_ot_layout_language_get_feature_tags(face, | |
| tag, | |
| scriptIndex, | |
| languageIndex, | |
| 0, | |
| &featureCount, | |
| featureTags.data()); | |
| for (auto featureTag : featureTags) | |
| { | |
| features.emplace(featureTag); | |
| } | |
| } | |
| static void fillFeatures(hb_face_t* face, | |
| hb_tag_t tag, | |
| std::unordered_set<uint32_t>& features) | |
| { | |
| auto scriptCount = | |
| hb_ot_layout_table_get_script_tags(face, tag, 0, nullptr, nullptr); | |
| auto scripts = std::vector<hb_tag_t>(scriptCount); | |
| hb_ot_layout_table_get_script_tags(face, | |
| tag, | |
| 0, | |
| &scriptCount, | |
| scripts.data()); | |
| for (uint32_t i = 0; i < scriptCount; ++i) | |
| { | |
| auto languageCount = hb_ot_layout_script_get_language_tags(face, | |
| tag, | |
| i, | |
| 0, | |
| nullptr, | |
| nullptr); | |
| if (languageCount > 0) | |
| { | |
| auto languages = std::vector<hb_tag_t>(languageCount); | |
| hb_ot_layout_script_get_language_tags(face, | |
| tag, | |
| i, | |
| 0, | |
| &languageCount, | |
| languages.data()); | |
| for (uint32_t j = 0; j < languageCount; ++j) | |
| { | |
| fillLanguageFeatures(face, tag, i, j, features); | |
| } | |
| } | |
| else | |
| { | |
| fillLanguageFeatures(face, | |
| tag, | |
| i, | |
| HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, | |
| features); | |
| } | |
| } | |
| } | |
| rive::SimpleArray<uint32_t> HBFont::features() const | |
| { | |
| std::unordered_set<uint32_t> features; | |
| auto face = hb_font_get_face(m_font); | |
| fillFeatures(face, HB_OT_TAG_GSUB, features); | |
| fillFeatures(face, HB_OT_TAG_GPOS, features); | |
| rive::SimpleArray<uint32_t> result(features.size()); | |
| uint32_t index = 0; | |
| for (auto tag : features) | |
| { | |
| result[index++] = tag; | |
| } | |
| return result; | |
| } | |
| rive::Font::Axis HBFont::getAxis(uint16_t index) const | |
| { | |
| auto face = hb_font_get_face(m_font); | |
| assert(index < hb_ot_var_get_axis_count(face)); | |
| unsigned n = 1; | |
| hb_ot_var_axis_info_t info; | |
| hb_ot_var_get_axis_infos(face, index, &n, &info); | |
| assert(n == 1); | |
| return {info.tag, info.min_value, info.default_value, info.max_value}; | |
| } | |
| uint16_t HBFont::getAxisCount() const | |
| { | |
| auto face = hb_font_get_face(m_font); | |
| return (uint16_t)hb_ot_var_get_axis_count(face); | |
| } | |
| float HBFont::getAxisValue(uint32_t axisTag) const | |
| { | |
| auto itr = m_axisValues.find(axisTag); | |
| if (itr != m_axisValues.end()) | |
| { | |
| return itr->second; | |
| } | |
| auto face = hb_font_get_face(m_font); | |
| // No value specified, we're using a default. | |
| uint32_t axisCount = hb_ot_var_get_axis_count(face); | |
| for (uint32_t i = 0; i < axisCount; ++i) | |
| { | |
| hb_ot_var_axis_info_t info; | |
| uint32_t n = 1; | |
| hb_ot_var_get_axis_infos(face, i, &n, &info); | |
| if (info.tag == axisTag) | |
| { | |
| return info.default_value; | |
| } | |
| } | |
| return 0.0f; | |
| } | |
| uint32_t HBFont::getFeatureValue(uint32_t featureTag) const | |
| { | |
| auto itr = m_featureValues.find(featureTag); | |
| if (itr != m_featureValues.end()) | |
| { | |
| return itr->second; | |
| } | |
| return (uint32_t)-1; | |
| } | |
| uint16_t HBFont::getWeight() const | |
| { | |
| uint32_t tag = HB_TAG('w', 'g', 'h', 't'); | |
| float res = HBFont::GetStyle(m_font, tag); | |
| return static_cast<uint16_t>(res); | |
| } | |
| bool HBFont::isItalic() const | |
| { | |
| uint32_t tag = HB_TAG('i', 't', 'a', 'l'); | |
| float res = HBFont::GetStyle(m_font, tag); | |
| return res != 0.0; | |
| } | |
| rive::rcp<rive::Font> HBFont::withOptions( | |
| rive::Span<const Coord> coords, | |
| rive::Span<const Feature> features) const | |
| { | |
| // Merges previous options with current ones. | |
| std::unordered_map<hb_tag_t, float> axisValues = m_axisValues; | |
| for (size_t i = 0; i < coords.size(); ++i) | |
| { | |
| axisValues[coords[i].axis] = coords[i].value; | |
| } | |
| AutoSTArray<16, hb_variation_t> vars(axisValues.size()); | |
| size_t i = 0; | |
| for (auto itr = axisValues.begin(); itr != axisValues.end(); itr++) | |
| { | |
| vars[i++] = {itr->first, itr->second}; | |
| } | |
| auto font = hb_font_create_sub_font(m_font); | |
| hb_font_set_variations(font, vars.data(), (unsigned int)vars.size()); | |
| std::vector<hb_feature_t> hbFeatures; | |
| std::unordered_map<hb_tag_t, uint32_t> featureValues = m_featureValues; | |
| for (auto feature : features) | |
| { | |
| featureValues[feature.tag] = feature.value; | |
| } | |
| for (auto itr = featureValues.begin(); itr != featureValues.end(); itr++) | |
| { | |
| hbFeatures.push_back({itr->first, | |
| itr->second, | |
| HB_FEATURE_GLOBAL_START, | |
| HB_FEATURE_GLOBAL_END}); | |
| } | |
| return rive::rcp<rive::Font>( | |
| new HBFont(font, axisValues, featureValues, hbFeatures)); | |
| } | |
| rive::RawPath HBFont::getPath(rive::GlyphID glyph) const | |
| { | |
| rive::RawPath rpath; | |
| hb_font_draw_glyph(m_font, glyph, m_drawFuncs, &rpath); | |
| return rpath; | |
| } | |
| bool HBFont::hasColorGlyphs() const | |
| { | |
| return m_hasColorLayers || m_hasColorPaint || m_hasPNG; | |
| } | |
| bool HBFont::isColorGlyph(rive::GlyphID glyph) const | |
| { | |
| if (!m_hasColorLayers && !m_hasColorPaint && !m_hasPNG) | |
| { | |
| return false; | |
| } | |
| hb_face_t* face = hb_font_get_face(m_font); | |
| // Check COLRv0 layers first. | |
| if (m_hasColorLayers) | |
| { | |
| unsigned int total = | |
| hb_ot_color_glyph_get_layers(face, glyph, 0, nullptr, nullptr); | |
| if (total > 0) | |
| { | |
| return true; | |
| } | |
| } | |
| // Check COLRv1 paint. | |
| if (m_hasColorPaint) | |
| { | |
| if (hb_ot_color_glyph_has_paint(face, glyph)) | |
| { | |
| return true; | |
| } | |
| } | |
| // Check SBIX/CBDT PNG. | |
| if (m_hasPNG) | |
| { | |
| hb_blob_t* blob = hb_ot_color_glyph_reference_png(m_font, glyph); | |
| if (blob != nullptr) | |
| { | |
| bool has = hb_blob_get_length(blob) > 0; | |
| hb_blob_destroy(blob); | |
| return has; | |
| } | |
| } | |
| return false; | |
| } | |
| size_t HBFont::getColorLayers(rive::GlyphID glyph, | |
| std::vector<ColorGlyphLayer>& out, | |
| rive::ColorInt foreground) const | |
| { | |
| if (!m_hasColorLayers && !m_hasColorPaint && !m_hasPNG) | |
| { | |
| return 0; | |
| } | |
| // Check cache first. | |
| auto cacheIt = m_colorLayerCache.find(glyph); | |
| if (cacheIt != m_colorLayerCache.end()) | |
| { | |
| // Copy from cache, but update foreground colors. | |
| for (ColorGlyphLayer layer : cacheIt->second) | |
| { | |
| if (layer.useForeground) | |
| { | |
| layer.color = foreground; | |
| } | |
| for (auto& stop : layer.stops) | |
| { | |
| if (stop.isForeground) | |
| { | |
| stop.color = foreground; | |
| } | |
| } | |
| out.push_back(std::move(layer)); | |
| } | |
| return cacheIt->second.size(); | |
| } | |
| std::vector<ColorGlyphLayer> layers; | |
| hb_face_t* face = hb_font_get_face(m_font); | |
| // Try COLRv0 first. | |
| if (m_hasColorLayers) | |
| { | |
| unsigned int layerCount = | |
| hb_ot_color_glyph_get_layers(face, glyph, 0, nullptr, nullptr); | |
| if (layerCount > 0) | |
| { | |
| std::vector<hb_ot_color_layer_t> hbLayers(layerCount); | |
| hb_ot_color_glyph_get_layers(face, | |
| glyph, | |
| 0, | |
| &layerCount, | |
| hbLayers.data()); | |
| layers.reserve(layerCount); | |
| for (unsigned int i = 0; i < layerCount; i++) | |
| { | |
| ColorGlyphLayer layer; | |
| hb_font_draw_glyph(m_font, | |
| hbLayers[i].glyph, | |
| m_drawFuncs, | |
| &layer.path); | |
| if (hbLayers[i].color_index == 0xFFFF) | |
| { | |
| layer.useForeground = true; | |
| layer.color = foreground; | |
| } | |
| else | |
| { | |
| layer.useForeground = false; | |
| if (hbLayers[i].color_index < m_paletteColors.size()) | |
| { | |
| layer.color = hbColorToColorInt( | |
| m_paletteColors[hbLayers[i].color_index]); | |
| } | |
| else | |
| { | |
| layer.color = 0xFF000000; | |
| } | |
| } | |
| layers.push_back(std::move(layer)); | |
| } | |
| } | |
| } | |
| // Fall back to COLRv1 paint if no COLRv0 layers found. | |
| if (layers.empty() && (m_hasColorPaint || m_hasPNG)) | |
| { | |
| // hb_font_paint_glyph handles both COLRv1 and SBIX/CBDT. | |
| // For COLRv1 it calls paint_solid/gradient callbacks; | |
| // for SBIX/CBDT it calls paint_image. | |
| PaintState state; | |
| state.font = m_font; | |
| state.drawFuncs = m_drawFuncs; | |
| state.layers = &layers; | |
| state.foreground = foreground; | |
| hb_font_paint_glyph(m_font, | |
| glyph, | |
| m_paintFuncs, | |
| &state, | |
| 0, // palette_index | |
| HB_COLOR(0, 0, 0, 255)); // foreground | |
| } | |
| if (layers.empty()) | |
| { | |
| return 0; | |
| } | |
| size_t count = layers.size(); | |
| // Cache the result. | |
| m_colorLayerCache[glyph] = layers; | |
| // Move into output. | |
| for (auto& layer : layers) | |
| { | |
| out.push_back(std::move(layer)); | |
| } | |
| return count; | |
| } | |
| /////////////////////////////////////////////////////////// | |
| static rive::GlyphRun shape_run(const rive::Unichar text[], | |
| const rive::TextRun& tr, | |
| unsigned textOffset) | |
| { | |
| hb_buffer_t* buf = hb_buffer_create(); | |
| hb_buffer_add_utf32(buf, text, tr.unicharCount, 0, tr.unicharCount); | |
| hb_buffer_set_direction(buf, | |
| tr.level & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); | |
| hb_buffer_set_script(buf, (hb_script_t)tr.script); | |
| hb_buffer_set_language(buf, hb_language_get_default()); | |
| auto hbfont = (HBFont*)tr.font.get(); | |
| hb_shape(hbfont->m_font, | |
| buf, | |
| hbfont->m_features.data(), | |
| (unsigned int)hbfont->m_features.size()); | |
| unsigned int glyph_count; | |
| hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count); | |
| hb_glyph_position_t* glyph_pos = | |
| hb_buffer_get_glyph_positions(buf, &glyph_count); | |
| // todo: check for missing glyphs, and perform font-substitution | |
| rive::GlyphRun gr(glyph_count); | |
| gr.font = tr.font; | |
| gr.size = tr.size; | |
| gr.lineHeight = tr.lineHeight; | |
| gr.letterSpacing = tr.letterSpacing; | |
| gr.styleId = tr.styleId; | |
| gr.level = tr.level; | |
| const float scale = tr.size / kStdScale; | |
| for (unsigned int i = 0; i < glyph_count; i++) | |
| { | |
| unsigned int index = tr.level & 1 ? glyph_count - 1 - i : i; | |
| gr.glyphs[i] = (uint16_t)glyph_info[index].codepoint; | |
| gr.textIndices[i] = textOffset + glyph_info[index].cluster; | |
| gr.advances[i] = gr.xpos[i] = | |
| glyph_pos[index].x_advance * scale + tr.letterSpacing; | |
| gr.offsets[i] = rive::Vec2D(glyph_pos[index].x_offset * scale, | |
| -glyph_pos[index].y_offset * scale); | |
| } | |
| gr.xpos[glyph_count] = 0; // so the next run can line up snug | |
| hb_buffer_destroy(buf); | |
| return gr; | |
| } | |
| static rive::GlyphRun extract_subset(const rive::GlyphRun& orig, | |
| size_t start, | |
| size_t end) | |
| { | |
| auto count = end - start; | |
| rive::GlyphRun subset( | |
| rive::SimpleArray<rive::GlyphID>(&orig.glyphs[start], count), | |
| rive::SimpleArray<uint32_t>(&orig.textIndices[start], count), | |
| rive::SimpleArray<float>(&orig.advances[start], count), | |
| rive::SimpleArray<float>(&orig.xpos[start], count + 1), | |
| rive::SimpleArray<rive::Vec2D>(&orig.offsets[start], count)); | |
| subset.font = std::move(orig.font); | |
| subset.size = orig.size; | |
| subset.lineHeight = orig.lineHeight; | |
| subset.letterSpacing = orig.letterSpacing; | |
| subset.level = orig.level; | |
| subset.xpos.back() = 0; // since we're now the end of a run | |
| subset.styleId = orig.styleId; | |
| return subset; | |
| } | |
| static void perform_fallback(rive::rcp<rive::Font> fallbackFont, | |
| rive::SimpleArrayBuilder<rive::GlyphRun>& gruns, | |
| const rive::Unichar text[], | |
| const rive::GlyphRun& orig, | |
| const rive::TextRun& origTextRun, | |
| const uint32_t fallbackIndex) | |
| { | |
| assert(orig.glyphs.size() > 0); | |
| const size_t count = orig.glyphs.size(); | |
| size_t startI = 0; | |
| while (startI < count) | |
| { | |
| size_t endI = startI + 1; | |
| if (orig.glyphs[startI] == 0) | |
| { | |
| while (endI < count && orig.glyphs[endI] == 0) | |
| { | |
| ++endI; | |
| } | |
| auto textStart = orig.textIndices[startI]; | |
| auto textCount = | |
| endI == count | |
| ? origTextRun.unicharCount - | |
| (orig.textIndices[startI] - orig.textIndices[0]) | |
| : orig.textIndices[endI] - textStart; | |
| auto tr = rive::TextRun{ | |
| fallbackFont, | |
| orig.size, | |
| orig.lineHeight, | |
| origTextRun.letterSpacing, | |
| textCount, | |
| origTextRun.script, | |
| orig.styleId, | |
| orig.level, | |
| }; | |
| static_cast<HBFont*>(fallbackFont.get()) | |
| ->shapeFallbackRun(gruns, | |
| text, | |
| textStart, | |
| tr, | |
| origTextRun, | |
| fallbackIndex); | |
| } | |
| else | |
| { | |
| while (endI < count && orig.glyphs[endI] != 0) | |
| { | |
| ++endI; | |
| } | |
| gruns.add(extract_subset(orig, startI, endI)); | |
| } | |
| startI = endI; | |
| } | |
| } | |
| void HBFont::shapeFallbackRun(rive::SimpleArrayBuilder<rive::GlyphRun>& gruns, | |
| const rive::Unichar text[], | |
| const unsigned textStart, | |
| const rive::TextRun& textRun, | |
| const rive::TextRun& originalTextRun, | |
| const uint32_t fallbackIndex) | |
| { | |
| auto gr = shape_run(&text[textStart], textRun, textStart); | |
| auto end = gr.glyphs.end(); | |
| auto iter = std::find(gr.glyphs.begin(), end, 0); | |
| if (iter == end) | |
| { | |
| if (gr.glyphs.size() > 0) | |
| { | |
| gruns.add(std::move(gr)); | |
| } | |
| } | |
| else | |
| { | |
| // found at least 1 zero in glyphs, so need to perform | |
| // font-fallback | |
| size_t index = iter - gr.glyphs.begin(); | |
| rive::Unichar missing = text[gr.textIndices[index]]; | |
| auto fallback = HBFont::gFallbackProc(missing, fallbackIndex, this); | |
| if (fallback && fallback.get() != this) | |
| { | |
| perform_fallback(fallback, | |
| gruns, | |
| text, | |
| gr, | |
| textRun, | |
| fallbackIndex + 1); | |
| } | |
| else if (gr.glyphs.size() > 0) | |
| { | |
| gruns.add(std::move(gr)); | |
| } | |
| } | |
| } | |
| rive::SimpleArray<rive::Paragraph> HBFont::onShapeText( | |
| rive::Span<const rive::Unichar> text, | |
| rive::Span<const rive::TextRun> truns, | |
| int textDirectionFlag) const | |
| { | |
| rive::SimpleArrayBuilder<rive::Paragraph> paragraphs; | |
| SBCodepointSequence codepointSequence = {SBStringEncodingUTF32, | |
| (void*)text.data(), | |
| text.size()}; | |
| hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_get_default(); | |
| // Split runs by bidi types. | |
| uint32_t textIndex = 0; | |
| uint32_t runIndex = 0; | |
| uint32_t runStartTextIndex = 0; | |
| SBUInteger paragraphStart = 0; | |
| SBAlgorithmRef bidiAlgorithm = SBAlgorithmCreate(&codepointSequence); | |
| uint32_t unicharIndex = 0; | |
| uint32_t runTextIndex = 0; | |
| SBLevel defaultLevel; | |
| switch (textDirectionFlag) | |
| { | |
| case 0: | |
| defaultLevel = 0; | |
| break; | |
| case 1: | |
| defaultLevel = 1; | |
| break; | |
| default: | |
| defaultLevel = SBLevelDefaultLTR; | |
| break; | |
| } | |
| while (paragraphStart < text.size()) | |
| { | |
| SBParagraphRef paragraph = SBAlgorithmCreateParagraph(bidiAlgorithm, | |
| paragraphStart, | |
| INT32_MAX, | |
| defaultLevel); | |
| SBUInteger paragraphLength = SBParagraphGetLength(paragraph); | |
| // Next iteration reads the next paragraph (if any remain). | |
| paragraphStart += paragraphLength; | |
| const SBLevel* bidiLevels = SBParagraphGetLevelsPtr(paragraph); | |
| SBLevel paragraphLevel = SBParagraphGetBaseLevel(paragraph); | |
| uint32_t paragraphTextIndex = 0; | |
| std::vector<rive::TextRun> bidiRuns; | |
| bidiRuns.reserve(truns.size()); | |
| while (runIndex < truns.size()) | |
| { | |
| const auto& tr = truns[runIndex]; | |
| assert(tr.unicharCount != 0); | |
| SBLevel lastLevel = bidiLevels[paragraphTextIndex]; | |
| auto point = text[textIndex]; | |
| hb_script_t lastScript = hb_unicode_script(ufuncs, point); | |
| rive::TextRun splitRun = { | |
| tr.font, | |
| tr.size, | |
| tr.lineHeight, | |
| tr.letterSpacing, | |
| tr.unicharCount - runTextIndex, | |
| (uint32_t)lastScript, | |
| tr.styleId, | |
| (uint8_t)lastLevel, | |
| }; | |
| runStartTextIndex = textIndex; | |
| runTextIndex++; | |
| textIndex++; | |
| paragraphTextIndex++; | |
| bidiRuns.push_back(splitRun); | |
| while (runTextIndex < tr.unicharCount && | |
| paragraphTextIndex < paragraphLength) | |
| { | |
| auto point = text[textIndex]; | |
| hb_script_t script = | |
| hb_unicode_general_category(ufuncs, point) == | |
| HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK | |
| ? HB_SCRIPT_INHERITED | |
| : hb_unicode_script(ufuncs, point); | |
| switch (script) | |
| { | |
| case HB_SCRIPT_COMMON: | |
| case HB_SCRIPT_INHERITED: | |
| // Propagate last seen "real" script value. | |
| script = lastScript; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (bidiLevels[paragraphTextIndex] != lastLevel || | |
| script != lastScript) | |
| { | |
| lastScript = script; | |
| auto& back = bidiRuns.back(); | |
| back.unicharCount = textIndex - runStartTextIndex; | |
| lastLevel = bidiLevels[paragraphTextIndex]; | |
| rive::TextRun backRun = { | |
| back.font, | |
| back.size, | |
| back.lineHeight, | |
| back.letterSpacing, | |
| tr.unicharCount - runTextIndex, | |
| (uint32_t)script, | |
| back.styleId, | |
| (uint8_t)lastLevel, | |
| }; | |
| runStartTextIndex = textIndex; | |
| bidiRuns.push_back(backRun); | |
| } | |
| runTextIndex++; | |
| textIndex++; | |
| paragraphTextIndex++; | |
| } | |
| // Reached the end of the run? | |
| if (runTextIndex == tr.unicharCount) | |
| { | |
| runIndex++; | |
| runTextIndex = 0; | |
| } | |
| // We consumed the whole paragraph. | |
| if (paragraphTextIndex == paragraphLength) | |
| { | |
| // Close off the last run. | |
| auto& back = bidiRuns.back(); | |
| back.unicharCount = textIndex - runStartTextIndex; | |
| break; | |
| } | |
| } | |
| rive::SimpleArrayBuilder<rive::GlyphRun> gruns(bidiRuns.size()); | |
| for (const auto& tr : bidiRuns) | |
| { | |
| auto gr = shape_run(&text[unicharIndex], tr, unicharIndex); | |
| unicharIndex += tr.unicharCount; | |
| // Handle dual-presentation characters (e.g. ☠ U+2620) that have a | |
| // text-form glyph in the current font but are followed by U+FE0F | |
| // (VARIATION SELECTOR-16), which requests emoji presentation. | |
| // The standard fallback only fires for glyph ID 0, so we explicitly | |
| // force such glyphs to 0 here — but only when a color emoji | |
| // fallback font is actually available for that codepoint, to avoid | |
| // regressing the rendering when no emoji font is loaded. | |
| if (gFallbackProc && gFallbackProcEnabled && !hasColorGlyphs()) | |
| { | |
| for (size_t gi = 0; gi < gr.glyphs.size(); ++gi) | |
| { | |
| if (gr.glyphs[gi] != 0) | |
| { | |
| auto textPos = gr.textIndices[gi]; | |
| if (textPos + 1 < text.size() && | |
| text[textPos + 1] == 0xFE0F) | |
| { | |
| auto emojiFont = | |
| gFallbackProc(text[textPos], 0, this); | |
| if (emojiFont && emojiFont->hasColorGlyphs()) | |
| { | |
| gr.glyphs[gi] = 0; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| auto end = gr.glyphs.end(); | |
| auto iter = std::find(gr.glyphs.begin(), end, 0); | |
| if (!gFallbackProc || iter == end || !gFallbackProcEnabled) | |
| { | |
| if (gr.glyphs.size() > 0) | |
| { | |
| gruns.add(std::move(gr)); | |
| } | |
| } | |
| else | |
| { | |
| // found at least 1 zero in glyphs, so need to perform | |
| // font-fallback | |
| size_t index = iter - gr.glyphs.begin(); | |
| rive::Unichar missing = text[gr.textIndices[index]]; | |
| // todo: consider sending more chars if that helps choose a font | |
| auto fallback = gFallbackProc(missing, 0, this); | |
| if (fallback) | |
| { | |
| perform_fallback(fallback, gruns, text.data(), gr, tr, 1); | |
| } | |
| else if (gr.glyphs.size() > 0) | |
| { | |
| // oh well, just keep the missing glyphs | |
| gruns.add(std::move(gr)); | |
| } | |
| } | |
| } | |
| // turn the advances we stored in xpos[] into actual x-positions | |
| // for logical order. | |
| float pos = 0; | |
| for (auto& gr : gruns) | |
| { | |
| for (auto& xp : gr.xpos) | |
| { | |
| float adv = xp; | |
| xp = pos; | |
| pos += adv; | |
| } | |
| } | |
| paragraphs.add({ | |
| std::move(gruns), | |
| (uint8_t)paragraphLevel, | |
| }); | |
| SBParagraphRelease(paragraph); | |
| } | |
| SBAlgorithmRelease(bidiAlgorithm); | |
| return paragraphs; | |
| } | |
| bool HBFont::hasGlyph(const rive::Unichar missing) const | |
| { | |
| hb_codepoint_t glyph; | |
| return hb_font_get_nominal_glyph(m_font, missing, &glyph); | |
| } | |
| #endif |