rebase
diff --git a/include/rive/render_text.hpp b/include/rive/render_text.hpp
new file mode 100644
index 0000000..20d7206
--- /dev/null
+++ b/include/rive/render_text.hpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_RENDER_TEXT_HPP_
+#define _RIVE_RENDER_TEXT_HPP_
+
+#include "rive/math/raw_path.hpp"
+#include "rive/refcnt.hpp"
+#include "rive/span.hpp"
+
+namespace rive {
+
+using Unichar = int32_t;
+using GlyphID = uint16_t;
+
+class RenderFont : public RefCnt {
+public:
+    // This is experimental
+    // -- may only be needed by Editor
+    // -- so it may be removed from here later
+    //
+    struct Axis {
+        uint32_t    tag;
+        float       min;
+        float       def;    // default value
+        float       max;
+    };
+
+    // Returns the canonical set of Axes for this font. Use this to know
+    // what variations are possible. If you want to know the specific
+    // coordinate within that variations space for *this* font, call
+    // getCoords().
+    //
+    virtual std::vector<Axis> getAxes() const = 0;
+
+    struct Coord {
+        uint32_t    axis;
+        float       value;
+    };
+
+    // Returns the specific coords in variation space for this font.
+    // If you want to have a description of the entire variation space,
+    // call getAxes().
+    //
+    virtual std::vector<Coord> getCoords() const = 0;
+
+    virtual rcp<RenderFont> makeAtCoords(Span<const Coord>) const = 0;
+
+    rcp<RenderFont> makeAtCoord(Coord c) {
+        return this->makeAtCoords(Span<const Coord>(&c, 1));
+    }
+
+    // Returns a 1-point path for this glyph. It will be positioned
+    // relative to (0,0) with the typographic baseline at y = 0.
+    //
+    virtual RawPath getPath(GlyphID) const = 0;
+};
+
+struct RenderTextRun {
+    rcp<RenderFont> font;
+    float           size;
+    uint32_t        unicharCount;
+};
+
+struct RenderGlyphRun {
+    rcp<RenderFont>         font;
+    float                   size;
+    uint32_t                startTextIndex;
+
+    std::vector<GlyphID>    glyphs;
+    std::vector<float>      xpos;   // xpos.size() == glyphs.size() + 1
+};
+
+} // namespace rive
+#endif
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp
index 440d371..85559b9 100644
--- a/include/rive/renderer.hpp
+++ b/include/rive/renderer.hpp
@@ -37,39 +37,6 @@
         size_t count() const { return m_Count; }
     };
 
