Files
rive-flutter/wasm/rive_text_bindings.cpp
2022-11-02 14:50:18 -07:00

230 lines
7.1 KiB
C++

#include "hb-ot.h"
#include "hb.h"
#include "rive/text/font_hb.hpp"
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <stdint.h>
#include <stdio.h>
using namespace emscripten;
using WasmPtr = uint32_t;
WasmPtr makeFont(emscripten::val byteArray)
{
std::vector<unsigned char> bytes;
const auto l = byteArray["byteLength"].as<unsigned>();
bytes.resize(l);
emscripten::val memoryView{emscripten::typed_memory_view(l, bytes.data())};
memoryView.call<void>("set", byteArray);
auto result = HBFont::Decode(bytes);
if (result)
{
return (WasmPtr)result.release();
}
return (WasmPtr) nullptr;
}
void deleteFont(WasmPtr font) { reinterpret_cast<HBFont*>(font)->unref(); }
struct GlyphPath
{
WasmPtr rawPath;
WasmPtr points;
WasmPtr verbs;
uint16_t verbCount;
};
GlyphPath makeGlyphPath(WasmPtr fontPtr, rive::GlyphID id)
{
auto font = reinterpret_cast<HBFont*>(fontPtr);
rive::RawPath* path = new rive::RawPath(font->getPath(id));
return {
.rawPath = (WasmPtr)path,
.points = (WasmPtr)path->points().data(),
.verbs = (WasmPtr)path->verbs().data(),
.verbCount = (uint16_t)path->verbs().size(),
};
}
void deleteGlyphPath(WasmPtr rawPath) { delete reinterpret_cast<rive::RawPath*>(rawPath); }
void deleteShapeResult(WasmPtr shaperResult)
{
delete reinterpret_cast<rive::SimpleArray<rive::Paragraph>*>(shaperResult);
}
WasmPtr breakLines(WasmPtr paragraphsPtr, float width, uint8_t align)
{
bool autoWidth = width == -1.0f;
auto paragraphs = reinterpret_cast<rive::SimpleArray<rive::Paragraph>*>(paragraphsPtr);
float paragraphWidth = width;
rive::SimpleArrayBuilder<uint16_t> paragraphLines;
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, autoWidth ? -1.0f : width);
if (autoWidth)
{
paragraphWidth =
std::max(paragraphWidth,
rive::GlyphLine::ComputeMaxWidth(linesRef[paragraphIndex], para.runs));
}
paragraphIndex++;
}
paragraphIndex = 0;
for (auto& para : *paragraphs)
{
rive::GlyphLine::ComputeLineSpacing(linesRef[paragraphIndex++],
para.runs,
paragraphWidth,
(rive::TextAlign)align);
}
return (WasmPtr)lines;
}
void deleteLines(WasmPtr lines)
{
delete reinterpret_cast<rive::SimpleArray<rive::SimpleArray<rive::GlyphLine>>*>(lines);
}
std::vector<rive::Font*> fallbackFonts;
void setFallbackFonts(emscripten::val fontsList)
{
std::vector<int> fonts(fontsList["length"].as<unsigned>());
{
emscripten::val memoryView{emscripten::typed_memory_view(fonts.size(), fonts.data())};
memoryView.call<void>("set", fontsList);
}
fallbackFonts = std::vector<rive::Font*>();
for (auto fontPtr : fonts)
{
fallbackFonts.push_back(reinterpret_cast<rive::Font*>(fontPtr));
}
}
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]);
if (i == length - 1 || font->hasGlyph(missing))
{
rive::rcp<rive::Font> rcFont = rive::rcp<rive::Font>(font);
// because the font was released at load time, we need to give it an
// extra ref whenever we bump it to a reference counted pointer.
rcFont->ref();
return rcFont;
}
}
return nullptr;
}
WasmPtr shapeText(emscripten::val codeUnits, emscripten::val runsList)
{
std::vector<uint8_t> runsBytes(runsList["byteLength"].as<unsigned>());
{
emscripten::val memoryView{
emscripten::typed_memory_view(runsBytes.size(), runsBytes.data())};
memoryView.call<void>("set", runsList);
}
std::vector<uint32_t> codeUnitArray(codeUnits["length"].as<unsigned>());
{
emscripten::val memoryView{
emscripten::typed_memory_view(codeUnitArray.size(), codeUnitArray.data())};
memoryView.call<void>("set", codeUnits);
}
auto runCount = runsBytes.size() / sizeof(rive::TextRun);
rive::TextRun* runs = reinterpret_cast<rive::TextRun*>(runsBytes.data());
if (runCount > 0)
{
auto result = (WasmPtr) new rive::SimpleArray<rive::Paragraph>(
runs[0].font->shapeText(codeUnitArray, rive::Span(runs, runCount)));
return result;
}
return {};
}
void init()
{
fallbackFonts.clear();
HBFont::gFallbackProc = pickFallbackFont;
}
#ifdef DEBUG
#define OFFSET_OF(type, member) ((int)(intptr_t) & (((type*)(void*)0)->member))
void assertSomeAssumptions()
{
// These assumptions are important as our rive_text_wasm.dart integration
// relies on knowing the exact offsets of these struct elements. When and if
// we ever move to the proposed Wasm64 (currently not a standard), we'll
// need to make adjustements here.
assert(sizeof(rive::TextRun) == 20);
assert(OFFSET_OF(rive::TextRun, font) == 0);
assert(OFFSET_OF(rive::TextRun, size) == 4);
assert(OFFSET_OF(rive::TextRun, unicharCount) == 8);
assert(OFFSET_OF(rive::TextRun, script) == 12);
assert(OFFSET_OF(rive::TextRun, styleId) == 16);
assert(OFFSET_OF(rive::TextRun, dir) == 18);
assert(sizeof(rive::Paragraph) == 12);
assert(OFFSET_OF(rive::Paragraph, runs) == 0);
assert(OFFSET_OF(rive::Paragraph, baseDirection) == 8);
assert(sizeof(rive::GlyphRun) == 52);
assert(OFFSET_OF(rive::GlyphRun, font) == 0);
assert(OFFSET_OF(rive::GlyphRun, size) == 4);
assert(OFFSET_OF(rive::GlyphRun, glyphs) == 8);
assert(OFFSET_OF(rive::GlyphRun, textIndices) == 16);
assert(OFFSET_OF(rive::GlyphRun, advances) == 24);
assert(OFFSET_OF(rive::GlyphRun, xpos) == 32);
assert(OFFSET_OF(rive::GlyphRun, breaks) == 40);
assert(OFFSET_OF(rive::GlyphRun, styleId) == 48);
assert(OFFSET_OF(rive::GlyphRun, dir) == 50);
assert(sizeof(rive::GlyphLine) == 32);
}
#endif
EMSCRIPTEN_BINDINGS(RiveText)
{
function("makeFont", &makeFont, allow_raw_pointers());
function("deleteFont", &deleteFont);
value_array<GlyphPath>("GlyphPath")
.element(&GlyphPath::rawPath)
.element(&GlyphPath::points)
.element(&GlyphPath::verbs)
.element(&GlyphPath::verbCount);
function("makeGlyphPath", &makeGlyphPath);
function("deleteGlyphPath", &deleteGlyphPath);
function("shapeText", &shapeText);
function("setFallbackFonts", &setFallbackFonts);
function("deleteShapeResult", &deleteShapeResult);
function("breakLines", &breakLines);
function("deleteLines", &deleteLines);
function("init", &init);
#ifdef DEBUG
function("assertSomeAssumptions", &assertSomeAssumptions);
#endif
}