blob: dae93ad51e6ca7a3fdb8790e52761d85579a1881 [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/text.hpp"
#include "rive/text/font_hb.hpp"
#include <algorithm>
using FontTextRuns = std::vector<rive::TextRun>;
using FontGlyphRuns = rive::SimpleArray<rive::GlyphRun>;
using FontFactory = rive::rcp<rive::Font> (*)(const rive::Span<const uint8_t>);
static float drawrun(rive::Factory* factory,
rive::Renderer* renderer,
const rive::GlyphRun& 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);
float x = origin.x;
assert(startIndex >= 0 && endIndex <= run.glyphs.size());
int i, end, inc;
if (run.dir == rive::TextDirection::rtl)
{
i = endIndex - 1;
end = startIndex - 1;
inc = -1;
}
else
{
i = startIndex;
end = endIndex;
inc = 1;
}
while (i != end)
{
auto trans = rive::Mat2D::fromTranslate(x, origin.y);
x += run.advances[i];
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());
i += inc;
}
return x;
}
static float drawpara(rive::Factory* factory,
rive::Renderer* renderer,
const rive::Paragraph& paragraph,
const rive::SimpleArray<rive::GlyphLine>& lines,
rive::Vec2D origin)
{
for (const auto& line : lines)
{
float x = line.startX + origin.x;
int runIndex, endRun, runInc;
if (paragraph.baseDirection == rive::TextDirection::rtl)
{
runIndex = line.endRunIndex;
endRun = line.startRunIndex - 1;
runInc = -1;
}
else
{
runIndex = line.startRunIndex;
endRun = line.endRunIndex + 1;
runInc = 1;
}
while (runIndex != endRun)
{
const auto& run = paragraph.runs[runIndex];
int startGIndex = runIndex == line.startRunIndex ? line.startGlyphIndex : 0;
int endGIndex = runIndex == line.endRunIndex ? line.endGlyphIndex : run.glyphs.size();
x = drawrun(factory,
renderer,
run,
startGIndex,
endGIndex,
{x, origin.y + line.baseline});
runIndex += runInc;
}
}
return origin.y + lines.back().bottom;
}
////////////////////////////////////////////////////////////////////////////////////
std::vector<rive::rcp<rive::Font>> fallbackFonts;
static rive::rcp<rive::Font> pickFallbackFont(rive::Span<const rive::Unichar> missing)
{
size_t length = fallbackFonts.size();
for (size_t i = 0; i < length; i++)
{
HBFont* font = static_cast<HBFont*>(fallbackFonts[i].get());
if (i == length - 1 || font->hasGlyph(missing))
{
return fallbackFonts[i];
}
}
return nullptr;
}
#ifdef RIVE_USING_HAFBUZZ_FONTS
static rive::rcp<rive::Font> load_fallback_font(rive::Span<const rive::Unichar> missing)
{
static rive::rcp<rive::Font> 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 = HBFont::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::TextRun append(std::vector<rive::Unichar>* unichars,
rive::rcp<rive::Font> 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;
rive::SimpleArray<rive::Paragraph> m_paragraphs;
rive::Mat2D m_xform;
float m_width = 300;
bool m_autoWidth = false;
int m_align = 0;
FontTextRuns make_truns(FontFactory fact)
{
auto loader = [fact](const char filename[]) -> rive::rcp<rive::Font> {
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/Montserrat.ttf",
"../../../test/assets/IBMPlexSansArabic-Regular.ttf",
};
auto font0 = loader(fontFiles[0]);
auto font1 = loader(fontFiles[1]);
auto font2 = loader(fontFiles[2]);
assert(font0);
assert(font1);
assert(font2);
fallbackFonts.push_back(font2);
rive::Font::Coord c1 = {'wght', 100.f}, c2 = {'wght', 800.f};
FontTextRuns truns;
// truns.push_back(
// append(&m_unichars, font0->makeAtCoord(c2), 32, "No one ever left alive in "));
// truns.push_back(append(&m_unichars, font0->makeAtCoord(c2), 54, "nineteen hundred"));
// truns.push_back(append(&m_unichars, font0->makeAtCoord(c1), 30, "ne漢字asy"));
// truns.push_back(append(&m_unichars, font2, 30, " its the 
"));
// truns.push_back(append(&m_unichars, font2, 40, "cRown"));
// truns.push_back(append(&m_unichars, font2, 30, "a b c d"));
// truns.push_back(append(&m_unichars, font1->makeAtCoord(c1), 30, " that often"));
// truns.push_back(append(&m_unichars, font0, 30, " lies the head."));
// truns.push_back(append(&m_unichars, font0->makeAtCoord(c2), 60, "hi one two"));
// truns.push_back(append(&m_unichars, font2, 32.0f, "في 10-12 آذار 1997 بمدينة"));
// truns.push_back(append(&m_unichars, font2, 32.0f, "في 10-12 آذار 1997 بمدينة"));
truns.push_back(append(&m_unichars,
font1,
32.0f,
// clang-format off
"لمفاتيح ABC"));
// truns.push_back(append(&m_unichars,
// font1,
// 28.0f,
// // clang-format off
// " DEF\n"));
// truns.push_back(append(&m_unichars,
// font0,
// 32.0f,
// // clang-format off
// " one ever ff ffi left alive in "));
// truns.push_back(append(&m_unichars,
// font1,
// 54.0f,
// // clang-format off
// "nineteen hundred"));
// truns.push_back(append(&m_unichars,
// font0,
// 32.0f,
// // clang-format off
// "and eighty five"));
// TODO: test case from flutter causing assertion break
// "لمفاتيح ABC DEF
في 10-12 آذار 1997 بمدينة\nabc def ghi jkl mnop\nلكن لا بد أن أوضح لك
// أن كل"));
// "hello look\u2028here\nsecond paragraph"));
// clang-format on
// truns.push_back(append(&m_unichars, font2, 32.0f, "abc
def\nghijkl"));
// truns.push_back(append(&m_unichars, font2, 32.0f, "DEF
دنة"));
// truns.push_back(append(&m_unichars, font2, 32.0f, "AفيB"));
// truns.push_back(append(&m_unichars, font0, 42.0f, "OT\nHER\n"));
// truns.push_back(append(&m_unichars, font1, 62.0f, "VERY LARGE FONT HERE"));
// truns.push_back(
// append(&m_unichars, font0, 52.0f, "one two three\n\n\nfour five six seven"));
// truns.push_back(append(&m_unichars, font0, 32.0f, "ab"));
// truns.push_back(append(&m_unichars, font0, 60.0f, "ee\n four"));
return truns;
}
public:
TextContent()
{
fallbackFonts.clear();
HBFont::gFallbackProc = pickFallbackFont;
auto truns = this->make_truns(ViewerContent::DecodeFont);
m_paragraphs = 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 rive::SimpleArray<rive::Paragraph>& paragraphs)
{
renderer->save();
renderer->transform(m_xform);
float y = 0.0f;
float paragraphWidth = m_autoWidth ? -1.0f : width;
rive::SimpleArray<rive::SimpleArray<rive::GlyphLine>>* lines =
new rive::SimpleArray<rive::SimpleArray<rive::GlyphLine>>(paragraphs.size());
rive::SimpleArray<rive::SimpleArray<rive::GlyphLine>>& linesRef = *lines;
size_t paragraphIndex = 0;
for (auto& para : paragraphs)
{
linesRef[paragraphIndex] =
rive::GlyphLine::BreakLines(para.runs, m_autoWidth ? -1.0f : width);
if (m_autoWidth)
{
paragraphWidth =
std::max(paragraphWidth,
rive::GlyphLine::ComputeMaxWidth(linesRef[paragraphIndex], para.runs));
}
paragraphIndex++;
}
paragraphIndex = 0;
for (auto& para : paragraphs)
{
rive::SimpleArray<rive::GlyphLine>& lines = linesRef[paragraphIndex++];
rive::GlyphLine::ComputeLineSpacing(lines,
para.runs,
paragraphWidth,
(rive::TextAlign)m_align);
y = drawpara(RiveFactory(), renderer, para, lines, {0, y}) + 20.0f;
}
if (!m_autoWidth)
{
draw_line(RiveFactory(), renderer, width);
}
renderer->restore();
}
void handleDraw(rive::Renderer* renderer, double) override
{
this->draw(renderer, m_width, m_paragraphs);
}
void handleResize(int width, int height) override {}
void handleImgui() override
{
const char* alignOptions[] = {"left", "right", "center"};
ImGui::Begin("text", nullptr);
ImGui::SliderFloat("Width", &m_width, 1, 400);
ImGui::Checkbox("Autowidth", &m_autoWidth);
ImGui::Combo("combo", &m_align, alignOptions, IM_ARRAYSIZE(alignOptions));
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;
}