Bidi Text Support
Adding support for bidirectional text using SheenBidi to break styled runs into directional runs. This also needs to introduce the "baseDirection" of a paragraph (and the concept of a paragraph) in order to properly flow the runs after shaping.
Diffs=
291a3a02b Bidi Text Support (#4282)
diff --git a/.rive_head b/.rive_head
index ec72cc2..2cc4a4c 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-0ffa502c470709177f941dca669e3a9cfb9612bf
+291a3a02bce12a2c92a456f6a71ad3a410162f0f
diff --git a/build.sh b/build.sh
index 1534111..f6430b7 100755
--- a/build.sh
+++ b/build.sh
@@ -1,6 +1,7 @@
#!/bin/bash
-set -e
+set -e
+source dependencies/config_directories.sh
pushd build &>/dev/null
while getopts p: flag; do
diff --git a/build/dependency.lua b/build/dependency.lua
new file mode 100644
index 0000000..bef9145
--- /dev/null
+++ b/build/dependency.lua
@@ -0,0 +1,49 @@
+local m = {}
+
+local last_str = ''
+
+function iop(str)
+ io.write(('\b \b'):rep(#last_str)) -- erase old line
+ io.write(str) -- write new line
+ io.flush()
+ last_str = str
+end
+
+function m.github(project, tag)
+ local dependencies = os.getenv('DEPENDENCIES')
+ if dependencies == nil then
+ dependencies = path.getabsolute(_WORKING_DIR) .. '/dependencies'
+ os.mkdir(dependencies)
+ end
+ local hash = string.sha1(project .. tag)
+ if not os.isdir(dependencies .. '/' .. hash) then
+ function progress(total, current)
+ local ratio = current / total
+ ratio = math.min(math.max(ratio, 0), 1)
+ local percent = math.floor(ratio * 100)
+ if total == current then
+ iop('')
+ else
+ iop('Downloading ' .. project .. ' ' .. percent .. '%')
+ end
+ end
+
+ local downloadFilename = dependencies .. '/' .. hash .. '.zip'
+ http.download(
+ 'https://github.com/' .. project .. '/archive/' .. tag .. '.zip',
+ downloadFilename,
+ {progress = progress}
+ )
+ print('Downloaded ' .. project .. '.')
+ zip.extract(downloadFilename, dependencies .. '/' .. hash)
+ os.remove(downloadFilename)
+ end
+ local dirs = os.matchdirs(dependencies .. '/' .. hash .. '/*')
+
+ local iter = pairs(dirs)
+ local currentKey, currentValue = iter(dirs)
+ print('Dependency ' .. project .. ' located at:')
+ print(' ' .. currentValue)
+ return currentValue
+end
+return m
diff --git a/build/premake5.lua b/build/premake5.lua
index 9cafaf2..507d7c2 100644
--- a/build/premake5.lua
+++ b/build/premake5.lua
@@ -4,11 +4,14 @@
do
defines {'WITH_RIVE_TOOLS'}
end
-filter {'options:with_rive_tools'}
+filter {'options:with_rive_text'}
do
- defines {'WITH_RIVE_TOOLS'}
+ defines {'WITH_RIVE_TEXT'}
end
+dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_harfbuzz.lua'))
+dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_sheenbidi.lua'))
+
WINDOWS_CLANG_CL_SUPPRESSED_WARNINGS = {
'-Wno-c++98-compat',
'-Wno-c++98-compat-pedantic',
@@ -28,7 +31,9 @@
'-Wno-sign-compare',
'-Wno-sign-conversion',
'-Wno-unused-macros',
- '-Wno-unused-parameter'
+ '-Wno-unused-parameter',
+ '-Wno-switch-enum',
+ '-Wno-missing-field-initializers'
}
project 'rive'
@@ -39,7 +44,11 @@
toolset 'clang'
targetdir '%{cfg.system}/bin/%{cfg.buildcfg}'
objdir '%{cfg.system}/obj/%{cfg.buildcfg}'
- includedirs {'../include'}
+ includedirs {
+ '../include',
+ harfbuzz .. '/src',
+ sheenbidi .. '/Headers'
+ }
files {'../src/**.cpp'}
diff --git a/dependencies/linux/get_harfbuzz.sh b/dependencies/linux/get_harfbuzz.sh
deleted file mode 120000
index 994888b..0000000
--- a/dependencies/linux/get_harfbuzz.sh
+++ /dev/null
@@ -1 +0,0 @@
-../macosx/get_harfbuzz.sh
\ No newline at end of file
diff --git a/dependencies/macosx/get_harfbuzz.sh b/dependencies/macosx/get_harfbuzz.sh
deleted file mode 100755
index 6ad6b12..0000000
--- a/dependencies/macosx/get_harfbuzz.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-# if you're looking at this in dependencies/linux, please note it's a symbolic
-# link to dependencies/macosx/get_harfbuzz.sh if changes need to be made here we
-# should let these two scripts diverge.
-set -e
-
-if [ -z "${DEPENDENCIES}" ]; then
- echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts."
- exit 1
-fi
-pushd $DEPENDENCIES
-
-if [ ! -d harfbuzz ]; then
- echo "Cloning Harfbuzz."
- git clone https://github.com/harfbuzz/harfbuzz
- cd harfbuzz
- git checkout 858570b1d9912a1b746ab39fbe62a646c4f7a5b1 .
-fi
diff --git a/dependencies/premake5_harfbuzz.lua b/dependencies/premake5_harfbuzz.lua
index 9c31248..3fefe81 100644
--- a/dependencies/premake5_harfbuzz.lua
+++ b/dependencies/premake5_harfbuzz.lua
@@ -1,6 +1,5 @@
-dependencies = os.getenv('DEPENDENCIES')
-
-harfbuzz = dependencies .. '/harfbuzz'
+local dependency = require 'dependency'
+harfbuzz = dependency.github('harfbuzz/harfbuzz', '858570b1d9912a1b746ab39fbe62a646c4f7a5b1')
workspace 'rive'
configurations {'debug', 'release'}
diff --git a/dependencies/premake5_sheenbidi.lua b/dependencies/premake5_sheenbidi.lua
new file mode 100644
index 0000000..bebb582
--- /dev/null
+++ b/dependencies/premake5_sheenbidi.lua
@@ -0,0 +1,116 @@
+local dependency = require 'dependency'
+sheenbidi = dependency.github('Tehreer/SheenBidi', 'v2.6')
+
+workspace 'rive'
+configurations {'debug', 'release'}
+
+project 'rive_sheenbidi'
+do
+ kind 'StaticLib'
+ language 'C'
+ toolset 'clang'
+ targetdir '%{cfg.system}/cache/bin/%{cfg.buildcfg}/'
+ objdir '%{cfg.system}/cache/obj/%{cfg.buildcfg}/'
+
+ includedirs {
+ sheenbidi .. '/Headers'
+ }
+
+ filter 'configurations:debug'
+ do
+ files {
+ sheenbidi .. '/Source/BidiChain.c',
+ sheenbidi .. '/Source/BidiTypeLookup.c',
+ sheenbidi .. '/Source/BracketQueue.c',
+ sheenbidi .. '/Source/GeneralCategoryLookup.c',
+ sheenbidi .. '/Source/IsolatingRun.c',
+ sheenbidi .. '/Source/LevelRun.c',
+ sheenbidi .. '/Source/PairingLookup.c',
+ sheenbidi .. '/Source/RunQueue.c',
+ sheenbidi .. '/Source/SBAlgorithm.c',
+ sheenbidi .. '/Source/SBBase.c',
+ sheenbidi .. '/Source/SBCodepointSequence.c',
+ sheenbidi .. '/Source/SBLine.c',
+ sheenbidi .. '/Source/SBLog.c',
+ sheenbidi .. '/Source/SBMirrorLocator.c',
+ sheenbidi .. '/Source/SBParagraph.c',
+ sheenbidi .. '/Source/SBScriptLocator.c',
+ sheenbidi .. '/Source/ScriptLookup.c',
+ sheenbidi .. '/Source/ScriptStack.c',
+ sheenbidi .. '/Source/StatusStack.c'
+ }
+ end
+ filter 'configurations:release'
+ do
+ files {
+ sheenbidi .. '/Source/SheenBidi.c'
+ }
+ end
+
+ buildoptions {
+ '-Wall',
+ '-ansi',
+ '-pedantic'
+ }
+
+ linkoptions {'-r'}
+
+ filter 'configurations:debug'
+ do
+ buildoptions {'-g', '-O0'}
+ defines {'DEBUG'}
+ symbols 'On'
+ end
+
+ filter 'configurations:release'
+ do
+ buildoptions {'-Oz'}
+ defines {'RELEASE', 'NDEBUG', 'SB_CONFIG_UNITY'}
+ optimize 'On'
+ end
+
+ filter 'system:windows'
+ do
+ removebuildoptions {
+ -- vs clang doesn't recognize these on windows
+ '-fno-exceptions',
+ '-fno-rtti'
+ }
+ architecture 'x64'
+ buildoptions {
+ '-Wno-c++98-compat',
+ '-Wno-c++98-compat-pedantic',
+ '-Wno-c99-extensions',
+ '-Wno-ctad-maybe-unsupported',
+ '-Wno-deprecated-copy-with-user-provided-dtor',
+ '-Wno-deprecated-declarations',
+ '-Wno-documentation',
+ '-Wno-documentation-pedantic',
+ '-Wno-documentation-unknown-command',
+ '-Wno-double-promotion',
+ '-Wno-exit-time-destructors',
+ '-Wno-float-equal',
+ '-Wno-global-constructors',
+ '-Wno-implicit-float-conversion',
+ '-Wno-newline-eof',
+ '-Wno-old-style-cast',
+ '-Wno-reserved-identifier',
+ '-Wno-shadow',
+ '-Wno-sign-compare',
+ '-Wno-sign-conversion',
+ '-Wno-unused-macros',
+ '-Wno-unused-parameter',
+ '-Wno-used-but-marked-unused',
+ '-Wno-cast-qual',
+ '-Wno-unused-template',
+ '-Wno-zero-as-null-pointer-constant',
+ '-Wno-extra-semi',
+ '-Wno-undef',
+ '-Wno-comma',
+ '-Wno-nonportable-system-include-path',
+ '-Wno-covered-switch-default',
+ '-Wno-microsoft-enum-value',
+ '-Wno-deprecated-declarations'
+ }
+ end
+end
diff --git a/dependencies/windows/get_harfbuzz.bat b/dependencies/windows/get_harfbuzz.bat
deleted file mode 100644
index e225413..0000000
--- a/dependencies/windows/get_harfbuzz.bat
+++ /dev/null
@@ -1,11 +0,0 @@
-@echo off
-pushd %DEPENDENCIES%
-@echo off
-if not exist ".\harfbuzz" (
- echo "Cloning Harfbuzz."
- git clone https://github.com/harfbuzz/harfbuzz
- pushd harfbuzz
- git checkout 858570b1d9912a1b746ab39fbe62a646c4f7a5b1 .
- popd
-)
-popd
\ No newline at end of file
diff --git a/dev/test.bat b/dev/test.bat
index a238f75..3846a44 100644
--- a/dev/test.bat
+++ b/dev/test.bat
@@ -7,15 +7,9 @@
popd
)
-if not exist "%DEPENDENCIES%\harfbuzz\" (
- pushd "%DEPENDENCIES_SCRIPTS%"
- call .\get_harfbuzz.bat || goto :error
- popd
-)
-
set "PREMAKE=%DEPENDENCIES%\bin\premake5.exe"
pushd test
-%PREMAKE% vs2022
+%PREMAKE% --scripts=..\..\build vs2022
MSBuild.exe /? 2> NUL
if not %ERRORLEVEL%==9009 (
diff --git a/dev/test.sh b/dev/test.sh
index 3a0ea33..2707558 100755
--- a/dev/test.sh
+++ b/dev/test.sh
@@ -8,19 +8,13 @@
OPTION=$1
UTILITY=
-if [[ ! -d "$DEPENDENCIES/harfbuzz" ]]; then
- pushd $DEPENDENCIES_SCRIPTS
- ./get_harfbuzz.sh
- popd
-fi
-
if [ "$OPTION" = "help" ]; then
echo test.sh - run the tests
echo test.sh clean - clean and run the tests
exit
elif [ "$OPTION" = "clean" ]; then
echo Cleaning project ...
- premake5 clean || exit 1
+ premake5 --scripts=../../build clean || exit 1
shift
elif [ "$OPTION" = "memory" ]; then
echo Will perform memory checks...
@@ -32,7 +26,7 @@
shift
fi
-premake5 gmake2 || exit 1
+premake5 --scripts=../../build gmake2 || exit 1
make -j7 || exit 1
for file in ./build/bin/debug/*; do
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua
index 07a3d35..501c4f6 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -16,8 +16,8 @@
workspace 'rive'
configurations {'debug'}
-dependencies = os.getenv('DEPENDENCIES')
-dofile(path.join(path.getabsolute(dependencies) .. '/../..', 'premake5_harfbuzz.lua'))
+dofile(path.join(path.getabsolute('../../dependencies/'), 'premake5_harfbuzz.lua'))
+dofile(path.join(path.getabsolute('../../dependencies/'), 'premake5_sheenbidi.lua'))
project('tests')
do
@@ -30,9 +30,15 @@
buildoptions {'-Wall', '-fno-exceptions', '-fno-rtti'}
- includedirs {'./include', '../../include', dependencies .. '/harfbuzz/src'}
+ includedirs {
+ './include',
+ '../../include',
+ harfbuzz .. '/src',
+ sheenbidi .. '/Headers'
+ }
links {
- 'rive_harfbuzz'
+ 'rive_harfbuzz',
+ 'rive_sheenbidi'
}
files {
@@ -86,7 +92,9 @@
'-Wno-unused-macros',
'-Wno-unused-parameter',
'-Wno-four-char-constants',
- '-Wno-unreachable-code'
+ '-Wno-unreachable-code',
+ '-Wno-switch-enum',
+ '-Wno-missing-field-initializers'
}
end
end
diff --git a/include/rive/factory.hpp b/include/rive/factory.hpp
index fd0232d..6d69bad 100644
--- a/include/rive/factory.hpp
+++ b/include/rive/factory.hpp
@@ -6,7 +6,7 @@
#define _RIVE_FACTORY_HPP_
#include "rive/renderer.hpp"
-#include "rive/render_text.hpp"
+#include "rive/text.hpp"
#include "rive/refcnt.hpp"
#include "rive/span.hpp"
#include "rive/math/aabb.hpp"
@@ -58,7 +58,7 @@
virtual std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) = 0;
- virtual rcp<RenderFont> decodeFont(Span<const uint8_t>) { return nullptr; }
+ virtual rcp<Font> decodeFont(Span<const uint8_t>) { return nullptr; }
// Non-virtual helpers
diff --git a/include/rive/render_text.hpp b/include/rive/render_text.hpp
deleted file mode 100644
index d7827ed..0000000
--- a/include/rive/render_text.hpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#ifndef _RIVE_RENDER_TEXT_HPP_
-#define _RIVE_RENDER_TEXT_HPP_
-
-#include "rive/math/raw_path.hpp"
-#include "rive/refcnt.hpp"
-#include "rive/span.hpp"
-#include "rive/simple_array.hpp"
-
-namespace rive
-{
-
-using Unichar = uint32_t;
-using GlyphID = uint16_t;
-
-struct RenderTextRun;
-struct RenderGlyphRun;
-
-class RenderFont : public RefCnt<RenderFont>
-{
-public:
- virtual ~RenderFont() {}
-
- struct LineMetrics
- {
- float ascent, descent;
- };
-
- const LineMetrics& lineMetrics() const { return m_LineMetrics; }
-
- // This is experimental
- // -- may only be needed by Editor
- // -- so it may be removed from here later
- //
- struct Axis
- {
- uint32_t tag;
- float min;
- float def; // default value
- float max;
- };
-
- // Returns the canonical set of Axes for this font. Use this to know
- // what variations are possible. If you want to know the specific
- // coordinate within that variations space for *this* font, call
- // getCoords().
- //
- virtual std::vector<Axis> getAxes() const = 0;
-
- struct Coord
- {
- uint32_t axis;
- float value;
- };
-
- // Returns the specific coords in variation space for this font.
- // If you want to have a description of the entire variation space,
- // call getAxes().
- //
- virtual std::vector<Coord> getCoords() const = 0;
-
- virtual rcp<RenderFont> makeAtCoords(Span<const Coord>) const = 0;
-
- rcp<RenderFont> makeAtCoord(Coord c) { return this->makeAtCoords(Span<const Coord>(&c, 1)); }
-
- // Returns a 1-point path for this glyph. It will be positioned
- // relative to (0,0) with the typographic baseline at y = 0.
- //
- virtual RawPath getPath(GlyphID) const = 0;
-
- rive::SimpleArray<RenderGlyphRun> shapeText(rive::Span<const rive::Unichar> text,
- rive::Span<const rive::RenderTextRun> runs) const;
-
-protected:
- RenderFont(const LineMetrics& lm) : m_LineMetrics(lm) {}
-
- virtual rive::SimpleArray<RenderGlyphRun>
- onShapeText(rive::Span<const rive::Unichar> text,
- rive::Span<const rive::RenderTextRun> runs) const = 0;
-
-private:
- const LineMetrics m_LineMetrics;
-};
-
-struct RenderTextRun
-{
- rcp<RenderFont> font;
- float size;
- uint32_t unicharCount;
-};
-
-struct RenderGlyphRun
-{
- RenderGlyphRun(size_t glyphCount = 0) :
- glyphs(glyphCount), textIndices(glyphCount), xpos(glyphCount + 1)
- {}
-
- RenderGlyphRun(rive::SimpleArray<GlyphID> glyphIds,
- rive::SimpleArray<uint32_t> offsets,
- rive::SimpleArray<float> xs) :
- glyphs(glyphIds), textIndices(offsets), xpos(xs)
- {}
-
- rcp<RenderFont> font;
- float size;
- // List of glyphs, represented by font specific glyph ids. Length is equal to number of glyphs
- // in the run.
- rive::SimpleArray<GlyphID> glyphs;
-
- // Index in the unicode text array representing the text displayed in this run. Because each
- // glyph can be composed of multiple unicode values, this index points to the first index in the
- // unicode text. Length is equal to number of glyphs in the run.
- rive::SimpleArray<uint32_t> textIndices;
-
- // X position of each glyph, with an extra value at the end for the right most extent of the
- // last glyph.
- rive::SimpleArray<float> xpos;
-
- // List of possible indices to line break at. Has a stride of 2 uint32_ts where each pair marks
- // the start and end of a word, with the exception of a return character (forced linebreak)
- // which is represented as a 0 length word (where start/end index is the same).
- rive::SimpleArray<uint32_t> breaks;
-};
-
-} // namespace rive
-#endif
diff --git a/include/rive/text.hpp b/include/rive/text.hpp
new file mode 100644
index 0000000..13aa7cd
--- /dev/null
+++ b/include/rive/text.hpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_TEXT_HPP_
+#define _RIVE_TEXT_HPP_
+
+#include "rive/math/raw_path.hpp"
+#include "rive/refcnt.hpp"
+#include "rive/span.hpp"
+#include "rive/simple_array.hpp"
+
+namespace rive
+{
+
+// Representation of a single unicode codepoint.
+using Unichar = uint32_t;
+// Id for a glyph within a font.
+using GlyphID = uint16_t;
+
+struct TextRun;
+struct GlyphRun;
+
+// Direction a paragraph or run flows in.
+enum class TextDirection : uint8_t
+{
+ ltr = 0,
+ rtl = 1
+};
+
+// The alignment of each word wrapped line in a paragraph.
+enum class TextAlign : uint8_t
+{
+ left = 0,
+ right = 1,
+ center = 2
+};
+
+// A horizontal line of text with a paragraph, after line-breaking.
+struct GlyphLine
+{
+ uint32_t startRunIndex;
+ uint32_t startGlyphIndex;
+ uint32_t endRunIndex;
+ uint32_t endGlyphIndex;
+ float startX;
+ float top = 0, baseline = 0, bottom = 0;
+
+ bool operator==(const GlyphLine& o) const
+ {
+ return startRunIndex == o.startRunIndex && startGlyphIndex == o.startGlyphIndex &&
+ endRunIndex == o.endRunIndex && endGlyphIndex == o.endGlyphIndex;
+ }
+
+ GlyphLine() :
+ startRunIndex(0), startGlyphIndex(0), endRunIndex(0), endGlyphIndex(0), startX(0.0f)
+ {}
+ GlyphLine(uint32_t run, uint32_t index) :
+ startRunIndex(run),
+ startGlyphIndex(index),
+ endRunIndex(run),
+ endGlyphIndex(index),
+ startX(0.0f)
+ {}
+
+ bool empty() const { return startRunIndex == endRunIndex && startGlyphIndex == endGlyphIndex; }
+
+ static SimpleArray<GlyphLine> BreakLines(Span<const GlyphRun> runs, float width);
+
+ // Compute values for top/baseline/bottom per line
+ static void
+ ComputeLineSpacing(Span<GlyphLine>, Span<const GlyphRun>, float width, TextAlign align);
+
+ static float ComputeMaxWidth(Span<GlyphLine> lines, Span<const GlyphRun> runs);
+};
+
+// A paragraph represents of set of runs that flow in a specific direction. The
+// runs are always provided in LTR and must be drawn in reverse when the
+// baseDirection is RTL. These are built by the system during shaping where the
+// user provided string and text styling is converted to shaped paragraphs.
+struct Paragraph
+{
+ SimpleArray<GlyphRun> runs;
+ TextDirection baseDirection;
+};
+
+// An abstraction for interfacing with an individual font.
+class Font : public RefCnt<Font>
+{
+public:
+ virtual ~Font() {}
+
+ struct LineMetrics
+ {
+ float ascent, descent;
+ };
+
+ const LineMetrics& lineMetrics() const { return m_LineMetrics; }
+
+ // This is experimental
+ // -- may only be needed by Editor
+ // -- so it may be removed from here later
+ //
+ struct Axis
+ {
+ uint32_t tag;
+ float min;
+ float def; // default value
+ float max;
+ };
+
+ // Returns the canonical set of Axes for this font. Use this to know
+ // what variations are possible. If you want to know the specific
+ // coordinate within that variations space for *this* font, call
+ // getCoords().
+ //
+ virtual std::vector<Axis> getAxes() const = 0;
+
+ struct Coord
+ {
+ uint32_t axis;
+ float value;
+ };
+
+ // Returns the specific coords in variation space for this font.
+ // If you want to have a description of the entire variation space,
+ // call getAxes().
+ //
+ virtual std::vector<Coord> getCoords() const = 0;
+
+ virtual rcp<Font> makeAtCoords(Span<const Coord>) const = 0;
+
+ rcp<Font> makeAtCoord(Coord c) { return this->makeAtCoords(Span<const Coord>(&c, 1)); }
+
+ // Returns a 1-point path for this glyph. It will be positioned
+ // relative to (0,0) with the typographic baseline at y = 0.
+ //
+ virtual RawPath getPath(GlyphID) const = 0;
+
+ SimpleArray<Paragraph> shapeText(Span<const Unichar> text, Span<const TextRun> runs) const;
+
+protected:
+ Font(const LineMetrics& lm) : m_LineMetrics(lm) {}
+
+ virtual SimpleArray<Paragraph> onShapeText(Span<const Unichar> text,
+ Span<const TextRun> runs) const = 0;
+
+private:
+ const LineMetrics m_LineMetrics;
+};
+
+// A user defined styling guide for a set of unicode codepoints within a larger text string.
+struct TextRun
+{
+ rcp<Font> font;
+ float size;
+ uint32_t unicharCount;
+ uint32_t script;
+ uint16_t styleId;
+ TextDirection dir;
+};
+
+// The corresponding system generated run for the user provided TextRuns. GlyphRuns may not match
+// TextRuns if the system needs to split the run (for fallback fonts) or if codepoints get
+// ligated/shaped to a single glyph.
+struct GlyphRun
+{
+ GlyphRun(size_t glyphCount = 0) :
+ glyphs(glyphCount), textIndices(glyphCount), advances(glyphCount), xpos(glyphCount + 1)
+ {}
+
+ GlyphRun(SimpleArray<GlyphID> glyphIds,
+ SimpleArray<uint32_t> offsets,
+ SimpleArray<float> ws,
+ SimpleArray<float> xs) :
+ glyphs(glyphIds), textIndices(offsets), advances(ws), xpos(xs)
+ {}
+
+ rcp<Font> font;
+ float size;
+
+ // List of glyphs, represented by font specific glyph ids. Length is equal to number of glyphs
+ // in the run.
+ SimpleArray<GlyphID> glyphs;
+
+ // Index in the unicode text array representing the text displayed in this run. Because each
+ // glyph can be composed of multiple unicode values, this index points to the first index in the
+ // unicode text. Length is equal to number of glyphs in the run.
+ SimpleArray<uint32_t> textIndices;
+
+ // X position of each glyph in visual order (xpos is in logical/memory order).
+ SimpleArray<float> advances;
+
+ // X position of each glyph, with an extra value at the end for the right most extent of the
+ // last glyph.
+ SimpleArray<float> xpos;
+
+ // List of possible indices to line break at. Has a stride of 2 uint32_ts where each pair marks
+ // the start and end of a word, with the exception of a return character (forced linebreak)
+ // which is represented as a 0 length word (where start/end index is the same).
+ SimpleArray<uint32_t> breaks;
+
+ // The unique identifier for the styling (fill/stroke colors, anything not determined by the
+ // font or font size) applied to this run.
+ uint16_t styleId;
+
+ // The text direction (LTR = 0/RTL = 1)
+ TextDirection dir;
+};
+
+} // namespace rive
+#endif
diff --git a/include/rive/text/font_hb.hpp b/include/rive/text/font_hb.hpp
new file mode 100644
index 0000000..d4c290f
--- /dev/null
+++ b/include/rive/text/font_hb.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_FONT_HB_HPP_
+#define _RIVE_FONT_HB_HPP_
+
+#include "rive/factory.hpp"
+#include "rive/text.hpp"
+
+struct hb_font_t;
+struct hb_draw_funcs_t;
+
+class HBFont : public rive::Font
+{
+ hb_draw_funcs_t* m_DrawFuncs;
+
+public:
+ hb_font_t* m_Font;
+
+ // We assume ownership of font!
+ HBFont(hb_font_t* font);
+ ~HBFont() override;
+
+ std::vector<Axis> getAxes() const override;
+ std::vector<Coord> getCoords() const override;
+ rive::rcp<rive::Font> makeAtCoords(rive::Span<const Coord>) const override;
+ rive::RawPath getPath(rive::GlyphID) const override;
+ rive::SimpleArray<rive::Paragraph> onShapeText(rive::Span<const rive::Unichar>,
+ rive::Span<const rive::TextRun>) const override;
+
+ static rive::rcp<rive::Font> Decode(rive::Span<const uint8_t>);
+
+ // If the platform can supply fallback font(s), set this function pointer.
+ // It will be called with a span of unichars, and the platform attempts to
+ // return a font that can draw (at least some of) them. If no font is available
+ // just return nullptr.
+
+ using FallbackProc = rive::rcp<rive::Font> (*)(rive::Span<const rive::Unichar>);
+
+ static FallbackProc gFallbackProc;
+};
+
+#endif
diff --git a/include/rive/text/line_breaker.hpp b/include/rive/text/line_breaker.hpp
deleted file mode 100644
index 827873d..0000000
--- a/include/rive/text/line_breaker.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#ifndef _RIVE_RENDER_GLYPH_LINE_H_
-#define _RIVE_RENDER_GLYPH_LINE_H_
-
-#include "rive/render_text.hpp"
-
-namespace rive
-{
-
-enum class RenderTextAlign : uint8_t
-{
- left = 0,
- right = 1,
- center = 2
-};
-
-struct RenderGlyphLine
-{
- uint32_t startRun;
- uint32_t startIndex;
- uint32_t endRun;
- uint32_t endIndex;
- float startX;
- float top = 0, baseline = 0, bottom = 0;
-
- bool operator==(const RenderGlyphLine& o) const
- {
- return startRun == o.startRun && startIndex == o.startIndex && endRun == o.endRun &&
- endIndex == o.endIndex;
- }
-
- RenderGlyphLine() : startRun(0), startIndex(0), endRun(0), endIndex(0), startX(0.0f) {}
- RenderGlyphLine(uint32_t run, uint32_t index) :
- startRun(run), startIndex(index), endRun(run), endIndex(index), startX(0.0f)
- {}
-
- bool empty() const { return startRun == endRun && startIndex == endIndex; }
- static std::vector<RenderGlyphLine> BreakLines(Span<const RenderGlyphRun> runs,
- float width,
- RenderTextAlign align);
-
- // Compute values for top/baseline/bottom per line
- static void ComputeLineSpacing(rive::Span<RenderGlyphLine>,
- rive::Span<const RenderGlyphRun>,
- float width,
- RenderTextAlign align);
-};
-
-} // namespace rive
-
-#endif
diff --git a/include/rive/text/renderfont_hb.hpp b/include/rive/text/renderfont_hb.hpp
deleted file mode 100644
index 865afc2..0000000
--- a/include/rive/text/renderfont_hb.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#ifndef _RIVE_RENDERFONT_HB_HPP_
-#define _RIVE_RENDERFONT_HB_HPP_
-
-#include "rive/factory.hpp"
-#include "rive/render_text.hpp"
-
-struct hb_font_t;
-struct hb_draw_funcs_t;
-
-class HBRenderFont : public rive::RenderFont
-{
- hb_draw_funcs_t* m_DrawFuncs;
-
-public:
- hb_font_t* m_Font;
-
- // We assume ownership of font!
- HBRenderFont(hb_font_t* font);
- ~HBRenderFont() override;
-
- std::vector<Axis> getAxes() const override;
- std::vector<Coord> getCoords() const override;
- rive::rcp<rive::RenderFont> makeAtCoords(rive::Span<const Coord>) const override;
- rive::RawPath getPath(rive::GlyphID) const override;
- rive::SimpleArray<rive::RenderGlyphRun>
- onShapeText(rive::Span<const rive::Unichar>,
- rive::Span<const rive::RenderTextRun>) const override;
-
- static rive::rcp<rive::RenderFont> Decode(rive::Span<const uint8_t>);
-
- // If the platform can supply fallback font(s), set this function pointer.
- // It will be called with a span of unichars, and the platform attempts to
- // return a font that can draw (at least some of) them. If no font is available
- // just return nullptr.
-
- using FallbackProc = rive::rcp<rive::RenderFont> (*)(rive::Span<const rive::Unichar>);
-
- static FallbackProc gFallbackProc;
-};
-
-#endif
diff --git a/include/utils/rive_utf.hpp b/include/utils/rive_utf.hpp
index 780292f..e5a79aa 100644
--- a/include/utils/rive_utf.hpp
+++ b/include/utils/rive_utf.hpp
@@ -5,7 +5,7 @@
#ifndef _RIVE_UTF_HPP_
#define _RIVE_UTF_HPP_
-#include "rive/render_text.hpp"
+#include "rive/text.hpp"
namespace rive
{
diff --git a/skia/renderer/build.sh b/skia/renderer/build.sh
index 5e51dae..4683ea1 100755
--- a/skia/renderer/build.sh
+++ b/skia/renderer/build.sh
@@ -4,12 +4,6 @@
export SKIA_DIR="skia"
source ../../dependencies/config_directories.sh
-if [[ ! -d "$DEPENDENCIES/harfbuzz" ]]; then
- pushd $DEPENDENCIES_SCRIPTS
- ./get_harfbuzz.sh
- popd
-fi
-
# build main rive
cd ../..
./build.sh "$@"
@@ -48,7 +42,7 @@
else
build() {
echo "Building Rive Renderer for platform=$platform option=$OPTION"
- PREMAKE="premake5 gmake2 $1"
+ PREMAKE="premake5 --scripts=../../../build gmake2 $1"
eval "$PREMAKE"
if [ "$OPTION" = "clean" ]; then
make clean
diff --git a/skia/renderer/include/renderfont_coretext.hpp b/skia/renderer/include/renderfont_coretext.hpp
deleted file mode 100644
index de13daf..0000000
--- a/skia/renderer/include/renderfont_coretext.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#ifndef _RIVE_RENDERFONT_CORETEXT_HPP_
-#define _RIVE_RENDERFONT_CORETEXT_HPP_
-
-#include "rive/factory.hpp"
-#include "rive/render_text.hpp"
-
-#if defined(RIVE_BUILD_FOR_OSX)
-#include <ApplicationServices/ApplicationServices.h>
-#elif defined(RIVE_BUILD_FOR_IOS)
-#include <CoreText/CoreText.h>
-#endif
-
-class CoreTextRenderFont : public rive::RenderFont
-{
-public:
- CTFontRef m_font;
- const std::vector<Axis> m_axes;
- const std::vector<Coord> m_coords;
-
- // We assume ownership of font!
- CoreTextRenderFont(CTFontRef, std::vector<Axis>);
- ~CoreTextRenderFont() override;
-
- std::vector<Axis> getAxes() const override { return m_axes; }
- std::vector<Coord> getCoords() const override { return m_coords; }
- rive::rcp<rive::RenderFont> makeAtCoords(rive::Span<const Coord>) const override;
- rive::RawPath getPath(rive::GlyphID) const override;
- rive::SimpleArray<rive::RenderGlyphRun>
- onShapeText(rive::Span<const rive::Unichar>,
- rive::Span<const rive::RenderTextRun>) const override;
-
- static rive::rcp<rive::RenderFont> Decode(rive::Span<const uint8_t>);
- static rive::rcp<rive::RenderFont> FromCT(CTFontRef);
-};
-
-#endif
diff --git a/skia/renderer/src/renderfont_coretext.cpp b/skia/renderer/src/renderfont_coretext.cpp
deleted file mode 100644
index 36771da..0000000
--- a/skia/renderer/src/renderfont_coretext.cpp
+++ /dev/null
@@ -1,372 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#include "rive/rive_types.hpp"
-#include "utils/rive_utf.hpp"
-
-#if defined(RIVE_BUILD_FOR_APPLE) && defined(WITH_RIVE_TEXT)
-#include "renderfont_coretext.hpp"
-#include "mac_utils.hpp"
-
-#include "rive/factory.hpp"
-#include "rive/render_text.hpp"
-#include "rive/core/type_conversions.hpp"
-
-#if defined(RIVE_BUILD_FOR_OSX)
-#include <ApplicationServices/ApplicationServices.h>
-#elif defined(RIVE_BUILD_FOR_IOS)
-#include <CoreText/CoreText.h>
-#include <CoreText/CTFontManager.h>
-#include <CoreGraphics/CoreGraphics.h>
-#include <CoreFoundation/CoreFoundation.h>
-#endif
-
-constexpr int kStdScale = 2048;
-constexpr float gInvScale = 1.0f / kStdScale;
-
-static std::vector<rive::RenderFont::Axis> compute_axes(CTFontRef font)
-{
- std::vector<rive::RenderFont::Axis> axes;
-
- AutoCF array = CTFontCopyVariationAxes(font);
- if (auto count = array.get() ? CFArrayGetCount(array.get()) : 0)
- {
- axes.reserve(count);
-
- for (auto i = 0; i < count; ++i)
- {
- auto axis = (CFDictionaryRef)CFArrayGetValueAtIndex(array, i);
-
- auto tag = find_u32(axis, kCTFontVariationAxisIdentifierKey);
- auto min = find_float(axis, kCTFontVariationAxisMinimumValueKey);
- auto def = find_float(axis, kCTFontVariationAxisDefaultValueKey);
- auto max = find_float(axis, kCTFontVariationAxisMaximumValueKey);
- // printf("%08X %g %g %g\n", tag, min, def, max);
-
- axes.push_back({tag, min, def, max});
- }
- }
- return axes;
-}
-
-static std::vector<rive::RenderFont::Coord> compute_coords(CTFontRef font)
-{
- std::vector<rive::RenderFont::Coord> coords(0);
- AutoCF dict = CTFontCopyVariation(font);
- if (dict)
- {
- int count = CFDictionaryGetCount(dict);
- if (count > 0)
- {
- coords.resize(count);
-
- AutoSTArray<100, const void*> ptrs(count * 2);
- const void** keys = &ptrs[0];
- const void** values = &ptrs[count];
- CFDictionaryGetKeysAndValues(dict, keys, values);
- for (int i = 0; i < count; ++i)
- {
- uint32_t tag = number_as_u32((CFNumberRef)keys[i]);
- float value = number_as_float((CFNumberRef)values[i]);
- // printf("[%d] %08X %s %g\n", i, tag, tag2str(tag).c_str(), value);
- coords[i] = {tag, value};
- }
- }
- }
- return coords;
-}
-
-static rive::RenderFont::LineMetrics make_lmx(CTFontRef font)
-{
- return {
- (float)-CTFontGetAscent(font) * gInvScale,
- (float)CTFontGetDescent(font) * gInvScale,
- };
-}
-
-CoreTextRenderFont::CoreTextRenderFont(CTFontRef font, std::vector<rive::RenderFont::Axis> axes) :
- rive::RenderFont(make_lmx(font)),
- m_font(font), // we take ownership of font
- m_axes(std::move(axes)),
- m_coords(compute_coords(font))
-{}
-
-CoreTextRenderFont::~CoreTextRenderFont() { CFRelease(m_font); }
-
-rive::rcp<rive::RenderFont> CoreTextRenderFont::makeAtCoords(rive::Span<const Coord> coords) const
-{
- AutoCF vars = CFDictionaryCreateMutable(kCFAllocatorDefault,
- coords.size(),
- &kCFTypeDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks);
- for (const auto& c : coords)
- {
- AutoCF tagNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &c.axis);
- AutoCF valueNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &c.value);
- CFDictionaryAddValue(vars.get(), tagNum.get(), valueNum.get());
- }
-
- AutoCF attrs = CFDictionaryCreateMutable(kCFAllocatorDefault,
- 1,
- &kCFTypeDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks);
- CFDictionarySetValue(attrs.get(), kCTFontVariationAttribute, vars.get());
-
- AutoCF desc = (CTFontDescriptorRef)CTFontDescriptorCreateWithAttributes(attrs.get());
-
- auto font = CTFontCreateCopyWithAttributes(m_font, 0, nullptr, desc.get());
-
- return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(font, compute_axes(font)));
-}
-
-static CTFontRef font_from_run(CTRunRef run)
-{
- auto attr = CTRunGetAttributes(run);
- assert(attr);
- CTFontRef ct = (CTFontRef)CFDictionaryGetValue(attr, kCTFontAttributeName);
- assert(ct);
- return ct;
-}
-
-static rive::rcp<rive::RenderFont> convert_to_renderfont(CTFontRef ct,
- rive::rcp<rive::RenderFont> rf)
-{
- auto ctrf = static_cast<CoreTextRenderFont*>(rf.get());
- if (ctrf->m_font == ct)
- {
- return rf;
- }
- CFRetain(ct);
- return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(ct, compute_axes(ct)));
-}
-
-static void apply_element(void* ctx, const CGPathElement* element)
-{
- auto path = (rive::RawPath*)ctx;
- const CGPoint* points = element->points;
-
- switch (element->type)
- {
- case kCGPathElementMoveToPoint:
- path->moveTo(points[0].x, points[0].y);
- break;
-
- case kCGPathElementAddLineToPoint:
- path->lineTo(points[0].x, points[0].y);
- break;
-
- case kCGPathElementAddQuadCurveToPoint:
- path->quadTo(points[0].x, points[0].y, points[1].x, points[1].y);
- break;
-
- case kCGPathElementAddCurveToPoint:
- path->cubicTo(points[0].x,
- points[0].y,
- points[1].x,
- points[1].y,
- points[2].x,
- points[2].y);
- break;
-
- case kCGPathElementCloseSubpath:
- path->close();
- break;
-
- default:
- assert(false);
- break;
- }
-}
-
-rive::RawPath CoreTextRenderFont::getPath(rive::GlyphID glyph) const
-{
- rive::RawPath rpath;
-
- AutoCF cgPath = CTFontCreatePathForGlyph(m_font, glyph, nullptr);
- if (!cgPath)
- {
- return rpath;
- }
-
- CGPathApply(cgPath.get(), &rpath, apply_element);
- rpath.transformInPlace(rive::Mat2D::fromScale(gInvScale, -gInvScale));
- return rpath;
-}
-
-////////////////////////////////////////////////////////////////////////////////////
-
-struct AutoUTF16
-{
- std::vector<uint16_t> array;
-
- AutoUTF16(const rive::Unichar uni[], int count)
- {
- array.reserve(count);
- for (int i = 0; i < count; ++i)
- {
- uint16_t tmp[2];
- int n = rive::UTF::ToUTF16(uni[i], tmp);
-
- for (int i = 0; i < n; ++i)
- {
- array.push_back(tmp[i]);
- }
- }
- }
-};
-
-static rive::RenderGlyphRun add_run(CTRunRef run, uint32_t textStart, float textSize, float& startX)
-{
- if (auto count = CTRunGetGlyphCount(run))
- {
- const float scale = textSize * gInvScale;
-
- rive::RenderGlyphRun gr(count);
- gr.size = textSize;
-
- CTRunGetGlyphs(run, {0, count}, gr.glyphs.data());
-
- AutoSTArray<1024, CFIndex> indices(count);
- AutoSTArray<1024, CGSize> advances(count);
-
- CTRunGetAdvances(run, {0, count}, advances.data());
- CTRunGetStringIndices(run, {0, count}, indices.data());
-
- for (CFIndex i = 0; i < count; ++i)
- {
- gr.xpos[i] = startX;
- gr.textIndices[i] = textStart + indices[i]; // utf16 offsets, will fix-up later
- startX += advances[i].width * scale;
- }
- gr.xpos[count] = startX;
- return gr;
- }
- return rive::RenderGlyphRun();
-}
-
-rive::SimpleArray<rive::RenderGlyphRun>
-CoreTextRenderFont::onShapeText(rive::Span<const rive::Unichar> text,
- rive::Span<const rive::RenderTextRun> truns) const
-{
- rive::SimpleArrayBuilder<rive::RenderGlyphRun> gruns(truns.size());
-
- uint32_t textIndex = 0;
- float startX = 0;
- for (const auto& tr : truns)
- {
- CTFontRef font = ((CoreTextRenderFont*)tr.font.get())->m_font;
-
- AutoUTF16 utf16(&text[textIndex], tr.unicharCount);
- const bool hasSurrogates = utf16.array.size() != tr.unicharCount;
- assert(!hasSurrogates);
-
- AutoCF string = CFStringCreateWithCharactersNoCopy(nullptr,
- utf16.array.data(),
- utf16.array.size(),
- kCFAllocatorNull);
-
- AutoCF attr = CFDictionaryCreateMutable(kCFAllocatorDefault,
- 0,
- &kCFTypeDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks);
- CFDictionaryAddValue(attr.get(), kCTFontAttributeName, font);
-
- AutoCF attrString = CFAttributedStringCreate(kCFAllocatorDefault, string.get(), attr.get());
-
- AutoCF typesetter = CTTypesetterCreateWithAttributedString(attrString.get());
-
- AutoCF line = CTTypesetterCreateLine(typesetter.get(), {0, tr.unicharCount});
-
- CFArrayRef run_array = CTLineGetGlyphRuns(line.get());
- CFIndex runCount = CFArrayGetCount(run_array);
- for (CFIndex i = 0; i < runCount; ++i)
- {
- CTRunRef runref = (CTRunRef)CFArrayGetValueAtIndex(run_array, i);
- rive::RenderGlyphRun grun = add_run(runref, textIndex, tr.size, startX);
- if (grun.glyphs.size() > 0)
- {
- auto ct = font_from_run(runref);
- grun.font = convert_to_renderfont(ct, tr.font);
- grun.size = tr.size;
- gruns.add(std::move(grun));
- }
- }
- textIndex += tr.unicharCount;
- }
-
- return std::move(gruns);
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////
-
-rive::rcp<rive::RenderFont> CoreTextRenderFont::FromCT(CTFontRef ctfont)
-{
- if (!ctfont)
- {
- return nullptr;
- }
-
- // We always want the ctfont at our magic size
- if (CTFontGetSize(ctfont) != kStdScale)
- {
- ctfont = CTFontCreateCopyWithAttributes(ctfont, kStdScale, nullptr, nullptr);
- }
- else
- {
- CFRetain(ctfont);
- }
-
- // Apple may have secretly set the opsz axis based on the size. We want to undo this
- // since our stdsize isn't really the size we'll show it at.
- auto axes = compute_axes(ctfont);
- if (axes.size() > 0)
- {
- constexpr uint32_t kOPSZ = make_tag('o', 'p', 's', 'z');
- for (const auto& ax : axes)
- {
- if (ax.tag == kOPSZ)
- {
- auto xform = CGAffineTransformMakeScale(kStdScale / ax.def, kStdScale / ax.def);
- // Recreate the font at this size, but with a balancing transform,
- // so we get the 'default' shapes w.r.t. the opsz axis
- auto newfont = CTFontCreateCopyWithAttributes(ctfont, ax.def, &xform, nullptr);
- CFRelease(ctfont);
- ctfont = newfont;
- break;
- }
- }
- }
-
- return rive::rcp<rive::RenderFont>(new CoreTextRenderFont(ctfont, std::move(axes)));
-}
-
-rive::rcp<rive::RenderFont> CoreTextRenderFont::Decode(rive::Span<const uint8_t> span)
-{
- AutoCF data = CFDataCreate(nullptr, span.data(), span.size()); // makes a copy
- if (!data)
- {
- assert(false);
- return nullptr;
- }
-
- AutoCF desc = CTFontManagerCreateFontDescriptorFromData(data.get());
- if (!desc)
- {
- assert(false);
- return nullptr;
- }
-
- CTFontOptions options = kCTFontOptionsPreventAutoActivation;
-
- AutoCF ctfont =
- CTFontCreateWithFontDescriptorAndOptions(desc.get(), kStdScale, nullptr, options);
- if (!ctfont)
- {
- assert(false);
- return nullptr;
- }
- return FromCT(ctfont.get());
-}
-
-#endif
diff --git a/src/renderer.cpp b/src/renderer.cpp
index ebcabb1..5447c66 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -99,17 +99,15 @@
RenderPath::RenderPath() { Counter::update(Counter::kPath, 1); }
RenderPath::~RenderPath() { Counter::update(Counter::kPath, -1); }
-#include "rive/render_text.hpp"
+#include "rive/text.hpp"
-static bool isWhiteSpace(rive::Unichar c) { return c <= ' '; }
+static bool isWhiteSpace(Unichar c) { return c <= ' ' || c == 0x2028; }
-rive::SimpleArray<RenderGlyphRun>
-RenderFont::shapeText(rive::Span<const rive::Unichar> text,
- rive::Span<const rive::RenderTextRun> runs) const
+SimpleArray<Paragraph> Font::shapeText(Span<const Unichar> text, Span<const TextRun> runs) const
{
#ifdef DEBUG
size_t count = 0;
- for (const auto& tr : runs)
+ for (const TextRun& tr : runs)
{
assert(tr.unicharCount > 0);
count += tr.unicharCount;
@@ -117,39 +115,40 @@
assert(count <= text.size());
#endif
- auto gruns = onShapeText(text, runs);
+ SimpleArray<Paragraph> paragraphs = onShapeText(text, runs);
bool wantWhiteSpace = false;
-
- rive::RenderGlyphRun* lastRun = nullptr;
+ GlyphRun* lastRun = nullptr;
size_t reserveSize = text.size() / 4;
- rive::SimpleArrayBuilder<uint32_t> breakBuilder(reserveSize);
- for (auto& gr : gruns)
+ SimpleArrayBuilder<uint32_t> breakBuilder(reserveSize);
+ for (const Paragraph& para : paragraphs)
{
- if (lastRun != nullptr)
+ for (GlyphRun& gr : para.runs)
{
- lastRun->breaks = std::move(breakBuilder);
- // Reset the builder.
- breakBuilder = rive::SimpleArrayBuilder<uint32_t>(reserveSize);
- }
- uint32_t glyphIndex = 0;
- for (auto offset : gr.textIndices)
- {
-
- auto unicode = text[offset];
- if (unicode == '\n')
+ if (lastRun != nullptr)
{
- breakBuilder.add(glyphIndex);
- breakBuilder.add(glyphIndex);
+ lastRun->breaks = std::move(breakBuilder);
+ // Reset the builder.
+ breakBuilder = SimpleArrayBuilder<uint32_t>(reserveSize);
}
- if (wantWhiteSpace == isWhiteSpace(unicode))
+ uint32_t glyphIndex = 0;
+ for (uint32_t offset : gr.textIndices)
{
- breakBuilder.add(glyphIndex);
- wantWhiteSpace = !wantWhiteSpace;
+ Unichar unicode = text[offset];
+ if (unicode == '\n' || unicode == 0x2028)
+ {
+ breakBuilder.add(glyphIndex);
+ breakBuilder.add(glyphIndex);
+ }
+ if (wantWhiteSpace == isWhiteSpace(unicode))
+ {
+ breakBuilder.add(glyphIndex);
+ wantWhiteSpace = !wantWhiteSpace;
+ }
+ glyphIndex++;
}
- glyphIndex++;
- }
- lastRun = &gr;
+ lastRun = &gr;
+ }
}
if (lastRun != nullptr)
{
@@ -161,12 +160,15 @@
}
#ifdef DEBUG
- for (const auto& gr : gruns)
+ for (const Paragraph& para : paragraphs)
{
- assert(gr.glyphs.size() > 0);
- assert(gr.glyphs.size() == gr.textIndices.size());
- assert(gr.glyphs.size() + 1 == gr.xpos.size());
+ for (const GlyphRun& gr : para.runs)
+ {
+ assert(gr.glyphs.size() > 0);
+ assert(gr.glyphs.size() == gr.textIndices.size());
+ assert(gr.glyphs.size() + 1 == gr.xpos.size());
+ }
}
#endif
- return gruns;
+ return paragraphs;
}
diff --git a/src/text/font_hb.cpp b/src/text/font_hb.cpp
new file mode 100644
index 0000000..d1bea79
--- /dev/null
+++ b/src/text/font_hb.cpp
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#include "rive/text.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);
+}
+
+std::vector<rive::Font::Axis> HBFont::getAxes() const
+{
+ auto face = hb_font_get_face(m_Font);
+ std::vector<rive::Font::Axis> axes;
+
+ const int count = hb_ot_var_get_axis_count(face);
+ if (count > 0)
+ {
+ axes.resize(count);
+
+ hb_ot_var_axis_info_t info;
+ for (int i = 0; i < count; ++i)
+ {
+ unsigned n = 1;
+ hb_ot_var_get_axis_infos(face, i, &n, &info);
+ assert(n == 1);
+ axes[i] = {info.tag, info.min_value, info.default_value, info.max_value};
+ // printf("[%d] %08X %g %g %g\n", i, info.tag, info.min_value, info.default_value,
+ // info.max_value);
+ }
+ }
+ return axes;
+}
+
+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_SCRIPT_ARABIC);
+ hb_buffer_set_language(buf, hb_language_from_string("fa", -1));
+
+ 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.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));
+ subset.font = std::move(orig.font);
+ subset.size = orig.size;
+ 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,
+ };
+ gruns.add(shape_run(&text[textStart], tr, textStart));
+ }
+ 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 = {
+ .font = tr.font,
+ .size = tr.size,
+ .unicharCount = tr.unicharCount - runTextIndex,
+ .script = (uint32_t)lastScript,
+ .styleId = tr.styleId,
+ .dir = 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 splitRun = {
+ .font = back.font,
+ .size = back.size,
+ .unicharCount = tr.unicharCount - runTextIndex,
+ .script = (uint32_t)script,
+ .styleId = back.styleId,
+ .dir = lastLevel & 1 ? rive::TextDirection::rtl : rive::TextDirection::ltr,
+ };
+ runStartTextIndex = textIndex;
+ bidiRuns.push_back(splitRun);
+ }
+ 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)
+ {
+ 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
+ {
+ 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;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/text/line_breaker.cpp b/src/text/line_breaker.cpp
index 190cac0..996d6dd 100644
--- a/src/text/line_breaker.cpp
+++ b/src/text/line_breaker.cpp
@@ -2,40 +2,36 @@
* Copyright 2022 Rive
*/
-#include "rive/text/line_breaker.hpp"
+#include "rive/text.hpp"
#include <limits>
+#include <algorithm>
using namespace rive;
static bool autowidth(float width) { return width < 0.0f; }
-void RenderGlyphLine::ComputeLineSpacing(Span<RenderGlyphLine> lines,
- Span<const RenderGlyphRun> runs,
- float width,
- RenderTextAlign align)
-{
+float GlyphLine::ComputeMaxWidth(Span<GlyphLine> lines, Span<const GlyphRun> runs)
+{
float maxLineWidth = 0.0f;
- if (autowidth(width))
+ for (auto& line : lines)
{
- for (auto& line : lines)
- {
- auto lineWidth =
- runs[line.endRun].xpos[line.endIndex] - runs[line.startRun].xpos[line.startIndex];
- if (lineWidth > maxLineWidth)
- {
- maxLineWidth = lineWidth;
- }
- }
+ maxLineWidth = std::max(maxLineWidth,
+ runs[line.endRunIndex].xpos[line.endGlyphIndex] -
+ runs[line.startRunIndex].xpos[line.startGlyphIndex]);
}
- else
- {
- maxLineWidth = width;
- }
+ return maxLineWidth;
+}
+
+void GlyphLine::ComputeLineSpacing(Span<GlyphLine> lines,
+ Span<const GlyphRun> runs,
+ float width,
+ TextAlign align)
+{
float Y = 0; // top of our frame
for (auto& line : lines)
{
float asc = 0;
float des = 0;
- for (int i = line.startRun; i <= line.endRun; ++i)
+ for (int i = line.startRunIndex; i <= line.endRunIndex; ++i)
{
const auto& run = runs[i];
@@ -47,18 +43,18 @@
line.baseline = Y;
Y += des;
line.bottom = Y;
- auto lineWidth =
- runs[line.endRun].xpos[line.endIndex] - runs[line.startRun].xpos[line.startIndex];
+ auto lineWidth = runs[line.endRunIndex].xpos[line.endGlyphIndex] -
+ runs[line.startRunIndex].xpos[line.startGlyphIndex];
switch (align)
{
- case RenderTextAlign::right:
- line.startX = maxLineWidth - lineWidth;
+ case TextAlign::right:
+ line.startX = width - lineWidth;
break;
- case RenderTextAlign::left:
+ case TextAlign::left:
line.startX = 0;
break;
- case RenderTextAlign::center:
- line.startX = maxLineWidth / 2.0f - lineWidth / 2.0f;
+ case TextAlign::center:
+ line.startX = width / 2.0f - lineWidth / 2.0f;
break;
}
}
@@ -66,15 +62,14 @@
struct WordMarker
{
- const RenderGlyphRun* run;
+ const GlyphRun* run;
uint32_t index;
- bool next(Span<const RenderGlyphRun> runs)
+ bool next(Span<const GlyphRun> runs)
{
index += 2;
while (index >= run->breaks.size())
{
-
index -= run->breaks.size();
run++;
if (run == runs.end())
@@ -88,12 +83,12 @@
class RunIterator
{
- Span<const RenderGlyphRun> m_runs;
- const RenderGlyphRun* m_run;
+ Span<const GlyphRun> m_runs;
+ const GlyphRun* m_run;
uint32_t m_index;
public:
- RunIterator(Span<const RenderGlyphRun> runs, const RenderGlyphRun* run, uint32_t index) :
+ RunIterator(Span<const GlyphRun> runs, const GlyphRun* run, uint32_t index) :
m_runs(runs), m_run(run), m_index(index)
{}
@@ -147,20 +142,17 @@
float x() const { return m_run->xpos[m_index]; }
- const RenderGlyphRun* run() const { return m_run; }
+ const GlyphRun* run() const { return m_run; }
uint32_t index() const { return m_index; }
bool operator==(const RunIterator& o) const { return m_run == o.m_run && m_index == o.m_index; }
};
-std::vector<RenderGlyphLine> RenderGlyphLine::BreakLines(Span<const RenderGlyphRun> runs,
- float width,
- RenderTextAlign align)
+SimpleArray<GlyphLine> GlyphLine::BreakLines(Span<const GlyphRun> runs, float width)
{
-
float maxLineWidth = autowidth(width) ? std::numeric_limits<float>::max() : width;
- std::vector<RenderGlyphLine> lines;
+ SimpleArrayBuilder<GlyphLine> lines;
if (runs.empty())
{
@@ -171,19 +163,26 @@
bool advanceWord = false;
WordMarker start = {runs.begin(), 0};
- WordMarker end = {runs.begin(), 1};
+ WordMarker end = {runs.begin(), (uint32_t)-1};
+ if (!end.next(runs))
+ {
+ return lines;
+ }
- RenderGlyphLine line = RenderGlyphLine();
+ GlyphLine line = GlyphLine();
uint32_t breakIndex = end.run->breaks[end.index];
- uint32_t lastEndIndex = end.index;
+ const GlyphRun* breakRun = end.run;
+ uint32_t lastEndGlyphIndex = end.index;
uint32_t startBreakIndex = start.run->breaks[start.index];
+ const GlyphRun* startBreakRun = start.run;
+
float x = end.run->xpos[breakIndex];
while (true)
{
if (advanceWord)
{
- lastEndIndex = end.index;
+ lastEndGlyphIndex = end.index;
if (!start.next(runs))
{
@@ -197,26 +196,29 @@
advanceWord = false;
breakIndex = end.run->breaks[end.index];
+ breakRun = end.run;
startBreakIndex = start.run->breaks[start.index];
+ startBreakRun = start.run;
x = end.run->xpos[breakIndex];
}
- if (breakIndex != startBreakIndex && x > limit)
+ bool isForcedBreak = breakRun == startBreakRun && breakIndex == startBreakIndex;
+
+ if (!isForcedBreak && x > limit)
{
- uint32_t startRun = (uint32_t)(start.run - runs.begin());
+ uint32_t startRunIndex = (uint32_t)(start.run - runs.begin());
// A whole word overflowed, break until we can no longer break (or
// it fits).
- if (line.startRun == startRun && line.startIndex == startBreakIndex)
+ if (line.startRunIndex == startRunIndex && line.startGlyphIndex == startBreakIndex)
{
bool canBreakMore = true;
while (canBreakMore && x > limit)
{
RunIterator lineStart =
- RunIterator(runs, runs.begin() + line.startRun, line.startIndex);
+ RunIterator(runs, runs.begin() + line.startRunIndex, line.startGlyphIndex);
RunIterator lineEnd = RunIterator(runs, end.run, end.run->breaks[end.index]);
-
// Look for the next character that doesn't overflow.
while (true)
{
@@ -237,8 +239,8 @@
}
else
{
- line.endRun = (uint32_t)(lineEnd.run() - runs.begin());
- line.endIndex = lineEnd.index();
+ line.endRunIndex = (uint32_t)(lineEnd.run() - runs.begin());
+ line.endGlyphIndex = lineEnd.index();
}
break;
}
@@ -249,11 +251,10 @@
limit = lineEnd.x() + maxLineWidth;
if (!line.empty())
{
- lines.push_back(line);
+ lines.add(line);
}
// Setup the next line.
- line = RenderGlyphLine((uint32_t)(lineEnd.run() - runs.begin()),
- lineEnd.index());
+ line = GlyphLine((uint32_t)(lineEnd.run() - runs.begin()), lineEnd.index());
}
}
}
@@ -263,35 +264,34 @@
auto startX = start.run->xpos[start.run->breaks[start.index]];
limit = startX + maxLineWidth;
- if (!line.empty() || start.index - lastEndIndex > 1)
+ if (!line.empty() || start.index - lastEndGlyphIndex > 1)
{
- lines.push_back(line);
+ lines.add(line);
}
- line = RenderGlyphLine(startRun, startBreakIndex);
+ line = GlyphLine(startRunIndex, startBreakIndex);
}
}
else
{
- line.endRun = (uint32_t)(end.run - runs.begin());
- line.endIndex = end.run->breaks[end.index];
+ line.endRunIndex = (uint32_t)(end.run - runs.begin());
+ line.endGlyphIndex = end.run->breaks[end.index];
advanceWord = true;
// Forced BR.
- if (breakIndex == startBreakIndex)
+ if (isForcedBreak)
{
- lines.push_back(line);
- auto startX = start.run->xpos[start.run->breaks[start.index]];
+ lines.add(line);
+ auto startX = start.run->xpos[start.run->breaks[start.index] + 1];
limit = startX + maxLineWidth;
- line = RenderGlyphLine((uint32_t)(start.run - runs.begin()), startBreakIndex + 1);
+ line = GlyphLine((uint32_t)(start.run - runs.begin()), startBreakIndex + 1);
}
}
}
// Don't add a line that starts/ends at the same spot.
if (!line.empty())
{
- lines.push_back(line);
+ lines.add(line);
}
- ComputeLineSpacing(lines, runs, width, align);
return lines;
}
\ No newline at end of file
diff --git a/src/text/renderfont_hb.cpp b/src/text/renderfont_hb.cpp
deleted file mode 100644
index 5b536aa..0000000
--- a/src/text/renderfont_hb.cpp
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#include "rive/render_text.hpp"
-
-#ifdef WITH_RIVE_TEXT
-#include "rive/text/renderfont_hb.hpp"
-
-#include "rive/factory.hpp"
-#include "rive/renderer_utils.hpp"
-
-#include "hb.h"
-#include "hb-ot.h"
-
-// Initialized to null. Client can set this to a callback.
-HBRenderFont::FallbackProc HBRenderFont::gFallbackProc;
-
-rive::rcp<rive::RenderFont> HBRenderFont::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::RenderFont>(new HBRenderFont(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::RenderFont::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};
-}
-
-HBRenderFont::HBRenderFont(hb_font_t* font) :
- RenderFont(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);
-}
-
-HBRenderFont::~HBRenderFont()
-{
- hb_draw_funcs_destroy(m_DrawFuncs);
- hb_font_destroy(m_Font);
-}
-
-std::vector<rive::RenderFont::Axis> HBRenderFont::getAxes() const
-{
- auto face = hb_font_get_face(m_Font);
- std::vector<rive::RenderFont::Axis> axes;
-
- const int count = hb_ot_var_get_axis_count(face);
- if (count > 0)
- {
- axes.resize(count);
-
- hb_ot_var_axis_info_t info;
- for (int i = 0; i < count; ++i)
- {
- unsigned n = 1;
- hb_ot_var_get_axis_infos(face, i, &n, &info);
- assert(n == 1);
- axes[i] = {info.tag, info.min_value, info.default_value, info.max_value};
- // printf("[%d] %08X %g %g %g\n", i, info.tag, info.min_value, info.default_value,
- // info.max_value);
- }
- }
- return axes;
-}
-
-std::vector<rive::RenderFont::Coord> HBRenderFont::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::RenderFont::Coord> coords(length);
- for (unsigned i = 0; i < length; ++i)
- {
- coords[i] = {axes[i].tag, values[i]};
- }
- return coords;
-}
-
-rive::rcp<rive::RenderFont> HBRenderFont::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::RenderFont>(new HBRenderFont(font));
-}
-
-rive::RawPath HBRenderFont::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},
- {'kern', 1, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END},
-};
-constexpr int gNumFeatures = sizeof(gFeatures) / sizeof(gFeatures[0]);
-
-static rive::RenderGlyphRun shape_run(const rive::Unichar text[],
- const rive::RenderTextRun& 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, HB_DIRECTION_LTR);
- hb_buffer_set_script(buf, HB_SCRIPT_LATIN);
- hb_buffer_set_language(buf, hb_language_from_string("en", -1));
-
- auto hbfont = (HBRenderFont*)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::RenderGlyphRun gr(glyph_count);
- gr.font = tr.font;
- gr.size = tr.size;
-
- 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;
-
- gr.glyphs[i] = (uint16_t)glyph_info[i].codepoint;
- gr.textIndices[i] = textOffset + glyph_info[i].cluster;
- gr.xpos[i] = glyph_pos[i].x_advance * scale;
- }
- gr.xpos[glyph_count] = 0; // so the next run can line up snug
- hb_buffer_destroy(buf);
- return gr;
-}
-
-static rive::RenderGlyphRun extract_subset(const rive::RenderGlyphRun& orig,
- size_t start,
- size_t end)
-{
- auto count = end - start;
- rive::RenderGlyphRun subset(rive::SimpleArray<rive::GlyphID>(&orig.glyphs[start], count),
- rive::SimpleArray<uint32_t>(&orig.textIndices[start], count),
- rive::SimpleArray<float>(&orig.xpos[start], count));
- subset.font = std::move(orig.font);
- subset.size = orig.size;
- subset.xpos.back() = 0; // since we're now the end of a run
-
- return subset;
-}
-
-static void perform_fallback(rive::rcp<rive::RenderFont> fallbackFont,
- rive::SimpleArrayBuilder<rive::RenderGlyphRun>& gruns,
- const rive::Unichar text[],
- const rive::RenderGlyphRun& orig)
-{
- 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::RenderTextRun{fallbackFont, orig.size, textCount};
- gruns.add(shape_run(&text[textStart], tr, textStart));
- }
- else
- {
- while (endI < count && orig.glyphs[endI] != 0)
- {
- ++endI;
- }
- gruns.add(extract_subset(orig, startI, endI));
- }
- startI = endI;
- }
-}
-
-rive::SimpleArray<rive::RenderGlyphRun>
-HBRenderFont::onShapeText(rive::Span<const rive::Unichar> text,
- rive::Span<const rive::RenderTextRun> truns) const
-{
- rive::SimpleArrayBuilder<rive::RenderGlyphRun> gruns(truns.size());
-
- /////////////////
-
- uint32_t unicharIndex = 0;
- for (const auto& tr : truns)
- {
- 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)
- {
- 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);
- }
- else
- {
- gruns.add(std::move(gr)); // oh well, just keep the missing glyphs
- }
- }
- }
-
- // now turn the advances (widths) we stored in xpos[] into actual x-positions
- float pos = 0;
- for (auto& gr : gruns)
- {
- for (auto& xp : gr.xpos)
- {
- float adv = xp;
- xp = pos;
- pos += adv;
- }
- }
- return std::move(gruns);
-}
-
-#endif
\ No newline at end of file
diff --git a/tess/build/macosx/build_tess.sh b/tess/build/macosx/build_tess.sh
index 0054e3d..13df959 100755
--- a/tess/build/macosx/build_tess.sh
+++ b/tess/build/macosx/build_tess.sh
@@ -51,7 +51,7 @@
fi
done
-$PREMAKE --file=./premake5_tess.lua gmake2 --graphics=$GRAPHICS --with_rive_tools
+$PREMAKE --scripts=../../build --file=./premake5_tess.lua gmake2 --graphics=$GRAPHICS --with_rive_tools
for var in "$@"; do
if [[ $var = "clean" ]]; then
diff --git a/test/assets/IBMPlexSansArabic-Regular.ttf b/test/assets/IBMPlexSansArabic-Regular.ttf
new file mode 100644
index 0000000..da3eba1
--- /dev/null
+++ b/test/assets/IBMPlexSansArabic-Regular.ttf
Binary files differ
diff --git a/test/assets/NotoSansArabic-VariableFont_wdth,wght.ttf b/test/assets/NotoSansArabic-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..0356020
--- /dev/null
+++ b/test/assets/NotoSansArabic-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/test/line_break_test.cpp b/test/line_break_test.cpp
index da18244..ecde0ac 100644
--- a/test/line_break_test.cpp
+++ b/test/line_break_test.cpp
@@ -4,17 +4,16 @@
#include <rive/simple_array.hpp>
#include <catch.hpp>
-#include <rive/render_text.hpp>
-#include <rive/text/renderfont_hb.hpp>
-#include <rive/text/line_breaker.hpp>
+#include <rive/text.hpp>
+#include <rive/text/font_hb.hpp>
#include "utils/rive_utf.hpp"
using namespace rive;
-static rive::RenderTextRun append(std::vector<rive::Unichar>* unichars,
- rive::rcp<rive::RenderFont> font,
- float size,
- const char text[])
+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;
@@ -23,10 +22,10 @@
unichars->push_back(rive::UTF::NextUTF8(&ptr));
n += 1;
}
- return {std::move(font), size, n};
+ return {std::move(font), size, n, 0};
}
-static rcp<RenderFont> loadFont(const char* filename)
+static rcp<Font> loadFont(const char* filename)
{
FILE* fp = fopen("../../test/assets/RobotoFlex.ttf", "rb");
REQUIRE(fp != nullptr);
@@ -38,7 +37,7 @@
REQUIRE(fread(bytes.data(), 1, length, fp) == length);
fclose(fp);
- return HBRenderFont::Decode(bytes);
+ return HBFont::Decode(bytes);
}
TEST_CASE("line breaker separates words", "[line break]")
@@ -47,13 +46,15 @@
REQUIRE(font != nullptr);
// one two⏎ three
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
truns.push_back(append(&unichars, font, 32.0f, "one two three"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 1);
- auto run = shape.front();
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 1);
+ const auto& run = paragraph.runs.front();
REQUIRE(run.breaks.size() == 6);
REQUIRE(run.breaks[0] == 0);
REQUIRE(run.breaks[1] == 3);
@@ -68,15 +69,18 @@
auto font = loadFont("../../test/assets/RobotoFlex.ttf");
REQUIRE(font != nullptr);
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
truns.push_back(append(&unichars, font, 32.0f, "one two thr"));
truns.push_back(append(&unichars, font, 60.0f, "ee four"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 2);
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+
+ REQUIRE(paragraph.runs.size() == 2);
{
- auto run = shape.front();
+ const auto& run = paragraph.runs.front();
REQUIRE(run.breaks.size() == 5);
REQUIRE(run.breaks[0] == 0);
REQUIRE(run.breaks[1] == 3);
@@ -85,7 +89,7 @@
REQUIRE(run.breaks[4] == 8);
}
{
- auto run = shape.back();
+ const auto& run = paragraph.runs.back();
REQUIRE(run.breaks.size() == 3);
REQUIRE(run.breaks[0] == 2);
REQUIRE(run.breaks[1] == 3);
@@ -98,15 +102,16 @@
auto font = loadFont("../../test/assets/RobotoFlex.ttf");
REQUIRE(font != nullptr);
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
truns.push_back(append(&unichars, font, 32.0f, "one two thr"));
- truns.push_back(append(&unichars, font, 60.0f, "ee\n four"));
+ truns.push_back(append(&unichars, font, 60.0f, "ee\u2028 four"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 2);
+ auto paragraphs = font->shapeText(unichars, truns);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 2);
{
- auto run = shape.front();
+ const auto& run = paragraph.runs.front();
REQUIRE(run.breaks.size() == 5);
REQUIRE(run.breaks[0] == 0);
REQUIRE(run.breaks[1] == 3);
@@ -115,7 +120,7 @@
REQUIRE(run.breaks[4] == 8);
}
{
- auto run = shape.back();
+ const auto& run = paragraph.runs.back();
REQUIRE(run.breaks.size() == 5);
REQUIRE(run.breaks[0] == 2);
REQUIRE(run.breaks[1] == 2);
@@ -131,42 +136,43 @@
REQUIRE(font != nullptr);
// one two⏎ three
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
truns.push_back(append(&unichars, font, 32.0f, "one two three"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 1);
- auto run = shape.front();
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 1);
// at 194 everything fits in one line
{
- auto lines = RenderGlyphLine::BreakLines(shape, 194.0f, RenderTextAlign::left);
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 194.0f);
REQUIRE(lines.size() == 1);
auto line = lines.back();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 0);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 13);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 0);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 13);
}
// at 191 "three" should pop to second line
{
- auto lines = RenderGlyphLine::BreakLines(shape, 191.0f, RenderTextAlign::left);
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 191.0f);
REQUIRE(lines.size() == 2);
{
auto line = lines.front();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 0);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 7);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 0);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 7);
}
{
auto line = lines.back();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 8);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 13);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 8);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 13);
}
}
}
@@ -177,51 +183,52 @@
REQUIRE(font != nullptr);
// one two⏎ three
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
truns.push_back(append(&unichars, font, 32.0f, "ab"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 1);
- auto run = shape.front();
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 1);
{
- auto lines = RenderGlyphLine::BreakLines(shape, 17.0f, RenderTextAlign::left);
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 17.0f);
REQUIRE(lines.size() == 2);
{
auto line = lines.front();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 0);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 1);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 0);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 1);
}
{
auto line = lines.back();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 1);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 2);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 1);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 2);
}
}
// Test that it also handles 0 width.
{
- auto lines = RenderGlyphLine::BreakLines(shape, 0.0f, RenderTextAlign::left);
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 0.0f);
REQUIRE(lines.size() == 2);
{
auto line = lines.front();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 0);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 1);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 0);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 1);
}
{
auto line = lines.back();
- REQUIRE(line.startRun == 0);
- REQUIRE(line.startIndex == 1);
- REQUIRE(line.endRun == 0);
- REQUIRE(line.endIndex == 2);
+ REQUIRE(line.startRunIndex == 0);
+ REQUIRE(line.startGlyphIndex == 1);
+ REQUIRE(line.endRunIndex == 0);
+ REQUIRE(line.endGlyphIndex == 2);
}
}
}
@@ -232,16 +239,78 @@
REQUIRE(font != nullptr);
// one two⏎ three
- std::vector<rive::RenderTextRun> truns;
+ std::vector<rive::TextRun> truns;
std::vector<rive::Unichar> unichars;
- truns.push_back(append(&unichars, font, 32.0f, "hello look\nhere"));
+ truns.push_back(append(&unichars, font, 32.0f, "hello look\u2028here"));
- auto shape = font->shapeText(unichars, truns);
- REQUIRE(shape.size() == 1);
- auto run = shape.front();
-
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 1);
{
- auto lines = RenderGlyphLine::BreakLines(shape, 300.0f, RenderTextAlign::left);
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 300.0f);
REQUIRE(lines.size() == 2);
}
}
+
+TEST_CASE("shaper separates paragraphs", "[shaper]")
+{
+ auto font = loadFont("../../test/assets/RobotoFlex.ttf");
+ REQUIRE(font != nullptr);
+
+ // one two⏎ three
+ std::vector<rive::TextRun> truns;
+ std::vector<rive::Unichar> unichars;
+ truns.push_back(append(&unichars, font, 32.0f, "hello look\u2028here\nsecond paragraph"));
+
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 2);
+ {
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.runs.size() == 1);
+ REQUIRE(paragraph.baseDirection == rive::TextDirection::ltr);
+
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 300.0f);
+ REQUIRE(lines.size() == 2);
+ }
+ {
+ const auto& paragraph = paragraphs.back();
+ REQUIRE(paragraph.runs.size() == 1);
+ REQUIRE(paragraph.baseDirection == rive::TextDirection::ltr);
+
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 300.0f);
+ REQUIRE(lines.size() == 1);
+ }
+}
+
+TEST_CASE("shaper handles RTL", "[shaper]")
+{
+ auto font = loadFont("../../test/assets/IBMPlexSansArabic-Regular.ttf");
+ REQUIRE(font != nullptr);
+
+ // one two⏎ three
+ std::vector<rive::TextRun> truns;
+ std::vector<rive::Unichar> unichars;
+ truns.push_back(append(&unichars, font, 32.0f, "لمفاتيح ABC DEF"));
+
+ auto paragraphs = font->shapeText(unichars, truns);
+ REQUIRE(paragraphs.size() == 1);
+ const auto& paragraph = paragraphs.front();
+ REQUIRE(paragraph.baseDirection == rive::TextDirection::rtl);
+ {
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 300.0f);
+ REQUIRE(lines.size() == 1);
+ }
+ {
+ auto lines = GlyphLine::BreakLines(paragraph.runs, 196.0f);
+ REQUIRE(lines.size() == 2);
+
+ // The second line should start with DEF as it's the first word to wrap.
+ const auto& line = lines.back();
+ const auto& run = paragraph.runs[line.startRunIndex];
+ auto index = run.textIndices[line.startGlyphIndex];
+ REQUIRE(unichars[index] == 'D');
+ REQUIRE(unichars[index + 1] == 'E');
+ REQUIRE(unichars[index + 2] == 'F');
+ }
+}
diff --git a/test/simple_array_test.cpp b/test/simple_array_test.cpp
index 78e7463..3263ba7 100644
--- a/test/simple_array_test.cpp
+++ b/test/simple_array_test.cpp
@@ -4,7 +4,7 @@
#include <rive/simple_array.hpp>
#include <catch.hpp>
-#include <rive/render_text.hpp>
+#include <rive/text.hpp>
using namespace rive;
diff --git a/viewer/build/macosx/build_viewer.sh b/viewer/build/macosx/build_viewer.sh
index dbb4803..9dcac0d 100755
--- a/viewer/build/macosx/build_viewer.sh
+++ b/viewer/build/macosx/build_viewer.sh
@@ -46,12 +46,6 @@
popd
fi
-if [[ ! -d "$DEPENDENCIES/harfbuzz" ]]; then
- pushd $DEPENDENCIES_SCRIPTS
- ./get_harfbuzz.sh
- popd
-fi
-
if [ $RENDERER = "skia" ]; then
pushd ../../../skia/renderer/build/macosx
./build_skia_renderer.sh text $@
@@ -68,7 +62,7 @@
pushd ..
-$PREMAKE --file=./premake5_viewer.lua gmake2 --graphics=$GRAPHICS --renderer=$RENDERER --with_rive_tools --with_rive_text
+$PREMAKE --scripts=../../build --file=./premake5_viewer.lua gmake2 --graphics=$GRAPHICS --renderer=$RENDERER --with_rive_tools --with_rive_text
for var in "$@"; do
if [[ $var = "clean" ]]; then
diff --git a/viewer/build/premake5_viewer.lua b/viewer/build/premake5_viewer.lua
index a7d79a0..d01b75e 100644
--- a/viewer/build/premake5_viewer.lua
+++ b/viewer/build/premake5_viewer.lua
@@ -12,7 +12,6 @@
skia = dependencies .. '/skia'
libpng = dependencies .. '/libpng'
-dofile(path.join(path.getabsolute(dependencies) .. '/../..', 'premake5_harfbuzz.lua'))
if _OPTIONS.renderer == 'tess' then
dofile(path.join(path.getabsolute(dependencies) .. '/../..', 'premake5_libpng.lua'))
dofile(path.join(path.getabsolute(rive_tess) .. '/build', 'premake5_tess.lua'))
@@ -38,16 +37,17 @@
includedirs {
'../include',
rive .. '/include',
- rive .. '/skia/renderer/include', -- for renderfont backends
+ rive .. '/skia/renderer/include', -- for font backends
dependencies,
dependencies .. '/sokol',
- dependencies .. '/imgui',
- dependencies .. '/harfbuzz/src'
+ dependencies .. '/imgui'
}
links {
'rive',
- 'rive_harfbuzz'
+ 'rive_harfbuzz',
+ -- 'rive_fribidi'
+ 'rive_sheenbidi'
}
libdirs {
@@ -57,7 +57,6 @@
files {
'../src/**.cpp',
rive .. '/utils/**.cpp',
- rive .. '/skia/renderer/src/renderfont_coretext.cpp',
dependencies .. '/imgui/imgui.cpp',
dependencies .. '/imgui/imgui_widgets.cpp',
dependencies .. '/imgui/imgui_tables.cpp',
diff --git a/viewer/include/viewer/viewer_content.hpp b/viewer/include/viewer/viewer_content.hpp
index e607929..ad7ba60 100644
--- a/viewer/include/viewer/viewer_content.hpp
+++ b/viewer/include/viewer/viewer_content.hpp
@@ -14,7 +14,7 @@
{
class Renderer;
class Factory;
-class RenderFont;
+class Font;
} // namespace rive
class ViewerContent
@@ -65,7 +65,7 @@
static rive::Factory* RiveFactory();
// Abstracts which font backend is currently used.
- static rive::rcp<rive::RenderFont> DecodeFont(rive::Span<const uint8_t>);
+ static rive::rcp<rive::Font> DecodeFont(rive::Span<const uint8_t>);
};
#endif
diff --git a/viewer/include/viewer/viewer_host.hpp b/viewer/include/viewer/viewer_host.hpp
index bf64439..1993cfe 100644
--- a/viewer/include/viewer/viewer_host.hpp
+++ b/viewer/include/viewer/viewer_host.hpp
@@ -7,7 +7,7 @@
#include "rive/factory.hpp"
#include "rive/renderer.hpp"
-#include "rive/render_text.hpp"
+#include "rive/text.hpp"
#include "sokol_gfx.h"
diff --git a/viewer/src/viewer_content/text_content.cpp b/viewer/src/viewer_content/text_content.cpp
index 44e70f4..d054482 100644
--- a/viewer/src/viewer_content/text_content.cpp
+++ b/viewer/src/viewer_content/text_content.cpp
@@ -8,12 +8,12 @@
#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"
+#include "rive/text.hpp"
+#include <algorithm>
-using RenderFontTextRuns = std::vector<rive::RenderTextRun>;
-using RenderFontGlyphRuns = rive::SimpleArray<rive::RenderGlyphRun>;
-using RenderFontFactory = rive::rcp<rive::RenderFont> (*)(const rive::Span<const uint8_t>);
+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 bool ws(rive::Unichar c) { return c <= ' '; }
@@ -41,60 +41,95 @@
return breaks;
}
-static void drawrun(rive::Factory* factory,
- rive::Renderer* renderer,
- const rive::RenderGlyphRun& run,
- unsigned startIndex,
- unsigned endIndex,
- rive::Vec2D origin)
+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());
- for (size_t i = startIndex; i < endIndex; ++i)
+ int i, end, inc;
+ if (run.dir == rive::TextDirection::rtl)
{
- auto trans = rive::Mat2D::fromTranslate(origin.x + run.xpos[i], origin.y);
+ 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 void drawpara(rive::Factory* factory,
- rive::Renderer* renderer,
- rive::Span<const rive::RenderGlyphLine> lines,
- rive::Span<const rive::RenderGlyphRun> runs,
- rive::Vec2D origin)
+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)
{
- const float x0 = runs[line.startRun].xpos[line.startIndex];
- int startGIndex = line.startIndex;
- for (int runIndex = line.startRun; runIndex <= line.endRun; ++runIndex)
+
+ float x = line.startX + origin.x;
+ int runIndex, endRun, runInc;
+ if (paragraph.baseDirection == rive::TextDirection::rtl)
{
- const auto& run = runs[runIndex];
- int endGIndex = runIndex == line.endRun ? line.endIndex : run.glyphs.size();
- drawrun(factory,
- renderer,
- run,
- startGIndex,
- endGIndex,
- {origin.x - x0 + line.startX, origin.y + line.baseline});
- startGIndex = 0;
+ 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;
}
////////////////////////////////////////////////////////////////////////////////////
#ifdef RIVE_USING_HAFBUZZ_FONTS
-static rive::rcp<rive::RenderFont> load_fallback_font(rive::Span<const rive::Unichar> missing)
+static rive::rcp<rive::Font> load_fallback_font(rive::Span<const rive::Unichar> missing)
{
- static rive::rcp<rive::RenderFont> gFallbackFont;
+ static rive::rcp<rive::Font> gFallbackFont;
printf("missing chars:");
for (auto m : missing)
@@ -121,7 +156,7 @@
fclose(fp);
assert(bytesRead == size);
- gFallbackFont = HBRenderFont::Decode(bytes);
+ gFallbackFont = HBFont::Decode(bytes);
}
return gFallbackFont;
}
@@ -147,10 +182,10 @@
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[])
+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;
@@ -167,15 +202,15 @@
std::vector<rive::Unichar> m_unichars;
std::vector<int> m_breaks;
- std::vector<RenderFontGlyphRuns> m_gruns;
+ rive::SimpleArray<rive::Paragraph> m_paragraphs;
rive::Mat2D m_xform;
float m_width = 300;
bool m_autoWidth = false;
int m_align = 0;
- RenderFontTextRuns make_truns(RenderFontFactory fact)
+ FontTextRuns make_truns(FontFactory fact)
{
- auto loader = [fact](const char filename[]) -> rive::rcp<rive::RenderFont> {
+ auto loader = [fact](const char filename[]) -> rive::rcp<rive::Font> {
auto bytes = ViewerContent::LoadFile(filename);
if (bytes.size() == 0)
{
@@ -188,27 +223,55 @@
const char* fontFiles[] = {
"../../../test/assets/RobotoFlex.ttf",
"../../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf",
+ "../../../test/assets/IBMPlexSansArabic-Regular.ttf",
};
auto font0 = loader(fontFiles[0]);
- auto font1 = loader(fontFiles[1]);
- assert(font0);
- assert(font1);
+ // auto font1 = loader(fontFiles[1]);
+ auto font2 = loader(fontFiles[2]);
+ // assert(font0);
+ // assert(font1);
+ assert(font2);
- rive::RenderFont::Coord c1 = {'wght', 100.f}, c2 = {'wght', 800.f};
+ rive::Font::Coord c1 = {'wght', 100.f}, c2 = {'wght', 800.f};
- RenderFontTextRuns truns;
+ FontTextRuns truns;
- // truns.push_back(append(&m_unichars, font0->makeAtCoord(c2), 60, "U"));
+ // 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, font1, 30, " fits the \ncRown"));
+
+ // 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, font0, 32.0f, "one two three"));
- 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, font2, 32.0f, "في 10-12 آذار 1997 بمدينة"));
+ // truns.push_back(append(&m_unichars, font2, 32.0f, "في 10-12 آذار 1997 بمدينة"));
+
+ truns.push_back(append(&m_unichars,
+ font2,
+ 32.0f,
+ // clang-format off
+ "لمفاتيح ABC DEF"));
+ // "لمفاتيح 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"));
@@ -224,21 +287,47 @@
TextContent()
{
auto truns = this->make_truns(ViewerContent::DecodeFont);
- m_gruns.push_back(truns[0].font->shapeText(m_unichars, truns));
+ 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 RenderFontGlyphRuns& gruns)
+ 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;
- auto lines = rive::RenderGlyphLine::BreakLines(gruns,
- m_autoWidth ? -1.0f : width,
- (rive::RenderTextAlign)m_align);
+ 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);
- drawpara(RiveFactory(), renderer, lines, gruns, {0, 0});
+ if (m_autoWidth)
+ {
+ paragraphWidth =
+ std::max(paragraphWidth,
+ rive::GlyphLine::ComputeMaxWidth(linesRef[paragraphIndex], para.runs));
+ }
+ }
+ 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);
@@ -249,11 +338,7 @@
void handleDraw(rive::Renderer* renderer, double) override
{
- for (auto& grun : m_gruns)
- {
- this->draw(renderer, m_width, grun);
- renderer->translate(1200, 0);
- }
+ this->draw(renderer, m_width, m_paragraphs);
}
void handleResize(int width, int height) override {}
diff --git a/viewer/src/viewer_content/textpath_content.cpp b/viewer/src/viewer_content/textpath_content.cpp
index f511a63..0b78ae6 100644
--- a/viewer/src/viewer_content/textpath_content.cpp
+++ b/viewer/src/viewer_content/textpath_content.cpp
@@ -1,3 +1,4 @@
+
/*
* Copyright 2022 Rive
*/
@@ -8,18 +9,16 @@
#include "rive/math/raw_path.hpp"
#include "rive/refcnt.hpp"
#include "rive/factory.hpp"
-#include "rive/render_text.hpp"
+#include "rive/text.hpp"
#include "rive/math/contour_measure.hpp"
-#include "rive/text/line_breaker.hpp"
using namespace rive;
+#if 0
+using FontTextRuns = std::vector<TextRun>;
+using FontGlyphRuns = rive::SimpleArray<GlyphRun>;
+using FontFactory = rcp<Font> (*)(const Span<const uint8_t>);
-using RenderFontTextRuns = std::vector<RenderTextRun>;
-using RenderFontGlyphRuns = rive::SimpleArray<RenderGlyphRun>;
-using RenderFontFactory = rcp<RenderFont> (*)(const Span<const uint8_t>);
-
-template <typename Handler>
-void visit(const Span<RenderGlyphRun>& gruns, Vec2D origin, Handler proc)
+template <typename Handler> void visit(const Span<GlyphRun>& gruns, Vec2D origin, Handler proc)
{
for (const auto& gr : gruns)
{
@@ -99,8 +98,7 @@
fill_rect(renderer, {p.x - r, p.y - r, p.x + r, p.y + r}, paint);
}
-static RenderTextRun
-append(std::vector<Unichar>* unichars, rcp<RenderFont> font, float size, const char text[])
+static TextRun append(std::vector<Unichar>* unichars, rcp<Font> font, float size, const char text[])
{
const uint8_t* ptr = (const uint8_t*)text;
uint32_t n = 0;
@@ -115,7 +113,7 @@
class TextPathContent : public ViewerContent
{
std::vector<Unichar> m_unichars;
- RenderFontGlyphRuns m_gruns;
+ FontGlyphRuns m_gruns;
std::unique_ptr<RenderPaint> m_paint;
AABB m_gbounds;
@@ -133,9 +131,9 @@
m_windowWidth = 1, // %
m_windowOffset = 0; // %
- RenderFontTextRuns make_truns(RenderFontFactory fact)
+ FontTextRuns make_truns(FontFactory fact)
{
- auto loader = [fact](const char filename[]) -> rcp<RenderFont> {
+ auto loader = [fact](const char filename[]) -> rcp<Font> {
auto bytes = ViewerContent::LoadFile(filename);
if (bytes.size() == 0)
{
@@ -155,9 +153,9 @@
assert(font0);
assert(font1);
- RenderFont::Coord c1 = {'wght', 100.f}, c2 = {'wght', 800.f};
+ Font::Coord c1 = {'wght', 100.f}, c2 = {'wght', 800.f};
- RenderFontTextRuns truns;
+ FontTextRuns truns;
truns.push_back(append(&m_unichars, font0->makeAtCoord(c2), 60, "U"));
truns.push_back(append(&m_unichars, font0->makeAtCoord(c1), 30, "ne漢字asy"));
@@ -171,7 +169,7 @@
public:
TextPathContent()
{
- auto compute_bounds = [](const rive::SimpleArray<RenderGlyphRun>& gruns) {
+ auto compute_bounds = [](const rive::SimpleArray<GlyphRun>& gruns) {
AABB bounds = {};
for (const auto& gr : gruns)
{
@@ -216,7 +214,7 @@
}
}
- static size_t count_glyphs(const RenderFontGlyphRuns& gruns)
+ static size_t count_glyphs(const FontGlyphRuns& gruns)
{
size_t n = 0;
for (const auto& gr : gruns)
@@ -228,9 +226,9 @@
void modify(float amount) { m_paint->color(0xFFFFFFFF); }
- void draw(Renderer* renderer, const RenderFontGlyphRuns& gruns)
+ void draw(Renderer* renderer, const FontGlyphRuns& gruns)
{
- auto get_path = [this](const RenderGlyphRun& run, int index, float dx) {
+ auto get_path = [this](const GlyphRun& 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));
@@ -406,3 +404,6 @@
{
return std::make_unique<TextPathContent>();
}
+#else
+std::unique_ptr<ViewerContent> ViewerContent::TextPath(const char filename[]) { return nullptr; }
+#endif
diff --git a/viewer/src/viewer_content/viewer_content.cpp b/viewer/src/viewer_content/viewer_content.cpp
index e60dfd2..44a63b3 100644
--- a/viewer/src/viewer_content/viewer_content.cpp
+++ b/viewer/src/viewer_content/viewer_content.cpp
@@ -67,17 +67,8 @@
rive::Factory* ViewerContent::RiveFactory() { return ViewerHost::Factory(); }
-#ifdef RIVE_BUILD_FOR_APPLE
-// note: we can use harfbuzz even on apple ... (if we want)
-#include "renderfont_coretext.hpp"
-rive::rcp<rive::RenderFont> ViewerContent::DecodeFont(rive::Span<const uint8_t> span)
+#include "rive/text/font_hb.hpp"
+rive::rcp<rive::Font> ViewerContent::DecodeFont(rive::Span<const uint8_t> span)
{
- return CoreTextRenderFont::Decode(span);
+ return HBFont::Decode(span);
}
-#else
-#include "renderfont_hb.hpp"
-rive::rcp<rive::RenderFont> ViewerContent::DecodeFont(rive::Span<const uint8_t> span)
-{
- return HBRenderFont::Decode(span);
-}
-#endif