mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
font/opentype: add table parsing for head, hhea, post, and OS/2
(and a utility for parsing SFNT font files and fetching table data from them)
This commit is contained in:
@ -1,6 +1,16 @@
|
|||||||
|
pub const sfnt = @import("opentype/sfnt.zig");
|
||||||
|
|
||||||
const svg = @import("opentype/svg.zig");
|
const svg = @import("opentype/svg.zig");
|
||||||
|
const os2 = @import("opentype/os2.zig");
|
||||||
|
const post = @import("opentype/post.zig");
|
||||||
|
const hhea = @import("opentype/hhea.zig");
|
||||||
|
const head = @import("opentype/head.zig");
|
||||||
|
|
||||||
pub const SVG = svg.SVG;
|
pub const SVG = svg.SVG;
|
||||||
|
pub const OS2 = os2.OS2;
|
||||||
|
pub const Post = post.Post;
|
||||||
|
pub const Hhea = hhea.Hhea;
|
||||||
|
pub const Head = head.Head;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
179
src/font/opentype/head.zig
Normal file
179
src/font/opentype/head.zig
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const sfnt = @import("sfnt.zig");
|
||||||
|
|
||||||
|
/// Font Header Table
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/head
|
||||||
|
pub const Head = extern struct {
|
||||||
|
/// Major version number of the font header table — set to 1.
|
||||||
|
majorVersion: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Minor version number of the font header table — set to 0.
|
||||||
|
minorVersion: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Set by font manufacturer.
|
||||||
|
fontRevision: sfnt.Fixed align(1),
|
||||||
|
|
||||||
|
/// To compute: set it to 0, sum the entire font as uint32, then store
|
||||||
|
/// 0xB1B0AFBA - sum. If the font is used as a component in a font
|
||||||
|
/// collection file, the value of this field will be invalidated by
|
||||||
|
/// changes to the file structure and font table directory, and must
|
||||||
|
/// be ignored.
|
||||||
|
checksumAdjustment: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Set to 0x5F0F3CF5.
|
||||||
|
magicNumber: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Bit 0: Baseline for font at y=0.
|
||||||
|
///
|
||||||
|
/// Bit 1: Left sidebearing point at x=0
|
||||||
|
/// (relevant only for TrueType rasterizers)
|
||||||
|
///
|
||||||
|
/// Bit 2: Instructions may depend on point size.
|
||||||
|
///
|
||||||
|
/// Bit 3: Force ppem to integer values for all internal scaler math; may
|
||||||
|
/// use fractional ppem sizes if this bit is clear. It is strongly
|
||||||
|
/// recommended that this be set in hinted fonts.
|
||||||
|
///
|
||||||
|
/// Bit 4: Instructions may alter advance width
|
||||||
|
/// (the advance widths might not scale linearly).
|
||||||
|
///
|
||||||
|
/// Bit 5: This bit is not used in OpenType, and should not be set in order
|
||||||
|
/// to ensure compatible behavior on all platforms. If set, it may
|
||||||
|
/// result in different behavior for vertical layout in some
|
||||||
|
/// platforms.
|
||||||
|
///
|
||||||
|
/// (See Apple’s specification for details
|
||||||
|
/// regarding behavior in Apple platforms.)
|
||||||
|
///
|
||||||
|
/// Bits 6 – 10: These bits are not used in OpenType and should always be
|
||||||
|
/// cleared.
|
||||||
|
///
|
||||||
|
/// (See Apple’s specification for details
|
||||||
|
/// regarding legacy use in Apple platforms.)
|
||||||
|
///
|
||||||
|
/// Bit 11: Font data is “lossless” as a result of having been
|
||||||
|
/// subjected to optimizing transformation and/or compression
|
||||||
|
/// (such as compression mechanisms defined by ISO/IEC 14496-18,
|
||||||
|
/// MicroType® Express, WOFF 2.0, or similar) where the original
|
||||||
|
/// font functionality and features are retained but the binary
|
||||||
|
/// compatibility between input and output font files is not
|
||||||
|
/// guaranteed. As a result of the applied transform, the DSIG
|
||||||
|
/// table may also be invalidated.
|
||||||
|
///
|
||||||
|
/// Bit 12: Font converted (produce compatible metrics).
|
||||||
|
///
|
||||||
|
/// Bit 13: Font optimized for ClearType®. Note, fonts that rely on embedded
|
||||||
|
/// bitmaps (EBDT) for rendering should not be considered optimized
|
||||||
|
/// for ClearType, and therefore should keep this bit cleared.
|
||||||
|
///
|
||||||
|
/// Bit 14: Last Resort font. If set, indicates that the glyphs encoded in
|
||||||
|
/// the 'cmap' subtables are simply generic symbolic representations
|
||||||
|
/// of code point ranges and do not truly represent support for
|
||||||
|
/// those code points. If unset, indicates that the glyphs encoded
|
||||||
|
/// in the 'cmap' subtables represent proper support for those code
|
||||||
|
/// points.
|
||||||
|
///
|
||||||
|
/// Bit 15: Reserved, set to 0.
|
||||||
|
flags: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Set to a value from 16 to 16384. Any value in this range is valid.
|
||||||
|
///
|
||||||
|
/// In fonts that have TrueType outlines, a power of 2 is recommended
|
||||||
|
/// as this allows performance optimization in some rasterizers.
|
||||||
|
unitsPerEm: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Number of seconds since 12:00 midnight that started
|
||||||
|
/// January 1st, 1904, in GMT/UTC time zone.
|
||||||
|
created: sfnt.LONGDATETIME align(1),
|
||||||
|
|
||||||
|
/// Number of seconds since 12:00 midnight that started
|
||||||
|
/// January 1st, 1904, in GMT/UTC time zone.
|
||||||
|
modified: sfnt.LONGDATETIME align(1),
|
||||||
|
|
||||||
|
/// Minimum x coordinate across all glyph bounding boxes.
|
||||||
|
xMin: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Minimum y coordinate across all glyph bounding boxes.
|
||||||
|
yMin: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Maximum x coordinate across all glyph bounding boxes.
|
||||||
|
xMax: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Maximum y coordinate across all glyph bounding boxes.
|
||||||
|
yMax: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Bit 0: Bold (if set to 1);
|
||||||
|
/// Bit 1: Italic (if set to 1)
|
||||||
|
/// Bit 2: Underline (if set to 1)
|
||||||
|
/// Bit 3: Outline (if set to 1)
|
||||||
|
/// Bit 4: Shadow (if set to 1)
|
||||||
|
/// Bit 5: Condensed (if set to 1)
|
||||||
|
/// Bit 6: Extended (if set to 1)
|
||||||
|
/// Bits 7 – 15: Reserved (set to 0).
|
||||||
|
macStyle: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Smallest readable size in pixels.
|
||||||
|
lowestRecPPEM: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Deprecated (Set to 2).
|
||||||
|
/// 0: Fully mixed directional glyphs;
|
||||||
|
/// 1: Only strongly left to right;
|
||||||
|
/// 2: Like 1 but also contains neutrals;
|
||||||
|
/// -1: Only strongly right to left;
|
||||||
|
/// -2: Like -1 but also contains neutrals.
|
||||||
|
fontDirectionHint: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// 0 for short offsets (Offset16), 1 for long (Offset32).
|
||||||
|
indexToLocFormat: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// 0 for current format.
|
||||||
|
glyphDataFormat: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Parse the table from raw data.
|
||||||
|
pub fn init(data: []const u8) !Head {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
return try reader.readStructEndian(Head, .big);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "head" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const font = try sfnt.SFNT.init(test_font, alloc);
|
||||||
|
defer font.deinit(alloc);
|
||||||
|
|
||||||
|
const table = font.getTable("head").?;
|
||||||
|
|
||||||
|
const head = try Head.init(table);
|
||||||
|
|
||||||
|
try testing.expectEqualDeep(
|
||||||
|
Head{
|
||||||
|
.majorVersion = 1,
|
||||||
|
.minorVersion = 0,
|
||||||
|
.fontRevision = sfnt.Fixed.from(0.05499267578125),
|
||||||
|
.checksumAdjustment = 1007668681,
|
||||||
|
.magicNumber = 1594834165,
|
||||||
|
.flags = 7,
|
||||||
|
.unitsPerEm = 2000,
|
||||||
|
.created = 3797757830,
|
||||||
|
.modified = 3797760444,
|
||||||
|
.xMin = -1000,
|
||||||
|
.yMin = -1058,
|
||||||
|
.xMax = 3089,
|
||||||
|
.yMax = 2400,
|
||||||
|
.macStyle = 0,
|
||||||
|
.lowestRecPPEM = 7,
|
||||||
|
.fontDirectionHint = 2,
|
||||||
|
.indexToLocFormat = 1,
|
||||||
|
.glyphDataFormat = 0,
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
);
|
||||||
|
}
|
115
src/font/opentype/hhea.zig
Normal file
115
src/font/opentype/hhea.zig
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const sfnt = @import("sfnt.zig");
|
||||||
|
|
||||||
|
/// Horizontal Header Table
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/hhea
|
||||||
|
pub const Hhea = extern struct {
|
||||||
|
/// Major version number of the horizontal header table — set to 1.
|
||||||
|
majorVersion: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Minor version number of the horizontal header table — set to 0.
|
||||||
|
minorVersion: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Typographic ascent—see remarks below.
|
||||||
|
ascender: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Typographic descent—see remarks below.
|
||||||
|
descender: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Typographic line gap.
|
||||||
|
///
|
||||||
|
/// Negative lineGap values are treated as zero
|
||||||
|
/// in some legacy platform implementations.
|
||||||
|
lineGap: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Maximum advance width value in 'hmtx' table.
|
||||||
|
advanceWidthMax: sfnt.UFWORD align(1),
|
||||||
|
|
||||||
|
/// Minimum left sidebearing value in 'hmtx' table for
|
||||||
|
/// glyphs with contours (empty glyphs should be ignored).
|
||||||
|
minLeftSideBearing: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Minimum right sidebearing value; calculated as
|
||||||
|
/// min(aw - (lsb + xMax - xMin)) for glyphs with
|
||||||
|
/// contours (empty glyphs should be ignored).
|
||||||
|
minRightSideBearing: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Max(lsb + (xMax - xMin)).
|
||||||
|
xMaxExtent: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Used to calculate the slope of the cursor (rise/run); 1 for vertical.
|
||||||
|
caretSlopeRise: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// 0 for vertical.
|
||||||
|
caretSlopeRun: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// The amount by which a slanted highlight on a glyph needs to be shifted
|
||||||
|
/// to produce the best appearance. Set to 0 for non-slanted fonts
|
||||||
|
caretOffset: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// set to 0
|
||||||
|
_reserved0: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// set to 0
|
||||||
|
_reserved1: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// set to 0
|
||||||
|
_reserved2: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// set to 0
|
||||||
|
_reserved3: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// 0 for current format.
|
||||||
|
metricDataFormat: sfnt.int16 align(1),
|
||||||
|
|
||||||
|
/// Number of hMetric entries in 'hmtx' table
|
||||||
|
numberOfHMetrics: sfnt.uint16 align(1),
|
||||||
|
|
||||||
|
/// Parse the table from raw data.
|
||||||
|
pub fn init(data: []const u8) !Hhea {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
return try reader.readStructEndian(Hhea, .big);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "hhea" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const font = try sfnt.SFNT.init(test_font, alloc);
|
||||||
|
defer font.deinit(alloc);
|
||||||
|
|
||||||
|
const table = font.getTable("hhea").?;
|
||||||
|
|
||||||
|
const hhea = try Hhea.init(table);
|
||||||
|
|
||||||
|
try testing.expectEqualDeep(
|
||||||
|
Hhea{
|
||||||
|
.majorVersion = 1,
|
||||||
|
.minorVersion = 0,
|
||||||
|
.ascender = 1900,
|
||||||
|
.descender = -450,
|
||||||
|
.lineGap = 0,
|
||||||
|
.advanceWidthMax = 1200,
|
||||||
|
.minLeftSideBearing = -1000,
|
||||||
|
.minRightSideBearing = -1889,
|
||||||
|
.xMaxExtent = 3089,
|
||||||
|
.caretSlopeRise = 1,
|
||||||
|
.caretSlopeRun = 0,
|
||||||
|
.caretOffset = 0,
|
||||||
|
._reserved0 = 0,
|
||||||
|
._reserved1 = 0,
|
||||||
|
._reserved2 = 0,
|
||||||
|
._reserved3 = 0,
|
||||||
|
.metricDataFormat = 0,
|
||||||
|
.numberOfHMetrics = 2,
|
||||||
|
},
|
||||||
|
hhea,
|
||||||
|
);
|
||||||
|
}
|
577
src/font/opentype/os2.zig
Normal file
577
src/font/opentype/os2.zig
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const sfnt = @import("sfnt.zig");
|
||||||
|
|
||||||
|
pub const FSSelection = packed struct(sfnt.uint16) {
|
||||||
|
/// Font contains italic or oblique glyphs, otherwise they are upright.
|
||||||
|
italic: bool = false,
|
||||||
|
|
||||||
|
/// Glyphs are underscored.
|
||||||
|
underscore: bool = false,
|
||||||
|
|
||||||
|
/// Glyphs have their foreground and background reversed.
|
||||||
|
negative: bool = false,
|
||||||
|
|
||||||
|
/// Outline (hollow) glyphs, otherwise they are solid.
|
||||||
|
outlined: bool = false,
|
||||||
|
|
||||||
|
/// Glyphs are overstruck.
|
||||||
|
strikeout: bool = false,
|
||||||
|
|
||||||
|
/// Glyphs are emboldened.
|
||||||
|
bold: bool = false,
|
||||||
|
|
||||||
|
/// Glyphs are in the standard weight/style for the font.
|
||||||
|
regular: bool = false,
|
||||||
|
|
||||||
|
/// If set, it is strongly recommended that applications use
|
||||||
|
/// OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap
|
||||||
|
/// as the default line spacing for this font.
|
||||||
|
use_typo_metrics: bool = false,
|
||||||
|
|
||||||
|
/// The font has 'name' table strings consistent with a weight/width/slope
|
||||||
|
/// family without requiring use of name IDs 21 and 22.
|
||||||
|
wws: bool = false,
|
||||||
|
|
||||||
|
/// Font contains oblique glyphs.
|
||||||
|
oblique: bool = false,
|
||||||
|
|
||||||
|
_reserved: u6 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// OS/2 and Windows Metrics Table
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/os2
|
||||||
|
pub const OS2v5 = extern struct {
|
||||||
|
version: sfnt.uint16 align(1),
|
||||||
|
xAvgCharWidth: sfnt.FWORD align(1),
|
||||||
|
usWeightClass: sfnt.uint16 align(1),
|
||||||
|
usWidthClass: sfnt.uint16 align(1),
|
||||||
|
fsType: sfnt.uint16 align(1),
|
||||||
|
ySubscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutSize: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutPosition: sfnt.FWORD align(1),
|
||||||
|
sFamilyClass: sfnt.int16 align(1),
|
||||||
|
panose: [10]sfnt.uint8 align(1),
|
||||||
|
ulUnicodeRange1: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange2: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange3: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange4: sfnt.uint32 align(1),
|
||||||
|
achVendID: sfnt.Tag align(1),
|
||||||
|
fsSelection: FSSelection align(1),
|
||||||
|
usFirstCharIndex: sfnt.uint16 align(1),
|
||||||
|
usLastCharIndex: sfnt.uint16 align(1),
|
||||||
|
sTypoAscender: sfnt.FWORD align(1),
|
||||||
|
sTypoDescender: sfnt.FWORD align(1),
|
||||||
|
sTypoLineGap: sfnt.FWORD align(1),
|
||||||
|
usWinAscent: sfnt.UFWORD align(1),
|
||||||
|
usWinDescent: sfnt.UFWORD align(1),
|
||||||
|
ulCodePageRange1: sfnt.uint32 align(1),
|
||||||
|
ulCodePageRange2: sfnt.uint32 align(1),
|
||||||
|
sxHeight: sfnt.FWORD align(1),
|
||||||
|
sCapHeight: sfnt.FWORD align(1),
|
||||||
|
usDefaultChar: sfnt.uint16 align(1),
|
||||||
|
usBreakChar: sfnt.uint16 align(1),
|
||||||
|
usMaxContext: sfnt.uint16 align(1),
|
||||||
|
usLowerOpticalPointSize: sfnt.uint16 align(1),
|
||||||
|
usUpperOpticalPointSize: sfnt.uint16 align(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OS2v4_3_2 = extern struct {
|
||||||
|
version: sfnt.uint16 align(1),
|
||||||
|
xAvgCharWidth: sfnt.FWORD align(1),
|
||||||
|
usWeightClass: sfnt.uint16 align(1),
|
||||||
|
usWidthClass: sfnt.uint16 align(1),
|
||||||
|
fsType: sfnt.uint16 align(1),
|
||||||
|
ySubscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutSize: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutPosition: sfnt.FWORD align(1),
|
||||||
|
sFamilyClass: sfnt.int16 align(1),
|
||||||
|
panose: [10]sfnt.uint8 align(1),
|
||||||
|
ulUnicodeRange1: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange2: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange3: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange4: sfnt.uint32 align(1),
|
||||||
|
achVendID: sfnt.Tag align(1),
|
||||||
|
fsSelection: FSSelection align(1),
|
||||||
|
usFirstCharIndex: sfnt.uint16 align(1),
|
||||||
|
usLastCharIndex: sfnt.uint16 align(1),
|
||||||
|
sTypoAscender: sfnt.FWORD align(1),
|
||||||
|
sTypoDescender: sfnt.FWORD align(1),
|
||||||
|
sTypoLineGap: sfnt.FWORD align(1),
|
||||||
|
usWinAscent: sfnt.UFWORD align(1),
|
||||||
|
usWinDescent: sfnt.UFWORD align(1),
|
||||||
|
ulCodePageRange1: sfnt.uint32 align(1),
|
||||||
|
ulCodePageRange2: sfnt.uint32 align(1),
|
||||||
|
sxHeight: sfnt.FWORD align(1),
|
||||||
|
sCapHeight: sfnt.FWORD align(1),
|
||||||
|
usDefaultChar: sfnt.uint16 align(1),
|
||||||
|
usBreakChar: sfnt.uint16 align(1),
|
||||||
|
usMaxContext: sfnt.uint16 align(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OS2v1 = extern struct {
|
||||||
|
version: sfnt.uint16 align(1),
|
||||||
|
xAvgCharWidth: sfnt.FWORD align(1),
|
||||||
|
usWeightClass: sfnt.uint16 align(1),
|
||||||
|
usWidthClass: sfnt.uint16 align(1),
|
||||||
|
fsType: sfnt.uint16 align(1),
|
||||||
|
ySubscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutSize: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutPosition: sfnt.FWORD align(1),
|
||||||
|
sFamilyClass: sfnt.int16 align(1),
|
||||||
|
panose: [10]sfnt.uint8 align(1),
|
||||||
|
ulUnicodeRange1: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange2: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange3: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange4: sfnt.uint32 align(1),
|
||||||
|
achVendID: sfnt.Tag align(1),
|
||||||
|
fsSelection: FSSelection align(1),
|
||||||
|
usFirstCharIndex: sfnt.uint16 align(1),
|
||||||
|
usLastCharIndex: sfnt.uint16 align(1),
|
||||||
|
sTypoAscender: sfnt.FWORD align(1),
|
||||||
|
sTypoDescender: sfnt.FWORD align(1),
|
||||||
|
sTypoLineGap: sfnt.FWORD align(1),
|
||||||
|
usWinAscent: sfnt.UFWORD align(1),
|
||||||
|
usWinDescent: sfnt.UFWORD align(1),
|
||||||
|
ulCodePageRange1: sfnt.uint32 align(1),
|
||||||
|
ulCodePageRange2: sfnt.uint32 align(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OS2v0 = extern struct {
|
||||||
|
version: sfnt.uint16 align(1),
|
||||||
|
xAvgCharWidth: sfnt.FWORD align(1),
|
||||||
|
usWeightClass: sfnt.uint16 align(1),
|
||||||
|
usWidthClass: sfnt.uint16 align(1),
|
||||||
|
fsType: sfnt.uint16 align(1),
|
||||||
|
ySubscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySubscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySubscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYSize: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptXOffset: sfnt.FWORD align(1),
|
||||||
|
ySuperscriptYOffset: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutSize: sfnt.FWORD align(1),
|
||||||
|
yStrikeoutPosition: sfnt.FWORD align(1),
|
||||||
|
sFamilyClass: sfnt.int16 align(1),
|
||||||
|
panose: [10]sfnt.uint8 align(1),
|
||||||
|
ulUnicodeRange1: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange2: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange3: sfnt.uint32 align(1),
|
||||||
|
ulUnicodeRange4: sfnt.uint32 align(1),
|
||||||
|
achVendID: sfnt.Tag align(1),
|
||||||
|
fsSelection: FSSelection align(1),
|
||||||
|
usFirstCharIndex: sfnt.uint16 align(1),
|
||||||
|
usLastCharIndex: sfnt.uint16 align(1),
|
||||||
|
sTypoAscender: sfnt.FWORD align(1),
|
||||||
|
sTypoDescender: sfnt.FWORD align(1),
|
||||||
|
sTypoLineGap: sfnt.FWORD align(1),
|
||||||
|
usWinAscent: sfnt.UFWORD align(1),
|
||||||
|
usWinDescent: sfnt.UFWORD align(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Generic OS/2 table with optional fields
|
||||||
|
/// for those that don't exist in all versions.
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/os2
|
||||||
|
pub const OS2 = struct {
|
||||||
|
/// The version number for the OS/2 table: 0x0000 to 0x0005.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#version
|
||||||
|
version: u16,
|
||||||
|
/// The Average Character Width field specifies the arithmetic average of the escapement (width) of all non-zero width glyphs in the font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#xavgcharwidth
|
||||||
|
xAvgCharWidth: i16,
|
||||||
|
/// Indicates the visual weight (degree of blackness or thickness of strokes) of the characters in the font. Values from 1 to 1000 are valid.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usweightclass
|
||||||
|
usWeightClass: u16,
|
||||||
|
/// Indicates a relative change from the normal aspect ratio (width to height ratio) as specified by a font designer for the glyphs in a font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswidthclass
|
||||||
|
usWidthClass: u16,
|
||||||
|
/// Indicates font embedding licensing rights for the font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#fstype
|
||||||
|
fsType: u16,
|
||||||
|
/// The recommended horizontal size in font design units for subscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptxsize
|
||||||
|
ySubscriptXSize: i16,
|
||||||
|
/// The recommended vertical size in font design units for subscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptysize
|
||||||
|
ySubscriptYSize: i16,
|
||||||
|
/// The recommended horizontal offset in font design units for subscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptxoffset
|
||||||
|
ySubscriptXOffset: i16,
|
||||||
|
/// The recommended vertical offset in font design units from the baseline for subscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysubscriptyoffset
|
||||||
|
ySubscriptYOffset: i16,
|
||||||
|
/// The recommended horizontal size in font design units for superscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptxsize
|
||||||
|
ySuperscriptXSize: i16,
|
||||||
|
/// The recommended vertical size in font design units for superscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptysize
|
||||||
|
ySuperscriptYSize: i16,
|
||||||
|
/// The recommended horizontal offset in font design units for superscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptxoffset
|
||||||
|
ySuperscriptXOffset: i16,
|
||||||
|
/// The recommended vertical offset in font design units from the baseline for superscripts for this font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ysuperscriptyoffset
|
||||||
|
ySuperscriptYOffset: i16,
|
||||||
|
/// Thickness of the strikeout stroke in font design units. Should be > 0.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ystrikeoutsize
|
||||||
|
yStrikeoutSize: i16,
|
||||||
|
/// The position of the top of the strikeout stroke relative to the baseline in font design units.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ystrikeoutposition
|
||||||
|
yStrikeoutPosition: i16,
|
||||||
|
/// This field provides a classification of font-family design.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#sfamilyclass
|
||||||
|
sFamilyClass: i16,
|
||||||
|
/// This 10-byte array of numbers is used to describe the visual characteristics of a given typeface.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#panose
|
||||||
|
panose: [10]u8,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
|
||||||
|
ulUnicodeRange1: u32,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
|
||||||
|
ulUnicodeRange2: u32,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
|
||||||
|
ulUnicodeRange3: u32,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulunicoderange
|
||||||
|
ulUnicodeRange4: u32,
|
||||||
|
/// The four character identifier for the vendor of the given type face.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#achvendid
|
||||||
|
achVendID: [4]u8,
|
||||||
|
/// Contains information concerning the nature of the font patterns.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#fsselection
|
||||||
|
fsSelection: FSSelection,
|
||||||
|
/// The minimum Unicode index (character code) in this font, according to the 'cmap' subtable for platform ID 3 and platform-specific encoding ID 0 or 1.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usfirstcharindex
|
||||||
|
usFirstCharIndex: u16,
|
||||||
|
/// The maximum Unicode index (character code) in this font, according to the 'cmap' subtable for platform ID 3 and encoding ID 0 or 1.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uslastcharindex
|
||||||
|
usLastCharIndex: u16,
|
||||||
|
/// The typographic ascender for this font. This field should be combined with the sTypoDescender and sTypoLineGap values to determine default line spacing.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypoascender
|
||||||
|
sTypoAscender: i16,
|
||||||
|
/// The typographic descender for this font. This field should be combined with the sTypoAscender and sTypoLineGap values to determine default line spacing.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypodescender
|
||||||
|
sTypoDescender: i16,
|
||||||
|
/// The typographic line gap for this font. This field should be combined with the sTypoAscender and sTypoDescender values to determine default line spacing.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#stypolinegap
|
||||||
|
sTypoLineGap: i16,
|
||||||
|
/// The “Windows ascender” metric. This should be used to specify the height above the baseline for a clipping region.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswinascent
|
||||||
|
usWinAscent: u16,
|
||||||
|
/// The “Windows descender” metric. This should be used to specify the vertical extent below the baseline for a clipping region.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#uswindescent
|
||||||
|
usWinDescent: u16,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulcodepagerange
|
||||||
|
ulCodePageRange1: ?u32 = null,
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#ulcodepagerange
|
||||||
|
ulCodePageRange2: ?u32 = null,
|
||||||
|
/// This metric specifies the distance between the baseline and the approximate height of non-ascending lowercase letters measured in font design units.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#sxheight
|
||||||
|
sxHeight: ?i16 = null,
|
||||||
|
/// This metric specifies the distance between the baseline and the approximate height of uppercase letters measured in font design units.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#scapheight
|
||||||
|
sCapHeight: ?i16 = null,
|
||||||
|
/// This is the Unicode code point, in UTF-16 encoding, of a character that can be used for a default glyph if a requested character is not supported in the font.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usdefaultchar
|
||||||
|
usDefaultChar: ?u16 = null,
|
||||||
|
/// This is the Unicode code point, in UTF-16 encoding, of a character that can be used as a default break character.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usbreakchar
|
||||||
|
usBreakChar: ?u16 = null,
|
||||||
|
/// The maximum length of a target glyph context for any feature in this font. For example, a font which has only a pair kerning feature should set this field to 2.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usmaxcontext
|
||||||
|
usMaxContext: ?u16 = null,
|
||||||
|
/// This field is used for fonts with multiple optical styles.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usloweropticalpointsize
|
||||||
|
usLowerOpticalPointSize: ?u16 = null,
|
||||||
|
/// This field is used for fonts with multiple optical styles.
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2/#usupperopticalpointsize
|
||||||
|
usUpperOpticalPointSize: ?u16 = null,
|
||||||
|
|
||||||
|
/// Parse the table from raw data.
|
||||||
|
pub fn init(data: []const u8) !OS2 {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
const version = try reader.readInt(sfnt.uint16, .big);
|
||||||
|
|
||||||
|
// Return to the start, cause the version is part of the struct.
|
||||||
|
try fbs.seekTo(0);
|
||||||
|
|
||||||
|
switch (version) {
|
||||||
|
5 => {
|
||||||
|
const table = try reader.readStructEndian(OS2v5, .big);
|
||||||
|
return .{
|
||||||
|
.version = table.version,
|
||||||
|
.xAvgCharWidth = table.xAvgCharWidth,
|
||||||
|
.usWeightClass = table.usWeightClass,
|
||||||
|
.usWidthClass = table.usWidthClass,
|
||||||
|
.fsType = table.fsType,
|
||||||
|
.ySubscriptXSize = table.ySubscriptXSize,
|
||||||
|
.ySubscriptYSize = table.ySubscriptYSize,
|
||||||
|
.ySubscriptXOffset = table.ySubscriptXOffset,
|
||||||
|
.ySubscriptYOffset = table.ySubscriptYOffset,
|
||||||
|
.ySuperscriptXSize = table.ySuperscriptXSize,
|
||||||
|
.ySuperscriptYSize = table.ySuperscriptYSize,
|
||||||
|
.ySuperscriptXOffset = table.ySuperscriptXOffset,
|
||||||
|
.ySuperscriptYOffset = table.ySuperscriptYOffset,
|
||||||
|
.yStrikeoutSize = table.yStrikeoutSize,
|
||||||
|
.yStrikeoutPosition = table.yStrikeoutPosition,
|
||||||
|
.sFamilyClass = table.sFamilyClass,
|
||||||
|
.panose = table.panose,
|
||||||
|
.ulUnicodeRange1 = table.ulUnicodeRange1,
|
||||||
|
.ulUnicodeRange2 = table.ulUnicodeRange2,
|
||||||
|
.ulUnicodeRange3 = table.ulUnicodeRange3,
|
||||||
|
.ulUnicodeRange4 = table.ulUnicodeRange4,
|
||||||
|
.achVendID = table.achVendID,
|
||||||
|
.fsSelection = table.fsSelection,
|
||||||
|
.usFirstCharIndex = table.usFirstCharIndex,
|
||||||
|
.usLastCharIndex = table.usLastCharIndex,
|
||||||
|
.sTypoAscender = table.sTypoAscender,
|
||||||
|
.sTypoDescender = table.sTypoDescender,
|
||||||
|
.sTypoLineGap = table.sTypoLineGap,
|
||||||
|
.usWinAscent = table.usWinAscent,
|
||||||
|
.usWinDescent = table.usWinDescent,
|
||||||
|
.ulCodePageRange1 = table.ulCodePageRange1,
|
||||||
|
.ulCodePageRange2 = table.ulCodePageRange2,
|
||||||
|
.sxHeight = table.sxHeight,
|
||||||
|
.sCapHeight = table.sCapHeight,
|
||||||
|
.usDefaultChar = table.usDefaultChar,
|
||||||
|
.usBreakChar = table.usBreakChar,
|
||||||
|
.usMaxContext = table.usMaxContext,
|
||||||
|
.usLowerOpticalPointSize = table.usLowerOpticalPointSize,
|
||||||
|
.usUpperOpticalPointSize = table.usUpperOpticalPointSize,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
4, 3, 2 => {
|
||||||
|
const table = try reader.readStructEndian(OS2v4_3_2, .big);
|
||||||
|
return .{
|
||||||
|
.version = table.version,
|
||||||
|
.xAvgCharWidth = table.xAvgCharWidth,
|
||||||
|
.usWeightClass = table.usWeightClass,
|
||||||
|
.usWidthClass = table.usWidthClass,
|
||||||
|
.fsType = table.fsType,
|
||||||
|
.ySubscriptXSize = table.ySubscriptXSize,
|
||||||
|
.ySubscriptYSize = table.ySubscriptYSize,
|
||||||
|
.ySubscriptXOffset = table.ySubscriptXOffset,
|
||||||
|
.ySubscriptYOffset = table.ySubscriptYOffset,
|
||||||
|
.ySuperscriptXSize = table.ySuperscriptXSize,
|
||||||
|
.ySuperscriptYSize = table.ySuperscriptYSize,
|
||||||
|
.ySuperscriptXOffset = table.ySuperscriptXOffset,
|
||||||
|
.ySuperscriptYOffset = table.ySuperscriptYOffset,
|
||||||
|
.yStrikeoutSize = table.yStrikeoutSize,
|
||||||
|
.yStrikeoutPosition = table.yStrikeoutPosition,
|
||||||
|
.sFamilyClass = table.sFamilyClass,
|
||||||
|
.panose = table.panose,
|
||||||
|
.ulUnicodeRange1 = table.ulUnicodeRange1,
|
||||||
|
.ulUnicodeRange2 = table.ulUnicodeRange2,
|
||||||
|
.ulUnicodeRange3 = table.ulUnicodeRange3,
|
||||||
|
.ulUnicodeRange4 = table.ulUnicodeRange4,
|
||||||
|
.achVendID = table.achVendID,
|
||||||
|
.fsSelection = table.fsSelection,
|
||||||
|
.usFirstCharIndex = table.usFirstCharIndex,
|
||||||
|
.usLastCharIndex = table.usLastCharIndex,
|
||||||
|
.sTypoAscender = table.sTypoAscender,
|
||||||
|
.sTypoDescender = table.sTypoDescender,
|
||||||
|
.sTypoLineGap = table.sTypoLineGap,
|
||||||
|
.usWinAscent = table.usWinAscent,
|
||||||
|
.usWinDescent = table.usWinDescent,
|
||||||
|
.ulCodePageRange1 = table.ulCodePageRange1,
|
||||||
|
.ulCodePageRange2 = table.ulCodePageRange2,
|
||||||
|
.sxHeight = table.sxHeight,
|
||||||
|
.sCapHeight = table.sCapHeight,
|
||||||
|
.usDefaultChar = table.usDefaultChar,
|
||||||
|
.usBreakChar = table.usBreakChar,
|
||||||
|
.usMaxContext = table.usMaxContext,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
const table = try reader.readStructEndian(OS2v1, .big);
|
||||||
|
return .{
|
||||||
|
.version = table.version,
|
||||||
|
.xAvgCharWidth = table.xAvgCharWidth,
|
||||||
|
.usWeightClass = table.usWeightClass,
|
||||||
|
.usWidthClass = table.usWidthClass,
|
||||||
|
.fsType = table.fsType,
|
||||||
|
.ySubscriptXSize = table.ySubscriptXSize,
|
||||||
|
.ySubscriptYSize = table.ySubscriptYSize,
|
||||||
|
.ySubscriptXOffset = table.ySubscriptXOffset,
|
||||||
|
.ySubscriptYOffset = table.ySubscriptYOffset,
|
||||||
|
.ySuperscriptXSize = table.ySuperscriptXSize,
|
||||||
|
.ySuperscriptYSize = table.ySuperscriptYSize,
|
||||||
|
.ySuperscriptXOffset = table.ySuperscriptXOffset,
|
||||||
|
.ySuperscriptYOffset = table.ySuperscriptYOffset,
|
||||||
|
.yStrikeoutSize = table.yStrikeoutSize,
|
||||||
|
.yStrikeoutPosition = table.yStrikeoutPosition,
|
||||||
|
.sFamilyClass = table.sFamilyClass,
|
||||||
|
.panose = table.panose,
|
||||||
|
.ulUnicodeRange1 = table.ulUnicodeRange1,
|
||||||
|
.ulUnicodeRange2 = table.ulUnicodeRange2,
|
||||||
|
.ulUnicodeRange3 = table.ulUnicodeRange3,
|
||||||
|
.ulUnicodeRange4 = table.ulUnicodeRange4,
|
||||||
|
.achVendID = table.achVendID,
|
||||||
|
.fsSelection = table.fsSelection,
|
||||||
|
.usFirstCharIndex = table.usFirstCharIndex,
|
||||||
|
.usLastCharIndex = table.usLastCharIndex,
|
||||||
|
.sTypoAscender = table.sTypoAscender,
|
||||||
|
.sTypoDescender = table.sTypoDescender,
|
||||||
|
.sTypoLineGap = table.sTypoLineGap,
|
||||||
|
.usWinAscent = table.usWinAscent,
|
||||||
|
.usWinDescent = table.usWinDescent,
|
||||||
|
.ulCodePageRange1 = table.ulCodePageRange1,
|
||||||
|
.ulCodePageRange2 = table.ulCodePageRange2,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
0 => {
|
||||||
|
const table = try reader.readStructEndian(OS2v0, .big);
|
||||||
|
return .{
|
||||||
|
.version = table.version,
|
||||||
|
.xAvgCharWidth = table.xAvgCharWidth,
|
||||||
|
.usWeightClass = table.usWeightClass,
|
||||||
|
.usWidthClass = table.usWidthClass,
|
||||||
|
.fsType = table.fsType,
|
||||||
|
.ySubscriptXSize = table.ySubscriptXSize,
|
||||||
|
.ySubscriptYSize = table.ySubscriptYSize,
|
||||||
|
.ySubscriptXOffset = table.ySubscriptXOffset,
|
||||||
|
.ySubscriptYOffset = table.ySubscriptYOffset,
|
||||||
|
.ySuperscriptXSize = table.ySuperscriptXSize,
|
||||||
|
.ySuperscriptYSize = table.ySuperscriptYSize,
|
||||||
|
.ySuperscriptXOffset = table.ySuperscriptXOffset,
|
||||||
|
.ySuperscriptYOffset = table.ySuperscriptYOffset,
|
||||||
|
.yStrikeoutSize = table.yStrikeoutSize,
|
||||||
|
.yStrikeoutPosition = table.yStrikeoutPosition,
|
||||||
|
.sFamilyClass = table.sFamilyClass,
|
||||||
|
.panose = table.panose,
|
||||||
|
.ulUnicodeRange1 = table.ulUnicodeRange1,
|
||||||
|
.ulUnicodeRange2 = table.ulUnicodeRange2,
|
||||||
|
.ulUnicodeRange3 = table.ulUnicodeRange3,
|
||||||
|
.ulUnicodeRange4 = table.ulUnicodeRange4,
|
||||||
|
.achVendID = table.achVendID,
|
||||||
|
.fsSelection = table.fsSelection,
|
||||||
|
.usFirstCharIndex = table.usFirstCharIndex,
|
||||||
|
.usLastCharIndex = table.usLastCharIndex,
|
||||||
|
.sTypoAscender = table.sTypoAscender,
|
||||||
|
.sTypoDescender = table.sTypoDescender,
|
||||||
|
.sTypoLineGap = table.sTypoLineGap,
|
||||||
|
.usWinAscent = table.usWinAscent,
|
||||||
|
.usWinDescent = table.usWinDescent,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => return error.OS2VersionNotSupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "OS/2" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const font = try sfnt.SFNT.init(test_font, alloc);
|
||||||
|
defer font.deinit(alloc);
|
||||||
|
|
||||||
|
const table = font.getTable("OS/2").?;
|
||||||
|
|
||||||
|
const os2 = try OS2.init(table);
|
||||||
|
|
||||||
|
try testing.expectEqualDeep(OS2{
|
||||||
|
.version = 4,
|
||||||
|
.xAvgCharWidth = 1200,
|
||||||
|
.usWeightClass = 400,
|
||||||
|
.usWidthClass = 5,
|
||||||
|
.fsType = 0,
|
||||||
|
.ySubscriptXSize = 1300,
|
||||||
|
.ySubscriptYSize = 1200,
|
||||||
|
.ySubscriptXOffset = 0,
|
||||||
|
.ySubscriptYOffset = 150,
|
||||||
|
.ySuperscriptXSize = 1300,
|
||||||
|
.ySuperscriptYSize = 1200,
|
||||||
|
.ySuperscriptXOffset = 0,
|
||||||
|
.ySuperscriptYOffset = 700,
|
||||||
|
.yStrikeoutSize = 100,
|
||||||
|
.yStrikeoutPosition = 550,
|
||||||
|
.sFamilyClass = 0,
|
||||||
|
.panose = .{ 2, 11, 6, 9, 6, 3, 0, 2, 0, 4 },
|
||||||
|
.ulUnicodeRange1 = 3843162111,
|
||||||
|
.ulUnicodeRange2 = 3603300351,
|
||||||
|
.ulUnicodeRange3 = 117760229,
|
||||||
|
.ulUnicodeRange4 = 96510060,
|
||||||
|
.achVendID = "corm".*,
|
||||||
|
.fsSelection = .{
|
||||||
|
.regular = true,
|
||||||
|
.use_typo_metrics = true,
|
||||||
|
},
|
||||||
|
.usFirstCharIndex = 13,
|
||||||
|
.usLastCharIndex = 65535,
|
||||||
|
.sTypoAscender = 1900,
|
||||||
|
.sTypoDescender = -450,
|
||||||
|
.sTypoLineGap = 0,
|
||||||
|
.usWinAscent = 2400,
|
||||||
|
.usWinDescent = 450,
|
||||||
|
.ulCodePageRange1 = 1613234687,
|
||||||
|
.ulCodePageRange2 = 0,
|
||||||
|
.sxHeight = 1100,
|
||||||
|
.sCapHeight = 1450,
|
||||||
|
.usDefaultChar = 0,
|
||||||
|
.usBreakChar = 32,
|
||||||
|
.usMaxContext = 126,
|
||||||
|
.usLowerOpticalPointSize = null,
|
||||||
|
.usUpperOpticalPointSize = null,
|
||||||
|
}, os2);
|
||||||
|
}
|
82
src/font/opentype/post.zig
Normal file
82
src/font/opentype/post.zig
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const sfnt = @import("sfnt.zig");
|
||||||
|
|
||||||
|
/// PostScript Table
|
||||||
|
///
|
||||||
|
/// This implementation doesn't parse the
|
||||||
|
/// extra fields in versions 2.0 and 2.5.
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/post
|
||||||
|
pub const Post = extern struct {
|
||||||
|
version: sfnt.Version16Dot16 align(1),
|
||||||
|
|
||||||
|
/// Italic angle in counter-clockwise degrees from the vertical.
|
||||||
|
/// Zero for upright text, negative for text that leans to the
|
||||||
|
/// right (forward).
|
||||||
|
italicAngle: sfnt.Fixed align(1),
|
||||||
|
|
||||||
|
/// Suggested y-coordinate of the top of the underline.
|
||||||
|
underlinePosition: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Suggested values for the underline thickness.
|
||||||
|
/// In general, the underline thickness should match the thickness of
|
||||||
|
/// the underscore character (U+005F LOW LINE), and should also match
|
||||||
|
/// the strikeout thickness, which is specified in the OS/2 table.
|
||||||
|
underlineThickness: sfnt.FWORD align(1),
|
||||||
|
|
||||||
|
/// Set to 0 if the font is proportionally spaced, non-zero if
|
||||||
|
/// the font is not proportionally spaced (i.e. monospaced).
|
||||||
|
isFixedPitch: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Minimum memory usage when an OpenType font is downloaded.
|
||||||
|
minMemType42: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Maximum memory usage when an OpenType font is downloaded.
|
||||||
|
maxMemType42: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Minimum memory usage when an OpenType
|
||||||
|
/// font is downloaded as a Type 1 font.
|
||||||
|
minMemType1: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Maximum memory usage when an OpenType
|
||||||
|
/// font is downloaded as a Type 1 font.
|
||||||
|
maxMemType1: sfnt.uint32 align(1),
|
||||||
|
|
||||||
|
/// Parse the table from raw data.
|
||||||
|
pub fn init(data: []const u8) !Post {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
return try reader.readStructEndian(Post, .big);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "post" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const font = try sfnt.SFNT.init(test_font, alloc);
|
||||||
|
defer font.deinit(alloc);
|
||||||
|
|
||||||
|
const table = font.getTable("post").?;
|
||||||
|
|
||||||
|
const post = try Post.init(table);
|
||||||
|
|
||||||
|
try testing.expectEqualDeep(
|
||||||
|
Post{
|
||||||
|
.version = sfnt.Version16Dot16{ .minor = 0, .major = 2 },
|
||||||
|
.italicAngle = sfnt.Fixed.from(0.0),
|
||||||
|
.underlinePosition = -200,
|
||||||
|
.underlineThickness = 100,
|
||||||
|
.isFixedPitch = 1,
|
||||||
|
.minMemType42 = 0,
|
||||||
|
.maxMemType42 = 0,
|
||||||
|
.minMemType1 = 0,
|
||||||
|
.maxMemType1 = 0,
|
||||||
|
},
|
||||||
|
post,
|
||||||
|
);
|
||||||
|
}
|
314
src/font/opentype/sfnt.zig
Normal file
314
src/font/opentype/sfnt.zig
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// 8-bit unsigned integer.
|
||||||
|
pub const uint8 = u8;
|
||||||
|
|
||||||
|
/// 8-bit signed integer.
|
||||||
|
pub const int8 = i8;
|
||||||
|
|
||||||
|
/// 16-bit unsigned integer.
|
||||||
|
pub const uint16 = u16;
|
||||||
|
|
||||||
|
/// 16-bit signed integer.
|
||||||
|
pub const int16 = i16;
|
||||||
|
|
||||||
|
/// 24-bit unsigned integer.
|
||||||
|
pub const uint24 = u24;
|
||||||
|
|
||||||
|
/// 32-bit unsigned integer.
|
||||||
|
pub const uint32 = u32;
|
||||||
|
|
||||||
|
/// 32-bit signed integer.
|
||||||
|
pub const int32 = i32;
|
||||||
|
|
||||||
|
/// 32-bit signed fixed-point number (16.16)
|
||||||
|
pub const Fixed = FixedPoint(i32, 16, 16);
|
||||||
|
|
||||||
|
/// int16 that describes a quantity in font design units.
|
||||||
|
pub const FWORD = i16;
|
||||||
|
|
||||||
|
/// uint16 that describes a quantity in font design units.
|
||||||
|
pub const UFWORD = u16;
|
||||||
|
|
||||||
|
/// 16-bit signed fixed number with the low 14 bits of fraction (2.14).
|
||||||
|
pub const F2DOT14 = FixedPoint(i16, 2, 14);
|
||||||
|
|
||||||
|
/// Date and time represented in number of seconds since 12:00 midnight, January 1, 1904, UTC. The value is represented as a signed 64-bit integer.
|
||||||
|
pub const LONGDATETIME = i64;
|
||||||
|
|
||||||
|
/// Array of four uint8s (length = 32 bits) used to identify a table,
|
||||||
|
/// design-variation axis, script, language system, feature, or baseline.
|
||||||
|
pub const Tag = [4]u8;
|
||||||
|
|
||||||
|
/// 8-bit offset to a table, same as uint8, NULL offset = 0x00
|
||||||
|
pub const Offset8 = u8;
|
||||||
|
|
||||||
|
/// Short offset to a table, same as uint16, NULL offset = 0x0000
|
||||||
|
pub const Offset16 = u16;
|
||||||
|
|
||||||
|
/// 24-bit offset to a table, same as uint24, NULL offset = 0x000000
|
||||||
|
pub const Offset24 = u24;
|
||||||
|
|
||||||
|
/// Long offset to a table, same as uint32, NULL offset = 0x00000000
|
||||||
|
pub const Offset32 = u32;
|
||||||
|
|
||||||
|
/// Packed 32-bit value with major and minor version numbers
|
||||||
|
pub const Version16Dot16 = packed struct(u32) {
|
||||||
|
minor: u16,
|
||||||
|
major: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 32-bit signed 26.6 fixed point numbers.
|
||||||
|
pub const F26Dot6 = FixedPoint(i32, 26, 6);
|
||||||
|
|
||||||
|
fn FixedPoint(comptime T: type, int_bits: u64, frac_bits: u64) type {
|
||||||
|
const type_info: std.builtin.Type.Int = @typeInfo(T).Int;
|
||||||
|
comptime assert(int_bits + frac_bits == type_info.bits);
|
||||||
|
|
||||||
|
return packed struct(T) {
|
||||||
|
const Self = FixedPoint(T, int_bits, frac_bits);
|
||||||
|
const frac_factor: comptime_float = @floatFromInt(std.math.pow(
|
||||||
|
u64,
|
||||||
|
2,
|
||||||
|
frac_bits,
|
||||||
|
));
|
||||||
|
const half = @as(T, 1) << @intCast(frac_bits - 1);
|
||||||
|
|
||||||
|
frac: std.meta.Int(.unsigned, frac_bits),
|
||||||
|
int: std.meta.Int(type_info.signedness, int_bits),
|
||||||
|
|
||||||
|
pub fn to(self: Self, comptime FloatType: type) FloatType {
|
||||||
|
const i: FloatType = @floatFromInt(self.int);
|
||||||
|
const f: FloatType = @floatFromInt(self.frac);
|
||||||
|
|
||||||
|
return i + f / frac_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(float: anytype) Self {
|
||||||
|
const int = @floor(float);
|
||||||
|
const frac = @abs(float - int);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.int = @intFromFloat(int),
|
||||||
|
.frac = @intFromFloat(@round(frac * frac_factor)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Round to the nearest integer, .5 rounds away from 0.
|
||||||
|
pub fn round(self: Self) T {
|
||||||
|
if (self.frac & half != 0)
|
||||||
|
return self.int + 1
|
||||||
|
else
|
||||||
|
return self.int;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
self: Self,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("{d}", .{self.to(f64)});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test FixedPoint {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const p26d6 = F26Dot6.from(26.6);
|
||||||
|
try testing.expectEqual(F26Dot6{
|
||||||
|
.int = 26,
|
||||||
|
.frac = 38,
|
||||||
|
}, p26d6);
|
||||||
|
try testing.expectEqual(26.59375, p26d6.to(f64));
|
||||||
|
try testing.expectEqual(27, p26d6.round());
|
||||||
|
|
||||||
|
const n26d6 = F26Dot6.from(-26.6);
|
||||||
|
try testing.expectEqual(F26Dot6{
|
||||||
|
.int = -27,
|
||||||
|
.frac = 26,
|
||||||
|
}, n26d6);
|
||||||
|
try testing.expectEqual(-26.59375, n26d6.to(f64));
|
||||||
|
try testing.expectEqual(-27, n26d6.round());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for parsing a SFNT font and accessing its tables.
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/otff
|
||||||
|
/// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
|
||||||
|
pub const SFNT = struct {
|
||||||
|
const Directory = struct {
|
||||||
|
offset: OffsetSubtable,
|
||||||
|
records: []TableRecord,
|
||||||
|
|
||||||
|
/// The static (fixed-sized) portion of the table directory
|
||||||
|
///
|
||||||
|
/// This struct matches the memory layout of the TrueType/OpenType
|
||||||
|
/// TableDirectory, but does not include the TableRecord array, since
|
||||||
|
/// that is dynamically sized, so we parse it separately.
|
||||||
|
///
|
||||||
|
/// In the TrueType reference manual this
|
||||||
|
/// is referred to as the "offset subtable".
|
||||||
|
///
|
||||||
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
|
||||||
|
const OffsetSubtable = extern struct {
|
||||||
|
/// Indicates the type of font file we're reading.
|
||||||
|
/// - 0x00_01_00_00 ---- TrueType
|
||||||
|
/// - 0x74_72_75_65 'true' TrueType
|
||||||
|
/// - 0x4F_54_54_4F 'OTTO' OpenType
|
||||||
|
/// - 0x74_79_70_31 'typ1' PostScript
|
||||||
|
sfnt_version: uint32 align(1),
|
||||||
|
/// Number of tables.
|
||||||
|
num_tables: uint16 align(1),
|
||||||
|
/// Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator).
|
||||||
|
search_range: uint16 align(1),
|
||||||
|
/// Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
|
||||||
|
entry_selector: uint16 align(1),
|
||||||
|
/// numTables times 16, minus searchRange ((numTables * 16) - searchRange).
|
||||||
|
range_shift: uint16 align(1),
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
self: OffsetSubtable,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print(
|
||||||
|
"OffsetSubtable('{s}'){{ .num_tables = {} }}",
|
||||||
|
.{
|
||||||
|
if (self.sfnt_version == 0x00_01_00_00)
|
||||||
|
&@as([10]u8, "0x00010000".*)
|
||||||
|
else
|
||||||
|
&@as([4]u8, @bitCast(
|
||||||
|
std.mem.nativeToBig(u32, self.sfnt_version),
|
||||||
|
)),
|
||||||
|
self.num_tables,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableRecord = extern struct {
|
||||||
|
/// Table identifier.
|
||||||
|
tag: Tag align(1),
|
||||||
|
/// Checksum for this table.
|
||||||
|
checksum: uint32 align(1),
|
||||||
|
/// Offset from beginning of font file.
|
||||||
|
offset: Offset32 align(1),
|
||||||
|
/// Length of this table.
|
||||||
|
length: uint32 align(1),
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
self: TableRecord,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print(
|
||||||
|
"TableRecord(\"{s}\"){{ .checksum = {}, .offset = {}, .length = {} }}",
|
||||||
|
.{
|
||||||
|
self.tag,
|
||||||
|
self.checksum,
|
||||||
|
self.offset,
|
||||||
|
self.length,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
directory: Directory,
|
||||||
|
|
||||||
|
data: []const u8,
|
||||||
|
|
||||||
|
/// Parse a font from raw data. The struct will keep a
|
||||||
|
/// reference to `data` and use it for future operations.
|
||||||
|
pub fn init(data: []const u8, alloc: Allocator) !SFNT {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
// SFNT files use big endian, if our native endian is
|
||||||
|
// not big we'll need to byte swap the values we read.
|
||||||
|
const byte_swap = native_endian != .big;
|
||||||
|
|
||||||
|
var directory: Directory = undefined;
|
||||||
|
|
||||||
|
try reader.readNoEof(std.mem.asBytes(&directory.offset));
|
||||||
|
if (byte_swap) std.mem.byteSwapAllFields(
|
||||||
|
Directory.OffsetSubtable,
|
||||||
|
&directory.offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
directory.records = try alloc.alloc(Directory.TableRecord, directory.offset.num_tables);
|
||||||
|
|
||||||
|
try reader.readNoEof(std.mem.sliceAsBytes(directory.records));
|
||||||
|
if (byte_swap) for (directory.records) |*record| {
|
||||||
|
std.mem.byteSwapAllFields(
|
||||||
|
Directory.TableRecord,
|
||||||
|
record,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.directory = directory,
|
||||||
|
.data = data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: SFNT, alloc: Allocator) void {
|
||||||
|
alloc.free(self.directory.records);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bytes of the table with the provided tag if present.
|
||||||
|
pub fn getTable(self: SFNT, tag: *const [4]u8) ?[]const u8 {
|
||||||
|
for (self.directory.records) |record| {
|
||||||
|
if (std.mem.eql(u8, tag, &record.tag)) {
|
||||||
|
return self.data[record.offset..][0..record.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const native_endian = @import("builtin").target.cpu.arch.endian();
|
||||||
|
|
||||||
|
test "parse font" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const sfnt = try SFNT.init(&test_font.*, alloc);
|
||||||
|
defer sfnt.deinit(alloc);
|
||||||
|
|
||||||
|
try testing.expectEqual(19, sfnt.directory.offset.num_tables);
|
||||||
|
try testing.expectEqualStrings("prep", &sfnt.directory.records[18].tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "get table" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const test_font = @import("../embedded.zig").julia_mono;
|
||||||
|
|
||||||
|
const sfnt = try SFNT.init(&test_font.*, alloc);
|
||||||
|
defer sfnt.deinit(alloc);
|
||||||
|
|
||||||
|
const svg = sfnt.getTable("SVG ").?;
|
||||||
|
|
||||||
|
try testing.expectEqual(430, svg.len);
|
||||||
|
}
|
Reference in New Issue
Block a user