blob: b14b738f34bc400a68bf79c5fb2444c3f62d40a5 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "viewer_content.hpp"
#include "rive/refcnt.hpp"
#include "rive/render_text.hpp"
#include "contour_measure.hpp"
#include "skia_factory.hpp"
#include "skia_renderer.hpp"
#include "line_breaker.hpp"
#include "to_skia.hpp"
using namespace rive;
using RenderFontTextRuns = std::vector<RenderTextRun>;
using RenderFontGlyphRuns = std::vector<RenderGlyphRun>;
using RenderFontFactory = rcp<RenderFont> (*)(const Span<const uint8_t>);
template <typename Handler>
void visit(const std::vector<RenderGlyphRun>& gruns, Vec2D origin, Handler proc) {
for (const auto& gr : gruns) {
for (size_t i = 0; i < gr.glyphs.size(); ++i) {
auto path = gr.font->getPath(gr.glyphs[i]);
auto mx = Mat2D::fromTranslate(origin.x + gr.xpos[i], origin.y)
* Mat2D::fromScale(gr.size, gr.size);
path.transformInPlace(mx);
proc(path);
}
}
}
static Vec2D ave(Vec2D a, Vec2D b) {
return (a + b) * 0.5f;
}
static RawPath make_quad_path(Span<const Vec2D> pts) {
const int N = pts.size();
RawPath path;
if (N >= 2) {
path.move(pts[0]);
if (N == 2) {
path.line(pts[1]);
} else if (N == 3) {
path.quad(pts[1], pts[2]);
} else {
for (int i = 1; i < N - 2; ++i) {
path.quad(pts[i], ave(pts[i], pts[i+1]));
}
path.quad(pts[N-2], pts[N-1]);
}
}
return path;
}
////////////////////////////////////////////////////////////////////////////////////
#include "renderfont_skia.hpp"
#include "renderfont_hb.hpp"
#include "renderfont_coretext.hpp"
static SkiaFactory skiaFactory;
static std::unique_ptr<RenderPath> make_rpath(const RawPath& path) {
return skiaFactory.makeRenderPath(path.points(), path.verbsU8(), FillRule::nonZero);
}
static void stroke_path(Renderer* renderer, const RawPath& path, float size, ColorInt color) {
auto paint = skiaFactory.makeRenderPaint();
paint->color(color);
paint->thickness(size);
paint->style(RenderPaintStyle::stroke);
renderer->drawPath(make_rpath(path).get(), paint.get());
}
static void fill_rect(Renderer* renderer, const AABB& r, RenderPaint* paint) {
RawPath rp;
rp.addRect(r);
renderer->drawPath(make_rpath(rp).get(), paint);
}
static void fill_point(Renderer* renderer, Vec2D p, float r, RenderPaint* paint) {
fill_rect(renderer, {p.x - r, p.y - r, p.x + r, p.y + r}, paint);
}
typedef rcp<RenderFont> (*RenderFontFactory)(Span<const uint8_t>);
static RenderTextRun append(std::vector<Unichar>* unichars, rcp<RenderFont> font,
float size, const char text[]) {
uint32_t n = 0;
while (text[n]) {
unichars->push_back(text[n]); // todo: utf8 -> unichar
n += 1;
}
return { std::move(font), size, n };
}
class TextPathContent : public ViewerContent {
std::vector<Unichar> m_unichars;
RenderFontGlyphRuns m_gruns;
std::unique_ptr<RenderPaint> m_paint;
std::vector<Vec2D> m_pathpts;
Vec2D m_lastPt = {0,0};
int m_trackingIndex = -1;
Mat2D m_trans;
float m_alignment = 0,
m_scaleY = 1,
m_offsetY = 0,
m_windowWidth = 1, // %
m_windowOffset = 0; // %
RenderFontTextRuns make_truns(RenderFontFactory fact) {
auto loader = [fact](const char filename[]) -> rcp<RenderFont> {
auto bytes = ViewerContent::LoadFile(filename);
if (bytes.size() == 0) {
assert(false);
return nullptr;
}
return fact(toSpan(bytes));
};
const char* fontFiles[] = {
"../../test/assets/RobotoFlex.ttf",
"../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf",
};
auto font0 = loader(fontFiles[0]);
auto font1 = loader(fontFiles[1]);
assert(font0);
assert(font1);
RenderFont::Coord c1 = {'wght', 100.f},
c2 = {'wght', 800.f};
RenderFontTextRuns truns;
truns.push_back(append(&m_unichars, font0->makeAtCoord(c2), 60, "U"));
truns.push_back(append(&m_unichars, font0->makeAtCoord(c1), 30, "neasy"));
truns.push_back(append(&m_unichars, font1, 30, " fits the crown"));
truns.push_back(append(&m_unichars, font1->makeAtCoord(c1), 30, " that often"));
truns.push_back(append(&m_unichars, font0, 30, " lies the head."));
return truns;
}
public:
TextPathContent() {
auto truns = this->make_truns(CoreTextRenderFont::Decode);
m_gruns = truns[0].font->shapeText(toSpan(m_unichars), toSpan(truns));
m_paint = skiaFactory.makeRenderPaint();
m_paint->color(0xFFFFFFFF);
m_pathpts.push_back({ 20, 300});
m_pathpts.push_back({220, 100});
m_pathpts.push_back({420, 500});
m_pathpts.push_back({620, 100});
m_pathpts.push_back({820, 300});
m_trans = Mat2D::fromTranslate(200, 200) * Mat2D::fromScale(2, 2);
}
void draw_warp(Renderer* renderer, const RawPath& warp) {
stroke_path(renderer, warp, 0.5, 0xFF00FF00);
auto paint = skiaFactory.makeRenderPaint();
paint->color(0xFF008800);
const float r = 4;
for (auto p : m_pathpts) {
fill_point(renderer, p, r, paint.get());
}
}
static size_t count_glyphs(const RenderFontGlyphRuns& gruns) {
size_t n = 0;
for (const auto& gr : gruns) {
n += gr.glyphs.size();
}
return n;
}
void modify(float amount) {
m_paint->color(0xFFFFFFFF);
}
void draw(Renderer* renderer, const RenderFontGlyphRuns& gruns) {
auto get_path = [this](const RenderGlyphRun& run, int index, float dx) {
auto path = run.font->getPath(run.glyphs[index]);
path.transformInPlace(Mat2D::fromTranslate(run.xpos[index] + dx, m_offsetY)
* Mat2D::fromScale(run.size, run.size * m_scaleY));
return path;
};
renderer->save();
renderer->transform(m_trans);
RawPath warp = make_quad_path(toSpan(m_pathpts));
this->draw_warp(renderer, warp);
ContourMeasure meas(warp);
const float warpLength = meas.length();
const float textLength = gruns.back().xpos.back();
const float offset = (warpLength - textLength) * m_alignment;
const size_t glyphCount = count_glyphs(gruns);
size_t glyphIndex = 0;
float windowEnd = m_windowOffset + m_windowWidth;
for (const auto& gr : gruns) {
for (size_t i = 0; i < gr.glyphs.size(); ++i) {
float percent = glyphIndex / (float)(glyphCount - 1);
float amount = (percent >= m_windowOffset && percent <= windowEnd);
float scaleY = m_scaleY;
m_paint->color(0xFF666666);
m_paint->style(RenderPaintStyle::fill);
if (amount > 0) {
this->modify(amount);
}
auto path = meas.warp(get_path(gr, i, offset));
renderer->drawPath(make_rpath(path).get(), m_paint.get());
glyphIndex += 1;
m_scaleY = scaleY;
}
}
renderer->restore();
}
void handleDraw(SkCanvas* canvas, double) override {
SkiaRenderer renderer(canvas);
this->draw(&renderer, m_gruns);
}
void handlePointerMove(float x, float y) override {
m_lastPt = m_trans.invertOrIdentity() * Vec2D{x, y};
if (m_trackingIndex >= 0) {
m_pathpts[m_trackingIndex] = m_lastPt;
}
}
void handlePointerDown() override {
auto close_to = [](Vec2D a, Vec2D b) {
return Vec2D::distance(a, b) <= 10;
};
for (size_t i = 0; i < m_pathpts.size(); ++i) {
if (close_to(m_lastPt, m_pathpts[i])) {
m_trackingIndex = i;
break;
}
}
}
void handlePointerUp() override {
m_trackingIndex = -1;
}
void handleResize(int width, int height) override {}
void handleImgui() override {
ImGui::Begin("path", nullptr);
ImGui::SliderFloat("Alignment", &m_alignment, -3, 4);
ImGui::SliderFloat("Scale Y", &m_scaleY, 0.25f, 3.0f);
ImGui::SliderFloat("Offset Y", &m_offsetY, -100, 100);
ImGui::SliderFloat("Window Offset", &m_windowOffset, -1.1f, 1.1f);
ImGui::SliderFloat("Window Width", &m_windowWidth, 0, 1.2f);
ImGui::End();
}
};
std::unique_ptr<ViewerContent> ViewerContent::TextPath(const char filename[]) {
return std::make_unique<TextPathContent>();
}