mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
font/opentype: add SVG table parser, membership check
This commit is contained in:
@ -12,6 +12,7 @@ pub const DeferredFace = @import("DeferredFace.zig");
|
|||||||
pub const Face = face.Face;
|
pub const Face = face.Face;
|
||||||
pub const Glyph = @import("Glyph.zig");
|
pub const Glyph = @import("Glyph.zig");
|
||||||
pub const Metrics = face.Metrics;
|
pub const Metrics = face.Metrics;
|
||||||
|
pub const opentype = @import("opentype.zig");
|
||||||
pub const shape = @import("shape.zig");
|
pub const shape = @import("shape.zig");
|
||||||
pub const Shaper = shape.Shaper;
|
pub const Shaper = shape.Shaper;
|
||||||
pub const ShaperCache = shape.Cache;
|
pub const ShaperCache = shape.Cache;
|
||||||
|
7
src/font/opentype.zig
Normal file
7
src/font/opentype.zig
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const svg = @import("opentype/svg.zig");
|
||||||
|
|
||||||
|
pub const SVG = svg.SVG;
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
108
src/font/opentype/svg.zig
Normal file
108
src/font/opentype/svg.zig
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const font = @import("../main.zig");
|
||||||
|
|
||||||
|
/// SVG glyphs description table:
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// - https://www.w3.org/2013/10/SVG_in_OpenType/#thesvg
|
||||||
|
/// - https://learn.microsoft.com/en-us/typography/opentype/spec/svg
|
||||||
|
pub const SVG = struct {
|
||||||
|
/// The start and end glyph IDs (inclusive) that are present in the
|
||||||
|
/// table. This is used to very quickly include/exclude a glyph from
|
||||||
|
/// the table.
|
||||||
|
start_glyph_id: u16,
|
||||||
|
end_glyph_id: u16,
|
||||||
|
|
||||||
|
/// All records in the table.
|
||||||
|
records: []const [12]u8,
|
||||||
|
|
||||||
|
pub fn init(data: []const u8) !SVG {
|
||||||
|
var fbs = std.io.fixedBufferStream(data);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
|
||||||
|
// Version
|
||||||
|
if (try reader.readInt(u16, .big) != 0) {
|
||||||
|
return error.SVGVersionNotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset
|
||||||
|
const offset = try reader.readInt(u32, .big);
|
||||||
|
|
||||||
|
// Seek to the offset to get our document list
|
||||||
|
try fbs.seekTo(offset);
|
||||||
|
|
||||||
|
// Get our document records along with the start/end glyph range.
|
||||||
|
const len = try reader.readInt(u16, .big);
|
||||||
|
const records: [*]const [12]u8 = @ptrCast(data[try fbs.getPos()..]);
|
||||||
|
const start_range = try glyphRange(&records[0]);
|
||||||
|
const end_range = if (len == 1) start_range else try glyphRange(&records[(len - 1)]);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.start_glyph_id = start_range[0],
|
||||||
|
.end_glyph_id = end_range[1],
|
||||||
|
.records = records[0..len],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasGlyph(self: SVG, glyph_id: u16) bool {
|
||||||
|
// Fast path: outside the table range
|
||||||
|
if (glyph_id < self.start_glyph_id or glyph_id > self.end_glyph_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path, matches the start/end glyph IDs
|
||||||
|
if (glyph_id == self.start_glyph_id or glyph_id == self.end_glyph_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: binary search our records
|
||||||
|
return std.sort.binarySearch(
|
||||||
|
[12]u8,
|
||||||
|
glyph_id,
|
||||||
|
self.records,
|
||||||
|
{},
|
||||||
|
compareGlyphId,
|
||||||
|
) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compareGlyphId(_: void, glyph_id: u16, record: [12]u8) std.math.Order {
|
||||||
|
const start, const end = glyphRange(&record) catch return .lt;
|
||||||
|
if (glyph_id < start) {
|
||||||
|
return .lt;
|
||||||
|
} else if (glyph_id > end) {
|
||||||
|
return .gt;
|
||||||
|
} else {
|
||||||
|
return .eq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glyphRange(record: []const u8) !struct { u16, u16 } {
|
||||||
|
var fbs = std.io.fixedBufferStream(record);
|
||||||
|
const reader = fbs.reader();
|
||||||
|
return .{
|
||||||
|
try reader.readInt(u16, .big),
|
||||||
|
try reader.readInt(u16, .big),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "SVG" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const testFont = @import("../test.zig").fontJuliaMono;
|
||||||
|
|
||||||
|
var lib = try font.Library.init();
|
||||||
|
defer lib.deinit();
|
||||||
|
|
||||||
|
var face = try font.Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
|
||||||
|
defer face.deinit();
|
||||||
|
|
||||||
|
const table = (try face.copyTable(alloc, "SVG ")).?;
|
||||||
|
defer alloc.free(table);
|
||||||
|
|
||||||
|
const svg = try SVG.init(table);
|
||||||
|
try testing.expectEqual(11482, svg.start_glyph_id);
|
||||||
|
try testing.expectEqual(11482, svg.end_glyph_id);
|
||||||
|
try testing.expect(svg.hasGlyph(11482));
|
||||||
|
}
|
Reference in New Issue
Block a user