blob: 94a9472143319c7859f4ca28ff526694372c7aa4 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "viewer/viewer_content.hpp"
#include "utils/rive_utf.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/factory.hpp"
#include "rive/refcnt.hpp"
#include "rive/render_text.hpp"
#include "rive/text/line_breaker.hpp"
using RenderFontTextRuns = std::vector<rive::RenderTextRun>;
using RenderFontGlyphRuns = std::vector<rive::RenderGlyphRun>;
using RenderFontFactory = rive::rcp<rive::RenderFont> (*)(const rive::Span<const uint8_t>);
static bool ws(rive::Unichar c) { return c <= ' '; }
std::vector<int> compute_word_breaks(rive::Span<rive::Unichar> chars) {
std::vector<int> breaks;
const unsigned len = chars.size();
for (unsigned i = 0; i < len;) {
// skip ws
while (i < len && ws(chars[i])) {
++i;
}
breaks.push_back(i); // word start
// skip non-ws
while (i < len && !ws(chars[i])) {
++i;
}
breaks.push_back(i); // word end
}
assert(breaks[breaks.size() - 1] == len);
return breaks;
}
static void drawrun(rive::Factory* factory,
rive::Renderer* renderer,
const rive::RenderGlyphRun& run,
unsigned startIndex,
unsigned endIndex,
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);
assert(startIndex >= 0 && endIndex <= run.glyphs.size());
for (size_t i = startIndex; i < endIndex; ++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, rive::FillRule::nonZero);
renderer->drawPath(path.get(), paint.get());
}
}
static void drawpara(rive::Factory* factory,
rive::Renderer* renderer,
rive::Span<const rive::RenderGlyphLine> lines,
rive::Span<const rive::RenderGlyphRun> runs,
rive::Vec2D origin) {
for (const auto& line : lines) {
const float x0 = runs[line.startRun].xpos[line.startIndex];
int startGIndex = line.startIndex;
for (int runIndex = line.startRun; runIndex <= line.endRun; ++runIndex) {
const auto& run = runs[runIndex];
int endGIndex = runIndex == line.endRun ? line.endIndex : run.glyphs.size();
drawrun(factory,
renderer,
run,
startGIndex,
endGIndex,
{origin.x - x0, origin.y + line.baseline});
startGIndex = 0;
}
}
}
////////////////////////////////////////////////////////////////////////////////////
#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(bytes);
}
return gFallbackFont;
}
#endif
static std::unique_ptr<rive::RenderPath> make_line(rive::Factory* factory,
rive::Vec2D a,
rive::Vec2D b) {
rive::RawPath rawPath;
rawPath.move(a);
rawPath.line(b);
return factory->makeRenderPath(rawPath, rive::FillRule::nonZero);
}
static void draw_line(rive::Factory* factory, rive::Renderer* renderer, float x) {
auto paint = factory->makeRenderPaint();
paint->style(rive::RenderPaintStyle::stroke);
paint->thickness(1);
paint->color(0xFFFFFFFF);
auto path = make_line(factory, {x, 0}, {x, 1000});
renderer->drawPath(path.get(), paint.get());
}
static rive::RenderTextRun append(std::vector<rive::Unichar>* unichars,
rive::rcp<rive::RenderFont> font,
float size,
const char text[]) {
const uint8_t* ptr = (const uint8_t*)text;
uint32_t n = 0;
while (*ptr) {
unichars->push_back(rive::UTF::NextUTF8(&ptr));
n += 1;
}
return {std::move(font), size, n};
}
class TextContent : public ViewerContent {
std::vector<rive::Unichar> m_unichars;
std::vector<int> m_breaks;
std::vector<RenderFontGlyphRuns> m_gruns;
rive::Mat2D m_xform;
float m_width = 300;
RenderFontTextRuns make_truns(RenderFontFactory fact) {
auto loader = [fact](const char filename[]) -> rive::rcp<rive::RenderFont> {
auto bytes = ViewerContent::LoadFile(filename);
if (bytes.size() == 0) {
assert(false);
return nullptr;
}
return fact(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);
rive::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, "ne漢字asy"));
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."));
m_breaks = compute_word_breaks(m_unichars);
return truns;
}
public:
TextContent() {
auto truns = this->make_truns(ViewerContent::DecodeFont);
m_gruns.push_back(truns[0].font->shapeText(m_unichars, truns));
m_xform = rive::Mat2D::fromTranslate(10, 0) * rive::Mat2D::fromScale(3, 3);
}
void draw(rive::Renderer* renderer, float width, const RenderFontGlyphRuns& gruns) {
renderer->save();
renderer->transform(m_xform);
auto lines = rive::RenderGlyphLine::BreakLines(gruns, m_breaks, width);
drawpara(RiveFactory(), renderer, lines, gruns, {0, 0});
draw_line(RiveFactory(), renderer, width);
renderer->restore();
}
void handleDraw(rive::Renderer* renderer, double) override {
for (auto& grun : m_gruns) {
this->draw(renderer, m_width, grun);
renderer->translate(1200, 0);
}
}
void handleResize(int width, int height) override {}
void handleImgui() override {
ImGui::Begin("text", nullptr);
ImGui::SliderFloat("Width", &m_width, 10, 400);
ImGui::End();
}
};
static bool ends_width(const char str[], const char suffix[]) {
size_t ln = strlen(str);
size_t lx = strlen(suffix);
if (lx > ln) {
return false;
}
for (size_t i = 0; i < lx; ++i) {
if (str[ln - lx + i] != suffix[i]) {
return false;
}
}
return true;
}
std::unique_ptr<ViewerContent> ViewerContent::Text(const char filename[]) {
if (ends_width(filename, ".svg")) {
return std::make_unique<TextContent>();
}
return nullptr;
}