From cbb89315e70e5dcdb181f5581804c365c2ac0e2d Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 7 Jan 2024 11:39:17 +0100 Subject: [PATCH] Add internal truetype font class --- libraries/ZWidget/CMakeLists.txt | 2 + libraries/ZWidget/src/core/canvas.cpp | 109 +++ libraries/ZWidget/src/core/pathfill.cpp | 31 +- libraries/ZWidget/src/core/truetypefont.cpp | 832 ++++++++++++++++++++ libraries/ZWidget/src/core/truetypefont.h | 453 +++++++++++ 5 files changed, 1412 insertions(+), 15 deletions(-) create mode 100644 libraries/ZWidget/src/core/truetypefont.cpp create mode 100644 libraries/ZWidget/src/core/truetypefont.h diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 823b6ee884..894a916e3c 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -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 diff --git a/libraries/ZWidget/src/core/canvas.cpp b/libraries/ZWidget/src/core/canvas.cpp index 26f137ca89..e30c390d64 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.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 @@ -12,6 +14,8 @@ #include #include +// #define USE_INTERNAL_TTF + class CanvasTexture { public: @@ -20,6 +24,109 @@ public: std::vector 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 texture; +}; + +class CanvasFont +{ +public: + CanvasFont(const std::string& fontname, double height) : fontname(fontname), height(height) + { + ttf = std::make_unique(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(); + + 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(); + 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 ttf; + + std::string fontname; + double height = 0.0; + + TrueTypeTextMetrics textmetrics; + std::unordered_map> glyphs; +}; + +#else + class CanvasGlyph { public: @@ -152,6 +259,8 @@ private: std::vector data; }; +#endif + class CanvasFontGroup { public: diff --git a/libraries/ZWidget/src/core/pathfill.cpp b/libraries/ZWidget/src/core/pathfill.cpp index fb67951e64..bac3dfb507 100644 --- a/libraries/ZWidget/src/core/pathfill.cpp +++ b/libraries/ZWidget/src/core/pathfill.cpp @@ -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(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; diff --git a/libraries/ZWidget/src/core/truetypefont.cpp b/libraries/ZWidget/src/core/truetypefont.cpp new file mode 100644 index 0000000000..867898c681 --- /dev/null +++ b/libraries/ZWidget/src/core/truetypefont.cpp @@ -0,0 +1,832 @@ + +#include "core/truetypefont.h" +#include "core/pathfill.h" +#include +#include + +TrueTypeFont::TrueTypeFont(std::vector 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 endPtsOfContours; + endPtsOfContours.reserve(numberOfContours); + for (ttf_uint16 i = 0; i < numberOfContours; i++) + endPtsOfContours.push_back(reader.ReadUInt16()); + + ttf_uint16 instructionLength = reader.ReadUInt16(); + std::vector instructions; + instructions.resize(instructionLength); + reader.Read(instructions.data(), instructions.size()); + + int numPoints = (int)endPtsOfContours.back() + 1; + std::vector points(numPoints); + + std::vector 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; +} diff --git a/libraries/ZWidget/src/core/truetypefont.h b/libraries/ZWidget/src/core/truetypefont.h new file mode 100644 index 0000000000..dabc1bf59a --- /dev/null +++ b/libraries/ZWidget/src/core/truetypefont.h @@ -0,0 +1,453 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 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(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 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 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 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 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 endCode; + ttf_uint16 reservedPad = {}; + std::vector startCode; + std::vector idDelta; + std::vector idRangeOffsets; + std::vector 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 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 hMetrics; // [hhea.numberOfHMetrics] + std::vector 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; // [count] + + // v1 only: + ttf_uint16 langTagCount = {}; + std::vector 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 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 grayscale; +}; + +class TrueTypeTextMetrics +{ +public: + double ascender = 0.0; + double descender = 0.0; + double lineGap = 0.0; +}; + +class TrueTypeFont +{ +public: + TrueTypeFont(std::vector 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 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 Ranges; + std::vector ManyToOneRanges; +};