Skip to documentation
SLOP

tiny.filigree.font.outline

Reference tiny.filigree font outline

Defined in font.

API (6)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callsfontoutline
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/filigree/src/font/outline.zig

zig
const std = @import("std");const cff = @import("cff.zig");const face_table = @import("face.zig");pub const Error = error{    InvalidFont,    MissingTable,    UnsupportedOutline,    OutOfMemory,};pub const Bounds = struct {    x_min: i32 = 0,    y_min: i32 = 0,    x_max: i32 = 0,    y_max: i32 = 0,};pub const Point = struct {    x: i32,    y: i32,    on_curve: bool,};pub const Contour = struct {    start: usize,    end: usize,};pub const Glyph = struct {    bounds: Bounds = .{},    contours: []Contour = &.{},    points: []Point = &.{},    pub fn deinit(self: Glyph, allocator: std.mem.Allocator) void {        if (self.contours.len > 0) allocator.free(self.contours);        if (self.points.len > 0) allocator.free(self.points);    }};pub fn glyphAlloc(    allocator: std.mem.Allocator,    face: face_table.Face,    glyph_id: u32,) Error!Glyph {    if (glyph_id >= face.num_glyphs) return error.InvalidFont;    if (face.tableSlice("glyf") == null or face.tableSlice("loca") == null) {        if (face.tableSlice("CFF ") != null) return cffGlyphAlloc(allocator, face, glyph_id);    }    const head = face.tableSlice("head") orelse return error.MissingTable;    const loca = face.tableSlice("loca") orelse return error.MissingTable;    const glyf = face.tableSlice("glyf") orelse return error.MissingTable;    if (head.len < 52) return error.InvalidFont;    const loc_format = readI16(head, 50) catch return error.InvalidFont;    return glyphAllocFromTables(allocator, loca, glyf, loc_format, glyph_id, 0);}fn cffGlyphAlloc(    allocator: std.mem.Allocator,    face: face_table.Face,    glyph_id: u32,) Error!Glyph {    var builder = CffGlyphBuilder.init(allocator);    errdefer builder.deinit();    try cff.buildGlyph(face, glyph_id, &builder);    return try builder.toGlyph();}const CffGlyphBuilder = struct {    allocator: std.mem.Allocator,    contours: std.ArrayListUnmanaged(Contour) = .empty,    points: std.ArrayListUnmanaged(Point) = .empty,    contour_start: ?usize = null,    current_x: i32 = 0,    current_y: i32 = 0,    fn init(allocator: std.mem.Allocator) CffGlyphBuilder {        return .{ .allocator = allocator };    }    fn deinit(self: *CffGlyphBuilder) void {        self.contours.deinit(self.allocator);        self.points.deinit(self.allocator);    }    pub fn moveTo(self: *CffGlyphBuilder, x: i32, y: i32) Error!void {        try self.close();        self.contour_start = self.points.items.len;        try self.points.append(self.allocator, .{ .x = x, .y = y, .on_curve = true });        self.current_x = x;        self.current_y = y;    }    pub fn lineTo(self: *CffGlyphBuilder, x: i32, y: i32) Error!void {        try self.ensureContour();        try self.points.append(self.allocator, .{ .x = x, .y = y, .on_curve = true });        self.current_x = x;        self.current_y = y;    }    pub fn curveTo(self: *CffGlyphBuilder, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) Error!void {        try self.ensureContour();        const p0x: f32 = @floatFromInt(self.current_x);        const p0y: f32 = @floatFromInt(self.current_y);        const p1x: f32 = @floatFromInt(x1);        const p1y: f32 = @floatFromInt(y1);        const p2x: f32 = @floatFromInt(x2);        const p2y: f32 = @floatFromInt(y2);        const p3x: f32 = @floatFromInt(x3);        const p3y: f32 = @floatFromInt(y3);        const steps: usize = 12;        for (1..(steps + 1)) |step| {            const t = @as(f32, @floatFromInt(step)) / @as(f32, @floatFromInt(steps));            const mt = 1.0 - t;            const x = mt * mt * mt * p0x + 3.0 * mt * mt * t * p1x + 3.0 * mt * t * t * p2x + t * t * t * p3x;            const y = mt * mt * mt * p0y + 3.0 * mt * mt * t * p1y + 3.0 * mt * t * t * p2y + t * t * t * p3y;            try self.points.append(self.allocator, .{ .x = roundPoint(x), .y = roundPoint(y), .on_curve = true });        }        self.current_x = x3;        self.current_y = y3;    }    pub fn close(self: *CffGlyphBuilder) Error!void {        const start = self.contour_start orelse return;        if (self.points.items.len - start >= 3) {            try self.contours.append(self.allocator, .{ .start = start, .end = self.points.items.len });        } else {            self.points.shrinkRetainingCapacity(start);        }        self.contour_start = null;    }    fn toGlyph(self: *CffGlyphBuilder) Error!Glyph {        try self.close();        const contours = try self.contours.toOwnedSlice(self.allocator);        errdefer self.allocator.free(contours);        const points = try self.points.toOwnedSlice(self.allocator);        errdefer self.allocator.free(points);        return .{            .bounds = boundsFor(points),            .contours = contours,            .points = points,        };    }    fn ensureContour(self: *CffGlyphBuilder) Error!void {        if (self.contour_start != null) return;        try self.moveTo(self.current_x, self.current_y);    }};fn boundsFor(points: []const Point) Bounds {    if (points.len == 0) return .{};    var bounds = Bounds{        .x_min = points[0].x,        .y_min = points[0].y,        .x_max = points[0].x,        .y_max = points[0].y,    };    for (points[1..]) |point| {        bounds.x_min = @min(bounds.x_min, point.x);        bounds.y_min = @min(bounds.y_min, point.y);        bounds.x_max = @max(bounds.x_max, point.x);        bounds.y_max = @max(bounds.y_max, point.y);    }    return bounds;}fn roundPoint(value: f32) i32 {    return @intFromFloat(@round(value));}const max_component_depth = 16;fn glyphAllocFromTables(    allocator: std.mem.Allocator,    loca: []const u8,    glyf: []const u8,    loc_format: i16,    glyph_id: u32,    depth: usize,) Error!Glyph {    if (depth > max_component_depth) return error.InvalidFont;    const range = try glyphRange(loca, glyph_id, loc_format);    if (range.start == range.end) return .{};    if (range.start > range.end or range.end > glyf.len) return error.InvalidFont;    return parseGlyphAlloc(allocator, loca, glyf, loc_format, glyf[range.start..range.end], depth);}const GlyphRange = struct {    start: usize,    end: usize,};fn glyphRange(loca: []const u8, glyph_id: u32, loc_format: i16) Error!GlyphRange {    const index: usize = @intCast(glyph_id);    if (loc_format == 0) {        const offset = index * 2;        if (offset + 4 > loca.len) return error.InvalidFont;        return .{            .start = @as(usize, try readU16(loca, offset)) * 2,            .end = @as(usize, try readU16(loca, offset + 2)) * 2,        };    }    if (loc_format == 1) {        const offset = index * 4;        if (offset + 8 > loca.len) return error.InvalidFont;        return .{            .start = @intCast(try readU32(loca, offset)),            .end = @intCast(try readU32(loca, offset + 4)),        };    }    return error.InvalidFont;}fn parseGlyphAlloc(    allocator: std.mem.Allocator,    loca: []const u8,    glyf: []const u8,    loc_format: i16,    data: []const u8,    depth: usize,) Error!Glyph {    if (data.len < 10) return error.InvalidFont;    const contour_count = try readI16(data, 0);    const bounds = Bounds{        .x_min = try readI16(data, 2),        .y_min = try readI16(data, 4),        .x_max = try readI16(data, 6),        .y_max = try readI16(data, 8),    };    if (contour_count < 0) return parseCompositeGlyphAlloc(allocator, loca, glyf, loc_format, data, bounds, depth);    if (contour_count == 0) return .{};    const contour_count_usize: usize = @intCast(contour_count);    var offset: usize = 10;    const contours = try allocator.alloc(Contour, contour_count_usize);    errdefer allocator.free(contours);    var point_count: usize = 0;    for (0..contour_count_usize) |contour_index| {        const end_point = try readU16(data, offset);        offset += 2;        const start = point_count;        point_count = @as(usize, end_point) + 1;        if (point_count < start) return error.InvalidFont;        contours[contour_index] = .{ .start = start, .end = point_count };    }    const instruction_len = try readU16(data, offset);    offset += 2;    if (offset + instruction_len > data.len) return error.InvalidFont;    offset += instruction_len;    const flags = try allocator.alloc(u8, point_count);    defer allocator.free(flags);    var flag_count: usize = 0;    while (flag_count < point_count) {        if (offset >= data.len) return error.InvalidFont;        const flag = data[offset];        offset += 1;        var repeat_count: usize = 1;        if ((flag & 0x08) != 0) {            if (offset >= data.len) return error.InvalidFont;            repeat_count += data[offset];            offset += 1;        }        if (flag_count + repeat_count > point_count) return error.InvalidFont;        @memset(flags[flag_count..][0..repeat_count], flag);        flag_count += repeat_count;    }    const points = try allocator.alloc(Point, point_count);    errdefer allocator.free(points);    for (flags, points) |flag, *point| {        point.* = .{ .x = 0, .y = 0, .on_curve = (flag & 0x01) != 0 };    }    var x: i32 = 0;    for (flags, points) |flag, *point| {        const delta: i32 = if ((flag & 0x02) != 0) delta: {            if (offset >= data.len) return error.InvalidFont;            const value: i32 = data[offset];            offset += 1;            break :delta if ((flag & 0x10) != 0) value else -value;        } else if ((flag & 0x10) != 0)            0        else delta: {            const value = try readI16(data, offset);            offset += 2;            break :delta value;        };        x += delta;        point.x = x;    }    var y: i32 = 0;    for (flags, points) |flag, *point| {        const delta: i32 = if ((flag & 0x04) != 0) delta: {            if (offset >= data.len) return error.InvalidFont;            const value: i32 = data[offset];            offset += 1;            break :delta if ((flag & 0x20) != 0) value else -value;        } else if ((flag & 0x20) != 0)            0        else delta: {            const value = try readI16(data, offset);            offset += 2;            break :delta value;        };        y += delta;        point.y = y;    }    return .{        .bounds = bounds,        .contours = contours,        .points = points,    };}const component_arg_words = 0x0001;const component_args_xy = 0x0002;const component_has_scale = 0x0008;const component_more = 0x0020;const component_has_xy_scale = 0x0040;const component_has_2x2 = 0x0080;const component_has_instructions = 0x0100;const ComponentTransform = struct {    xx: f32 = 1,    yx: f32 = 0,    xy: f32 = 0,    yy: f32 = 1,    dx: i32 = 0,    dy: i32 = 0,    fn apply(self: ComponentTransform, point: Point) Point {        const x: f32 = @floatFromInt(point.x);        const y: f32 = @floatFromInt(point.y);        return .{            .x = roundTransformed(self.xx * x + self.xy * y + @as(f32, @floatFromInt(self.dx))),            .y = roundTransformed(self.yx * x + self.yy * y + @as(f32, @floatFromInt(self.dy))),            .on_curve = point.on_curve,        };    }};fn parseCompositeGlyphAlloc(    allocator: std.mem.Allocator,    loca: []const u8,    glyf: []const u8,    loc_format: i16,    data: []const u8,    bounds: Bounds,    depth: usize,) Error!Glyph {    var contours = std.ArrayListUnmanaged(Contour).empty;    errdefer contours.deinit(allocator);    var points = std.ArrayListUnmanaged(Point).empty;    errdefer points.deinit(allocator);    var offset: usize = 10;    var flags: u16 = component_more;    while ((flags & component_more) != 0) {        if (offset + 4 > data.len) return error.InvalidFont;        flags = try readU16(data, offset);        const component_glyph_id = try readU16(data, offset + 2);        offset += 4;        var transform = ComponentTransform{};        if ((flags & component_arg_words) != 0) {            if (offset + 4 > data.len) return error.InvalidFont;            transform.dx = try readI16(data, offset);            transform.dy = try readI16(data, offset + 2);            offset += 4;        } else {            if (offset + 2 > data.len) return error.InvalidFont;            transform.dx = try readI8(data, offset);            transform.dy = try readI8(data, offset + 1);            offset += 2;        }        if ((flags & component_args_xy) == 0) return error.UnsupportedOutline;        if ((flags & component_has_scale) != 0) {            if (offset + 2 > data.len) return error.InvalidFont;            const scale = try readF2Dot14(data, offset);            transform.xx = scale;            transform.yy = scale;            offset += 2;        } else if ((flags & component_has_xy_scale) != 0) {            if (offset + 4 > data.len) return error.InvalidFont;            transform.xx = try readF2Dot14(data, offset);            transform.yy = try readF2Dot14(data, offset + 2);            offset += 4;        } else if ((flags & component_has_2x2) != 0) {            if (offset + 8 > data.len) return error.InvalidFont;            transform.xx = try readF2Dot14(data, offset);            transform.yx = try readF2Dot14(data, offset + 2);            transform.xy = try readF2Dot14(data, offset + 4);            transform.yy = try readF2Dot14(data, offset + 6);            offset += 8;        }        var component = try glyphAllocFromTables(allocator, loca, glyf, loc_format, component_glyph_id, depth + 1);        defer component.deinit(allocator);        try appendComponent(allocator, &contours, &points, component, transform);    }    if ((flags & component_has_instructions) != 0) {        const instruction_len = try readU16(data, offset);        offset += 2;        if (offset + instruction_len > data.len) return error.InvalidFont;    }    const owned_contours = try contours.toOwnedSlice(allocator);    errdefer allocator.free(owned_contours);    const owned_points = try points.toOwnedSlice(allocator);    return .{        .bounds = bounds,        .contours = owned_contours,        .points = owned_points,    };}fn appendComponent(    allocator: std.mem.Allocator,    contours: *std.ArrayListUnmanaged(Contour),    points: *std.ArrayListUnmanaged(Point),    component: Glyph,    transform: ComponentTransform,) Error!void {    const point_offset = points.items.len;    try points.ensureUnusedCapacity(allocator, component.points.len);    for (component.points) |point| {        points.appendAssumeCapacity(transform.apply(point));    }    try contours.ensureUnusedCapacity(allocator, component.contours.len);    for (component.contours) |contour| {        contours.appendAssumeCapacity(.{            .start = point_offset + contour.start,            .end = point_offset + contour.end,        });    }}fn readI8(data: []const u8, offset: usize) Error!i8 {    if (offset >= data.len) return error.InvalidFont;    return @bitCast(data[offset]);}fn readF2Dot14(data: []const u8, offset: usize) Error!f32 {    const raw = try readI16(data, offset);    return @as(f32, @floatFromInt(raw)) / 16384.0;}fn roundTransformed(value: f32) i32 {    return @intFromFloat(@round(value));}fn readU16(data: []const u8, offset: usize) Error!u16 {    if (offset + 2 > data.len) return error.InvalidFont;    return std.mem.readInt(u16, data[offset..][0..2], .big);}fn readI16(data: []const u8, offset: usize) Error!i16 {    return @bitCast(try readU16(data, offset));}fn readU32(data: []const u8, offset: usize) Error!u32 {    if (offset + 4 > data.len) return error.InvalidFont;    return std.mem.readInt(u32, data[offset..][0..4], .big);}test "outline loads simple fixture glyph" {    const fixtures = @import("../fixture/root.zig");    const allocator = std.testing.allocator;    const bytes = try fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const face = try face_table.Face.init(bytes);    const glyph = try glyphAlloc(allocator, face, face.glyphId('A'));    defer glyph.deinit(allocator);    try std.testing.expectEqual(Bounds{ .x_min = 50, .y_min = 0, .x_max = 450, .y_max = 700 }, glyph.bounds);    try std.testing.expectEqual(@as(usize, 1), glyph.contours.len);    try std.testing.expectEqual(Contour{ .start = 0, .end = 4 }, glyph.contours[0]);    try std.testing.expectEqual(@as(usize, 4), glyph.points.len);    try std.testing.expectEqual(Point{ .x = 50, .y = 0, .on_curve = true }, glyph.points[0]);    try std.testing.expectEqual(Point{ .x = 450, .y = 700, .on_curve = true }, glyph.points[2]);}test "outline reports empty fixture glyphs" {    const fixtures = @import("../fixture/root.zig");    const allocator = std.testing.allocator;    const bytes = try fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const face = try face_table.Face.init(bytes);    const glyph = try glyphAlloc(allocator, face, face.glyphId('C'));    defer glyph.deinit(allocator);    try std.testing.expectEqual(@as(usize, 0), glyph.contours.len);    try std.testing.expectEqual(@as(usize, 0), glyph.points.len);}test "outline loads composite fixture glyph" {    const fixtures = @import("../fixture/root.zig");    const allocator = std.testing.allocator;    const bytes = try fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const face = try face_table.Face.init(bytes);    const glyph = try glyphAlloc(allocator, face, face.glyphId('i'));    defer glyph.deinit(allocator);    try std.testing.expectEqual(Bounds{ .x_min = 50, .y_min = 0, .x_max = 950, .y_max = 700 }, glyph.bounds);    try std.testing.expectEqual(@as(usize, 2), glyph.contours.len);    try std.testing.expectEqual(Contour{ .start = 0, .end = 4 }, glyph.contours[0]);    try std.testing.expectEqual(Contour{ .start = 4, .end = 8 }, glyph.contours[1]);    try std.testing.expectEqual(@as(usize, 8), glyph.points.len);    try std.testing.expectEqual(Point{ .x = 550, .y = 0, .on_curve = true }, glyph.points[4]);    try std.testing.expectEqual(Point{ .x = 950, .y = 700, .on_curve = true }, glyph.points[6]);}test "outline loads CFF fixture glyph" {    const fixtures = @import("../fixture/root.zig");    const allocator = std.testing.allocator;    const bytes = try fixtures.createWithCffOutlines(allocator);    defer allocator.free(bytes);    const face = try face_table.Face.init(bytes);    const glyph = try glyphAlloc(allocator, face, face.glyphId('A'));    defer glyph.deinit(allocator);    try std.testing.expectEqual(Bounds{ .x_min = 50, .y_min = 0, .x_max = 450, .y_max = 700 }, glyph.bounds);    try std.testing.expectEqual(@as(usize, 1), glyph.contours.len);    try std.testing.expectEqual(@as(usize, 4), glyph.points.len);    try std.testing.expectEqual(Point{ .x = 50, .y = 0, .on_curve = true }, glyph.points[0]);    try std.testing.expectEqual(Point{ .x = 450, .y = 700, .on_curve = true }, glyph.points[2]);}

Source: lib/filigree/src/font/root.zig:16

zig
pub const outline = @import("outline.zig");

Audit

Definitions2
Public names2
Members4
Version26.7.0
Revisiondaab053ee433