mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 05:33:36 +00:00
ttf_loader: Basic support for composite glyphs loading (#2600)
Adds the ability to load some composite glyphs and prevents an error when a composite glyph is used. Implementation based on ttf glyf table documentation: https://learn.microsoft.com/en-us/typography/opentype/spec/glyf There are still some missing features like scaling, parent glyph point based positioning etc. I think this is a topic for future work. Howerever, it looks like implemented features are enough for utf-8 latin subset in major fonts. issue: #2599
This commit is contained in:
parent
8c4b9b2772
commit
045b30b94f
4 changed files with 122 additions and 17 deletions
|
@ -289,7 +289,7 @@ bool TtfLoader::read()
|
|||
auto rglyph = reader.glyph(code[idx], gmetrics);
|
||||
if (rglyph != INVALID_GLYPH) {
|
||||
if (lglyph != INVALID_GLYPH) reader.kerning(lglyph, rglyph, kerning);
|
||||
if (!reader.convert(shape, gmetrics, offset, kerning)) break;
|
||||
if (!reader.convert(shape, gmetrics, offset, kerning, 1U)) break;
|
||||
}
|
||||
offset.x += (gmetrics.advanceWidth + kerning.x);
|
||||
lglyph = rglyph;
|
||||
|
|
|
@ -392,30 +392,35 @@ uint32_t TtfReader::glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics)
|
|||
return INVALID_GLYPH;
|
||||
}
|
||||
|
||||
return glyphMetrics(glyph, gmetrics) ? glyph : INVALID_GLYPH;
|
||||
}
|
||||
|
||||
bool TtfReader::glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics)
|
||||
{
|
||||
//horizontal metrics
|
||||
if (!hmtx) hmtx = table("hmtx");
|
||||
|
||||
//glyph is inside long metrics segment.
|
||||
if (glyph < metrics.numHmtx) {
|
||||
auto offset = hmtx + 4 * glyph;
|
||||
if (!validate(offset, 4)) return INVALID_GLYPH;
|
||||
if (glyphIndex < metrics.numHmtx) {
|
||||
auto offset = hmtx + 4 * glyphIndex;
|
||||
if (!validate(offset, 4)) return false;
|
||||
gmetrics.advanceWidth = _u16(data, offset);
|
||||
gmetrics.leftSideBearing = _i16(data, offset + 2);
|
||||
/* glyph is inside short metrics segment. */
|
||||
} else {
|
||||
auto boundary = hmtx + 4U * (uint32_t) metrics.numHmtx;
|
||||
if (boundary < 4) return INVALID_GLYPH;
|
||||
if (boundary < 4) return false;
|
||||
|
||||
auto offset = boundary - 4;
|
||||
if (!validate(offset, 4)) return INVALID_GLYPH;
|
||||
if (!validate(offset, 4)) return false;
|
||||
gmetrics.advanceWidth = _u16(data, offset);
|
||||
offset = boundary + 2 * (glyph - metrics.numHmtx);
|
||||
if (!validate(offset, 2)) return INVALID_GLYPH;
|
||||
offset = boundary + 2 * (glyphIndex - metrics.numHmtx);
|
||||
if (!validate(offset, 2)) return false;
|
||||
gmetrics.leftSideBearing = _i16(data, offset);
|
||||
}
|
||||
|
||||
gmetrics.outline = outlineOffset(glyph);
|
||||
if (!gmetrics.outline || !validate(gmetrics.outline, 10)) return INVALID_GLYPH;
|
||||
gmetrics.outline = outlineOffset(glyphIndex);
|
||||
if (!gmetrics.outline || !validate(gmetrics.outline, 10)) return false;
|
||||
|
||||
//read the bounding box from the font file verbatim.
|
||||
float bbox[4];
|
||||
|
@ -424,22 +429,31 @@ uint32_t TtfReader::glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics)
|
|||
bbox[2] = static_cast<float>(_i16(data, gmetrics.outline + 6));
|
||||
bbox[3] = static_cast<float>(_i16(data, gmetrics.outline + 8));
|
||||
|
||||
if (bbox[2] <= bbox[0] || bbox[3] <= bbox[1]) return INVALID_GLYPH;
|
||||
if (bbox[2] <= bbox[0] || bbox[3] <= bbox[1]) return false;
|
||||
|
||||
gmetrics.minw = bbox[2] - bbox[0] + 1;
|
||||
gmetrics.minh = bbox[3] - bbox[1] + 1;
|
||||
gmetrics.yOffset = bbox[3];
|
||||
|
||||
return glyph;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning)
|
||||
bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth)
|
||||
{
|
||||
#define ON_CURVE 0x01
|
||||
|
||||
auto cntrsCnt = (uint32_t) _i16(data, gmetrics.outline);
|
||||
if (cntrsCnt == 0) return false;
|
||||
auto outlineCnt = _i16(data, gmetrics.outline);
|
||||
if (outlineCnt == 0) return false;
|
||||
if (outlineCnt < 0) {
|
||||
uint16_t maxComponentDepth = 1U;
|
||||
if (!maxp) maxp = table("maxp");
|
||||
if (validate(maxp, 32) && _u32(data, maxp) >= 0x00010000U) { // >= version 1.0
|
||||
maxComponentDepth = _u16(data, maxp + 30);
|
||||
}
|
||||
if (componentDepth > maxComponentDepth) return false;
|
||||
return convertComposite(shape, gmetrics, offset, kerning, componentDepth + 1);
|
||||
}
|
||||
auto cntrsCnt = (uint32_t) outlineCnt;
|
||||
|
||||
auto outline = gmetrics.outline + 10;
|
||||
if (!validate(outline, cntrsCnt * 2 + 2)) return false;
|
||||
|
@ -512,6 +526,73 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TtfReader::convertComposite(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth)
|
||||
{
|
||||
#define ARG_1_AND_2_ARE_WORDS 0x0001
|
||||
#define ARGS_ARE_XY_VALUES 0x0002
|
||||
#define WE_HAVE_A_SCALE 0x0008
|
||||
#define MORE_COMPONENTS 0x0020
|
||||
#define WE_HAVE_AN_X_AND_Y_SCALE 0x0040
|
||||
#define WE_HAVE_A_TWO_BY_TWO 0x0080
|
||||
|
||||
TtfGlyphMetrics componentGmetrics;
|
||||
Point componentOffset;
|
||||
uint16_t flags, glyphIndex;
|
||||
uint32_t pointer = gmetrics.outline + 10;
|
||||
do {
|
||||
if (!validate(pointer, 4)) return false;
|
||||
flags = _u16(data, pointer);
|
||||
glyphIndex = _u16(data, pointer + 2U);
|
||||
pointer += 4U;
|
||||
if (flags & ARG_1_AND_2_ARE_WORDS) {
|
||||
if (!validate(pointer, 4)) return false;
|
||||
if(flags & ARGS_ARE_XY_VALUES) {
|
||||
componentOffset.x = static_cast<float>(_i16(data, pointer));
|
||||
componentOffset.y = -static_cast<float>(_i16(data, pointer + 2U));
|
||||
} else {
|
||||
// TODO align to parent point
|
||||
componentOffset.x = 0;
|
||||
componentOffset.y = 0;
|
||||
}
|
||||
pointer += 4U;
|
||||
} else {
|
||||
if (!validate(pointer, 2)) return false;
|
||||
if(flags & ARGS_ARE_XY_VALUES) {
|
||||
componentOffset.x = static_cast<float>((int8_t)_u8(data, pointer));
|
||||
componentOffset.y = -static_cast<float>((int8_t)_u8(data, pointer + 1U));
|
||||
} else {
|
||||
// TODO align to parent point
|
||||
componentOffset.x = 0;
|
||||
componentOffset.y = 0;
|
||||
}
|
||||
pointer += 2U;
|
||||
}
|
||||
if (flags & WE_HAVE_A_SCALE) {
|
||||
if (!validate(pointer, 2)) return false;
|
||||
// TODO transformation
|
||||
// F2DOT14 scale; /* Format 2.14 */
|
||||
pointer += 2U;
|
||||
} else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
|
||||
if (!validate(pointer, 4)) return false;
|
||||
// TODO transformation
|
||||
// F2DOT14 xscale; /* Format 2.14 */
|
||||
// F2DOT14 yscale; /* Format 2.14 */
|
||||
pointer += 4U;
|
||||
} else if (flags & WE_HAVE_A_TWO_BY_TWO) {
|
||||
if (!validate(pointer, 8)) return false;
|
||||
// TODO transformation
|
||||
// F2DOT14 xscale; /* Format 2.14 */
|
||||
// F2DOT14 scale01; /* Format 2.14 */
|
||||
// F2DOT14 scale10; /* Format 2.14 */
|
||||
// F2DOT14 yscale; /* Format 2.14 */
|
||||
pointer += 8U;
|
||||
}
|
||||
if (!glyphMetrics(glyphIndex, componentGmetrics)) return false;
|
||||
if (!convert(shape, componentGmetrics, offset + componentOffset, kerning, componentDepth)) return false;
|
||||
} while (flags & MORE_COMPONENTS);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void TtfReader::kerning(uint32_t lglyph, uint32_t rglyph, Point& out)
|
||||
{
|
||||
|
|
|
@ -64,7 +64,7 @@ public:
|
|||
bool header();
|
||||
uint32_t glyph(uint32_t codepoint, TtfGlyphMetrics& gmetrics);
|
||||
void kerning(uint32_t lglyph, uint32_t rglyph, Point& out);
|
||||
bool convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning);
|
||||
bool convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth);
|
||||
|
||||
private:
|
||||
//table offsets
|
||||
|
@ -73,6 +73,7 @@ private:
|
|||
uint32_t loca = 0;
|
||||
uint32_t glyf = 0;
|
||||
uint32_t kern = 0;
|
||||
uint32_t maxp = 0;
|
||||
|
||||
uint32_t cmap_12_13(uint32_t table, uint32_t codepoint, int which) const;
|
||||
uint32_t cmap_4(uint32_t table, uint32_t codepoint) const;
|
||||
|
@ -81,6 +82,8 @@ private:
|
|||
uint32_t table(const char* tag);
|
||||
uint32_t outlineOffset(uint32_t glyph);
|
||||
uint32_t glyph(uint32_t codepoint);
|
||||
bool glyphMetrics(uint32_t glyphIndex, TtfGlyphMetrics& gmetrics);
|
||||
bool convertComposite(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& offset, const Point& kerning, uint16_t componentDepth);
|
||||
bool genPath(uint8_t* flags, uint16_t basePoint, uint16_t count);
|
||||
bool genSimpleOutline(Shape* shape, uint32_t outline, uint32_t cntrsCnt);
|
||||
bool points(uint32_t outline, uint8_t* flags, Point* pts, uint32_t ptsCnt, const Point& offset);
|
||||
|
|
|
@ -141,4 +141,25 @@ TEST_CASE("Text Basic", "[tvgText]")
|
|||
Initializer::term(CanvasEngine::Sw);
|
||||
}
|
||||
|
||||
TEST_CASE("Text with composite glyphs", "[tvgText]")
|
||||
{
|
||||
Initializer::init(tvg::CanvasEngine::Sw, 0);
|
||||
|
||||
auto canvas = SwCanvas::gen();
|
||||
|
||||
auto text = Text::gen();
|
||||
REQUIRE(text);
|
||||
|
||||
REQUIRE(Text::load(TEST_DIR"/Arial.ttf") == tvg::Result::Success);
|
||||
REQUIRE(text->font("Arial", 80) == tvg::Result::Success);
|
||||
|
||||
REQUIRE(text->text("\xc5\xbb\x6f\xc5\x82\xc4\x85\x64\xc5\xba \xc8\xab") == tvg::Result::Success);
|
||||
|
||||
REQUIRE(text->fill(255, 255, 255) == tvg::Result::Success);
|
||||
|
||||
REQUIRE(canvas->push(std::move(text)) == Result::Success);
|
||||
|
||||
Initializer::term(tvg::CanvasEngine::Sw);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue