Fallback for harfbuzz

Plumbing is mostly in place
Testing is just a start : have hard-coded a specific unicode font (will improve in later PRs)

Design

Harfbuzz backend has a function-pointer it will call to try to get addtional fonts to fill in the gaps (missing glyphs).
It passes a span of some of the missing characters as a clue (though in this impl, we always just return the same
fallback font).

Likely to do a better job in CJK, we may want to know some language information from the client -- which we can
pass along to the fallback-proc.

Diffs=
b4eb6b315 Fallback for harfbuzz
diff --git a/.rive_head b/.rive_head
index 03577d8..13d8aaf 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-09df6f0b2bf8601a533ece9402f739c43ed92d50
+b4eb6b315ba31c8608b98799ac7f5b480a1045d4
diff --git a/skia/renderer/include/renderfont_hb.hpp b/skia/renderer/include/renderfont_hb.hpp
index 084efec..dd8ae3e 100644
--- a/skia/renderer/include/renderfont_hb.hpp
+++ b/skia/renderer/include/renderfont_hb.hpp
@@ -30,6 +30,15 @@
                     rive::Span<const rive::RenderTextRun>) const override;
 
     static rive::rcp<rive::RenderFont> Decode(rive::Span<const uint8_t>);
+
+    // If the platform can supply fallback font(s), set this function pointer.
+    // It will be called with a span of unichars, and the platform attempts to
+    // return a font that can draw (at least some of) them. If no font is available
+    // just return nullptr.
+
+    using FallbackProc = rive::rcp<rive::RenderFont> (*)(rive::Span<const rive::Unichar>);
+
+    static FallbackProc gFallbackProc;
 };
 
 #endif
diff --git a/skia/renderer/src/renderfont_hb.cpp b/skia/renderer/src/renderfont_hb.cpp
index e041a99..3b2cd1c 100644
--- a/skia/renderer/src/renderfont_hb.cpp
+++ b/skia/renderer/src/renderfont_hb.cpp
@@ -11,6 +11,9 @@
 #include "hb.h"
 #include "hb-ot.h"
 
+// Initialized to null. Client can set this to a callback.
+HBRenderFont::FallbackProc HBRenderFont::gFallbackProc;
+
 rive::rcp<rive::RenderFont> HBRenderFont::Decode(rive::Span<const uint8_t> span) {
     auto blob = hb_blob_create_or_fail((const char*)span.data(),
                                        (unsigned)span.size(),
@@ -165,11 +168,8 @@
 };
 constexpr int gNumFeatures = sizeof(gFeatures) / sizeof(gFeatures[0]);
 
-static float shape_run(std::vector<rive::RenderGlyphRun>* gruns,
-                       const rive::Unichar text[],
-                       const rive::RenderTextRun& tr,
-                       unsigned textOffset,
-                       float xpos) {
+static rive::RenderGlyphRun
+shape_run(const rive::Unichar text[], const rive::RenderTextRun& tr, unsigned textOffset) {
     hb_buffer_t* buf = hb_buffer_create();
     hb_buffer_add_utf32(buf, text, tr.unicharCount, 0, tr.unicharCount);
 
@@ -194,22 +194,59 @@
     gr.xpos.resize(glyph_count + 1);
 
     const float scale = tr.size / kStdScale;
-
     for (unsigned int i = 0; i < glyph_count; i++) {
         //            hb_position_t x_offset  = glyph_pos[i].x_offset;
         //            hb_position_t y_offset  = glyph_pos[i].y_offset;
 
         gr.glyphs[i] = (uint16_t)glyph_info[i].codepoint;
         gr.textOffsets[i] = textOffset + glyph_info[i].cluster;
-        gr.xpos[i] = xpos;
-
-        xpos += glyph_pos[i].x_advance * scale;
+        gr.xpos[i] = glyph_pos[i].x_advance * scale;
     }
-    gr.xpos[glyph_count] = xpos;
-    gruns->push_back(std::move(gr));
-
+    gr.xpos[glyph_count] = 0; // so the next run can line up snug
     hb_buffer_destroy(buf);
-    return xpos;
+    return gr;
+}
+
+static rive::RenderGlyphRun
+extract_subset(const rive::RenderGlyphRun& orig, size_t start, size_t end) {
+    rive::RenderGlyphRun subset;
+    subset.font = std::move(orig.font);
+    subset.size = orig.size;
+    subset.glyphs.insert(subset.glyphs.begin(), &orig.glyphs[start], &orig.glyphs[end]);
+    subset.textOffsets.insert(
+        subset.textOffsets.begin(), &orig.textOffsets[start], &orig.textOffsets[end]);
+    subset.xpos.insert(subset.xpos.begin(), &orig.xpos[start], &orig.xpos[end + 1]);
+    subset.xpos.back() = 0; // since we're now the end of a run
+    return subset;
+}
+
+static void perform_fallback(rive::rcp<rive::RenderFont> fallbackFont,
+                             std::vector<rive::RenderGlyphRun>* gruns,
+                             const rive::Unichar text[],
+                             const rive::RenderGlyphRun& orig) {
+    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.textOffsets[startI];
+            auto textCount = orig.textOffsets[endI - 1] - textStart + 1;
+            auto tr = rive::RenderTextRun{fallbackFont, orig.size, textCount};
+            auto gr = shape_run(&text[textStart], tr, textStart);
+            gruns->push_back(std::move(gr));
+        } else {
+            while (endI < count && orig.glyphs[endI] != 0) {
+                ++endI;
+            }
+            gruns->push_back(extract_subset(orig, startI, endI));
+        }
+        startI = endI;
+    }
 }
 
 std::vector<rive::RenderGlyphRun>
