tiny.filigree.font.cff
Defined in font.
API (2)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: lib/filigree/src/font/cff.zig
zig
const std = @import("std");const face_table = @import("face.zig");pub const Error = error{ InvalidFont, MissingTable, UnsupportedOutline, OutOfMemory,};const max_stack = 64;const max_subr_depth = 16;const max_ops = 65536;pub fn buildGlyph(face: face_table.Face, glyph_id: u32, sink: anytype) Error!void { if (glyph_id >= face.num_glyphs) return error.InvalidFont; const font = try Font.init(face); if (font.cid_keyed) return error.UnsupportedOutline; if (glyph_id >= font.charstrings.count) return error.InvalidFont; var interpreter = Interpreter.init(font); const charstring = try font.charstrings.item(glyph_id); switch (try interpreter.run(charstring, sink, 0)) { .ended => {}, .returned, .exhausted => return error.InvalidFont, } try sink.close();}const Font = struct { data: []const u8, charstrings: Index, global_subrs: Index, local_subrs: ?Index = null, cid_keyed: bool = false, fn init(face: face_table.Face) Error!Font { const data = face.tableSlice("CFF ") orelse return error.MissingTable; if (data.len < 4) return error.InvalidFont; if (data[0] != 1) return error.UnsupportedOutline; const header_size: usize = data[2]; if (header_size < 4 or header_size > data.len) return error.InvalidFont; const names = try Index.init(data, header_size); if (names.count != 1) return error.InvalidFont; const top_dicts = try Index.init(data, names.end); if (top_dicts.count == 0) return error.InvalidFont; const top = try parseTopDict(try top_dicts.item(0)); const strings = try Index.init(data, top_dicts.end); const global_subrs = try Index.init(data, strings.end); if (top.charstrings_offset == 0) return error.InvalidFont; const charstrings = try Index.init(data, top.charstrings_offset); if (charstrings.count != face.num_glyphs) return error.InvalidFont; var local_subrs: ?Index = null; if (top.private_size != 0) { const private_start: usize = @intCast(top.private_offset); const private_size: usize = @intCast(top.private_size); if (private_start > data.len or private_size > data.len - private_start) return error.InvalidFont; const private = try parsePrivateDict(data[private_start..][0..private_size]); if (private.local_subrs_offset) |offset| { const subrs_offset: usize = @intCast(offset); if (subrs_offset > data.len - private_start) return error.InvalidFont; local_subrs = try Index.init(data, private_start + subrs_offset); } } return .{ .data = data, .charstrings = charstrings, .global_subrs = global_subrs, .local_subrs = local_subrs, .cid_keyed = top.cid_keyed, }; }};const Index = struct { data: []const u8, count: u32 = 0, off_size: u8 = 0, offsets_offset: usize = 0, object_offset: usize = 0, end: usize = 0, fn init(data: []const u8, offset: usize) Error!Index { if (offset > data.len or data.len - offset < 2) return error.InvalidFont; const count = try readU16(data, offset); if (count == 0) { return .{ .data = data, .end = offset + 2, }; } if (data.len - offset < 3) return error.InvalidFont; const off_size = data[offset + 2]; if (off_size == 0 or off_size > 4) return error.InvalidFont; const count_usize: usize = count; const offsets_offset = offset + 3; const offset_bytes = (count_usize + 1) * @as(usize, off_size); if (offsets_offset > data.len or offset_bytes > data.len - offsets_offset) return error.InvalidFont; const object_offset = offsets_offset + offset_bytes; const first = try readIndexOffset(data, offsets_offset, off_size, 0); const last = try readIndexOffset(data, offsets_offset, off_size, count_usize); if (first != 1 or last == 0) return error.InvalidFont; const end = object_offset + @as(usize, @intCast(last - 1)); if (end > data.len) return error.InvalidFont; return .{ .data = data, .count = count, .off_size = off_size, .offsets_offset = offsets_offset, .object_offset = object_offset, .end = end, }; } fn item(self: Index, index: u32) Error![]const u8 { if (index >= self.count) return error.InvalidFont; const i: usize = @intCast(index); const start_offset = try readIndexOffset(self.data, self.offsets_offset, self.off_size, i); const end_offset = try readIndexOffset(self.data, self.offsets_offset, self.off_size, i + 1); if (start_offset == 0 or end_offset < start_offset) return error.InvalidFont; const start = self.object_offset + @as(usize, @intCast(start_offset - 1)); const end = self.object_offset + @as(usize, @intCast(end_offset - 1)); if (start > self.data.len or end > self.data.len or end < start) return error.InvalidFont; return self.data[start..end]; }};const TopDict = struct { charstrings_offset: u32 = 0, private_size: u32 = 0, private_offset: u32 = 0, cid_keyed: bool = false,};const PrivateDict = struct { local_subrs_offset: ?u32 = null,};fn parseTopDict(data: []const u8) Error!TopDict { var result = TopDict{}; var stack = DictStack{}; var offset: usize = 0; while (offset < data.len) { const first = data[offset]; offset += 1; if (isDictOperator(first)) { if (first == 12) { if (offset >= data.len) return error.InvalidFont; const op = data[offset]; offset += 1; switch (op) { 30, 36, 37 => result.cid_keyed = true, else => {}, } } else switch (first) { 17 => result.charstrings_offset = stack.lastU32() orelse return error.InvalidFont, 18 => { if (stack.len < 2) return error.InvalidFont; result.private_size = try stack.u32At(stack.len - 2); result.private_offset = try stack.u32At(stack.len - 1); }, else => {}, } stack.clear(); continue; } try stack.push(try readDictNumber(data, &offset, first)); } return result;}fn parsePrivateDict(data: []const u8) Error!PrivateDict { var result = PrivateDict{}; var stack = DictStack{}; var offset: usize = 0; while (offset < data.len) { const first = data[offset]; offset += 1; if (isDictOperator(first)) { if (first == 19) { result.local_subrs_offset = stack.lastU32() orelse return error.InvalidFont; } else if (first == 12) { if (offset >= data.len) return error.InvalidFont; offset += 1; } stack.clear(); continue; } try stack.push(try readDictNumber(data, &offset, first)); } return result;}const DictStack = struct { values: [max_stack]i32 = undefined, len: usize = 0, fn push(self: *DictStack, value: i32) Error!void { if (self.len >= self.values.len) return error.InvalidFont; self.values[self.len] = value; self.len += 1; } fn clear(self: *DictStack) void { self.len = 0; } fn lastU32(self: *const DictStack) ?u32 { if (self.len == 0) return null; return std.math.cast(u32, self.values[self.len - 1]); } fn u32At(self: *const DictStack, index: usize) Error!u32 { if (index >= self.len) return error.InvalidFont; return std.math.cast(u32, self.values[index]) orelse error.InvalidFont; }};const RunResult = enum { exhausted, returned, ended,};const OperatorResult = enum { none, ended, returned,};const Interpreter = struct { font: Font, stack: [max_stack]i32 = undefined, stack_len: usize = 0, x: i32 = 0, y: i32 = 0, stem_count: usize = 0, width_seen: bool = false, op_count: usize = 0, fn init(font: Font) Interpreter { return .{ .font = font }; } fn run(self: *Interpreter, data: []const u8, sink: anytype, depth: usize) Error!RunResult { if (depth > max_subr_depth) return error.InvalidFont; var offset: usize = 0; while (offset < data.len) { self.op_count += 1; if (self.op_count > max_ops) return error.InvalidFont; const first = data[offset]; offset += 1; if (first == 12) { if (offset >= data.len) return error.InvalidFont; const op = data[offset]; offset += 1; try self.escaped(op, sink); continue; } if (first == 28 or first > 31) { try self.push(try readCharNumber(data, &offset, first)); continue; } if (first <= 31) { switch (try self.operator(first, data, &offset, sink, depth)) { .ended => return .ended, .returned => return .returned, .none => {}, } continue; } } return .exhausted; } fn operator(self: *Interpreter, op: u8, data: []const u8, offset: *usize, sink: anytype, depth: usize) Error!OperatorResult { switch (op) { 1, 18, 3, 23 => self.consumeStems(), 19, 20 => try self.consumeMask(data, offset), 4 => try self.vmoveTo(sink), 5 => try self.rlineTo(sink), 6 => try self.alternatingLineTo(sink, true), 7 => try self.alternatingLineTo(sink, false), 8 => try self.rrcurveTo(sink), 10 => if (try self.callSubr(sink, depth)) return .ended, 11 => return .returned, 14 => { self.clear(); try sink.close(); return .ended; }, 21 => try self.rmoveTo(sink), 22 => try self.hmoveTo(sink), 24 => try self.rcurveLine(sink), 25 => try self.rlineCurve(sink), 26 => try self.vvcurveTo(sink), 27 => try self.hhcurveTo(sink), 29 => if (try self.callGlobalSubr(sink, depth)) return .ended, 30 => try self.alternatingCurveTo(sink, false), 31 => try self.alternatingCurveTo(sink, true), else => return error.UnsupportedOutline, } return .none; } fn escaped(self: *Interpreter, op: u8, sink: anytype) Error!void { switch (op) { 34 => try self.hflex(sink), 35 => try self.flex(sink), 36 => try self.hflex1(sink), 37 => try self.flex1(sink), else => return error.UnsupportedOutline, } } fn push(self: *Interpreter, value: i32) Error!void { if (self.stack_len >= self.stack.len) return error.InvalidFont; self.stack[self.stack_len] = value; self.stack_len += 1; } fn pop(self: *Interpreter) Error!i32 { if (self.stack_len == 0) return error.InvalidFont; self.stack_len -= 1; return self.stack[self.stack_len]; } fn clear(self: *Interpreter) void { self.stack_len = 0; } fn noteWidth(self: *Interpreter, expected_operands: usize) Error!void { if (!self.width_seen and self.stack_len > expected_operands) { try self.dropFirst(); } self.width_seen = true; } fn dropFirst(self: *Interpreter) Error!void { if (self.stack_len == 0) return error.InvalidFont; std.mem.copyForwards(i32, self.stack[0 .. self.stack_len - 1], self.stack[1..self.stack_len]); self.stack_len -= 1; } fn consumeStems(self: *Interpreter) void { var len = self.stack_len; if (!self.width_seen and len % 2 == 1) { std.mem.copyForwards(i32, self.stack[0 .. len - 1], self.stack[1..len]); len -= 1; } self.width_seen = true; self.stem_count += len / 2; self.clear(); } fn consumeMask(self: *Interpreter, data: []const u8, offset: *usize) Error!void { if (self.stack_len > 0) self.consumeStems(); const mask_len = (self.stem_count + 7) / 8; if (mask_len > data.len - offset.*) return error.InvalidFont; offset.* += mask_len; self.clear(); } fn rmoveTo(self: *Interpreter, sink: anytype) Error!void { try self.noteWidth(2); if (self.stack_len != 2) return error.InvalidFont; const dx = self.stack[0]; const dy = self.stack[1]; self.clear(); try self.moveRel(sink, dx, dy); } fn hmoveTo(self: *Interpreter, sink: anytype) Error!void { try self.noteWidth(1); if (self.stack_len != 1) return error.InvalidFont; const dx = self.stack[0]; self.clear(); try self.moveRel(sink, dx, 0); } fn vmoveTo(self: *Interpreter, sink: anytype) Error!void { try self.noteWidth(1); if (self.stack_len != 1) return error.InvalidFont; const dy = self.stack[0]; self.clear(); try self.moveRel(sink, 0, dy); } fn rlineTo(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len == 0 or self.stack_len % 2 != 0) return error.InvalidFont; var index: usize = 0; while (index < self.stack_len) : (index += 2) { try self.lineRel(sink, self.stack[index], self.stack[index + 1]); } self.clear(); } fn alternatingLineTo(self: *Interpreter, sink: anytype, horizontal_first: bool) Error!void { if (self.stack_len == 0) return error.InvalidFont; var horizontal = horizontal_first; for (self.stack[0..self.stack_len]) |value| { if (horizontal) { try self.lineRel(sink, value, 0); } else { try self.lineRel(sink, 0, value); } horizontal = !horizontal; } self.clear(); } fn rrcurveTo(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len == 0 or self.stack_len % 6 != 0) return error.InvalidFont; var index: usize = 0; while (index < self.stack_len) : (index += 6) { try self.curveRel(sink, self.stack[index], self.stack[index + 1], self.stack[index + 2], self.stack[index + 3], self.stack[index + 4], self.stack[index + 5]); } self.clear(); } fn rcurveLine(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len < 8 or (self.stack_len - 2) % 6 != 0) return error.InvalidFont; var index: usize = 0; while (index + 2 < self.stack_len) : (index += 6) { try self.curveRel(sink, self.stack[index], self.stack[index + 1], self.stack[index + 2], self.stack[index + 3], self.stack[index + 4], self.stack[index + 5]); } try self.lineRel(sink, self.stack[self.stack_len - 2], self.stack[self.stack_len - 1]); self.clear(); } fn rlineCurve(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len < 8 or (self.stack_len - 6) % 2 != 0) return error.InvalidFont; var index: usize = 0; while (index + 6 < self.stack_len) : (index += 2) { try self.lineRel(sink, self.stack[index], self.stack[index + 1]); } try self.curveRel(sink, self.stack[index], self.stack[index + 1], self.stack[index + 2], self.stack[index + 3], self.stack[index + 4], self.stack[index + 5]); self.clear(); } fn hhcurveTo(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len < 4) return error.InvalidFont; if (self.stack_len % 4 != 0 and self.stack_len % 4 != 1) return error.InvalidFont; var index: usize = 0; var dy1: i32 = 0; if (self.stack_len % 4 == 1) { dy1 = self.stack[0]; index = 1; } while (index < self.stack_len) : (index += 4) { try self.curveRel(sink, self.stack[index], dy1, self.stack[index + 1], self.stack[index + 2], self.stack[index + 3], 0); dy1 = 0; } self.clear(); } fn vvcurveTo(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len < 4) return error.InvalidFont; if (self.stack_len % 4 != 0 and self.stack_len % 4 != 1) return error.InvalidFont; var index: usize = 0; var dx1: i32 = 0; if (self.stack_len % 4 == 1) { dx1 = self.stack[0]; index = 1; } while (index < self.stack_len) : (index += 4) { try self.curveRel(sink, dx1, self.stack[index], self.stack[index + 1], self.stack[index + 2], 0, self.stack[index + 3]); dx1 = 0; } self.clear(); } fn alternatingCurveTo(self: *Interpreter, sink: anytype, horizontal_first: bool) Error!void { if (self.stack_len < 4) return error.InvalidFont; var index: usize = 0; var horizontal = horizontal_first; while (index < self.stack_len) { const remaining = self.stack_len - index; if (remaining != 4 and remaining != 5 and remaining < 8) return error.InvalidFont; if (horizontal) { const dx1 = self.stack[index]; const dx2 = self.stack[index + 1]; const dy2 = self.stack[index + 2]; const dy3 = self.stack[index + 3]; const dx3 = if (remaining == 5) self.stack[index + 4] else 0; try self.curveRel(sink, dx1, 0, dx2, dy2, dx3, dy3); index += if (remaining == 5) 5 else 4; } else { const dy1 = self.stack[index]; const dx2 = self.stack[index + 1]; const dy2 = self.stack[index + 2]; const dx3 = self.stack[index + 3]; const dy3 = if (remaining == 5) self.stack[index + 4] else 0; try self.curveRel(sink, 0, dy1, dx2, dy2, dx3, dy3); index += if (remaining == 5) 5 else 4; } horizontal = !horizontal; } self.clear(); } fn hflex(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len != 7) return error.InvalidFont; const a = self.stack[0..7]; try self.curveRel(sink, a[0], 0, a[1], a[2], a[3], 0); try self.curveRel(sink, a[4], 0, a[5], -a[2], a[6], 0); self.clear(); } fn flex(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len != 13) return error.InvalidFont; const a = self.stack[0..13]; try self.curveRel(sink, a[0], a[1], a[2], a[3], a[4], a[5]); try self.curveRel(sink, a[6], a[7], a[8], a[9], a[10], a[11]); self.clear(); } fn hflex1(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len != 9) return error.InvalidFont; const a = self.stack[0..9]; try self.curveRel(sink, a[0], a[1], a[2], a[3], a[4], 0); try self.curveRel(sink, a[5], 0, a[6], a[7], a[8], -(a[1] + a[3] + a[7])); self.clear(); } fn flex1(self: *Interpreter, sink: anytype) Error!void { if (self.stack_len != 11) return error.InvalidFont; const a = self.stack[0..11]; const dx_sum = a[0] + a[2] + a[4] + a[6] + a[8]; const dy_sum = a[1] + a[3] + a[5] + a[7] + a[9]; const dx6 = if (@abs(dx_sum) > @abs(dy_sum)) a[10] else -dx_sum; const dy6 = if (@abs(dx_sum) > @abs(dy_sum)) -dy_sum else a[10]; try self.curveRel(sink, a[0], a[1], a[2], a[3], a[4], a[5]); try self.curveRel(sink, a[6], a[7], a[8], a[9], dx6, dy6); self.clear(); } fn callSubr(self: *Interpreter, sink: anytype, depth: usize) Error!bool { const subrs = self.font.local_subrs orelse return error.InvalidFont; return try self.callIndexSubr(subrs, sink, depth); } fn callGlobalSubr(self: *Interpreter, sink: anytype, depth: usize) Error!bool { return try self.callIndexSubr(self.font.global_subrs, sink, depth); } fn callIndexSubr(self: *Interpreter, subrs: Index, sink: anytype, depth: usize) Error!bool { const raw = try self.pop(); const biased = raw + subrBias(subrs.count); if (biased < 0) return error.InvalidFont; const index: u32 = @intCast(biased); if (index >= subrs.count) return error.InvalidFont; const subr = try subrs.item(index); return switch (try self.run(subr, sink, depth + 1)) { .ended => true, .returned => false, .exhausted => error.InvalidFont, }; } fn moveRel(self: *Interpreter, sink: anytype, dx: i32, dy: i32) Error!void { self.x += dx; self.y += dy; try sink.moveTo(self.x, self.y); } fn lineRel(self: *Interpreter, sink: anytype, dx: i32, dy: i32) Error!void { self.x += dx; self.y += dy; try sink.lineTo(self.x, self.y); } fn curveRel(self: *Interpreter, sink: anytype, dx1: i32, dy1: i32, dx2: i32, dy2: i32, dx3: i32, dy3: i32) Error!void { const x1 = self.x + dx1; const y1 = self.y + dy1; const x2 = x1 + dx2; const y2 = y1 + dy2; self.x = x2 + dx3; self.y = y2 + dy3; try sink.curveTo(x1, y1, x2, y2, self.x, self.y); }};fn subrBias(count: u32) i32 { if (count < 1240) return 107; if (count < 33900) return 1131; return 32768;}fn isDictOperator(byte: u8) bool { return byte <= 21 and byte != 28;}fn readDictNumber(data: []const u8, offset: *usize, first: u8) Error!i32 { return switch (first) { 28 => value: { const value = try readI16(data, offset.*); offset.* += 2; break :value value; }, 29 => value: { const value = try readI32(data, offset.*); offset.* += 4; break :value value; }, 30 => value: { try skipReal(data, offset); break :value 0; }, 32...246 => @as(i32, first) - 139, 247...250 => value: { if (offset.* >= data.len) return error.InvalidFont; const b1 = data[offset.*]; offset.* += 1; break :value (@as(i32, first) - 247) * 256 + @as(i32, b1) + 108; }, 251...254 => value: { if (offset.* >= data.len) return error.InvalidFont; const b1 = data[offset.*]; offset.* += 1; break :value -((@as(i32, first) - 251) * 256) - @as(i32, b1) - 108; }, 255 => value: { const value = try readI32(data, offset.*); offset.* += 4; break :value value; }, else => error.InvalidFont, };}fn readCharNumber(data: []const u8, offset: *usize, first: u8) Error!i32 { return switch (first) { 28 => value: { const value = try readI16(data, offset.*); offset.* += 2; break :value value; }, 32...246 => @as(i32, first) - 139, 247...250 => value: { if (offset.* >= data.len) return error.InvalidFont; const b1 = data[offset.*]; offset.* += 1; break :value (@as(i32, first) - 247) * 256 + @as(i32, b1) + 108; }, 251...254 => value: { if (offset.* >= data.len) return error.InvalidFont; const b1 = data[offset.*]; offset.* += 1; break :value -((@as(i32, first) - 251) * 256) - @as(i32, b1) - 108; }, 255 => value: { const value = try readI32(data, offset.*); offset.* += 4; break :value value; }, else => error.InvalidFont, };}fn skipReal(data: []const u8, offset: *usize) Error!void { while (offset.* < data.len) { const byte = data[offset.*]; offset.* += 1; if ((byte >> 4) == 0xf or (byte & 0xf) == 0xf) return; } return error.InvalidFont;}fn readIndexOffset(data: []const u8, offsets_offset: usize, off_size: u8, index: usize) Error!u32 { const offset = offsets_offset + index * @as(usize, off_size); if (offset > data.len or @as(usize, off_size) > data.len - offset) return error.InvalidFont; var value: u32 = 0; var i: usize = 0; while (i < off_size) : (i += 1) { value = (value << 8) | data[offset + i]; } return 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 readI32(data: []const u8, offset: usize) Error!i32 { if (offset + 4 > data.len) return error.InvalidFont; return std.mem.readInt(i32, data[offset..][0..4], .big);}Source: lib/filigree/src/font/root.zig:2
zig
pub const cff = @import("cff.zig");Audit
| Definitions | 3 |
|---|---|
| Public names | 3 |
| Members | 4 |
| Version | 26.7.0 |
| Revision | daab053ee433 |