-    class RenderFont {
-    public:
-        struct AxisInfo {
-            uint32_t    tag;
-            float       min;
-            float       def;    // default value
-            float       max;
-        };
-        
-        virtual int countAxes() const { return 0; }
-        virtual std::vector<AxisInfo> getAxes() const { return std::vector<AxisInfo>; }
-        
-        // TODO: getGlyphPath(index) -> rawpath
-    };
-
-    struct RenderTextRun {
-        rcp<RenderFont> font;
-        float           size;
-        uint32_t        textCount;  // number of unichars in this run in text[]
-    };
-
-    struct RenderGlyphRun {
-        rcp<RenderFont>         font;
-        float                   size;
-
-        size_t                  startTextIndex;
-        std::vector<uint16_t>   glyphs;
-        std::vector<float>      xpos;   // xpos.size() == glyphs.size() + 1
-    };
-
-    extern std::vector<RenderGlyphRun> shapeText(const uint32_t text[], size_t textCount,
-                                                 const RenderTextRun[], size_t runCount);
-
     enum class RenderPaintStyle { stroke, fill };
 
     enum class RenderTileMode {
diff --git a/skia/viewer/src/drawtext.cpp b/skia/viewer/src/drawtext.cpp
new file mode 100644
index 0000000..6a5af71
--- /dev/null
+++ b/skia/viewer/src/drawtext.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#include "skia_factory.hpp"
+#include "skia_renderer.hpp"
+#include "skia_rive_fontmgr.hpp"
+
+#include <string>
+
+static void drawrun(rive::Factory* factory, rive::Renderer* renderer,
+                    const rive::RenderGlyphRun& run, rive::Vec2D origin) {
+    auto font = run.font.get();
+    const auto scale = rive::Mat2D::fromScale(run.size, run.size);
+    auto paint = factory->makeRenderPaint();
+    paint->color(0xFFFFFFFF);
+
+    for (size_t i = 0; i < run.glyphs.size(); ++i) {
+        auto trans = rive::Mat2D::fromTranslate(origin.x + run.xpos[i], origin.y);
+        auto rawpath = font->getPath(run.glyphs[i]);
+        rawpath.transformInPlace(trans * scale);
+        auto path = factory->makeRenderPath(rawpath.points(), rawpath.verbsU8(), rive::FillRule::nonZero);
+        renderer->drawPath(path.get(), paint.get());
+    }
+}
+
+struct UniString {
+    std::vector<rive::Unichar> array;
+
+    UniString(const char text[]) {
+        while (*text) {
+            array.push_back(*text++);
+        }
+    }
+};
+
+static std::string tagstr(uint32_t tag) {
+    std::string s("abcd");
+    s[0] = (tag >> 24) & 0xFF;
+    s[1] = (tag >> 16) & 0xFF;
+    s[2] = (tag >>  8) & 0xFF;
+    s[3] = (tag >>  0) & 0xFF;
+    return s;
+}
+
+void drawtext(rive::Factory* factory, rive::Renderer* renderer) {
+    static std::vector<rive::RenderGlyphRun> gruns;
+
+    if (gruns.size() == 0) {
+        SkiaRiveFontMgr fmgr;
+
+        auto font0 = fmgr.findFont("Times New Roman");
+        auto font1 = fmgr.findFont("Skia");
+
+        if (false) {
+            auto axes = font1->getAxes();
+            for (auto a : axes) {
+                printf("%s %g %g %g\n", tagstr(a.tag).c_str(), a.min, a.def, a.max);
+            }
+            auto coords = font1->getCoords();
+            for (auto c : coords) {
+                printf("%s %g\n", tagstr(c.axis).c_str(), c.value);
+            }
+        }
+
+        rive::RenderFont::Coord c0 = {'wght', 0.5f},
+                                c1 = {'wght', 2.0f};
+
+        UniString str("Uneasy lies the head that wears a crown.");
+        rive::RenderTextRun truns[] = {
+            { font0, 60, 1 },
+            { font0, 30, 6 },
+            { font1->makeAtCoord(c0), 30, 4 },
+            { font1, 30, 4 },
+            { font1->makeAtCoord(c1), 30, 5 },
+            { font0, 30, 20 },
+        };
+
+        gruns = fmgr.shapeText(rive::toSpan(str.array), rive::Span(truns, 6));
+    }
+
+    renderer->save();
+    renderer->scale(3, 3);
+    for (const auto& g : gruns) {
+        drawrun(factory, renderer, g, {10, 50});
+    }
+    renderer->restore();
+}
+
diff --git a/skia/viewer/src/fontmgr.cpp b/skia/viewer/src/fontmgr.cpp
new file mode 100644
index 0000000..4298c3c
--- /dev/null
+++ b/skia/viewer/src/fontmgr.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#include "rive/factory.hpp"
+#include "rive/render_text.hpp"
+#include "skia_rive_fontmgr.hpp"
+
+#include "include/core/SkTypeface.h"
+#include "include/core/SkFont.h"
+#include "include/core/SkPath.h"
+
+class SkiaRenderFont : public rive::RenderFont {
+public:
+    sk_sp<SkTypeface> m_Typeface;
+
+    SkiaRenderFont(sk_sp<SkTypeface> tf) : m_Typeface(std::move(tf)) {}
+
+    std::vector<Axis> getAxes() const override;
+    std::vector<Coord> getCoords() const override;
+    rive::rcp<rive::RenderFont> makeAtCoords(rive::Span<const Coord>) const override;
+    rive::RawPath getPath(rive::GlyphID) const override;
+};
+
+std::vector<rive::RenderFont::Axis> SkiaRenderFont::getAxes() const {
+    std::vector<rive::RenderFont::Axis> axes;
+    const int count = m_Typeface->getVariationDesignParameters(nullptr, 0);
+    if (count > 0) {
+        std::vector<SkFontParameters::Variation::Axis> src(count);
+        (void)m_Typeface->getVariationDesignParameters(src.data(), count);
+        axes.resize(count);
+        for (int i = 0; i < count; ++i) {
+            axes[i] = { src[i].tag, src[i].min, src[i].def, src[i].max };
+        }
+    }
+    return axes;
+}
+
+std::vector<rive::RenderFont::Coord> SkiaRenderFont::getCoords() const {
+    int count = m_Typeface->getVariationDesignPosition(nullptr, 0);
+    std::vector<SkFontArguments::VariationPosition::Coordinate> skcoord(count);
+    m_Typeface->getVariationDesignPosition(skcoord.data(), count);
+
+    std::vector<rive::RenderFont::Coord> coords(count);
+    for (int i = 0; i < count; ++i) {
+        coords[i] = { skcoord[i].axis, skcoord[i].value };
+    }
+    return coords;
+}
+
+rive::rcp<rive::RenderFont> SkiaRenderFont::makeAtCoords(rive::Span<const Coord> coords) const {
+    const int count = (int)coords.size();
+    SkAutoSTArray<16, SkFontArguments::VariationPosition::Coordinate> storage(count);
+    for (size_t i = 0; i < count; ++i) {
+        storage[i].axis = coords[i].axis;
+        storage[i].value = coords[i].value;
+    }
+    SkFontArguments args;
+    args.setVariationDesignPosition({storage.get(), count});
+
+    auto face = m_Typeface->makeClone(args);
+
+    if (face->uniqueID() == m_Typeface->uniqueID()) {
+        auto self = const_cast<SkiaRenderFont*>(this);
+        return rive::rcp<rive::RenderFont>(rive::safe_ref(self));
+    } else {
+        return rive::rcp<rive::RenderFont>(new SkiaRenderFont(std::move(face)));
+    }
+}
+
+static inline rive::Vec2D rv(SkPoint p) { return rive::Vec2D(p.fX, p.fY); }
+
+static void setupFont(SkFont* font) {
+    font->setLinearMetrics(true);
+    font->setBaselineSnap(false);
+    font->setHinting(SkFontHinting::kNone);
+}
+
+rive::RawPath SkiaRenderFont::getPath(rive::GlyphID glyph) const {
+    SkFont font(m_Typeface, 1.0f);
+    setupFont(&font);
+
+    SkPath skpath;
+    font.getPath(glyph, &skpath);
+
+    rive::RawPath rpath;
+    SkPath::RawIter iter(skpath);
+    SkPoint pts[4];
+    bool done = false;
+    while (!done) {
+        switch (iter.next(pts)) {
+            case SkPath::kMove_Verb:  rpath.move (rv(pts[0])); break;
+            case SkPath::kLine_Verb:  rpath.line (rv(pts[1])); break;
+            case SkPath::kQuad_Verb:  rpath.quad (rv(pts[1]), rv(pts[2])); break;
+            case SkPath::kConic_Verb: rpath.quad (rv(pts[1]), rv(pts[2])); break;  // TODO: convert
+            case SkPath::kCubic_Verb: rpath.cubic(rv(pts[1]), rv(pts[2]), rv(pts[3])); break;
+            case SkPath::kClose_Verb: rpath.close(); break;
+            case SkPath::kDone_Verb: done = true; break;
+        }
+    }
+    return rpath;
+}
+
+///////////////////////////////////////////////////////////
+
+rive::rcp<rive::RenderFont> SkiaRiveFontMgr::decodeFont(rive::Span<const uint8_t> data) {
+    auto tf = SkTypeface::MakeDefault(); // ignoring data for now
+    return rive::rcp<rive::RenderFont>(new SkiaRenderFont(std::move(tf)));
+}
+
+rive::rcp<rive::RenderFont> SkiaRiveFontMgr::findFont(const char name[]) {
+    auto tf = SkTypeface::MakeFromName(name, SkFontStyle());
+    if (!tf) {
+        tf = SkTypeface::MakeDefault();
+    }
+    return rive::rcp<rive::RenderFont>(new SkiaRenderFont(std::move(tf)));
+}
+
+static float shapeRun(rive::RenderGlyphRun* grun, const rive::RenderTextRun& trun,
+                      const rive::Unichar text[], size_t textOffset, float origin) {
+    grun->font = trun.font;
+    grun->size = trun.size;
+    grun->startTextIndex = textOffset;
+    grun->glyphs.resize(trun.unicharCount);
+    grun->xpos.resize(trun.unicharCount + 1);
+
+    const int count = SkToInt(trun.unicharCount);
+
+    SkiaRenderFont* rfont = static_cast<SkiaRenderFont*>(trun.font.get());
+    rfont->m_Typeface->unicharsToGlyphs(text + textOffset, count, grun->glyphs.data());
+
+    SkFont font(rfont->m_Typeface, grun->size);
+    setupFont(&font);
+
+    // We get 'widths' from skia, but then turn them into xpos
+    // this will write count values, but xpos has count+1 slots
+    font.getWidths(grun->glyphs.data(), count, grun->xpos.data());
+    for (auto& xp : grun->xpos) {
+        auto width = xp;
+        xp = origin;
+        origin += width;
+    }
+
+    return grun->xpos.data()[count];
+}
+
+std::vector<rive::RenderGlyphRun>
+SkiaRiveFontMgr::shapeText(rive::Span<const rive::Unichar> text,
+                           rive::Span<const rive::RenderTextRun> truns) {
+    std::vector<rive::RenderGlyphRun> gruns;
+
+    // sanity check
+    size_t count = 0;
+    for (const auto& tr : truns) {
+        count += tr.unicharCount;
+    }
+    if (count > text.size()) {
+        return gruns;   // not enough text, so abort
+    }
+
+    gruns.resize(truns.size());
+    int i = 0;
+    size_t offset = 0;
+    float origin = 0;
+    for (const auto& tr : truns) {
+        origin = shapeRun(&gruns[i], tr, text.data(), offset, origin);
+        offset += tr.unicharCount;
+        i += 1;
+    }
+
+    return gruns;
+}
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index 7901254..50a170c 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -45,6 +45,8 @@
 
 sk_sp<SkImage> gImage;
 
+extern void drawtext(rive::Factory*, rive::Renderer*);
+
 static void delete_file() {
     stateMachineIndex = -1;
     animationIndex = -1;
@@ -347,6 +349,10 @@
                 canvas->drawImage(gImage, 0, 0);
             }
         }
+        if (true) {
+            rive::SkiaRenderer renderer(canvas);
+            drawtext(&skiaFactory, &renderer);
+        }
         context->flush();
 
         ImGui_ImplOpenGL3_NewFrame();
diff --git a/skia/viewer/src/skia_rive_fontmgr.hpp b/skia/viewer/src/skia_rive_fontmgr.hpp
new file mode 100644
index 0000000..9777b11
--- /dev/null
+++ b/skia/viewer/src/skia_rive_fontmgr.hpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_SKIA_FONTMGR_HPP_
+#define _RIVE_SKIA_FONTMGR_HPP_
+
+#include "rive/refcnt.hpp"
+#include "rive/span.hpp"
+#include "rive/render_text.hpp"
+
+// This is a working model for how we might extend Factory.hpp
+class SkiaRiveFontMgr {
+public:
+    rive::rcp<rive::RenderFont> decodeFont(rive::Span<const uint8_t>);
+    rive::rcp<rive::RenderFont> findFont(const char name[]);
+    std::vector<rive::RenderGlyphRun> shapeText(rive::Span<const rive::Unichar> text,
+                                                rive::Span<const rive::RenderTextRun> runs);
+
+};
+
+#endif