Add internal truetype font class

This commit is contained in:
Magnus Norddahl 2024-01-07 11:39:17 +01:00 committed by Christoph Oelckers
parent 2334a88229
commit cbb89315e7
5 changed files with 1412 additions and 15 deletions

View file

@ -10,6 +10,8 @@ set(ZWIDGET_SOURCES
src/core/widget.cpp src/core/widget.cpp
src/core/utf8reader.cpp src/core/utf8reader.cpp
src/core/pathfill.cpp src/core/pathfill.cpp
src/core/truetypefont.cpp
src/core/truetypefont.h
src/core/schrift/schrift.cpp src/core/schrift/schrift.cpp
src/core/schrift/schrift.h src/core/schrift/schrift.h
src/core/picopng/picopng.cpp src/core/picopng/picopng.cpp

View file

@ -5,6 +5,8 @@
#include "core/utf8reader.h" #include "core/utf8reader.h"
#include "core/resourcedata.h" #include "core/resourcedata.h"
#include "core/image.h" #include "core/image.h"
#include "core/truetypefont.h"
#include "core/pathfill.h"
#include "window/window.h" #include "window/window.h"
#include "schrift/schrift.h" #include "schrift/schrift.h"
#include <vector> #include <vector>
@ -12,6 +14,8 @@
#include <stdexcept> #include <stdexcept>
#include <cstring> #include <cstring>
// #define USE_INTERNAL_TTF
class CanvasTexture class CanvasTexture
{ {
public: public:
@ -20,6 +24,109 @@ public:
std::vector<uint32_t> Data; std::vector<uint32_t> Data;
}; };
#if defined(USE_INTERNAL_TTF)
class CanvasGlyph
{
public:
struct
{
double leftSideBearing = 0.0;
double yOffset = 0.0;
double advanceWidth = 0.0;
} metrics;
double u = 0.0;
double v = 0.0;
double uvwidth = 0.0f;
double uvheight = 0.0f;
std::shared_ptr<CanvasTexture> texture;
};
class CanvasFont
{
public:
CanvasFont(const std::string& fontname, double height) : fontname(fontname), height(height)
{
ttf = std::make_unique<TrueTypeFont>(LoadWidgetFontData(fontname));
textmetrics = ttf->GetTextMetrics(height);
}
~CanvasFont()
{
}
CanvasGlyph* getGlyph(uint32_t utfchar)
{
uint32_t glyphIndex = ttf->GetGlyphIndex(utfchar);
auto& glyph = glyphs[glyphIndex];
if (glyph)
return glyph.get();
glyph = std::make_unique<CanvasGlyph>();
TrueTypeGlyph ttfglyph = ttf->LoadGlyph(glyphIndex, height);
// Create final subpixel version
int w = ttfglyph.width;
int h = ttfglyph.height;
int destwidth = (w + 2) / 3;
auto texture = std::make_shared<CanvasTexture>();
texture->Width = destwidth;
texture->Height = h;
texture->Data.resize(destwidth * h);
uint8_t* grayscale = ttfglyph.grayscale.get();
uint32_t* dest = (uint32_t*)texture->Data.data();
for (int y = 0; y < h; y++)
{
uint8_t* sline = grayscale + y * w;
uint32_t* dline = dest + y * destwidth;
for (int x = 0; x < w; x += 3)
{
uint32_t values[5] =
{
x > 0 ? sline[x - 1] : 0U,
sline[x],
x + 1 < w ? sline[x + 1] : 0U,
x + 2 < w ? sline[x + 2] : 0U,
x + 3 < w ? sline[x + 3] : 0U
};
uint32_t red = (values[0] + values[1] + values[1] + values[2] + 2) >> 2;
uint32_t green = (values[1] + values[2] + values[2] + values[3] + 2) >> 2;
uint32_t blue = (values[2] + values[3] + values[3] + values[4] + 2) >> 2;
uint32_t alpha = (red | green | blue) ? 255 : 0;
*(dline++) = (alpha << 24) | (red << 16) | (green << 8) | blue;
}
}
glyph->u = 0.0;
glyph->v = 0.0;
glyph->uvwidth = destwidth;
glyph->uvheight = h;
glyph->texture = std::move(texture);
glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3;
glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3;
glyph->metrics.yOffset = ttfglyph.yOffset;
return glyph.get();
}
std::unique_ptr<TrueTypeFont> ttf;
std::string fontname;
double height = 0.0;
TrueTypeTextMetrics textmetrics;
std::unordered_map<uint32_t, std::unique_ptr<CanvasGlyph>> glyphs;
};
#else
class CanvasGlyph class CanvasGlyph
{ {
public: public:
@ -152,6 +259,8 @@ private:
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
#endif
class CanvasFontGroup class CanvasFontGroup
{ {
public: public:

View file

@ -122,6 +122,12 @@ private:
void PathFillDesc::Rasterize(uint8_t* dest, int width, int height, bool blend) void PathFillDesc::Rasterize(uint8_t* dest, int width, int height, bool blend)
{ {
if (!blend)
{
memset(dest, 0, width * height);
}
PathFillRasterizer rasterizer;
rasterizer.Rasterize(*this, dest, width, height);
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@ -131,15 +137,15 @@ void PathFillRasterizer::Rasterize(const PathFillDesc& path, uint8_t* dest, int
Clear(); Clear();
// For simplicity of the code, ensure the mask is always a multiple of MaskBlockSize // For simplicity of the code, ensure the mask is always a multiple of MaskBlockSize
int block_width = MaskBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize); int block_width = ScanlineBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize);
int block_height = MaskBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize); int block_height = ScanlineBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize);
if (width != block_width || height != block_height) if (width != block_width || height != block_height)
{ {
width = block_width; width = block_width;
height = block_height; height = block_height;
scanlines.resize(block_height * AntialiasLevel); scanlines.resize(block_height);
first_scanline = scanlines.size(); first_scanline = scanlines.size();
last_scanline = 0; last_scanline = 0;
} }
@ -209,7 +215,7 @@ void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width,
{ {
for (int i = 0; i < count_y; i++) for (int i = 0; i < count_y; i++)
{ {
uint8_t* dline = dest + (dest_y + i) * dest_width; uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width;
memset(dline, 255, count_x); memset(dline, 255, count_x);
} }
} }
@ -218,7 +224,7 @@ void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width,
for (int i = 0; i < count_y; i++) for (int i = 0; i < count_y; i++)
{ {
const uint8_t* sline = mask_blocks.MaskBufferData + i * MaskBlockSize; const uint8_t* sline = mask_blocks.MaskBufferData + i * MaskBlockSize;
uint8_t* dline = dest + (dest_y + i) * dest_width; uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width;
for (int j = 0; j < count_x; j++) for (int j = 0; j < count_x; j++)
{ {
dline[j] = std::min((int)dline[j] + (int)sline[j], 255); dline[j] = std::min((int)dline[j] + (int)sline[j], 255);
@ -238,16 +244,11 @@ PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* sc
if (scanline->edges.empty()) if (scanline->edges.empty())
continue; continue;
if (scanline->edges[0].x < extent.left) extent.left = std::min(extent.left, (int)scanline->edges.front().x);
extent.left = scanline->edges[0].x; extent.right = std::max(extent.right, (int)scanline->edges.back().x);
if (scanline->edges[scanline->edges.size() - 1].x > extent.right)
extent.right = scanline->edges[scanline->edges.size() - 1].x;
} }
if (extent.left < 0) extent.left = std::max(extent.left, 0);
extent.left = 0; extent.right = std::min(extent.right, max_width);
if (extent.right > max_width)
extent.right = max_width;
return extent; return extent;
} }
@ -397,7 +398,7 @@ void PathFillRasterizer::Line(double x1, double y1)
int end_y = static_cast<int>(std::floor(std::max(y0, y1) - 0.5f)) + 1; int end_y = static_cast<int>(std::floor(std::max(y0, y1) - 0.5f)) + 1;
start_y = std::max(start_y, 0); start_y = std::max(start_y, 0);
end_y = std::min(end_y, height * AntialiasLevel); end_y = std::min(end_y, height);
double rcp_dy = 1.0 / dy; double rcp_dy = 1.0 / dy;

View file

@ -0,0 +1,832 @@
#include "core/truetypefont.h"
#include "core/pathfill.h"
#include <algorithm>
#include <cmath>
TrueTypeFont::TrueTypeFont(std::vector<uint8_t> initdata) : data(std::move(initdata))
{
if (data.size() > 0x7fffffff)
throw std::runtime_error("TTF file is larger than 2 gigabytes!");
TrueTypeFileReader reader(data.data(), data.size());
directory.Load(reader);
if (!directory.ContainsTTFOutlines())
throw std::runtime_error("Only truetype outline fonts are supported");
// Load required tables:
reader = directory.GetReader(data.data(), data.size(), "head");
head.Load(reader);
reader = directory.GetReader(data.data(), data.size(), "hhea");
hhea.Load(reader);
reader = directory.GetReader(data.data(), data.size(), "maxp");
maxp.Load(reader);
reader = directory.GetReader(data.data(), data.size(), "hmtx");
hmtx.Load(hhea, maxp, reader);
reader = directory.GetReader(data.data(), data.size(), "name");
name.Load(reader);
reader = directory.GetReader(data.data(), data.size(), "OS/2");
os2.Load(reader);
reader = directory.GetReader(data.data(), data.size(), "cmap");
cmap.Load(reader);
LoadCharacterMapEncoding(reader);
// Load TTF Outlines:
reader = directory.GetReader(data.data(), data.size(), "loca");
loca.Load(head, maxp, reader);
glyf = directory.GetRecord("glyf");
LoadGlyph(GetGlyphIndex(32), 13.0);
}
TrueTypeTextMetrics TrueTypeFont::GetTextMetrics(double height) const
{
double scale = height / head.unitsPerEm;
TrueTypeTextMetrics metrics;
metrics.ascender = os2.sTypoAscender * scale;
metrics.descender = os2.sTypoDescender * scale;
metrics.lineGap = os2.sTypoLineGap * scale;
return metrics;
}
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;
ttf_uint16 advanceWidth = 0;
ttf_int16 lsb = 0;
if (glyphIndex >= hhea.numberOfHMetrics)
{
advanceWidth = hmtx.hMetrics[hhea.numberOfHMetrics - 1].advanceWidth;
lsb = hmtx.leftSideBearings[glyphIndex - hhea.numberOfHMetrics];
}
else
{
advanceWidth = hmtx.hMetrics[glyphIndex].advanceWidth;
lsb = hmtx.hMetrics[glyphIndex].lsb;
}
// Glyph is missing if the offset is the same as the next glyph (0 bytes glyph length)
bool missing = glyphIndex + 1 < loca.offsets.size() ? loca.offsets[glyphIndex] == loca.offsets[glyphIndex + 1] : false;
if (missing)
{
TrueTypeGlyph glyph;
// TBD: gridfit or not?
glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX);
glyph.leftSideBearing = (int)std::round(lsb * scale * scaleX);
glyph.yOffset = 0;
return glyph;
}
TrueTypeFileReader reader = glyf.GetReader(data.data(), data.size());
reader.Seek(loca.offsets[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;
}
// Create glyph path:
PathFillDesc path;
path.fill_mode = PathFillMode::winding;
int startPoint = 0;
for (ttf_uint16 i = 0; i < numberOfContours; i++)
{
int endPoint = endPtsOfContours[i];
if (endPoint < startPoint)
throw std::runtime_error("Invalid glyph");
int pos = startPoint;
while (pos <= endPoint)
{
if (pos == startPoint)
{
path.MoveTo(Point(points[pos].x, points[pos].y) * scale);
pos++;
}
else if (flags[pos] & TTF_ON_CURVE_POINT)
{
if (flags[pos - 1] & TTF_ON_CURVE_POINT)
{
path.LineTo(Point(points[pos].x, 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);
}
pos++;
}
else
{
Point lastcontrolpoint(points[pos].x, points[pos].y);
Point controlpoint(points[pos - 1].x, points[pos - 1].y);
Point midpoint = (lastcontrolpoint + controlpoint) / 2;
path.BezierTo(lastcontrolpoint * scale, midpoint * scale);
pos++;
}
}
path.Close();
startPoint = endPoint + 1;
}
// Transform and find the final bounding box
Point bboxMin, bboxMax;
if (!path.subpaths.front().points.empty())
{
bboxMin = path.subpaths.front().points.front();
bboxMax = path.subpaths.front().points.front();
bboxMin.x *= scaleX;
bboxMin.y *= scaleY;
bboxMax.x *= scaleX;
bboxMax.y *= scaleY;
for (auto& subpath : path.subpaths)
{
for (auto& point : subpath.points)
{
point.x *= scaleX;
point.y *= scaleY;
bboxMin.x = std::min(bboxMin.x, point.x);
bboxMin.y = std::min(bboxMin.y, point.y);
bboxMax.x = std::max(bboxMax.x, point.x);
bboxMax.y = std::max(bboxMax.y, point.y);
}
}
}
bboxMin.x = std::floor(bboxMin.x);
bboxMin.y = std::floor(bboxMin.y);
// Reposition glyph to bitmap so it begins at (0,0) for our bitmap
for (auto& subpath : path.subpaths)
{
for (auto& point : subpath.points)
{
point.x -= bboxMin.x;
point.y -= bboxMin.y;
}
}
TrueTypeGlyph glyph;
// Rasterize the glyph
glyph.width = (int)std::floor(bboxMax.x - bboxMin.x) + 1;
glyph.height = (int)std::floor(bboxMax.y - bboxMin.y) + 1;
glyph.grayscale.reset(new uint8_t[glyph.width * glyph.height]);
uint8_t* grayscale = glyph.grayscale.get();
path.Rasterize(grayscale, glyph.width, glyph.height);
// TBD: gridfit or not?
glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX);
glyph.leftSideBearing = (int)std::round(lsb * scale * scaleX + bboxMin.x);
glyph.yOffset = (int)std::round(bboxMin.y);
return glyph;
}
else if (numberOfContours < 0) // Composite glyph
{
ttf_uint16 flags = reader.ReadUInt16();
ttf_uint16 glyphIndex = reader.ReadUInt16();
// To do: implement this
return {};
}
return {};
}
uint32_t TrueTypeFont::GetGlyphIndex(uint32_t c) const
{
auto it = std::lower_bound(Ranges.begin(), Ranges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; });
if (it != Ranges.end() && c >= it->startCharCode && c <= it->endCharCode)
{
return it->startGlyphID + (c - it->startCharCode);
}
it = std::lower_bound(ManyToOneRanges.begin(), ManyToOneRanges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; });
if (it != ManyToOneRanges.end() && c >= it->startCharCode && c <= it->endCharCode)
{
return it->startGlyphID;
}
return 0;
}
void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader)
{
// Look for the best encoding available that we support
TTF_EncodingRecord record;
if (!record.subtableOffset) record = cmap.GetEncoding(3, 12);
if (!record.subtableOffset) record = cmap.GetEncoding(0, 4);
if (!record.subtableOffset) record = cmap.GetEncoding(3, 1);
if (!record.subtableOffset) record = cmap.GetEncoding(0, 3);
if (!record.subtableOffset)
throw std::runtime_error("No supported cmap encoding found in truetype file");
reader.Seek(record.subtableOffset);
ttf_uint16 format = reader.ReadUInt16();
if (format == 4)
{
TTF_CMapSubtable4 subformat;
subformat.Load(reader);
for (uint16_t i = 0; i < subformat.segCount; i++)
{
ttf_uint16 startCode = subformat.startCode[i];
ttf_uint16 endCode = subformat.endCode[i];
ttf_uint16 idDelta = subformat.idDelta[i];
ttf_uint16 idRangeOffset = subformat.idRangeOffsets[i];
if (idRangeOffset == 0)
{
ttf_uint16 glyphId = startCode + idDelta; // Note: relies on modulo 65536
TTF_GlyphRange range;
range.startCharCode = startCode;
range.endCharCode = endCode;
range.startGlyphID = glyphId;
Ranges.push_back(range);
}
else if (startCode <= endCode)
{
TTF_GlyphRange range;
range.startCharCode = startCode;
bool firstGlyph = true;
for (ttf_uint16 c = startCode; c <= endCode; c++)
{
int offset = idRangeOffset / 2 + (c - startCode) - ((int)subformat.segCount - i);
if (offset >= 0 && offset < subformat.glyphIdArray.size())
{
int glyphId = subformat.glyphIdArray[offset];
if (firstGlyph)
{
range.startGlyphID = glyphId;
firstGlyph = false;
}
else if (range.startGlyphID + (c - range.startCharCode) != glyphId)
{
range.endCharCode = c - 1;
Ranges.push_back(range);
range.startCharCode = c;
range.startGlyphID = glyphId;
}
}
}
if (!firstGlyph)
{
range.endCharCode = endCode;
Ranges.push_back(range);
}
}
}
}
else if (format == 12 || format == 3)
{
TTF_CMapSubtable12 subformat;
subformat.Load(reader);
Ranges = std::move(subformat.groups);
}
else if (format == 13 || format == 3)
{
TTF_CMapSubtable13 subformat;
subformat.Load(reader);
ManyToOneRanges = std::move(subformat.groups);
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_CMapSubtable0::Load(TrueTypeFileReader& reader)
{
length = reader.ReadUInt16();
language = reader.ReadUInt16();
glyphIdArray.resize(256);
reader.Read(glyphIdArray.data(), glyphIdArray.size());
}
/////////////////////////////////////////////////////////////////////////////
void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader)
{
length = reader.ReadUInt16();
language = reader.ReadUInt16();
segCount = reader.ReadUInt16() / 2;
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
endCode.reserve(segCount);
startCode.reserve(segCount);
idDelta.reserve(segCount);
idRangeOffsets.reserve(segCount);
for (ttf_uint16 i = 0; i < segCount; i++) endCode.push_back(reader.ReadUInt16());
reservedPad = reader.ReadUInt16();
for (ttf_uint16 i = 0; i < segCount; i++) startCode.push_back(reader.ReadUInt16());
for (ttf_uint16 i = 0; i < segCount; i++) idDelta.push_back(reader.ReadInt16());
for (ttf_uint16 i = 0; i < segCount; i++) idRangeOffsets.push_back(reader.ReadUInt16());
int glyphIdArraySize = ((int)length - (8 + (int)segCount * 4) * sizeof(ttf_uint16)) / 2;
if (glyphIdArraySize < 0)
throw std::runtime_error("Invalid TTF cmap subtable 4 length");
glyphIdArray.reserve(glyphIdArraySize);
for (int i = 0; i < glyphIdArraySize; i++) glyphIdArray.push_back(reader.ReadUInt16());
}
/////////////////////////////////////////////////////////////////////////////
void TTF_CMapSubtable12::Load(TrueTypeFileReader& reader)
{
reserved = reader.ReadUInt16();
length = reader.ReadUInt32();
language = reader.ReadUInt32();
numGroups = reader.ReadUInt32();
for (ttf_uint32 i = 0; i < numGroups; i++)
{
TTF_GlyphRange range;
range.startCharCode = reader.ReadUInt32();
range.endCharCode = reader.ReadUInt32();
range.startGlyphID = reader.ReadUInt32();
groups.push_back(range);
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_TableRecord::Load(TrueTypeFileReader& reader)
{
tableTag = reader.ReadTag();
checksum = reader.ReadUInt32();
offset = reader.ReadOffset32();
length = reader.ReadUInt32();
}
/////////////////////////////////////////////////////////////////////////////
void TTF_TableDirectory::Load(TrueTypeFileReader& reader)
{
sfntVersion = reader.ReadUInt32();
numTables = reader.ReadUInt16();
// opentype spec says we can't use these for security reasons, so we pretend they never was part of the header
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
for (ttf_uint16 i = 0; i < numTables; i++)
{
TTF_TableRecord record;
record.Load(reader);
tableRecords.push_back(record);
}
}
/////////////////////////////////////////////////////////////////////////////
void TTC_Header::Load(TrueTypeFileReader& reader)
{
ttcTag = reader.ReadTag();
majorVersion = reader.ReadUInt16();
minorVersion = reader.ReadUInt16();
if (!(majorVersion == 1 && minorVersion == 0) && !(majorVersion == 2 && minorVersion == 0))
throw std::runtime_error("Unsupported TTC header version");
numFonts = reader.ReadUInt32();
for (ttf_uint16 i = 0; i < numFonts; i++)
{
tableDirectoryOffsets.push_back(reader.ReadOffset32());
}
if (majorVersion == 2 && minorVersion == 0)
{
dsigTag = reader.ReadUInt32();
dsigLength = reader.ReadUInt32();
dsigOffset = reader.ReadUInt32();
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_CMap::Load(TrueTypeFileReader& reader)
{
version = reader.ReadUInt16();
numTables = reader.ReadUInt16();
for (ttf_uint16 i = 0; i < numTables; i++)
{
TTF_EncodingRecord record;
record.platformID = reader.ReadUInt16();
record.encodingID = reader.ReadUInt16();
record.subtableOffset = reader.ReadOffset32();
encodingRecords.push_back(record);
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_FontHeader::Load(TrueTypeFileReader& reader)
{
majorVersion = reader.ReadUInt16();
minorVersion = reader.ReadUInt16();
fontRevision = reader.ReadFixed();
checksumAdjustment = reader.ReadUInt32();
magicNumber = reader.ReadUInt32();
flags = reader.ReadUInt16();
unitsPerEm = reader.ReadUInt16();
created = reader.ReadLONGDATETIME();
modified = reader.ReadLONGDATETIME();
xMin = reader.ReadInt16();
yMin = reader.ReadInt16();
xMax = reader.ReadInt16();
yMax = reader.ReadInt16();
macStyle = reader.ReadUInt16();
lowestRecPPEM = reader.ReadUInt16();
fontDirectionHint = reader.ReadInt16();
indexToLocFormat = reader.ReadInt16();
glyphDataFormat = reader.ReadInt16();
}
/////////////////////////////////////////////////////////////////////////////
void TTF_HorizontalHeader::Load(TrueTypeFileReader& reader)
{
majorVersion = reader.ReadUInt16();
minorVersion = reader.ReadUInt16();
ascender = reader.ReadFWORD();
descender = reader.ReadFWORD();
lineGap = reader.ReadFWORD();
advanceWidthMax = reader.ReadUFWORD();
minLeftSideBearing = reader.ReadFWORD();
minRightSideBearing = reader.ReadFWORD();
xMaxExtent = reader.ReadFWORD();
caretSlopeRise = reader.ReadInt16();
caretSlopeRun = reader.ReadInt16();
caretOffset = reader.ReadInt16();
reserved0 = reader.ReadInt16();
reserved1 = reader.ReadInt16();
reserved2 = reader.ReadInt16();
reserved3 = reader.ReadInt16();
metricDataFormat = reader.ReadInt16();
numberOfHMetrics = reader.ReadUInt16();
}
/////////////////////////////////////////////////////////////////////////////
void TTF_HorizontalMetrics::Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader)
{
for (ttf_uint16 i = 0; i < hhea.numberOfHMetrics; i++)
{
longHorMetric metric;
metric.advanceWidth = reader.ReadUInt16();
metric.lsb = reader.ReadInt16();
hMetrics.push_back(metric);
}
int count = (int)maxp.numGlyphs - (int)hhea.numberOfHMetrics;
if (count < 0)
throw std::runtime_error("Invalid TTF file");
for (int i = 0; i < count; i++)
{
leftSideBearings.push_back(reader.ReadInt16());
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_MaximumProfile::Load(TrueTypeFileReader& reader)
{
version = reader.ReadVersion16Dot16();
numGlyphs = reader.ReadUInt16();
if (version == (1 << 16)) // v1 only
{
maxPoints = reader.ReadUInt16();
maxContours = reader.ReadUInt16();
maxCompositePoints = reader.ReadUInt16();
maxCompositeContours = reader.ReadUInt16();
maxZones = reader.ReadUInt16();
maxTwilightPoints = reader.ReadUInt16();
maxStorage = reader.ReadUInt16();
maxFunctionDefs = reader.ReadUInt16();
maxInstructionDefs = reader.ReadUInt16();
maxStackElements = reader.ReadUInt16();
maxSizeOfInstructions = reader.ReadUInt16();
maxComponentElements = reader.ReadUInt16();
maxComponentDepth = reader.ReadUInt16();
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_NamingTable::Load(TrueTypeFileReader& reader)
{
version = reader.ReadUInt16();
count = reader.ReadUInt16();
storageOffset = reader.ReadOffset16();
for (ttf_uint16 i = 0; i < count; i++)
{
NameRecord record;
record.platformID = reader.ReadUInt16();
record.encodingID = reader.ReadUInt16();
record.languageID = reader.ReadUInt16();
record.nameID = reader.ReadUInt16();
record.length = reader.ReadUInt16();
record.stringOffset = reader.ReadOffset16();
nameRecord.push_back(record);
}
if (version == 1)
{
langTagCount = reader.ReadUInt16();
for (ttf_uint16 i = 0; i < langTagCount; i++)
{
LangTagRecord record;
ttf_uint16 length;
ttf_Offset16 langTagOffset;
langTagRecord.push_back(record);
}
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_OS2Windows::Load(TrueTypeFileReader& reader)
{
version = reader.ReadUInt16();
xAvgCharWidth = reader.ReadInt16();
usWeightClass = reader.ReadUInt16();
usWidthClass = reader.ReadUInt16();
fsType = reader.ReadUInt16();
ySubscriptXSize = reader.ReadInt16();
ySubscriptYSize = reader.ReadInt16();
ySubscriptXOffset = reader.ReadInt16();
ySubscriptYOffset = reader.ReadInt16();
ySuperscriptXSize = reader.ReadInt16();
ySuperscriptYSize = reader.ReadInt16();
ySuperscriptXOffset = reader.ReadInt16();
ySuperscriptYOffset = reader.ReadInt16();
yStrikeoutSize = reader.ReadInt16();
yStrikeoutPosition = reader.ReadInt16();
sFamilyClass = reader.ReadInt16();
for (int i = 0; i < 10; i++)
{
panose[i] = reader.ReadUInt8();
}
ulUnicodeRange1 = reader.ReadUInt32();
ulUnicodeRange2 = reader.ReadUInt32();
ulUnicodeRange3 = reader.ReadUInt32();
ulUnicodeRange4 = reader.ReadUInt32();
achVendID = reader.ReadTag();
fsSelection = reader.ReadUInt16();
usFirstCharIndex = reader.ReadUInt16();
usLastCharIndex = reader.ReadUInt16();
sTypoAscender = reader.ReadInt16();
sTypoDescender = reader.ReadInt16();
sTypoLineGap = reader.ReadInt16();
if (!reader.IsEndOfData()) // may be missing in v0 due to a bug in Apple's TTF documentation
{
usWinAscent = reader.ReadUInt16();
usWinDescent = reader.ReadUInt16();
}
if (version >= 1)
{
ulCodePageRange1 = reader.ReadUInt32();
ulCodePageRange2 = reader.ReadUInt32();
}
if (version >= 2)
{
sxHeight = reader.ReadInt16();
sCapHeight = reader.ReadInt16();
usDefaultChar = reader.ReadUInt16();
usBreakChar = reader.ReadUInt16();
usMaxContext = reader.ReadUInt16();
}
if (version >= 5)
{
usLowerOpticalPointSize = reader.ReadUInt16();
usUpperOpticalPointSize = reader.ReadUInt16();
}
}
/////////////////////////////////////////////////////////////////////////////
void TTF_IndexToLocation::Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader)
{
int count = (int)maxp.numGlyphs + 1;
if (head.indexToLocFormat == 0)
{
offsets.reserve(count);
for (int i = 0; i < count; i++)
{
offsets.push_back((ttf_Offset32)reader.ReadOffset16() * 2);
}
}
else
{
offsets.reserve(count);
for (int i = 0; i < count; i++)
{
offsets.push_back(reader.ReadOffset32());
}
}
}
/////////////////////////////////////////////////////////////////////////////
ttf_uint8 TrueTypeFileReader::ReadUInt8()
{
ttf_uint8 v; Read(&v, 1); return v;
}
ttf_uint16 TrueTypeFileReader::ReadUInt16()
{
ttf_uint8 v[2]; Read(v, 2); return (((ttf_uint16)v[0]) << 8) | (ttf_uint16)v[1];
}
ttf_uint24 TrueTypeFileReader::ReadUInt24()
{
ttf_uint8 v[3]; Read(v, 3); return (((ttf_uint32)v[0]) << 16) | (((ttf_uint32)v[1]) << 8) | (ttf_uint32)v[2];
}
ttf_uint32 TrueTypeFileReader::ReadUInt32()
{
ttf_uint8 v[4]; Read(v, 4); return (((ttf_uint32)v[0]) << 24) | (((ttf_uint32)v[1]) << 16) | (((ttf_uint32)v[2]) << 8) | (ttf_uint32)v[3];
}
ttf_int8 TrueTypeFileReader::ReadInt8()
{
return ReadUInt8();
}
ttf_int16 TrueTypeFileReader::ReadInt16()
{
return ReadUInt16();
}
ttf_int32 TrueTypeFileReader::ReadInt32()
{
return ReadUInt32();
}
ttf_Fixed TrueTypeFileReader::ReadFixed()
{
return ReadUInt32();
}
ttf_UFWORD TrueTypeFileReader::ReadUFWORD()
{
return ReadUInt16();
}
ttf_FWORD TrueTypeFileReader::ReadFWORD()
{
return ReadUInt16();
}
ttf_F2DOT14 TrueTypeFileReader::ReadF2DOT14()
{
return ReadUInt16();
}
ttf_LONGDATETIME TrueTypeFileReader::ReadLONGDATETIME()
{
ttf_uint8 v[8]; Read(v, 8);
return
(((ttf_LONGDATETIME)v[0]) << 56) | (((ttf_LONGDATETIME)v[1]) << 48) | (((ttf_LONGDATETIME)v[2]) << 40) | (((ttf_LONGDATETIME)v[3]) << 32) |
(((ttf_LONGDATETIME)v[4]) << 24) | (((ttf_LONGDATETIME)v[5]) << 16) | (((ttf_LONGDATETIME)v[6]) << 8) | (ttf_LONGDATETIME)v[7];
}
ttf_Tag TrueTypeFileReader::ReadTag()
{
ttf_Tag v; Read(v.data(), v.size()); return v;
}
ttf_Offset16 TrueTypeFileReader::ReadOffset16()
{
return ReadUInt16();
}
ttf_Offset24 TrueTypeFileReader::ReadOffset24()
{
return ReadUInt24();
}
ttf_Offset32 TrueTypeFileReader::ReadOffset32()
{
return ReadUInt32();
}
ttf_Version16Dot16 TrueTypeFileReader::ReadVersion16Dot16()
{
return ReadUInt32();
}
void TrueTypeFileReader::Seek(size_t newpos)
{
if (newpos > size)
throw std::runtime_error("Invalid TTF file");
pos = newpos;
}
void TrueTypeFileReader::Read(void* output, size_t count)
{
if (pos + count > size)
throw std::runtime_error("Unexpected end of TTF file");
memcpy(output, data + pos, count);
pos += count;
}

View file

@ -0,0 +1,453 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <stdexcept>
#include <memory>
typedef uint8_t ttf_uint8;
typedef uint16_t ttf_uint16;
typedef uint32_t ttf_uint24; // 24-bit unsigned integer
typedef uint32_t ttf_uint32;
typedef int8_t ttf_int8;
typedef int16_t ttf_int16;
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 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
typedef uint16_t ttf_Offset16; // Short offset to a table, same as uint16, NULL offset = 0x0000
typedef uint32_t ttf_Offset24; // 24-bit offset to a table, same as uint24, NULL offset = 0x000000
typedef uint32_t ttf_Offset32; // Long offset to a table, same as uint32, NULL offset = 0x00000000
typedef uint32_t ttf_Version16Dot16; // Packed 32-bit value with major and minor version numbers
class TrueTypeFileReader
{
public:
TrueTypeFileReader() = default;
TrueTypeFileReader(const void* data, size_t size) : data(static_cast<const uint8_t*>(data)), size(size) { }
bool IsEndOfData() const { return pos == size; }
ttf_uint8 ReadUInt8();
ttf_uint16 ReadUInt16();
ttf_uint24 ReadUInt24();
ttf_uint32 ReadUInt32();
ttf_int8 ReadInt8();
ttf_int16 ReadInt16();
ttf_int32 ReadInt32();
ttf_Fixed ReadFixed();
ttf_UFWORD ReadUFWORD();
ttf_FWORD ReadFWORD();
ttf_F2DOT14 ReadF2DOT14();
ttf_LONGDATETIME ReadLONGDATETIME();
ttf_Tag ReadTag();
ttf_Offset16 ReadOffset16();
ttf_Offset24 ReadOffset24();
ttf_Offset32 ReadOffset32();
ttf_Version16Dot16 ReadVersion16Dot16();
void Seek(size_t newpos);
void Read(void* output, size_t count);
private:
const uint8_t* data = nullptr;
size_t size = 0;
size_t pos = 0;
};
struct TTF_TableRecord
{
ttf_Tag tableTag = {};
ttf_uint32 checksum = {};
ttf_Offset32 offset = {};
ttf_uint32 length = {};
void Load(TrueTypeFileReader& reader);
TrueTypeFileReader GetReader(const void* filedata, size_t filesize) const
{
if ((size_t)offset + length > filesize)
throw std::runtime_error("Invalid TTF table directory record");
return TrueTypeFileReader((uint8_t*)filedata + offset, length);
}
};
struct TTF_TableDirectory
{
ttf_uint32 sfntVersion = {};
ttf_uint16 numTables = {};
std::vector<TTF_TableRecord> tableRecords;
// To do: Apple TTF fonts allow 'true' and 'typ1' for sfntVersion as well.
bool ContainsTTFOutlines() const { return sfntVersion == 0x00010000; }
bool ContainsCFFData() const { return sfntVersion == 0x4F54544F; }
void Load(TrueTypeFileReader& reader);
const TTF_TableRecord& GetRecord(const char* tag) const
{
for (const auto& record : tableRecords)
{
if (memcmp(record.tableTag.data(), tag, 4) == 0)
{
return record;
}
}
throw std::runtime_error(std::string("Could not find required '") + tag + "' table entry");
}
TrueTypeFileReader GetReader(const void* filedata, size_t filesize, const char* tag) const
{
return GetRecord(tag).GetReader(filedata, filesize);
}
};
struct TTC_Header
{
ttf_Tag ttcTag = {};
ttf_uint16 majorVersion = {};
ttf_uint16 minorVersion = {};
ttf_uint32 numFonts = {};
std::vector<ttf_Offset32> tableDirectoryOffsets;
// majorVersion = 2, minorVersion = 0:
ttf_uint32 dsigTag = {};
ttf_uint32 dsigLength = {};
ttf_uint32 dsigOffset = {};
void Load(TrueTypeFileReader& reader);
};
struct TTF_EncodingRecord
{
ttf_uint16 platformID = {};
ttf_uint16 encodingID = {};
ttf_Offset32 subtableOffset = {};
};
struct TTF_CMap // 'cmap' Character to glyph mapping
{
ttf_uint16 version = {};
ttf_uint16 numTables = {};
std::vector<TTF_EncodingRecord> encodingRecords; // [numTables]
void Load(TrueTypeFileReader& reader);
TTF_EncodingRecord GetEncoding(ttf_uint16 platformID, ttf_uint16 encodingID) const
{
for (const TTF_EncodingRecord& record : encodingRecords)
{
if (record.platformID == platformID && record.encodingID == encodingID)
{
return record;
}
}
return {};
}
};
struct TTF_GlyphRange
{
ttf_uint32 startCharCode = 0;
ttf_uint32 endCharCode = 0;
ttf_uint32 startGlyphID = 0;
};
struct TTF_CMapSubtable0 // Byte encoding table
{
ttf_uint16 length = {};
ttf_uint16 language = {};
std::vector<ttf_uint8> glyphIdArray;
void Load(TrueTypeFileReader& reader);
};
struct TTF_CMapSubtable4 // Segment mapping to delta values (U+0000 to U+FFFF)
{
ttf_uint16 length = {};
ttf_uint16 language = {};
ttf_uint16 segCount = {};
std::vector<ttf_uint16> endCode;
ttf_uint16 reservedPad = {};
std::vector<ttf_uint16> startCode;
std::vector<ttf_int16> idDelta;
std::vector<ttf_uint16> idRangeOffsets;
std::vector<ttf_uint16> glyphIdArray;
void Load(TrueTypeFileReader& reader);
};
struct TTF_CMapSubtable12 // Segmented coverage (U+0000 to U+10FFFF)
{
ttf_uint16 reserved;
ttf_uint32 length;
ttf_uint32 language;
ttf_uint32 numGroups;
std::vector<TTF_GlyphRange> groups;
void Load(TrueTypeFileReader& reader);
};
typedef TTF_CMapSubtable12 TTF_CMapSubtable13; // Many-to-one range mappings
struct TTF_FontHeader // 'head' Font header
{
ttf_uint16 majorVersion = {};
ttf_uint16 minorVersion = {};
ttf_Fixed fontRevision = {};
ttf_uint32 checksumAdjustment = {};
ttf_uint32 magicNumber = {};
ttf_uint16 flags = {};
ttf_uint16 unitsPerEm = {};
ttf_LONGDATETIME created = {};
ttf_LONGDATETIME modified = {};
ttf_int16 xMin = {};
ttf_int16 yMin = {};
ttf_int16 xMax = {};
ttf_int16 yMax = {};
ttf_uint16 macStyle = {};
ttf_uint16 lowestRecPPEM = {};
ttf_int16 fontDirectionHint = {};
ttf_int16 indexToLocFormat = {};
ttf_int16 glyphDataFormat = {};
void Load(TrueTypeFileReader& reader);
};
struct TTF_HorizontalHeader // 'hhea' Horizontal header
{
ttf_uint16 majorVersion = {};
ttf_uint16 minorVersion = {};
ttf_FWORD ascender = {};
ttf_FWORD descender = {};
ttf_FWORD lineGap = {};
ttf_UFWORD advanceWidthMax = {};
ttf_FWORD minLeftSideBearing = {};
ttf_FWORD minRightSideBearing = {};
ttf_FWORD xMaxExtent = {};
ttf_int16 caretSlopeRise = {};
ttf_int16 caretSlopeRun = {};
ttf_int16 caretOffset = {};
ttf_int16 reserved0 = {};
ttf_int16 reserved1 = {};
ttf_int16 reserved2 = {};
ttf_int16 reserved3 = {};
ttf_int16 metricDataFormat = {};
ttf_uint16 numberOfHMetrics = {};
void Load(TrueTypeFileReader& reader);
};
struct TTF_MaximumProfile;
struct TTF_HorizontalMetrics // 'hmtx' Horizontal metrics
{
struct longHorMetric
{
ttf_uint16 advanceWidth = {};
ttf_int16 lsb = {};
};
std::vector<longHorMetric> hMetrics; // [hhea.numberOfHMetrics]
std::vector<ttf_int16> leftSideBearings; // [maxp.numGlyphs - hhea.numberOfHMetrics]
void Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader);
};
struct TTF_MaximumProfile // 'maxp' Maximum profile
{
// v0.5 and v1:
ttf_Version16Dot16 version = {};
ttf_uint16 numGlyphs = {};
// v1 only:
ttf_uint16 maxPoints = {};
ttf_uint16 maxContours = {};
ttf_uint16 maxCompositePoints = {};
ttf_uint16 maxCompositeContours = {};
ttf_uint16 maxZones = {};
ttf_uint16 maxTwilightPoints = {};
ttf_uint16 maxStorage = {};
ttf_uint16 maxFunctionDefs = {};
ttf_uint16 maxInstructionDefs = {};
ttf_uint16 maxStackElements = {};
ttf_uint16 maxSizeOfInstructions = {};
ttf_uint16 maxComponentElements = {};
ttf_uint16 maxComponentDepth = {};
void Load(TrueTypeFileReader& reader);
};
struct TTF_NamingTable // 'name' Naming table
{
struct NameRecord
{
ttf_uint16 platformID = {};
ttf_uint16 encodingID = {};
ttf_uint16 languageID = {};
ttf_uint16 nameID = {};
ttf_uint16 length = {};
ttf_Offset16 stringOffset = {};
};
struct LangTagRecord
{
ttf_uint16 length = {};
ttf_Offset16 langTagOffset = {};
};
// v0 and v1:
ttf_uint16 version = {};
ttf_uint16 count = {};
ttf_Offset16 storageOffset = {};
std::vector<NameRecord> nameRecord; // [count]
// v1 only:
ttf_uint16 langTagCount = {};
std::vector<LangTagRecord> langTagRecord; // [langTagCount]
void Load(TrueTypeFileReader& reader);
};
struct TTF_OS2Windows // 'OS/2' Windows specific metrics
{
ttf_uint16 version = {};
ttf_int16 xAvgCharWidth = {};
ttf_uint16 usWeightClass = {};
ttf_uint16 usWidthClass = {};
ttf_uint16 fsType = {};
ttf_int16 ySubscriptXSize = {};
ttf_int16 ySubscriptYSize = {};
ttf_int16 ySubscriptXOffset = {};
ttf_int16 ySubscriptYOffset = {};
ttf_int16 ySuperscriptXSize = {};
ttf_int16 ySuperscriptYSize = {};
ttf_int16 ySuperscriptXOffset = {};
ttf_int16 ySuperscriptYOffset = {};
ttf_int16 yStrikeoutSize = {};
ttf_int16 yStrikeoutPosition = {};
ttf_int16 sFamilyClass = {};
ttf_uint8 panose[10] = {};
ttf_uint32 ulUnicodeRange1 = {};
ttf_uint32 ulUnicodeRange2 = {};
ttf_uint32 ulUnicodeRange3 = {};
ttf_uint32 ulUnicodeRange4 = {};
ttf_Tag achVendID = {};
ttf_uint16 fsSelection = {};
ttf_uint16 usFirstCharIndex = {};
ttf_uint16 usLastCharIndex = {};
ttf_int16 sTypoAscender = {};
ttf_int16 sTypoDescender = {};
ttf_int16 sTypoLineGap = {};
ttf_uint16 usWinAscent = {}; // may be missing in v0 due to bugs in Apple docs
ttf_uint16 usWinDescent = {}; // may be missing in v0 due to bugs in Apple docs
ttf_uint32 ulCodePageRange1 = {}; // v1
ttf_uint32 ulCodePageRange2 = {};
ttf_int16 sxHeight = {}; // v2, v3 and v4
ttf_int16 sCapHeight = {};
ttf_uint16 usDefaultChar = {};
ttf_uint16 usBreakChar = {};
ttf_uint16 usMaxContext = {};
ttf_uint16 usLowerOpticalPointSize = {}; // v5
ttf_uint16 usUpperOpticalPointSize = {};
void Load(TrueTypeFileReader& reader);
};
// Simple glyph flags:
#define TTF_ON_CURVE_POINT 0x01
#define TTF_X_SHORT_VECTOR 0x02
#define TTF_Y_SHORT_VECTOR 0x04
#define TTF_REPEAT_FLAG 0x08
#define TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR 0x10
#define TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR 0x20
#define TTF_OVERLAP_SIMPLE = 0x40
// Composite glyph flags:
#define TTF_ARG_1_AND_2_ARE_WORDS 0x0001
#define TTF_ARGS_ARE_XY_VALUES 0x0002
#define TTF_ROUND_XY_TO_GRID 0x0004
#define TTF_WE_HAVE_A_SCALE 0x0008
#define TTF_MORE_COMPONENTS 0x0020
#define TTF_WE_HAVE_AN_X_AND_Y_SCALE 0x0040
#define TTF_WE_HAVE_A_TWO_BY_TWO 0x0080
#define TTF_WE_HAVE_INSTRUCTIONS 0x0100
#define TTF_USE_MY_METRICS 0x0200
#define TTF_OVERLAP_COMPOUND 0x0400
#define TTF_SCALED_COMPONENT_OFFSET 0x0800
#define TTF_UNSCALED_COMPONENT_OFFSET 0x1000
struct TTF_IndexToLocation // 'loca' Index to location
{
std::vector<ttf_Offset32> offsets;
void Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader);
};
struct TTF_Point
{
ttf_int16 x;
ttf_int16 y;
};
class TrueTypeGlyph
{
public:
int advanceWidth = 0;
int leftSideBearing = 0;
int yOffset = 0;
int width = 0;
int height = 0;
std::unique_ptr<uint8_t[]> grayscale;
};
class TrueTypeTextMetrics
{
public:
double ascender = 0.0;
double descender = 0.0;
double lineGap = 0.0;
};
class TrueTypeFont
{
public:
TrueTypeFont(std::vector<uint8_t> data);
TrueTypeTextMetrics GetTextMetrics(double height) const;
uint32_t GetGlyphIndex(uint32_t codepoint) const;
TrueTypeGlyph LoadGlyph(uint32_t glyphIndex, double height) const;
private:
void LoadCharacterMapEncoding(TrueTypeFileReader& reader);
std::vector<uint8_t> data;
TTF_TableDirectory directory;
// Required for all OpenType fonts:
TTF_CMap cmap;
TTF_FontHeader head;
TTF_HorizontalHeader hhea;
TTF_HorizontalMetrics hmtx;
TTF_MaximumProfile maxp;
TTF_NamingTable name;
TTF_OS2Windows os2;
// TrueType outlines:
TTF_TableRecord glyf; // Parsed on a per glyph basis using offsets from other tables
TTF_IndexToLocation loca;
std::vector<TTF_GlyphRange> Ranges;
std::vector<TTF_GlyphRange> ManyToOneRanges;
};