@@ -221,10 +258,36 @@
     /////////////////
 
     uint32_t unicharIndex = 0;
-    float xpos = 0;
     for (const auto& tr : truns) {
-        xpos = shape_run(&gruns, &text[unicharIndex], tr, unicharIndex, xpos);
+        auto gr = shape_run(&text[unicharIndex], tr, unicharIndex);
         unicharIndex += tr.unicharCount;
+
+        auto end = gr.glyphs.end();
+        auto iter = std::find(gr.glyphs.begin(), end, 0);
+        if (!gFallbackProc || iter == end) {
+            gruns.push_back(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.textOffsets[index]];
+            // todo: consider sending more chars if that helps choose a font
+            auto fallback = gFallbackProc({&missing, 1});
+            if (fallback) {
+                perform_fallback(fallback, &gruns, text.data(), gr);
+            } else {
+                gruns.push_back(std::move(gr)); // oh well, just keep the missing glyphs
+            }
+        }
+    }
+
+    // now turn the advances (widths) we stored in xpos[] into actual x-positions
+    float pos = 0;
+    for (auto& gr : gruns) {
+        for (auto& xp : gr.xpos) {
+            float adv = xp;
+            xp = pos;
+            pos += adv;
+        }
     }
     return gruns;
 }
diff --git a/viewer/src/viewer_content/text_content.cpp b/viewer/src/viewer_content/text_content.cpp
index 3d42014..9676056 100644
--- a/viewer/src/viewer_content/text_content.cpp
+++ b/viewer/src/viewer_content/text_content.cpp
@@ -21,6 +21,7 @@
 #else
 #include "renderfont_hb.hpp"
 static RenderFontFactory gFontFactory = HBRenderFont::Decode;
+#define RIVE_USING_HAFBUZZ_FONTS
 #endif
 
 static bool ws(rive::Unichar c) { return c <= ' '; }
@@ -91,6 +92,38 @@
 
 ////////////////////////////////////////////////////////////////////////////////////
 
+#ifdef RIVE_USING_HAFBUZZ_FONTS
+static rive::rcp<rive::RenderFont> load_fallback_font(rive::Span<const rive::Unichar> missing) {
+    static rive::rcp<rive::RenderFont> gFallbackFont;
+
+    printf("missing chars:");
+    for (auto m : missing) {
+        printf(" %X", m);
+    }
+    printf("\n");
+
+    if (!gFallbackFont) {
+        // TODO: make this more sharable for our test apps
+        FILE* fp = fopen("/Users/mike/fonts/Arial Unicode.ttf", "rb");
+        if (!fp) {
+            return nullptr;
+        }
+
+        fseek(fp, 0, SEEK_END);
+        size_t size = ftell(fp);
+        fseek(fp, 0, SEEK_SET);
+
+        std::vector<uint8_t> bytes(size);
+        size_t bytesRead = fread(bytes.data(), 1, size, fp);
+        fclose(fp);
+
+        assert(bytesRead == size);
+        gFallbackFont = HBRenderFont::Decode(rive::toSpan(bytes));
+    }
+    return gFallbackFont;
+}
+#endif
+
 static void draw_line(rive::Factory* factory, rive::Renderer* renderer, float x) {
     auto paint = factory->makeRenderPaint();
     paint->style(rive::RenderPaintStyle::stroke);
@@ -160,6 +193,10 @@
 
 public:
     TextContent() {
+#ifdef RIVE_USING_HAFBUZZ_FONTS
+        HBRenderFont::gFallbackProc = load_fallback_font;
+#endif
+
         auto truns = this->make_truns(gFontFactory);
         m_gruns.push_back(truns[0].font->shapeText(rive::toSpan(m_unichars), rive::toSpan(truns)));