mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-25 21:41:03 +00:00
Add internal truetype font class
This commit is contained in:
parent
2334a88229
commit
cbb89315e7
5 changed files with 1412 additions and 15 deletions
|
@ -10,6 +10,8 @@ set(ZWIDGET_SOURCES
|
|||
src/core/widget.cpp
|
||||
src/core/utf8reader.cpp
|
||||
src/core/pathfill.cpp
|
||||
src/core/truetypefont.cpp
|
||||
src/core/truetypefont.h
|
||||
src/core/schrift/schrift.cpp
|
||||
src/core/schrift/schrift.h
|
||||
src/core/picopng/picopng.cpp
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "core/utf8reader.h"
|
||||
#include "core/resourcedata.h"
|
||||
#include "core/image.h"
|
||||
#include "core/truetypefont.h"
|
||||
#include "core/pathfill.h"
|
||||
#include "window/window.h"
|
||||
#include "schrift/schrift.h"
|
||||
#include <vector>
|
||||
|
@ -12,6 +14,8 @@
|
|||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
// #define USE_INTERNAL_TTF
|
||||
|
||||
class CanvasTexture
|
||||
{
|
||||
public:
|
||||
|
@ -20,6 +24,109 @@ public:
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -152,6 +259,8 @@ private:
|
|||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class CanvasFontGroup
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -122,6 +122,12 @@ private:
|
|||
|
||||
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();
|
||||
|
||||
// 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_height = MaskBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize);
|
||||
int block_width = ScanlineBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize);
|
||||
int block_height = ScanlineBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize);
|
||||
|
||||
if (width != block_width || height != block_height)
|
||||
{
|
||||
width = block_width;
|
||||
height = block_height;
|
||||
|
||||
scanlines.resize(block_height * AntialiasLevel);
|
||||
scanlines.resize(block_height);
|
||||
first_scanline = scanlines.size();
|
||||
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++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +224,7 @@ void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width,
|
|||
for (int i = 0; i < count_y; i++)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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())
|
||||
continue;
|
||||
|
||||
if (scanline->edges[0].x < extent.left)
|
||||
extent.left = scanline->edges[0].x;
|
||||
|
||||
if (scanline->edges[scanline->edges.size() - 1].x > extent.right)
|
||||
extent.right = scanline->edges[scanline->edges.size() - 1].x;
|
||||
extent.left = std::min(extent.left, (int)scanline->edges.front().x);
|
||||
extent.right = std::max(extent.right, (int)scanline->edges.back().x);
|
||||
}
|
||||
if (extent.left < 0)
|
||||
extent.left = 0;
|
||||
if (extent.right > max_width)
|
||||
extent.right = max_width;
|
||||
extent.left = std::max(extent.left, 0);
|
||||
extent.right = std::min(extent.right, max_width);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
|
|
832
libraries/ZWidget/src/core/truetypefont.cpp
Normal file
832
libraries/ZWidget/src/core/truetypefont.cpp
Normal 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;
|
||||
}
|
453
libraries/ZWidget/src/core/truetypefont.h
Normal file
453
libraries/ZWidget/src/core/truetypefont.h
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue