Add composite glyph support

This commit is contained in:
Magnus Norddahl 2024-01-08 00:49:11 +01:00 committed by Christoph Oelckers
parent e3d3ba2c86
commit edb1e3cb83
2 changed files with 290 additions and 134 deletions

View file

@ -63,9 +63,6 @@ TrueTypeTextMetrics TrueTypeFont::GetTextMetrics(double height) const
TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const
{
if (glyphIndex >= loca.offsets.size())
throw std::runtime_error("Glyph index out of bounds");
double scale = height / head.unitsPerEm;
double scaleX = 3.0f;
double scaleY = -1.0f;
@ -97,93 +94,19 @@ TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const
return glyph;
}
TrueTypeFileReader reader = glyf.GetReader(data.data(), data.size());
reader.Seek(loca.offsets[glyphIndex]);
TTF_SimpleGlyph g;
LoadGlyph(g, glyphIndex);
ttf_int16 numberOfContours = reader.ReadInt16();
ttf_int16 xMin = reader.ReadInt16();
ttf_int16 yMin = reader.ReadInt16();
ttf_int16 xMax = reader.ReadInt16();
ttf_int16 yMax = reader.ReadInt16();
if (numberOfContours > 0) // Simple glyph
{
std::vector<ttf_uint16> endPtsOfContours;
endPtsOfContours.reserve(numberOfContours);
for (ttf_uint16 i = 0; i < numberOfContours; i++)
endPtsOfContours.push_back(reader.ReadUInt16());
ttf_uint16 instructionLength = reader.ReadUInt16();
std::vector<ttf_uint8> instructions;
instructions.resize(instructionLength);
reader.Read(instructions.data(), instructions.size());
int numPoints = (int)endPtsOfContours.back() + 1;
std::vector<TTF_Point> points(numPoints);
std::vector<ttf_uint8> flags;
while (flags.size() < (size_t)numPoints)
{
ttf_uint8 flag = reader.ReadUInt8();
if (flag & TTF_REPEAT_FLAG)
{
ttf_uint8 repeatcount = reader.ReadUInt8();
for (ttf_uint8 i = 0; i < repeatcount; i++)
flags.push_back(flag);
}
flags.push_back(flag);
}
for (int i = 0; i < numPoints; i++)
{
if (flags[i] & TTF_X_SHORT_VECTOR)
{
ttf_int16 x = reader.ReadUInt8();
points[i].x = (flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x;
}
else if (flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)
{
points[i].x = 0;
}
else
{
points[i].x = reader.ReadInt16();
}
}
for (int i = 0; i < numPoints; i++)
{
if (flags[i] & TTF_Y_SHORT_VECTOR)
{
ttf_int16 y = reader.ReadUInt8();
points[i].y = (flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y;
}
else if (flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)
{
points[i].y = 0;
}
else
{
points[i].y = reader.ReadInt16();
}
}
// Convert from relative coordinates to absolute
for (int i = 1; i < numPoints; i++)
{
points[i].x += points[i - 1].x;
points[i].y += points[i - 1].y;
}
int numberOfContours = g.endPtsOfContours.size();
// Create glyph path:
PathFillDesc path;
path.fill_mode = PathFillMode::winding;
int startPoint = 0;
for (ttf_uint16 i = 0; i < numberOfContours; i++)
for (int i = 0; i < numberOfContours; i++)
{
int endPoint = endPtsOfContours[i];
int endPoint = g.endPtsOfContours[i];
if (endPoint < startPoint)
throw std::runtime_error("Invalid glyph");
@ -192,25 +115,25 @@ TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const
{
if (pos == startPoint)
{
path.MoveTo(Point(points[pos].x, points[pos].y) * scale);
path.MoveTo(Point(g.points[pos].x, g.points[pos].y) * scale);
pos++;
}
else if (flags[pos] & TTF_ON_CURVE_POINT)
else if (g.flags[pos] & TTF_ON_CURVE_POINT)
{
if (flags[pos - 1] & TTF_ON_CURVE_POINT)
if (g.flags[pos - 1] & TTF_ON_CURVE_POINT)
{
path.LineTo(Point(points[pos].x, points[pos].y) * scale);
path.LineTo(Point(g.points[pos].x, g.points[pos].y) * scale);
}
else
{
path.BezierTo(Point(points[pos - 1].x, points[pos - 1].y) * scale, Point(points[pos].x, points[pos].y) * scale);
path.BezierTo(Point(g.points[pos - 1].x, g.points[pos - 1].y) * scale, Point(g.points[pos].x, g.points[pos].y) * scale);
}
pos++;
}
else
{
Point lastcontrolpoint(points[pos].x, points[pos].y);
Point controlpoint(points[pos - 1].x, points[pos - 1].y);
Point lastcontrolpoint(g.points[pos].x, g.points[pos].y);
Point controlpoint(g.points[pos - 1].x, g.points[pos - 1].y);
Point midpoint = (lastcontrolpoint + controlpoint) / 2;
path.BezierTo(lastcontrolpoint * scale, midpoint * scale);
pos++;
@ -274,17 +197,240 @@ TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const
return glyph;
}
else if (numberOfContours < 0) // Composite glyph
void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compositeDepth) const
{
ttf_uint16 flags = reader.ReadUInt16();
ttf_uint16 glyphIndex = reader.ReadUInt16();
if (glyphIndex >= loca.offsets.size())
throw std::runtime_error("Glyph index out of bounds");
// To do: implement this
TrueTypeFileReader reader = glyf.GetReader(data.data(), data.size());
reader.Seek(loca.offsets[glyphIndex]);
return {};
ttf_int16 numberOfContours = reader.ReadInt16();
ttf_int16 xMin = reader.ReadInt16();
ttf_int16 yMin = reader.ReadInt16();
ttf_int16 xMax = reader.ReadInt16();
ttf_int16 yMax = reader.ReadInt16();
if (numberOfContours > 0) // Simple glyph
{
int pointsOffset = g.points.size();
for (ttf_uint16 i = 0; i < numberOfContours; i++)
g.endPtsOfContours.push_back(pointsOffset + reader.ReadUInt16());
ttf_uint16 instructionLength = reader.ReadUInt16();
std::vector<ttf_uint8> instructions;
instructions.resize(instructionLength);
reader.Read(instructions.data(), instructions.size());
int numPoints = (int)g.endPtsOfContours.back() - pointsOffset + 1;
g.points.resize(pointsOffset + numPoints);
while (g.flags.size() < g.points.size())
{
ttf_uint8 flag = reader.ReadUInt8();
if (flag & TTF_REPEAT_FLAG)
{
ttf_uint8 repeatcount = reader.ReadUInt8();
for (ttf_uint8 i = 0; i < repeatcount; i++)
g.flags.push_back(flag);
}
g.flags.push_back(flag);
}
return {};
for (int i = pointsOffset; i < pointsOffset + numPoints; i++)
{
if (g.flags[i] & TTF_X_SHORT_VECTOR)
{
ttf_int16 x = reader.ReadUInt8();
g.points[i].x = (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x;
}
else if (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)
{
g.points[i].x = 0;
}
else
{
g.points[i].x = reader.ReadInt16();
}
}
for (int i = pointsOffset; i < pointsOffset + numPoints; i++)
{
if (g.flags[i] & TTF_Y_SHORT_VECTOR)
{
ttf_int16 y = reader.ReadUInt8();
g.points[i].y = (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y;
}
else if (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)
{
g.points[i].y = 0;
}
else
{
g.points[i].y = reader.ReadInt16();
}
}
// Convert from relative coordinates to absolute
for (int i = pointsOffset + 1; i < pointsOffset + numPoints; i++)
{
g.points[i].x += g.points[i - 1].x;
g.points[i].y += g.points[i - 1].y;
}
}
else if (numberOfContours < 0) // Composite glyph
{
if (compositeDepth == 8)
throw std::runtime_error("Composite glyph recursion exceeded");
int parentPointsOffset = g.points.size();
bool weHaveInstructions = false;
while (true)
{
ttf_uint16 flags = reader.ReadUInt16();
ttf_uint16 childGlyphIndex = reader.ReadUInt16();
int argument1, argument2;
if (flags & TTF_ARG_1_AND_2_ARE_WORDS)
{
if (flags & TTF_ARGS_ARE_XY_VALUES)
{
argument1 = reader.ReadInt16();
argument2 = reader.ReadInt16();
}
else
{
argument1 = reader.ReadUInt16();
argument2 = reader.ReadUInt16();
}
}
else
{
if (flags & TTF_ARGS_ARE_XY_VALUES)
{
argument1 = reader.ReadInt8();
argument2 = reader.ReadInt8();
}
else
{
argument1 = reader.ReadUInt8();
argument2 = reader.ReadUInt8();
}
}
float mat2x2[4];
bool transform = true;
if (flags & TTF_WE_HAVE_A_SCALE)
{
ttf_F2DOT14 scale = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[0] = scale;
mat2x2[1] = 0;
mat2x2[2] = 0;
mat2x2[3] = scale;
}
else if (flags & TTF_WE_HAVE_AN_X_AND_Y_SCALE)
{
mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[1] = 0;
mat2x2[2] = 0;
mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14());
}
else if (flags & TTF_WE_HAVE_A_TWO_BY_TWO)
{
mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[1] = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[2] = F2DOT14_ToFloat(reader.ReadF2DOT14());
mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14());
}
else
{
transform = false;
}
int childPointsOffset = g.points.size();
LoadGlyph(g, childGlyphIndex, compositeDepth + 1);
if (transform)
{
for (int i = childPointsOffset; i < g.points.size(); i++)
{
float x = g.points[i].x * mat2x2[0] + g.points[i].y * mat2x2[1];
float y = g.points[i].x * mat2x2[2] + g.points[i].y * mat2x2[3];
g.points[i].x = x;
g.points[i].y = y;
}
}
float dx, dy;
if (flags & TTF_ARGS_ARE_XY_VALUES)
{
dx = argument1;
dy = argument2;
// Spec states we must fall back to TTF_UNSCALED_COMPONENT_OFFSET if both flags are set
if ((flags & (TTF_SCALED_COMPONENT_OFFSET | TTF_UNSCALED_COMPONENT_OFFSET)) == TTF_SCALED_COMPONENT_OFFSET)
{
float x = dx * mat2x2[0] + dy * mat2x2[1];
float y = dx * mat2x2[2] + dy * mat2x2[3];
dx = x;
dy = y;
}
if (flags & TTF_ROUND_XY_TO_GRID)
{
// To do: round the offset to the pixel grid
}
}
else
{
int parentPointIndex = parentPointsOffset + argument1;
int childPointIndex = childPointsOffset + argument2;
if ((size_t)parentPointIndex >= g.points.size() || (size_t)childPointIndex >= g.points.size())
throw std::runtime_error("Invalid glyph offset");
dx = g.points[parentPointIndex].x - g.points[childPointIndex].x;
dy = g.points[parentPointIndex].y - g.points[childPointIndex].y;
}
for (int i = childPointsOffset; i < g.points.size(); i++)
{
g.points[i].x += dx;
g.points[i].y += dy;
}
if (flags & TTF_USE_MY_METRICS)
{
// To do: this affects lsb + rsb calculations somehow
}
if (flags & TTF_WE_HAVE_INSTRUCTIONS)
{
weHaveInstructions = true;
}
if (!(flags & TTF_MORE_COMPONENTS))
break;
}
if (weHaveInstructions)
{
ttf_uint16 instructionLength = reader.ReadUInt16();
std::vector<ttf_uint8> instructions;
instructions.resize(instructionLength);
reader.Read(instructions.data(), instructions.size());
}
}
}
float TrueTypeFont::F2DOT14_ToFloat(ttf_F2DOT14 v)
{
int a = ((ttf_int16)v) >> 14;
int b = (v & 0x3fff);
return a + b / (float)0x4000;
}
uint32_t TrueTypeFont::GetGlyphIndex(uint32_t c) const

View file

@ -6,6 +6,7 @@
#include <stdexcept>
#include <memory>
#include <cstring>
#include <string>
typedef uint8_t ttf_uint8;
typedef uint16_t ttf_uint16;
@ -19,7 +20,7 @@ typedef int32_t ttf_int32;
typedef uint32_t ttf_Fixed; // 32-bit signed fixed-point number (16.16)
typedef uint16_t ttf_UFWORD; // uint16 that describes a quantity in font design units
typedef int16_t ttf_FWORD; // int16 that describes a quantity in font design units
typedef uint32_t ttf_F2DOT14; // 16-bit signed fixed number with the low 14 bits of fraction (2.14)
typedef uint16_t ttf_F2DOT14; // 16-bit signed fixed number with the low 14 bits of fraction (2.14)
typedef uint64_t ttf_LONGDATETIME; // number of seconds since 12:00 midnight, January 1, 1904, UTC
typedef std::array<uint8_t, 4> ttf_Tag; // 4 byte identifier
@ -396,8 +397,15 @@ struct TTF_IndexToLocation // 'loca' Index to location
struct TTF_Point
{
ttf_int16 x;
ttf_int16 y;
float x;
float y;
};
struct TTF_SimpleGlyph
{
std::vector<int> endPtsOfContours;
std::vector<ttf_uint8> flags;
std::vector<TTF_Point> points;
};
class TrueTypeGlyph
@ -431,6 +439,8 @@ public:
private:
void LoadCharacterMapEncoding(TrueTypeFileReader& reader);
void LoadGlyph(TTF_SimpleGlyph& glyph, uint32_t glyphIndex, int compositeDepth = 0) const;
static float F2DOT14_ToFloat(ttf_F2DOT14 v);
std::vector<uint8_t> data;