blob: 2bd8eaea80522122ffcc3c19374435717b387d27 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/text_engine.hpp"
#ifdef WITH_RIVE_TEXT
#include "rive/text/font_hb.hpp"
#include "rive/factory.hpp"
#include "rive/renderer_utils.hpp"
#include "hb.h"
#include "hb-ot.h"
extern "C"
{
#include "SheenBidi.h"
}
// Initialized to null. Client can set this to a callback.
HBFont::FallbackProc HBFont::gFallbackProc;
rive::rcp<rive::Font> HBFont::Decode(rive::Span<const uint8_t> span)
{
auto blob = hb_blob_create_or_fail((const char*)span.data(),
(unsigned)span.size(),
HB_MEMORY_MODE_DUPLICATE,
nullptr,
nullptr);
if (blob)
{
auto face = hb_face_create(blob, 0);
hb_blob_destroy(blob);
if (face)
{
auto font = hb_font_create(face);
hb_face_destroy(face);
if (font)
{
return rive::rcp<rive::Font>(new HBFont(font));
}
}
}
return nullptr;
}
//////////////
constexpr int kStdScale = 2048;
constexpr float gInvScale = 1.0f / kStdScale;
extern "C"
{
static void rpath_move_to(hb_draw_funcs_t*,
void* rpath,
hb_draw_state_t*,
float x,
float y,
void*)
{
((rive::RawPath*)rpath)->moveTo(x * gInvScale, -y * gInvScale);
}
static void rpath_line_to(hb_draw_funcs_t*,
void* rpath,
hb_draw_state_t*,
float x1,
float y1,
void*)
{
((rive::RawPath*)rpath)->lineTo(x1 * gInvScale, -y1 * gInvScale);
}
static void rpath_quad_to(hb_draw_funcs_t*,
void* rpath,
hb_draw_state_t*,
float x1,
float y1,
float x2,
float y2,
void*)
{
((rive::RawPath*)rpath)
->quadTo(x1 * gInvScale, -y1 * gInvScale, x2 * gInvScale, -y2 * gInvScale);
}
static void rpath_cubic_to(hb_draw_funcs_t*,
void* rpath,
hb_draw_state_t*,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3,
void*)
{
((rive::RawPath*)rpath)
->cubicTo(x1 * gInvScale,
-y1 * gInvScale,
x2 * gInvScale,
-y2 * gInvScale,
x3 * gInvScale,
-y3 * gInvScale);
}
static void rpath_close(hb_draw_funcs_t*, void* rpath, hb_draw_state_t*, void*)
{
((rive::RawPath*)rpath)->close();
}
}
static rive::Font::LineMetrics make_lmx(hb_font_t* font)
{
// premable on font...
hb_ot_font_set_funcs(font);
hb_font_set_scale(font, kStdScale, kStdScale);
hb_font_extents_t extents;
hb_font_get_h_extents(font, &extents);
return {-extents.ascender * gInvScale, -extents.descender * gInvScale};
}
HBFont::HBFont(hb_font_t* font) :
Font(make_lmx(font)), m_Font(font) // we just take ownership, no need to call reference()
{
m_DrawFuncs = hb_draw_funcs_create();
hb_draw_funcs_set_move_to_func(m_DrawFuncs, rpath_move_to, nullptr, nullptr);
hb_draw_funcs_set_line_to_func(m_DrawFuncs, rpath_line_to, nullptr, nullptr);
hb_draw_funcs_set_quadratic_to_func(m_DrawFuncs, rpath_quad_to, nullptr, nullptr);
hb_draw_funcs_set_cubic_to_func(m_DrawFuncs, rpath_cubic_to, nullptr, nullptr);
hb_draw_funcs_set_close_path_func(m_DrawFuncs, rpath_close, nullptr, nullptr);
hb_draw_funcs_make_immutable(m_DrawFuncs);
}
HBFont::~HBFont()
{
hb_draw_funcs_destroy(m_DrawFuncs);
hb_font_destroy(m_Font);
}
rive::Font::Axis HBFont::getAxis(uint16_t index) const
{
auto face = hb_font_get_face(m_Font);
assert(index < hb_ot_var_get_axis_count(face));
unsigned n = 1;
hb_ot_var_axis_info_t info;
hb_ot_var_get_axis_infos(face, index, &n, &info);
assert(n == 1);
return {info.tag, info.min_value, info.default_value, info.max_value};
}
uint16_t HBFont::getAxisCount() const
{
auto face = hb_font_get_face(m_Font);
return (uint16_t)hb_ot_var_get_axis_count(face);
}
float HBFont::getAxisValue(uint32_t axisTag) const
{
auto face = hb_font_get_face(m_Font);
uint32_t length;
// Check if we have a sepecified value.
const float* values = hb_font_get_var_coords_design(m_Font, &length);
for (uint32_t i = 0; i < length; ++i)
{
hb_ot_var_axis_info_t info;
uint32_t n = 1;
hb_ot_var_get_axis_infos(face, i, &n, &info);
if (info.tag == axisTag)
{
return values[i];
}
}
// No value specified, we're using a default.
uint32_t axisCount = hb_ot_var_get_axis_count(face);
for (uint32_t i = 0; i < axisCount; ++i)
{
hb_ot_var_axis_info_t info;
uint32_t n = 1;
hb_ot_var_get_axis_infos(face, i, &n, &info);
if (info.tag == axisTag)
{
return info.default_value;
}
}
return 0.0f;
}
std::vector<rive::Font::Coord> HBFont::getCoords() const
{
auto axes = this->getAxes();
// const int count = (int)axes.size();
unsigned length;
const float* values = hb_font_get_var_coords_design(m_Font, &length);
std::vector<rive::Font::Coord> coords(length);
for (unsigned i = 0; i < length; ++i)
{
coords[i] = {axes[i].tag, values[i]};
}
return coords;
}
rive::rcp<rive::Font> HBFont::makeAtCoords(rive::Span<const Coord> coords) const
{
AutoSTArray<16, hb_variation_t> vars(coords.size());
for (size_t i = 0; i < coords.size(); ++i)
{
vars[i] = {coords[i].axis, coords[i].value};
}
auto font = hb_font_create_sub_font(m_Font);
hb_font_set_variations(font, vars.data(), (unsigned int)vars.size());
return rive::rcp<rive::Font>(new HBFont(font));
}
rive::RawPath HBFont::getPath(rive::GlyphID glyph) const
{
rive::RawPath rpath;
hb_font_get_glyph_shape(m_Font, glyph, m_DrawFuncs, &rpath);
return rpath;
}
///////////////////////////////////////////////////////////
const hb_feature_t gFeatures[] = {
{'liga', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
{'dlig', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
// {'clig', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
// {'calt', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
{'kern', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
};
constexpr int gNumFeatures = sizeof(gFeatures) / sizeof(gFeatures[0]);
static rive::GlyphRun shape_run(const rive::Unichar text[],
const rive::TextRun& tr,
unsigned textOffset)
{
hb_buffer_t* buf = hb_buffer_create();
hb_buffer_add_utf32(buf, text, tr.unicharCount, 0, tr.unicharCount);
hb_buffer_set_direction(buf,
tr.dir == rive::TextDirection::rtl ? HB_DIRECTION_RTL
: HB_DIRECTION_LTR);
hb_buffer_set_script(buf, (hb_script_t)tr.script);
hb_buffer_set_language(buf, hb_language_get_default());
auto hbfont = (HBFont*)tr.font.get();
hb_shape(hbfont->m_Font, buf, gFeatures, gNumFeatures);
unsigned int glyph_count;
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
// todo: check for missing glyphs, and perform font-substitution
rive::GlyphRun gr(glyph_count);
gr.font = tr.font;
gr.size = tr.size;
gr.styleId = tr.styleId;
gr.dir = tr.dir;
const float scale = tr.size / kStdScale;
for (unsigned int i = 0; i < glyph_count; i++)
{
// hb_position_t x_offset = glyph_pos[i].x_offset;
// hb_position_t y_offset = glyph_pos[i].y_offset;
unsigned int index = tr.dir == rive::TextDirection::rtl ? glyph_count - 1 - i : i;
gr.glyphs[i] = (uint16_t)glyph_info[index].codepoint;
gr.textIndices[i] = textOffset + glyph_info[index].cluster;
gr.advances[i] = gr.xpos[i] = glyph_pos[index].x_advance * scale;
gr.offsets[i] =
rive::Vec2D(glyph_pos[index].x_offset * scale, -glyph_pos[index].y_offset * scale);
}
gr.xpos[glyph_count] = 0; // so the next run can line up snug
hb_buffer_destroy(buf);
return gr;
}
static rive::GlyphRun extract_subset(const rive::GlyphRun& orig, size_t start, size_t end)
{
auto count = end - start;
rive::GlyphRun subset(rive::SimpleArray<rive::GlyphID>(&orig.glyphs[start], count),
rive::SimpleArray<uint32_t>(&orig.textIndices[start], count),
rive::SimpleArray<float>(&orig.advances[start], count),
rive::SimpleArray<float>(&orig.xpos[start], count + 1),
rive::SimpleArray<rive::Vec2D>(&orig.offsets[start], count));
subset.font = std::move(orig.font);
subset.size = orig.size;
subset.dir = orig.dir;
subset.xpos.back() = 0; // since we're now the end of a run
subset.styleId = orig.styleId;
return subset;
}
static void perform_fallback(rive::rcp<rive::Font> fallbackFont,
rive::SimpleArrayBuilder<rive::GlyphRun>& gruns,
const rive::Unichar text[],
const rive::GlyphRun& orig,
const rive::TextRun& origTextRun)
{
assert(orig.glyphs.size() > 0);
const size_t count = orig.glyphs.size();
size_t startI = 0;
while (startI < count)
{
size_t endI = startI + 1;
if (orig.glyphs[startI] == 0)
{
while (endI < count && orig.glyphs[endI] == 0)
{
++endI;
}
auto textStart = orig.textIndices[startI];
auto textCount = orig.textIndices[endI - 1] - textStart + 1;
auto tr = rive::TextRun{
fallbackFont,
orig.size,
textCount,
origTextRun.script,
orig.styleId,
orig.dir,
};
auto gr = shape_run(&text[textStart], tr, textStart);
if (gr.glyphs.size() > 0)
{
gruns.add(std::move(gr));
}
}
else
{
while (endI < count && orig.glyphs[endI] != 0)
{
++endI;
}
gruns.add(extract_subset(orig, startI, endI));
}
startI = endI;
}
}
rive::SimpleArray<rive::Paragraph> HBFont::onShapeText(rive::Span<const rive::Unichar> text,
rive::Span<const rive::TextRun> truns) const
{
rive::SimpleArrayBuilder<rive::Paragraph> paragraphs;
SBCodepointSequence codepointSequence = {SBStringEncodingUTF32,
(void*)text.data(),
text.size()};
hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_get_default();
// Split runs by bidi types.
uint32_t textIndex = 0;
uint32_t runIndex = 0;
uint32_t runStartTextIndex = 0;
SBUInteger paragraphStart = 0;
SBAlgorithmRef bidiAlgorithm = SBAlgorithmCreate(&codepointSequence);
uint32_t unicharIndex = 0;
uint32_t runTextIndex = 0;
while (paragraphStart < text.size())
{
SBParagraphRef paragraph =
SBAlgorithmCreateParagraph(bidiAlgorithm, paragraphStart, INT32_MAX, SBLevelDefaultLTR);
SBUInteger paragraphLength = SBParagraphGetLength(paragraph);
// Next iteration reads the next paragraph (if any remain).
paragraphStart += paragraphLength;
const SBLevel* bidiLevels = SBParagraphGetLevelsPtr(paragraph);
SBLevel paragraphLevel = SBParagraphGetBaseLevel(paragraph);
uint32_t paragraphTextIndex = 0;
std::vector<rive::TextRun> bidiRuns;
bidiRuns.reserve(truns.size());
while (runIndex < truns.size())
{
const auto& tr = truns[runIndex];
assert(tr.unicharCount != 0);
SBLevel lastLevel = bidiLevels[paragraphTextIndex];
hb_script_t lastScript = hb_unicode_script(ufuncs, text[textIndex]);
rive::TextRun splitRun = {
tr.font,
tr.size,
tr.unicharCount - runTextIndex,
(uint32_t)lastScript,
tr.styleId,
lastLevel & 1 ? rive::TextDirection::rtl : rive::TextDirection::ltr,
};
runStartTextIndex = textIndex;
runTextIndex++;
textIndex++;
paragraphTextIndex++;
bidiRuns.push_back(splitRun);
while (runTextIndex < tr.unicharCount && paragraphTextIndex < paragraphLength)
{
hb_script_t script = hb_unicode_script(ufuncs, text[textIndex]);
switch (script)
{
case HB_SCRIPT_COMMON:
case HB_SCRIPT_INHERITED:
// Propagate last seen "real" script value.
script = lastScript;
break;
default:
break;
}
if (bidiLevels[paragraphTextIndex] != lastLevel || script != lastScript)
{
lastScript = script;
auto& back = bidiRuns.back();
back.unicharCount = textIndex - runStartTextIndex;
lastLevel = bidiLevels[paragraphTextIndex];
rive::TextRun backRun = {
back.font,
back.size,
tr.unicharCount - runTextIndex,
(uint32_t)script,
back.styleId,
lastLevel & 1 ? rive::TextDirection::rtl : rive::TextDirection::ltr,
};
runStartTextIndex = textIndex;
bidiRuns.push_back(backRun);
}
runTextIndex++;
textIndex++;
paragraphTextIndex++;
}
// Reached the end of the run?
if (runTextIndex == tr.unicharCount)
{
runIndex++;
runTextIndex = 0;
}
// We consumed the whole paragraph.
if (paragraphTextIndex == paragraphLength)
{
// Close off the last run.
auto& back = bidiRuns.back();
back.unicharCount = textIndex - runStartTextIndex;
break;
}
}
rive::SimpleArrayBuilder<rive::GlyphRun> gruns(bidiRuns.size());
for (const auto& tr : bidiRuns)
{
auto gr = shape_run(&text[unicharIndex], tr, unicharIndex);
unicharIndex += tr.unicharCount;
auto end = gr.glyphs.end();
auto iter = std::find(gr.glyphs.begin(), end, 0);
if (!gFallbackProc || iter == end)
{
if (gr.glyphs.size() > 0)
{
gruns.add(std::move(gr));
}
}
else
{
// found at least 1 zero in glyphs, so need to perform font-fallback
size_t index = iter - gr.glyphs.begin();
rive::Unichar missing = text[gr.textIndices[index]];
// todo: consider sending more chars if that helps choose a font
auto fallback = gFallbackProc({&missing, 1});
if (fallback)
{
perform_fallback(fallback, gruns, text.data(), gr, tr);
}
else if (gr.glyphs.size() > 0)
{
gruns.add(std::move(gr)); // oh well, just keep the missing glyphs
}
}
}
// turn the advances we stored in xpos[] into actual x-positions
// for logical order.
float pos = 0;
for (auto& gr : gruns)
{
for (auto& xp : gr.xpos)
{
float adv = xp;
xp = pos;
pos += adv;
}
}
paragraphs.add({
std::move(gruns),
paragraphLevel & 1 ? rive::TextDirection::rtl : rive::TextDirection::ltr,
});
SBParagraphRelease(paragraph);
}
SBAlgorithmRelease(bidiAlgorithm);
return paragraphs;
}
bool HBFont::hasGlyph(rive::Span<const rive::Unichar> missing)
{
hb_codepoint_t glyph;
return !missing.empty() && hb_font_get_nominal_glyph(m_Font, missing[0], &glyph);
}
#endif