Skip to documentation
SLOP

tiny.termtex.image

Reference tiny.termtex image

Defined in tiny.termtex.

API (5)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallstest sourcelib.termtex.src.imagetest: image renderer accepts CFF outl...test sourcelib.termtex.src.imagetest: image renderer accepts fallback...test sourcelib.termtex.src.imagetest: image renderer adds automatic b...test sourcelib.termtex.src.imagetest: image renderer applies explicit...test sourcelib.termtex.src.imagetest: image renderer draws brace anno...+37 moreimagerenderExprimagerender
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsimagerenderprivate sourcelib.termtex.src.imagefillprivate sourcelib.termtex.src.imageimageDimensionprivate sourcelib.termtex.src.imageloadFallbackFontsprivate sourcelib.termtex.src.imageunloadFallbackFontsprivate sourcelib.termtex.src.imagevalidateOptionsimagerenderExpr
Static calls · unresolved targets: 0 · external targets: 10.

Source: lib/termtex/src/image.zig

zig
const std = @import("std");const filigree = @import("filigree");const ast = @import("ast.zig");const operator = @import("operator.zig");const parse = @import("parse.zig");pub const Color = filigree.render.Color;pub const Options = struct {    font_bytes: []const u8,    fallback_font_bytes: []const []const u8 = &.{},    font_size: i32 = 24,    /// Output storage acquired once per image and reused for every text run.    shaping_output: filigree.Output.Limits = .{        .max_glyphs = 4096,        .max_ligature_carets = 4096,    },    padding_x: u32 = 4,    padding_y: u32 = 4,    foreground: Color = .{},    background: Color = .{ .a = 0 },};pub const Image = struct {    allocator: std.mem.Allocator,    pixels: []u8,    width: u32,    height: u32,    stride: u32,    baseline: u32,    pub fn deinit(self: *Image) void {        self.allocator.free(self.pixels);        self.* = undefined;    }};const Style = struct {    font_size: i32,};const Metrics = struct {    ascender: i32,    descender: i32,    line_gap: i32,    line_height: i32,};const Box = struct {    width: i32,    height: i32,    baseline: i32,    commands: []const Command,    class: MathClass = .ordinary,};const MathClass = enum {    ordinary,    operator,    binary,    relation,    open,    close,    punctuation,    inner,    spacing,};const Command = union(enum) {    text: Text,    rect: Rect,    line: Line,    glyph: Glyph,};const Text = struct {    x: i32,    y: i32,    value: []const u8,    font_size: i32,};const Rect = struct {    x: i32,    y: i32,    width: i32,    height: i32,};const Line = struct {    x0: i32,    y0: i32,    x1: i32,    y1: i32,    width: i32,};const Glyph = struct {    x: i32,    y: i32,    glyph_id: u32,    font_size: i32,};const GlyphInkBounds = struct {    left: i32,    top: i32,    right: i32,    bottom: i32,};const MathRadicalSign = struct {    width: i32,    height: i32,    commands: []const Command,};const Context = struct {    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    options: Options,    font: filigree.Font,    fallback_fonts: []const filigree.Font,    shaping_output: *filigree.Output,    const layoutExpr = layoutExprImpl;    const layoutText = layoutTextImpl;    const layoutOperator = layoutOperatorImpl;    const layoutSpace = layoutSpaceImpl;    const layoutRow = layoutRowImpl;    const layoutFraction = layoutFractionImpl;    const layoutSqrt = layoutSqrtImpl;    const layoutScripts = layoutScriptsImpl;    const layoutLimits = layoutLimitsImpl;    const layoutMathHorizontalArrow = layoutMathHorizontalArrowImpl;    const layoutStretchArrow = layoutStretchArrowImpl;    const layoutAccent = layoutAccentImpl;    const layoutNativeAccent = layoutNativeAccentImpl;    const layoutAnnotation = layoutAnnotationImpl;    const layoutStack = layoutStackImpl;    const layoutBrace = layoutBraceImpl;    const layoutBoxed = layoutBoxedImpl;    const layoutDelimited = layoutDelimitedImpl;    const layoutDelimiter = layoutDelimiterImpl;    const layoutDelimiterShape = layoutDelimiterShapeImpl;    const layoutGrid = layoutGridImpl;    const paint = paintImpl;    const fontMetrics = fontMetricsImpl;    const fallbackCandidates = fallbackCandidatesImpl;    const makeBox = makeBoxImpl;    const boxFromList = boxFromListImpl;};pub fn render(allocator: std.mem.Allocator, source: []const u8, options: Options) !Image {    var arena = std.heap.ArenaAllocator.init(allocator);    defer arena.deinit();    const expr = try parse.parse(arena.allocator(), source);    return renderExpr(allocator, arena.allocator(), expr, options);}pub fn renderExpr(    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    expr: *const ast.Expr,    options: Options,) !Image {    try validateOptions(options);    var font = filigree.Font.initFromBytes(options.font_bytes.ptr, options.font_bytes.len) orelse return error.InvalidFont;    defer font.deinit();    font.setScale(@floatFromInt(options.font_size), 72);    const fallback_fonts = try loadFallbackFonts(allocator, options.fallback_font_bytes);    defer unloadFallbackFonts(allocator, fallback_fonts);    var shaping_output = try filigree.Output.init(allocator, options.shaping_output);    defer shaping_output.deinit(allocator);    var context = Context{        .allocator = allocator,        .scratch = scratch,        .options = options,        .font = font,        .fallback_fonts = fallback_fonts,        .shaping_output = &shaping_output,    };    const box = try context.layoutExpr(expr, .{ .font_size = options.font_size });    const width = try imageDimension(box.width, options.padding_x);    const height = try imageDimension(box.height, options.padding_y);    const stride = try std.math.mul(u32, width, 4);    const pixels = try allocator.alloc(u8, try std.math.mul(usize, stride, height));    errdefer allocator.free(pixels);    fill(pixels, width, height, stride, options.background);    const canvas = try filigree.render.Canvas.init(pixels, width, height, stride);    try context.paint(canvas, box.commands, @intCast(options.padding_x), @intCast(options.padding_y));    return .{        .allocator = allocator,        .pixels = pixels,        .width = width,        .height = height,        .stride = stride,        .baseline = @intCast(@max(0, box.baseline + @as(i32, @intCast(options.padding_y)))),    };}fn layoutExprImpl(context: *Context, expr: *const ast.Expr, style: Style) anyerror!Box {    return switch (expr.*) {        .row => |items| try context.layoutRow(items, style),        .text => |value| try layoutTextWithDisplayOperator(context, value, style),        .operator => |value| try context.layoutOperator(value, style),        .space => |value| try context.layoutSpace(value, style),        .fraction => |value| try context.layoutFraction(value, style),        .sqrt => |value| try context.layoutSqrt(value, style),        .scripts => |value| try context.layoutScripts(value, style),        .accent => |value| try context.layoutAccent(value, style),        .annotation => |value| try context.layoutAnnotation(value, style),        .delimited => |value| try context.layoutDelimited(value, style),        .grid => |value| try context.layoutGrid(value, style),    };}fn layoutTextWithDisplayOperator(context: *Context, value: []const u8, style: Style) anyerror!Box {    if (!operator.displayOperatorText(value)) return context.layoutText(value, style);    const min_height = displayOperatorMinHeight(context, style) orelse return context.layoutText(value, style);    const nominal = try context.layoutText(value, style);    if (nominal.height >= min_height) return nominal;    var out = try context.layoutText(value, delimiterStyle(context, min_height, style));    out.class = .operator;    return out;}fn layoutTextImpl(context: *Context, value: []const u8, style: Style) anyerror!Box {    const metrics = context.fontMetrics(style.font_size);    if (value.len == 0) return context.makeBox(0, metrics.line_height, metrics.ascender, &.{});    var shaped_font = context.font;    shaped_font.setScale(@floatFromInt(style.font_size), 72);    const fallback_candidates = try context.fallbackCandidates(style.font_size);    const shaped = if (fallback_candidates.len == 0)        try filigree.measureUtf8(context.allocator, context.shaping_output, &shaped_font, value)    else        try filigree.measureFallbackUtf8(            context.allocator,            context.shaping_output,            &shaped_font,            fallback_candidates,            value,        );    var command = [_]Command{.{ .text = .{        .x = 0,        .y = 0,        .value = value,        .font_size = style.font_size,    } }};    var box = try context.makeBox(@max(1, shaped.advance_x), metrics.line_height, metrics.ascender, &command);    box.class = mathClassForText(value);    return box;}fn layoutOperatorImpl(context: *Context, value: ast.Operator, style: Style) anyerror!Box {    var out = try context.layoutExpr(value.body, style);    out.class = .operator;    return out;}fn layoutSpaceImpl(context: *Context, value: ast.Space, style: Style) anyerror!Box {    const metrics = context.fontMetrics(style.font_size);    var box = try context.makeBox(spacePixels(value, style), metrics.line_height, metrics.ascender, &.{});    box.class = .spacing;    return box;}fn layoutRowImpl(context: *Context, items: []const *ast.Expr, style: Style) anyerror!Box {    if (items.len == 0) {        const metrics = context.fontMetrics(style.font_size);        return context.makeBox(0, metrics.line_height, metrics.ascender, &.{});    }    const children = try context.scratch.alloc(Box, items.len);    var baseline: i32 = 0;    var descent: i32 = 0;    for (items, 0..) |item, index| {        children[index] = try context.layoutExpr(item, style);        baseline = @max(baseline, children[index].baseline);        descent = @max(descent, children[index].height - children[index].baseline);    }    var width_cursor: i32 = 0;    var min_x: i32 = 0;    var max_x: i32 = 0;    for (children, 0..) |child, index| {        if (index != 0) width_cursor += mathClassSpace(children, index, style);        min_x = @min(min_x, width_cursor);        max_x = @max(max_x, width_cursor + child.width);        width_cursor += child.width;        min_x = @min(min_x, width_cursor);        max_x = @max(max_x, width_cursor);    }    var commands: std.ArrayListUnmanaged(Command) = .empty;    var x: i32 = -min_x;    for (children, 0..) |child, index| {        if (index != 0) x += mathClassSpace(children, index, style);        const y = baseline - child.baseline;        try appendCommands(context.scratch, &commands, child.commands, x, y);        x += child.width;    }    return context.boxFromList(max_x - min_x, baseline + descent, baseline, &commands);}fn layoutFractionImpl(context: *Context, value: ast.Fraction, style: Style) anyerror!Box {    const numerator = try context.layoutExpr(value.numerator, style);    const denominator = try context.layoutExpr(value.denominator, style);    const side = @max(2, @divTrunc(style.font_size, 4));    const numerator_gap = fractionNumeratorGap(context, style, value.style);    const denominator_gap = fractionDenominatorGap(context, style, value.style);    const rule = fractionRuleWidth(context, style);    const inner = @max(numerator.width, denominator.width);    const width = inner + side * 2;    if (mathConstants(context) != null) return try layoutMathFraction(context, numerator, denominator, width, rule, numerator_gap, denominator_gap, style, value.style);    const rule_y = numerator.height + numerator_gap;    const denominator_y = rule_y + rule + denominator_gap;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, numerator.commands, centered(width, numerator.width), 0);    try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = rule_y, .width = width, .height = rule } });    try appendCommands(context.scratch, &commands, denominator.commands, centered(width, denominator.width), denominator_y);    return context.boxFromList(width, denominator_y + denominator.height, rule_y + rule, &commands);}fn layoutMathFraction(context: *Context, numerator: Box, denominator: Box, width: i32, rule: i32, numerator_gap: i32, denominator_gap: i32, style: Style, fraction_style: ast.FractionStyle) anyerror!Box {    const rule_y = -mathAxisHeight(context, style) - @divTrunc(rule, 2);    var numerator_y = -fractionNumeratorShiftUp(context, style, fraction_style) - numerator.baseline;    const numerator_current_gap = rule_y - (numerator_y + numerator.height);    if (numerator_current_gap < numerator_gap) numerator_y -= numerator_gap - numerator_current_gap;    var denominator_y = fractionDenominatorShiftDown(context, style, fraction_style) - denominator.baseline;    const denominator_current_gap = denominator_y - (rule_y + rule);    if (denominator_current_gap < denominator_gap) denominator_y += denominator_gap - denominator_current_gap;    const min_y = @min(numerator_y, rule_y);    const max_y = @max(denominator_y + denominator.height, rule_y + rule);    const y_shift = if (min_y < 0) -min_y else 0;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, numerator.commands, centered(width, numerator.width), numerator_y + y_shift);    try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = rule_y + y_shift, .width = width, .height = rule } });    try appendCommands(context.scratch, &commands, denominator.commands, centered(width, denominator.width), denominator_y + y_shift);    return context.boxFromList(width, max_y - min_y, y_shift, &commands);}fn layoutSqrtImpl(context: *Context, value: ast.Radical, style: Style) anyerror!Box {    const body = try context.layoutExpr(value.body, style);    const rooted = try layoutSqrtBody(context, body, style);    const index_expr = value.index orelse return rooted;    const index = try context.layoutExpr(index_expr, scriptScriptStyle(context, style));    return try layoutRootDegree(context, rooted, index, style);}fn layoutSqrtBody(context: *Context, body: Box, style: Style) anyerror!Box {    const rule = radicalRuleWidth(context, style);    const clearance = radicalClearance(context, style);    const extra_ascender = radicalExtraAscender(context, style);    const rule_y = extra_ascender;    const body_y = rule_y + rule + clearance;    const target_height = body.height + clearance + rule;    if (try layoutMathRadicalSign(context, target_height, style)) |radical| {        const body_x = radical.width;        const height = @max(body_y + body.height, rule_y + radical.height);        var commands: std.ArrayListUnmanaged(Command) = .empty;        try appendCommands(context.scratch, &commands, radical.commands, 0, rule_y);        try commands.append(context.scratch, .{ .rect = .{            .x = body_x,            .y = rule_y,            .width = body.width,            .height = rule,        } });        try appendCommands(context.scratch, &commands, body.commands, body_x, body_y);        return context.boxFromList(body_x + body.width, height, body_y + body.baseline, &commands);    }    const radical_width = radicalWidth(context, target_height, style);    const body_gap = @max(2, @divTrunc(style.font_size, 8));    const body_x = radical_width + body_gap;    const radical_x0 = 0;    const radical_x1 = @max(rule * 2, @divTrunc(radical_width, 3));    const radical_x2 = body_x - body_gap;    const radical_y0 = body_y + @divTrunc(body.height * 3, 5);    const radical_y1 = body_y + body.height - rule;    const radical_y2 = rule_y + rule;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try commands.append(context.scratch, .{ .line = .{ .x0 = radical_x0, .y0 = radical_y0, .x1 = radical_x1, .y1 = radical_y1, .width = rule } });    try commands.append(context.scratch, .{ .line = .{ .x0 = radical_x1, .y0 = radical_y1, .x1 = radical_x2, .y1 = radical_y2, .width = rule } });    try commands.append(context.scratch, .{ .rect = .{        .x = radical_x2,        .y = rule_y,        .width = body.width + body_gap,        .height = rule,    } });    try appendCommands(context.scratch, &commands, body.commands, body_x, body_y);    return context.boxFromList(body_x + body.width, body_y + body.height, body_y + body.baseline, &commands);}fn layoutRootDegree(context: *Context, rooted: Box, degree: Box, style: Style) anyerror!Box {    const before = @max(0, radicalKernBeforeDegree(context, style));    const after = @max(-degree.width, radicalKernAfterDegree(context, style));    const radical_height = @max(1, rooted.height);    const raise = @divTrunc(@as(i64, radical_height) * radicalDegreeBottomRaisePercent(context) + 50, 100);    const degree_y = radical_height - @as(i32, @intCast(raise)) - degree.height;    const min_y = @min(0, degree_y);    const max_y = @max(rooted.height, degree_y + degree.height);    const rooted_x = before + degree.width + after;    const width = @max(rooted_x + rooted.width, before + degree.width);    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, degree.commands, before, degree_y - min_y);    try appendCommands(context.scratch, &commands, rooted.commands, rooted_x, -min_y);    var out = try context.boxFromList(width, max_y - min_y, rooted.baseline - min_y, &commands);    out.class = rooted.class;    return out;}fn layoutMathRadicalSign(context: *Context, target_height: i32, style: Style) anyerror!?MathRadicalSign {    const glyph_id = context.font.face.glyphId(0x221a);    if (glyph_id == 0) return null;    const target = pixelHeightToDesignUnits(context, target_height, style);    if (context.font.face.mathVerticalVariant(glyph_id, target)) |variant| {        if (variant.advance_measurement >= target) return try layoutMathRadicalGlyph(context, variant.glyph_id, variant.advance_measurement, target_height, style);    }    return try layoutMathRadicalAssembly(context, glyph_id, target, target_height, style);}fn layoutMathRadicalGlyph(context: *Context, glyph_id: u32, advance_measurement: u16, target_height: i32, style: Style) anyerror!?MathRadicalSign {    const bounds = (try glyphInkBounds(context, glyph_id, style)) orelse return null;    const ink_width = @max(1, bounds.right - bounds.left);    const ink_height = @max(1, bounds.bottom - bounds.top);    const advance_height = designUnitsToPixelsCeil(context, advance_measurement, style);    const height = @max(target_height, @max(ink_height, advance_height));    var commands = [_]Command{.{ .glyph = .{        .x = -bounds.left,        .y = -bounds.top,        .glyph_id = glyph_id,        .font_size = style.font_size,    } }};    const owned = try context.scratch.dupe(Command, &commands);    return .{ .width = ink_width, .height = height, .commands = owned };}fn layoutMathRadicalAssembly(context: *Context, glyph_id: u32, target: u16, target_height: i32, style: Style) anyerror!?MathRadicalSign {    const assembly = (try context.font.face.mathVerticalAssemblyAlloc(context.scratch, glyph_id, target)) orelse return null;    if (assembly.parts.len == 0 or assembly.advance_measurement < target) return null;    const assembly_height = @max(1, designUnitsToPixelsCeil(context, assembly.advance_measurement, style));    const bounds = try context.scratch.alloc(GlyphInkBounds, assembly.parts.len);    var left_bound: i32 = std.math.maxInt(i32);    var right_bound: i32 = std.math.minInt(i32);    var height = @max(target_height, assembly_height);    for (assembly.parts, 0..) |part, index| {        bounds[index] = (try glyphInkBounds(context, part.glyph_id, style)) orelse return null;        left_bound = @min(left_bound, bounds[index].left);        right_bound = @max(right_bound, bounds[index].right);        const part_end = @as(u32, part.advance_offset) + @as(u32, part.full_advance);        if (part_end > assembly.advance_measurement) return null;        const part_top_design: u16 = @intCast(@as(u32, assembly.advance_measurement) - part_end);        const part_top = designUnitsToPixelsFloor(context, part_top_design, style);        height = @max(height, part_top + @max(1, bounds[index].bottom - bounds[index].top));    }    if (left_bound >= right_bound) return null;    var commands: std.ArrayListUnmanaged(Command) = .empty;    for (assembly.parts, 0..) |part, index| {        const part_end = @as(u32, part.advance_offset) + @as(u32, part.full_advance);        const part_top_design: u16 = @intCast(@as(u32, assembly.advance_measurement) - part_end);        const part_top = designUnitsToPixelsFloor(context, part_top_design, style);        try commands.append(context.scratch, .{ .glyph = .{            .x = -left_bound,            .y = part_top - bounds[index].top,            .glyph_id = part.glyph_id,            .font_size = style.font_size,        } });    }    return .{        .width = right_bound - left_bound,        .height = height,        .commands = try commands.toOwnedSlice(context.scratch),    };}fn layoutScriptsImpl(context: *Context, value: ast.Scripts, style: Style) anyerror!Box {    if (operator.limitsBase(value.base)) return context.layoutLimits(value, style);    const base = try context.layoutExpr(value.base, style);    const script = scriptStyle(context, style);    const sup = if (value.sup) |expr| try context.layoutExpr(expr, script) else null;    const sub = if (value.sub) |expr| try context.layoutExpr(expr, script) else null;    if (mathConstants(context) != null) return try layoutMathScripts(context, base, sup, sub, style);    const gap = @max(1, @divTrunc(style.font_size, 8));    const script_width = @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0);    const base_y = if (sup) |box| @max(0, box.height - @divTrunc(base.baseline, 2)) else 0;    const baseline = base_y + base.baseline;    const sub_y = baseline + gap;    var height = base_y + base.height;    if (sub) |box| height = @max(height, sub_y + box.height);    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, base.commands, 0, base_y);    if (sup) |box| try appendCommands(context.scratch, &commands, box.commands, base.width + gap, 0);    if (sub) |box| try appendCommands(context.scratch, &commands, box.commands, base.width + gap, sub_y);    var out = try context.boxFromList(base.width + if (script_width == 0) 0 else gap + script_width, height, baseline, &commands);    out.class = base.class;    return out;}fn layoutMathScripts(context: *Context, base: Box, sup: ?Box, sub: ?Box, style: Style) anyerror!Box {    const script_gap = scriptHorizontalGap(context, style);    const script_width = @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0);    const script_x = base.width + if (script_width == 0) 0 else script_gap;    const metrics = context.fontMetrics(style.font_size);    const extended_base = base.height > metrics.line_height;    const base_y = -base.baseline;    var sup_y: ?i32 = null;    var sub_y: ?i32 = null;    if (sup) |box| {        var shift = @max(            superscriptShiftUp(context, style),            box.height - box.baseline + superscriptBottomMin(context, style),        );        if (extended_base) shift = @max(shift, base.baseline - superscriptBaselineDropMax(context, style));        sup_y = -shift - box.baseline;    }    if (sub) |box| {        var shift = @max(            subscriptShiftDown(context, style),            box.baseline - subscriptTopMax(context, style),        );        if (extended_base) shift = @max(shift, base.height - base.baseline + subscriptBaselineDropMin(context, style));        sub_y = shift - box.baseline;    }    if (sup != null and sub != null) {        const sup_box = sup.?;        var above_y = sup_y.?;        var below_y = sub_y.?;        const needed_gap = subSuperscriptGap(context, style);        const current_gap = below_y - (above_y + sup_box.height);        if (current_gap < needed_gap) {            var remaining = needed_gap - current_gap;            const lowest_sup_bottom = -superscriptBottomMaxWithSubscript(context, style);            const sup_bottom = above_y + sup_box.height;            if (sup_bottom > lowest_sup_bottom) {                const lift = @min(remaining, sup_bottom - lowest_sup_bottom);                above_y -= lift;                remaining -= lift;            }            below_y += remaining;        }        sup_y = above_y;        sub_y = below_y;    }    var min_y = base_y;    var max_y = base_y + base.height;    if (sup) |box| {        min_y = @min(min_y, sup_y.?);        max_y = @max(max_y, sup_y.? + box.height);    }    if (sub) |box| {        min_y = @min(min_y, sub_y.?);        max_y = @max(max_y, sub_y.? + box.height);    }    const y_shift = if (min_y < 0) -min_y else 0;    const width = base.width + if (script_width == 0) 0 else script_gap + script_width;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, base.commands, 0, base_y + y_shift);    if (sup) |box| try appendCommands(context.scratch, &commands, box.commands, script_x, sup_y.? + y_shift);    if (sub) |box| try appendCommands(context.scratch, &commands, box.commands, script_x, sub_y.? + y_shift);    var out = try context.boxFromList(width, max_y - min_y, y_shift, &commands);    out.class = base.class;    return out;}fn layoutLimitsImpl(context: *Context, value: ast.Scripts, style: Style) anyerror!Box {    var base = try context.layoutExpr(value.base, style);    const script = scriptStyle(context, style);    const sup = if (value.sup) |expr| try context.layoutExpr(expr, script) else null;    const sub = if (value.sub) |expr| try context.layoutExpr(expr, script) else null;    const stretch_arrow = operator.stretchArrowBase(value.base);    if (stretch_arrow) |arrow| {        const label_width = @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0);        const target_width = @max(base.width, label_width + stretchArrowLabelPadding(style) * 2);        base = try context.layoutStretchArrow(arrow, target_width, base, value.base, style);    }    const stretch_stack = stretch_arrow != null;    const top_shift = if (stretch_stack) stretchStackTopShiftUp(context, style) else upperLimitBaselineRise(context, style);    const top_gap = if (stretch_stack) stretchStackGapAbove(context, style) else upperLimitGap(context, style);    const bottom_shift = if (stretch_stack) stretchStackBottomShiftDown(context, style) else lowerLimitBaselineDrop(context, style);    const bottom_gap = if (stretch_stack) stretchStackGapBelow(context, style) else lowerLimitGap(context, style);    const width = @max(base.width, @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0));    const base_y = if (sup) |box| @max(        top_shift + box.baseline,        top_gap + box.height,    ) else 0;    const base_bottom = base_y + base.height;    const sub_y = if (sub) |box| base_bottom + @max(        bottom_gap,        bottom_shift - box.baseline,    ) else base_bottom;    var height = base_bottom;    if (sub) |box| height = sub_y + box.height;    var commands: std.ArrayListUnmanaged(Command) = .empty;    if (sup) |box| try appendCommands(context.scratch, &commands, box.commands, centered(width, box.width), 0);    try appendCommands(context.scratch, &commands, base.commands, centered(width, base.width), base_y);    if (sub) |box| try appendCommands(context.scratch, &commands, box.commands, centered(width, box.width), sub_y);    var out = try context.boxFromList(width, height, base_y + base.baseline, &commands);    out.class = base.class;    return out;}fn layoutStretchArrowImpl(context: *Context, arrow: operator.StretchArrow, target_width: i32, nominal: Box, base_expr: *const ast.Expr, style: Style) anyerror!Box {    if (try context.layoutMathHorizontalArrow(base_expr, target_width, nominal, style)) |box| return box;    const left_head = if (arrow.left_head) |text| try context.layoutText(text, style) else null;    const right_head = if (arrow.right_head) |text| try context.layoutText(text, style) else null;    const width = @max(target_width, stretchArrowMinimumWidth(left_head, right_head, style));    const metrics = context.fontMetrics(style.font_size);    var ascender = @max(nominal.baseline, metrics.ascender);    var descender = @max(nominal.height - nominal.baseline, metrics.line_height - metrics.ascender);    if (left_head) |box| {        ascender = @max(ascender, box.baseline);        descender = @max(descender, box.height - box.baseline);    }    if (right_head) |box| {        ascender = @max(ascender, box.baseline);        descender = @max(descender, box.height - box.baseline);    }    const baseline = ascender;    const height = ascender + descender;    const rule = strokeWidth(context, style);    const axis = stretchArrowAxis(context, style, baseline, height, rule);    const overlap = stretchArrowHeadOverlap(style);    var shaft_start: i32 = 0;    if (left_head) |box| shaft_start = @max(0, box.width - overlap);    var shaft_end = width;    if (right_head) |box| shaft_end = width - @max(0, box.width - overlap);    if (shaft_end <= shaft_start) {        shaft_start = 0;        shaft_end = width;    }    var commands: std.ArrayListUnmanaged(Command) = .empty;    switch (arrow.shaft) {        .single => try appendStretchArrowShaft(context.scratch, &commands, shaft_start, shaft_end, axis, rule),        .double => try appendDoubleStretchArrowShaft(context.scratch, &commands, shaft_start, shaft_end, axis, rule, style),        .squiggle => try appendSquiggleStretchArrowShaft(context.scratch, &commands, shaft_start, shaft_end, axis, rule, style),    }    if (arrow.left_bar) try appendStretchArrowBar(context.scratch, &commands, axis, rule, style);    if (left_head) |box| try appendCommands(context.scratch, &commands, box.commands, 0, baseline - box.baseline);    if (right_head) |box| try appendCommands(context.scratch, &commands, box.commands, width - box.width, baseline - box.baseline);    var out = try context.boxFromList(width, height, baseline, &commands);    out.class = nominal.class;    return out;}fn layoutMathHorizontalArrowImpl(context: *Context, base_expr: *const ast.Expr, target_width: i32, nominal: Box, style: Style) anyerror!?Box {    const glyph_id = singleGlyphId(context, base_expr) orelse return null;    const target = pixelWidthToDesignUnits(context, target_width, style);    if (context.font.face.mathHorizontalVariant(glyph_id, target)) |variant| {        if (variant.advance_measurement >= target) return try layoutMathHorizontalGlyphArrow(context, variant.glyph_id, variant.advance_measurement, target_width, nominal, style);    }    return try layoutMathHorizontalAssemblyArrow(context, glyph_id, target, target_width, nominal, style);}fn layoutMathHorizontalGlyphArrow(context: *Context, glyph_id: u32, advance_measurement: u16, target_width: i32, nominal: Box, style: Style) anyerror!?Box {    const bounds = (try glyphInkBounds(context, glyph_id, style)) orelse return null;    const metrics = context.fontMetrics(style.font_size);    const baseline = @max(nominal.baseline, metrics.ascender);    const height = @max(nominal.height, metrics.line_height);    const x_shift = @max(0, -bounds.left);    const advance_width = designUnitsToPixelsCeil(context, advance_measurement, style);    const width = @max(target_width, @max(advance_width + x_shift, bounds.right + x_shift));    var commands = [_]Command{.{ .glyph = .{        .x = x_shift,        .y = baseline - metrics.ascender,        .glyph_id = glyph_id,        .font_size = style.font_size,    } }};    var out = try context.makeBox(width, height, baseline, &commands);    out.class = nominal.class;    return out;}fn layoutMathHorizontalAssemblyArrow(context: *Context, glyph_id: u32, target: u16, target_width: i32, nominal: Box, style: Style) anyerror!?Box {    const assembly = (try context.font.face.mathHorizontalAssemblyAlloc(context.scratch, glyph_id, target)) orelse return null;    if (assembly.parts.len == 0 or assembly.advance_measurement < target) return null;    const metrics = context.fontMetrics(style.font_size);    const baseline = @max(nominal.baseline, metrics.ascender);    const height = @max(nominal.height, metrics.line_height);    const bounds = try context.scratch.alloc(GlyphInkBounds, assembly.parts.len);    var left_bound: i32 = std.math.maxInt(i32);    var right_bound: i32 = std.math.minInt(i32);    for (assembly.parts, 0..) |part, index| {        const part_end = @as(u32, part.advance_offset) + @as(u32, part.full_advance);        if (part_end > assembly.advance_measurement) return null;        const part_x = designUnitsToPixelsFloor(context, part.advance_offset, style);        bounds[index] = (try glyphInkBounds(context, part.glyph_id, style)) orelse return null;        left_bound = @min(left_bound, part_x + bounds[index].left);        right_bound = @max(right_bound, part_x + bounds[index].right);    }    if (left_bound >= right_bound) return null;    const x_shift = @max(0, -left_bound);    const assembly_width = designUnitsToPixelsCeil(context, assembly.advance_measurement, style);    const width = @max(target_width, @max(assembly_width + x_shift, right_bound + x_shift));    var commands: std.ArrayListUnmanaged(Command) = .empty;    for (assembly.parts) |part| {        try commands.append(context.scratch, .{ .glyph = .{            .x = x_shift + designUnitsToPixelsFloor(context, part.advance_offset, style),            .y = baseline - metrics.ascender,            .glyph_id = part.glyph_id,            .font_size = style.font_size,        } });    }    var out = try context.boxFromList(width, height, baseline, &commands);    out.class = nominal.class;    return out;}fn layoutAccentImpl(context: *Context, value: ast.Accent, style: Style) anyerror!Box {    const body = try context.layoutExpr(value.body, style);    var commands: std.ArrayListUnmanaged(Command) = .empty;    switch (value.mark) {        .bar => {            const rule = overbarRuleWidth(context, style);            const gap = overbarGap(context, style);            const extra = overbarExtraAscender(context, style);            const body_y = extra + rule + gap;            try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = extra, .width = body.width, .height = rule } });            try appendCommands(context.scratch, &commands, body.commands, 0, body_y);            var out = try context.boxFromList(body.width, body.height + body_y, body.baseline + body_y, &commands);            out.class = body.class;            return out;        },        .underline => {            const rule = underbarRuleWidth(context, style);            const gap = underbarGap(context, style);            const extra = underbarExtraDescender(context, style);            try appendCommands(context.scratch, &commands, body.commands, 0, 0);            try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = body.height + gap, .width = body.width, .height = rule } });            var out = try context.boxFromList(body.width, body.height + gap + rule + extra, body.baseline, &commands);            out.class = body.class;            return out;        },        else => {            if (try context.layoutNativeAccent(value.mark, body, style)) |box| return box;            const mark_text = accentText(value.mark);            const mark_style = scriptStyle(context, style);            const mark_box = try context.layoutText(mark_text, mark_style);            const mark_x = accentMarkX(context, value.body, body, mark_text, mark_box, style, mark_style);            const body_y = @divTrunc(mark_box.height, 2);            const min_x = @min(@as(i32, 0), mark_x);            const max_x = @max(body.width, mark_x + mark_box.width);            try appendCommands(context.scratch, &commands, mark_box.commands, mark_x - min_x, 0);            try appendCommands(context.scratch, &commands, body.commands, -min_x, body_y);            var out = try context.boxFromList(max_x - min_x, body.height + body_y, body.baseline + body_y, &commands);            out.class = body.class;            return out;        },    }}fn layoutNativeAccentImpl(context: *Context, mark: ast.AccentMark, body: Box, style: Style) anyerror!?Box {    const rule = strokeWidth(context, style);    const accent_height = wideAccentHeight(style, rule);    const accent_width = @max(body.width, wideAccentMinimumWidth(style, rule));    var accent_commands: std.ArrayListUnmanaged(Command) = .empty;    switch (mark) {        .hat => try appendWideHat(context.scratch, &accent_commands, accent_width, 0, accent_height, rule),        .tilde => try appendWideTilde(context.scratch, &accent_commands, accent_width, 0, accent_height, rule),        .check => try appendWideCheck(context.scratch, &accent_commands, accent_width, 0, accent_height, rule),        .breve => try appendWideBreve(context.scratch, &accent_commands, accent_width, 0, accent_height, rule),        .vec => try appendWideArrowAccent(context.scratch, &accent_commands, accent_width, 0, accent_height, rule, false, true),        .overleft => try appendWideArrowAccent(context.scratch, &accent_commands, accent_width, 0, accent_height, rule, true, false),        .overleftright => try appendWideArrowAccent(context.scratch, &accent_commands, accent_width, 0, accent_height, rule, true, true),        else => return null,    }    const gap = @max(1, @divTrunc(style.font_size, 12));    const width = @max(body.width, accent_width);    const body_y = accent_height + gap;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, accent_commands.items, centered(width, accent_width), 0);    try appendCommands(context.scratch, &commands, body.commands, centered(width, body.width), body_y);    var out = try context.boxFromList(width, body_y + body.height, body_y + body.baseline, &commands);    out.class = body.class;    return out;}fn layoutAnnotationImpl(context: *Context, value: ast.Annotation, style: Style) anyerror!Box {    return switch (value.kind) {        .plain => try context.layoutStack(value.base, value.over, value.under, style),        .overbrace => try context.layoutBrace(value.base, style, true),        .underbrace => try context.layoutBrace(value.base, style, false),        .boxed => try context.layoutBoxed(value.base, style),    };}fn layoutStackImpl(context: *Context, base_expr: *ast.Expr, over_expr: ?*ast.Expr, under_expr: ?*ast.Expr, style: Style) anyerror!Box {    const base = try context.layoutExpr(base_expr, style);    const script = scriptStyle(context, style);    const over = if (over_expr) |expr| try context.layoutExpr(expr, script) else null;    const under = if (under_expr) |expr| try context.layoutExpr(expr, script) else null;    const gap = stackGap(context, style);    const top_shift = stackTopShiftUp(context, style);    const bottom_shift = stackBottomShiftDown(context, style);    const width = @max(base.width, @max(if (over) |box| box.width else 0, if (under) |box| box.width else 0));    const base_y = if (over) |box| @max(        top_shift + box.baseline - base.baseline,        gap + box.height,    ) else 0;    const base_baseline = base_y + base.baseline;    const under_y = if (under) |box| @max(        base_y + base.height + gap,        base_baseline + bottom_shift - box.baseline,    ) else base_y + base.height;    var height = base_y + base.height;    if (under) |box| height = under_y + box.height;    var commands: std.ArrayListUnmanaged(Command) = .empty;    if (over) |box| try appendCommands(context.scratch, &commands, box.commands, centered(width, box.width), 0);    try appendCommands(context.scratch, &commands, base.commands, centered(width, base.width), base_y);    if (under) |box| try appendCommands(context.scratch, &commands, box.commands, centered(width, box.width), under_y);    return context.boxFromList(width, height, base_y + base.baseline, &commands);}fn layoutBraceImpl(context: *Context, body_expr: *ast.Expr, style: Style, over: bool) anyerror!Box {    const body = try context.layoutExpr(body_expr, style);    const rule = strokeWidth(context, style);    const gap = @max(2, @divTrunc(style.font_size, 8));    const brace_height = horizontalBraceHeight(style, rule);    var commands: std.ArrayListUnmanaged(Command) = .empty;    if (over) {        try appendHorizontalBrace(context.scratch, &commands, body.width, 0, brace_height, rule, true);        try appendCommands(context.scratch, &commands, body.commands, 0, brace_height + gap);        return context.boxFromList(body.width, body.height + brace_height + gap, body.baseline + brace_height + gap, &commands);    }    try appendCommands(context.scratch, &commands, body.commands, 0, 0);    try appendHorizontalBrace(context.scratch, &commands, body.width, body.height + gap, brace_height, rule, false);    return context.boxFromList(body.width, body.height + brace_height + gap, body.baseline, &commands);}fn layoutBoxedImpl(context: *Context, body_expr: *ast.Expr, style: Style) anyerror!Box {    const body = try context.layoutExpr(body_expr, style);    const rule = strokeWidth(context, style);    const pad = @max(3, @divTrunc(style.font_size, 5));    const width = body.width + pad * 2;    const height = body.height + pad * 2;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendFrame(context.scratch, &commands, width, height, rule);    try appendCommands(context.scratch, &commands, body.commands, pad, pad);    return context.boxFromList(width, height, body.baseline + pad, &commands);}fn layoutDelimitedImpl(context: *Context, value: ast.Delimited, style: Style) anyerror!Box {    const body = try context.layoutExpr(value.body, style);    const gap = @max(1, @divTrunc(style.font_size, 10));    const target_height = @max(body.height, delimitedSubFormulaMinHeight(context, style));    const target_baseline = body.baseline + centered(target_height, body.height);    const left = try context.layoutDelimiter(value.left, target_height, target_baseline, style, true);    const right = try context.layoutDelimiter(value.right, target_height, target_baseline, style, false);    const baseline = @max(body.baseline, @max(left.baseline, right.baseline));    const descent = @max(body.height - body.baseline, @max(left.height - left.baseline, right.height - right.baseline));    const height = baseline + descent;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, left.commands, 0, baseline - left.baseline);    try appendCommands(context.scratch, &commands, body.commands, left.width + gap, baseline - body.baseline);    try appendCommands(context.scratch, &commands, right.commands, left.width + gap + body.width + gap, baseline - right.baseline);    var out = try context.boxFromList(left.width + body.width + right.width + gap * 2, height, baseline, &commands);    out.class = .inner;    return out;}fn layoutDelimiterImpl(context: *Context, delimiter: ast.Delimiter, height: i32, baseline: i32, style: Style, left: bool) anyerror!Box {    return switch (delimiter) {        .none => context.makeBox(0, height, baseline, &.{}),        .text => |value| context.layoutText(value, style),        .shape => |shape| try context.layoutDelimiterShape(shape, height, baseline, style, left),    };}fn layoutDelimiterShapeImpl(context: *Context, shape: ast.DelimiterShape, height: i32, baseline: i32, style: Style, left: bool) anyerror!Box {    if (try layoutMathVariantDelimiter(context, shape, height, baseline, style)) |box| return box;    const rule = strokeWidth(context, style);    const width = @max(rule, @divTrunc(style.font_size, 3));    if (shape == .bar or shape == .double_bar) {        var commands: std.ArrayListUnmanaged(Command) = .empty;        const x = if (left) 0 else width - rule;        try commands.append(context.scratch, .{ .rect = .{ .x = x, .y = 0, .width = rule, .height = height } });        if (shape == .double_bar) try commands.append(context.scratch, .{ .rect = .{ .x = x + rule * 2, .y = 0, .width = rule, .height = height } });        return context.boxFromList(if (shape == .double_bar) width + rule * 2 else width, height, baseline, &commands);    }    if (shape == .left_bracket or shape == .right_bracket or shape == .left_floor or shape == .right_floor or shape == .left_ceil or shape == .right_ceil) {        var commands: std.ArrayListUnmanaged(Command) = .empty;        const x = if (left) 0 else width - rule;        try commands.append(context.scratch, .{ .rect = .{ .x = x, .y = 0, .width = rule, .height = height } });        if (shape == .left_bracket or shape == .right_bracket or shape == .left_ceil or shape == .right_ceil) {            try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = 0, .width = width, .height = rule } });        }        if (shape == .left_bracket or shape == .right_bracket or shape == .left_floor or shape == .right_floor) {            try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = height - rule, .width = width, .height = rule } });        }        return context.boxFromList(width, height, baseline, &commands);    }    if (shape == .left_double_bracket or shape == .right_double_bracket) {        const double_width = @max(rule * 5, @max(@divTrunc(style.font_size, 2), width));        var commands: std.ArrayListUnmanaged(Command) = .empty;        const outer = if (left) 0 else double_width - rule;        const inner = if (left) rule * 2 else double_width - rule * 3;        try commands.append(context.scratch, .{ .rect = .{ .x = outer, .y = 0, .width = rule, .height = height } });        try commands.append(context.scratch, .{ .rect = .{ .x = inner, .y = 0, .width = rule, .height = height } });        try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = 0, .width = double_width, .height = rule } });        try commands.append(context.scratch, .{ .rect = .{ .x = 0, .y = height - rule, .width = double_width, .height = rule } });        return context.boxFromList(double_width, height, baseline, &commands);    }    if (try layoutStrokedDelimiter(context, shape, height, baseline, style)) |box| return box;    const box = try context.layoutText(delimiterShapeText(shape), delimiterStyle(context, height, style));    const out_height = @max(height, box.height);    const out_baseline = baseline + centered(out_height, height);    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &commands, box.commands, 0, centered(out_height, box.height));    return context.boxFromList(box.width, out_height, out_baseline, &commands);}fn layoutMathVariantDelimiter(context: *Context, shape: ast.DelimiterShape, height: i32, baseline: i32, style: Style) anyerror!?Box {    const codepoint = delimiterShapeCodepoint(shape) orelse return null;    const glyph_id = context.font.face.glyphId(codepoint);    if (glyph_id == 0) return null;    const target = pixelHeightToDesignUnits(context, height, style);    if (context.font.face.mathVerticalVariant(glyph_id, target)) |variant| {        if (variant.advance_measurement >= target) return try layoutMathGlyphDelimiter(context, variant.glyph_id, height, baseline, style);    }    return try layoutMathAssemblyDelimiter(context, glyph_id, target, height, baseline, style);}fn layoutMathGlyphDelimiter(context: *Context, glyph_id: u32, height: i32, baseline: i32, style: Style) anyerror!Box {    const bounds = (try glyphInkBounds(context, glyph_id, style)) orelse return error.InvalidFont;    const ink_width = @max(1, bounds.right - bounds.left);    const ink_height = @max(1, bounds.bottom - bounds.top);    const out_height = @max(height, ink_height);    const out_baseline = baseline + centered(out_height, height);    var commands = [_]Command{.{ .glyph = .{        .x = -bounds.left,        .y = centered(out_height, ink_height) - bounds.top,        .glyph_id = glyph_id,        .font_size = style.font_size,    } }};    return try context.makeBox(ink_width, out_height, out_baseline, &commands);}fn layoutMathAssemblyDelimiter(context: *Context, glyph_id: u32, target: u16, height: i32, baseline: i32, style: Style) anyerror!?Box {    const assembly = (try context.font.face.mathVerticalAssemblyAlloc(context.scratch, glyph_id, target)) orelse return null;    if (assembly.parts.len == 0 or assembly.advance_measurement < target) return null;    const assembly_height = @max(1, designUnitsToPixelsCeil(context, assembly.advance_measurement, style));    const out_height = @max(height, assembly_height);    const out_baseline = baseline + centered(out_height, height);    const assembly_y = centered(out_height, assembly_height);    const bounds = try context.scratch.alloc(GlyphInkBounds, assembly.parts.len);    var left_bound: i32 = std.math.maxInt(i32);    var right_bound: i32 = std.math.minInt(i32);    for (assembly.parts, 0..) |part, index| {        bounds[index] = (try glyphInkBounds(context, part.glyph_id, style)) orelse return null;        left_bound = @min(left_bound, bounds[index].left);        right_bound = @max(right_bound, bounds[index].right);    }    if (left_bound >= right_bound) return null;    var commands: std.ArrayListUnmanaged(Command) = .empty;    for (assembly.parts, 0..) |part, index| {        const part_end = @as(u32, part.advance_offset) + @as(u32, part.full_advance);        if (part_end > assembly.advance_measurement) return null;        const part_top_design: u16 = @intCast(@as(u32, assembly.advance_measurement) - part_end);        const part_top = assembly_y + designUnitsToPixelsFloor(context, part_top_design, style);        try commands.append(context.scratch, .{ .glyph = .{            .x = -left_bound,            .y = part_top - bounds[index].top,            .glyph_id = part.glyph_id,            .font_size = style.font_size,        } });    }    return try context.boxFromList(right_bound - left_bound, out_height, out_baseline, &commands);}fn layoutStrokedDelimiter(context: *Context, shape: ast.DelimiterShape, height: i32, baseline: i32, style: Style) anyerror!?Box {    return switch (shape) {        .left_paren => try layoutParenStroke(context, height, baseline, style, true),        .right_paren => try layoutParenStroke(context, height, baseline, style, false),        .left_angle => try layoutAngleStroke(context, height, baseline, style, true, false),        .right_angle => try layoutAngleStroke(context, height, baseline, style, false, false),        .left_double_angle => try layoutAngleStroke(context, height, baseline, style, true, true),        .right_double_angle => try layoutAngleStroke(context, height, baseline, style, false, true),        .left_brace => try layoutBraceStroke(context, height, baseline, style, true),        .right_brace => try layoutBraceStroke(context, height, baseline, style, false),        else => null,    };}fn layoutParenStroke(context: *Context, height: i32, baseline: i32, style: Style, left: bool) !Box {    const rule = strokeWidth(context, style);    const out_height = strokedDelimiterHeight(height, style);    const out_baseline = baseline + centered(out_height, height);    const width = @max(rule * 4, @max(@divTrunc(style.font_size, 2), @divTrunc(out_height, 8)));    var commands: std.ArrayListUnmanaged(Command) = .empty;    const outer = if (left) width - rule else 0;    const inner = if (left) 0 else width - rule;    const shoulder = if (left) @divTrunc(width, 3) else width - rule - @divTrunc(width, 3);    try commands.append(context.scratch, .{ .line = .{ .x0 = outer, .y0 = 0, .x1 = shoulder, .y1 = @divTrunc(out_height, 5), .width = rule } });    try commands.append(context.scratch, .{ .line = .{ .x0 = shoulder, .y0 = @divTrunc(out_height, 5), .x1 = inner, .y1 = @divTrunc(out_height, 2), .width = rule } });    try commands.append(context.scratch, .{ .line = .{ .x0 = inner, .y0 = @divTrunc(out_height, 2), .x1 = shoulder, .y1 = @divTrunc(out_height * 4, 5), .width = rule } });    try commands.append(context.scratch, .{ .line = .{ .x0 = shoulder, .y0 = @divTrunc(out_height * 4, 5), .x1 = outer, .y1 = out_height - rule, .width = rule } });    return context.boxFromList(width, out_height, out_baseline, &commands);}fn layoutAngleStroke(context: *Context, height: i32, baseline: i32, style: Style, left: bool, double: bool) !Box {    const rule = strokeWidth(context, style);    const out_height = strokedDelimiterHeight(height, style);    const out_baseline = baseline + centered(out_height, height);    const single_width = @max(rule * 4, @max(@divTrunc(style.font_size, 2), @divTrunc(out_height, 7)));    const gap = if (double) @max(rule * 2, @divTrunc(single_width, 4)) else 0;    const width = if (double) single_width + gap else single_width;    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendAngleStroke(context.scratch, &commands, single_width, out_height, rule, left, if (double and left) 0 else gap);    if (double) try appendAngleStroke(context.scratch, &commands, single_width, out_height, rule, left, if (left) gap else 0);    return context.boxFromList(width, out_height, out_baseline, &commands);}fn layoutBraceStroke(context: *Context, height: i32, baseline: i32, style: Style, left: bool) !Box {    const rule = strokeWidth(context, style);    const out_height = strokedDelimiterHeight(height, style);    const out_baseline = baseline + centered(out_height, height);    const width = @max(rule * 5, @max(@divTrunc(style.font_size, 2), @divTrunc(out_height, 9)));    var commands: std.ArrayListUnmanaged(Command) = .empty;    try appendVerticalBrace(context.scratch, &commands, width, out_height, rule, left, 0);    return context.boxFromList(width, out_height, out_baseline, &commands);}fn appendAngleStroke(allocator: std.mem.Allocator, commands: *std.ArrayListUnmanaged(Command), width: i32, height: i32, rule: i32, left: bool, dx: i32) !void {    const outer = if (left) width - rule else 0;    const inner = if (left) 0 else width - rule;    const middle_y = @divTrunc(height, 2);    try commands.append(allocator, .{ .line = .{ .x0 = dx + outer, .y0 = 0, .x1 = dx + inner, .y1 = middle_y, .width = rule } });    try commands.append(allocator, .{ .line = .{ .x0 = dx + inner, .y0 = middle_y, .x1 = dx + outer, .y1 = height - rule, .width = rule } });}fn strokedDelimiterHeight(height: i32, style: Style) i32 {    return height + @max(2, @divTrunc(style.font_size, 6));}fn layoutGridImpl(context: *Context, value: ast.Grid, style: Style) anyerror!Box {    if (value.rows.len == 0) return context.layoutText("", style);    var columns: usize = 0;    for (value.rows) |row| columns = @max(columns, row.cells.len);    if (columns == 0) return context.layoutText("", style);    const column_widths = try context.scratch.alloc(i32, columns);    @memset(column_widths, 0);    const row_baselines = try context.scratch.alloc(i32, value.rows.len);    const row_heights = try context.scratch.alloc(i32, value.rows.len);    const boxes = try context.scratch.alloc([]Box, value.rows.len);    for (value.rows, 0..) |row, row_index| {        boxes[row_index] = try context.scratch.alloc(Box, row.cells.len);        var baseline: i32 = 0;        var descent: i32 = 0;        for (row.cells, 0..) |cell, column_index| {            const box = try context.layoutExpr(cell, style);            boxes[row_index][column_index] = box;            column_widths[column_index] = @max(column_widths[column_index], box.width);            baseline = @max(baseline, box.baseline);            descent = @max(descent, box.height - box.baseline);        }        row_baselines[row_index] = baseline;        row_heights[row_index] = baseline + descent;    }    const gap = @max(4, @divTrunc(style.font_size, 2));    var body_width: i32 = gap * @as(i32, @intCast(columns - 1));    for (column_widths) |width| body_width += width;    var body_height: i32 = 0;    for (row_heights) |height| body_height += height;    var commands: std.ArrayListUnmanaged(Command) = .empty;    var y: i32 = 0;    for (value.rows, 0..) |row, row_index| {        var x: i32 = 0;        for (0..columns) |column_index| {            if (column_index < row.cells.len) {                const box = boxes[row_index][column_index];                const offset = switch (value.alignment) {                    .center => centered(column_widths[column_index], box.width),                    .left => 0,                };                try appendCommands(context.scratch, &commands, box.commands, x + offset, y + row_baselines[row_index] - box.baseline);            }            x += column_widths[column_index] + gap;        }        y += row_heights[row_index];    }    const body = try context.boxFromList(body_width, body_height, @divTrunc(body_height, 2), &commands);    const left = try context.layoutDelimiter(gridLeftDelimiter(value.fence), body.height, body.baseline, style, true);    const right = try context.layoutDelimiter(gridRightDelimiter(value.fence), body.height, body.baseline, style, false);    if (left.width == 0 and right.width == 0) return body;    const fence_gap = @max(1, @divTrunc(style.font_size, 10));    var framed: std.ArrayListUnmanaged(Command) = .empty;    try appendCommands(context.scratch, &framed, left.commands, 0, 0);    try appendCommands(context.scratch, &framed, body.commands, left.width + fence_gap, 0);    try appendCommands(context.scratch, &framed, right.commands, left.width + fence_gap + body.width + fence_gap, 0);    return context.boxFromList(left.width + body.width + right.width + fence_gap * 2, body.height, body.baseline, &framed);}fn paintImpl(context: *Context, canvas: filigree.render.Canvas, commands: []const Command, origin_x: i32, origin_y: i32) !void {    for (commands) |command| {        switch (command) {            .text => |text| {                var shaped_font = context.font;                shaped_font.setScale(@floatFromInt(text.font_size), 72);                const fallback_candidates = try context.fallbackCandidates(text.font_size);                if (fallback_candidates.len == 0) {                    _ = try filigree.drawUtf8(                        context.allocator,                        context.shaping_output,                        canvas,                        &shaped_font,                        text.value,                        text.font_size,                        origin_x + text.x,                        origin_y + text.y,                        .{ .color = context.options.foreground },                    );                } else {                    _ = try filigree.drawFallbackUtf8(                        context.allocator,                        context.shaping_output,                        canvas,                        &shaped_font,                        fallback_candidates,                        text.value,                        text.font_size,                        origin_x + text.x,                        origin_y + text.y,                        .{ .color = context.options.foreground },                    );                }            },            .rect => |rect| drawRect(canvas, origin_x + rect.x, origin_y + rect.y, rect.width, rect.height, context.options.foreground),            .line => |line| drawLine(canvas, origin_x + line.x0, origin_y + line.y0, origin_x + line.x1, origin_y + line.y1, line.width, context.options.foreground),            .glyph => |glyph| try drawGlyphCommand(context, canvas, glyph, origin_x, origin_y),        }    }}fn drawGlyphCommand(context: *Context, canvas: filigree.render.Canvas, glyph: Glyph, origin_x: i32, origin_y: i32) !void {    var glyphs = [_]filigree.ShapedGlyph{.{        .glyph_id = glyph.glyph_id,        .cluster = 0,        .x_advance = 0,        .y_advance = 0,        .x_offset = 0,        .y_offset = 0,    }};    const clusters = [_]filigree.Cluster{};    const carets = [_]filigree.LigatureCaret{};    const run = filigree.GlyphRun{        .glyphs = &glyphs,        .clusters = &clusters,        .ligature_carets = &carets,        .total_x_advance = 0,        .total_y_advance = 0,        .direction = .ltr,        .writing_mode = .horizontal,        .output_order = .logical,    };    _ = try filigree.drawGlyphRun(        context.allocator,        canvas,        context.font.face,        run,        glyph.font_size,        origin_x + glyph.x,        origin_y + glyph.y,        .{ .color = context.options.foreground },    );}fn fontMetricsImpl(context: *const Context, font_size: i32) Metrics {    const ascender = @max(1, scaleSigned(context.font.face.ascender, context.font.face.units_per_em, font_size));    const descender = @max(1, -scaleSigned(context.font.face.descender, context.font.face.units_per_em, font_size));    const line_gap = @max(0, scaleSigned(context.font.face.line_gap, context.font.face.units_per_em, font_size));    return .{        .ascender = ascender,        .descender = descender,        .line_gap = line_gap,        .line_height = @max(1, ascender + descender + line_gap),    };}fn fallbackCandidatesImpl(context: *Context, font_size: i32) ![]const filigree.FallbackCandidate {    if (context.fallback_fonts.len == 0) return &.{};    const fonts = try context.scratch.alloc(filigree.Font, context.fallback_fonts.len);    const candidates = try context.scratch.alloc(filigree.FallbackCandidate, context.fallback_fonts.len);    for (context.fallback_fonts, 0..) |font, index| {        fonts[index] = font;        fonts[index].setScale(@floatFromInt(font_size), 72);        candidates[index] = .{ .font = &fonts[index] };    }    return candidates;}fn makeBoxImpl(context: *Context, width: i32, height: i32, baseline: i32, commands: []const Command) !Box {    const owned = try context.scratch.dupe(Command, commands);    return .{ .width = width, .height = height, .baseline = baseline, .commands = owned };}fn boxFromListImpl(context: *Context, width: i32, height: i32, baseline: i32, commands: *std.ArrayListUnmanaged(Command)) !Box {    return .{        .width = width,        .height = height,        .baseline = baseline,        .commands = try commands.toOwnedSlice(context.scratch),    };}fn appendCommands(    allocator: std.mem.Allocator,    out: *std.ArrayListUnmanaged(Command),    commands: []const Command,    dx: i32,    dy: i32,) !void {    for (commands) |command| {        try out.append(allocator, offsetCommand(command, dx, dy));    }}fn offsetCommand(command: Command, dx: i32, dy: i32) Command {    return switch (command) {        .text => |text| .{ .text = .{            .x = text.x + dx,            .y = text.y + dy,            .value = text.value,            .font_size = text.font_size,        } },        .rect => |rect| .{ .rect = .{            .x = rect.x + dx,            .y = rect.y + dy,            .width = rect.width,            .height = rect.height,        } },        .line => |line| .{ .line = .{            .x0 = line.x0 + dx,            .y0 = line.y0 + dy,            .x1 = line.x1 + dx,            .y1 = line.y1 + dy,            .width = line.width,        } },        .glyph => |glyph| .{ .glyph = .{            .x = glyph.x + dx,            .y = glyph.y + dy,            .glyph_id = glyph.glyph_id,            .font_size = glyph.font_size,        } },    };}fn appendFrame(allocator: std.mem.Allocator, commands: *std.ArrayListUnmanaged(Command), width: i32, height: i32, rule: i32) !void {    try commands.append(allocator, .{ .rect = .{ .x = 0, .y = 0, .width = width, .height = rule } });    try commands.append(allocator, .{ .rect = .{ .x = 0, .y = height - rule, .width = width, .height = rule } });    try commands.append(allocator, .{ .rect = .{ .x = 0, .y = 0, .width = rule, .height = height } });    try commands.append(allocator, .{ .rect = .{ .x = width - rule, .y = 0, .width = rule, .height = height } });}fn appendStretchArrowShaft(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    x0: i32,    x1: i32,    axis: i32,    rule: i32,) !void {    if (x1 <= x0) return;    try commands.append(allocator, .{ .rect = .{        .x = x0,        .y = axis - @divTrunc(rule, 2),        .width = x1 - x0,        .height = rule,    } });}fn appendDoubleStretchArrowShaft(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    x0: i32,    x1: i32,    axis: i32,    rule: i32,    style: Style,) !void {    const separation = @max(rule * 2 + 1, @divTrunc(style.font_size, 5));    try appendStretchArrowShaft(allocator, commands, x0, x1, axis - @divTrunc(separation, 2), rule);    try appendStretchArrowShaft(allocator, commands, x0, x1, axis + @divTrunc(separation, 2), rule);}fn appendSquiggleStretchArrowShaft(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    x0: i32,    x1: i32,    axis: i32,    rule: i32,    style: Style,) !void {    if (x1 <= x0) return;    const step = @max(3, @divTrunc(style.font_size, 5));    const amplitude = @max(rule * 2, @divTrunc(style.font_size, 9));    var x = x0;    var y = axis;    var up = true;    while (x < x1) {        const next_x = @min(x1, x + step);        const next_y = axis + if (up) -amplitude else amplitude;        try commands.append(allocator, .{ .line = .{            .x0 = x,            .y0 = y,            .x1 = next_x,            .y1 = next_y,            .width = rule,        } });        x = next_x;        y = next_y;        up = !up;    }}fn appendStretchArrowBar(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    axis: i32,    rule: i32,    style: Style,) !void {    const height = @max(rule * 5, @divTrunc(style.font_size * 2, 3));    try commands.append(allocator, .{ .rect = .{        .x = 0,        .y = axis - @divTrunc(height, 2),        .width = rule,        .height = height,    } });}fn appendHorizontalBrace(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,    over: bool,) !void {    if (width <= 0 or height <= 0) return;    if (width <= rule * 4) {        try commands.append(allocator, .{ .rect = .{ .x = 0, .y = y, .width = width, .height = rule } });        return;    }    const right = width - 1;    const half = @divTrunc(right, 2);    const wing = @max(rule * 3, @min(@divTrunc(width, 4), @max(rule * 4, @divTrunc(width, 3))));    const notch = @max(rule * 2, @min(@divTrunc(width, 10), @max(rule * 3, 1)));    const left_peak = @min(wing, half);    const right_peak = @max(half, right - wing);    const mid_left = @max(left_peak, half - notch);    const mid_right = @min(right_peak, half + notch);    const high = y;    const low = y + height - rule;    const edge_y = if (over) low else high;    const peak_y = if (over) high else low;    const middle_y = if (over) low else high;    const segments = [_]Line{        .{ .x0 = 0, .y0 = edge_y, .x1 = left_peak, .y1 = peak_y, .width = rule },        .{ .x0 = left_peak, .y0 = peak_y, .x1 = mid_left, .y1 = peak_y, .width = rule },        .{ .x0 = mid_left, .y0 = peak_y, .x1 = half, .y1 = middle_y, .width = rule },        .{ .x0 = half, .y0 = middle_y, .x1 = mid_right, .y1 = peak_y, .width = rule },        .{ .x0 = mid_right, .y0 = peak_y, .x1 = right_peak, .y1 = peak_y, .width = rule },        .{ .x0 = right_peak, .y0 = peak_y, .x1 = right, .y1 = edge_y, .width = rule },    };    for (segments) |segment| try commands.append(allocator, .{ .line = segment });}fn appendVerticalBrace(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    height: i32,    rule: i32,    left: bool,    dx: i32,) !void {    if (width <= 0 or height <= 0) return;    const edge_x = if (left) width - rule else 0;    if (height <= rule * 4) {        try commands.append(allocator, .{ .rect = .{ .x = dx + edge_x, .y = 0, .width = rule, .height = height } });        return;    }    const bottom = height - rule;    const half = @divTrunc(bottom, 2);    const wing = @max(rule * 3, @min(@divTrunc(height, 4), @max(rule * 4, @divTrunc(height, 3))));    const notch = @max(rule * 2, @min(@divTrunc(height, 10), @max(rule * 3, 1)));    const top_peak = @min(wing, half);    const bottom_peak = @max(half, bottom - wing);    const mid_top = @max(top_peak, half - notch);    const mid_bottom = @min(bottom_peak, half + notch);    const peak_x = if (left) 0 else width - rule;    const middle_x = edge_x;    const segments = [_]Line{        .{ .x0 = dx + edge_x, .y0 = 0, .x1 = dx + peak_x, .y1 = top_peak, .width = rule },        .{ .x0 = dx + peak_x, .y0 = top_peak, .x1 = dx + peak_x, .y1 = mid_top, .width = rule },        .{ .x0 = dx + peak_x, .y0 = mid_top, .x1 = dx + middle_x, .y1 = half, .width = rule },        .{ .x0 = dx + middle_x, .y0 = half, .x1 = dx + peak_x, .y1 = mid_bottom, .width = rule },        .{ .x0 = dx + peak_x, .y0 = mid_bottom, .x1 = dx + peak_x, .y1 = bottom_peak, .width = rule },        .{ .x0 = dx + peak_x, .y0 = bottom_peak, .x1 = dx + edge_x, .y1 = bottom, .width = rule },    };    for (segments) |segment| try commands.append(allocator, .{ .line = segment });}fn appendWideHat(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,) !void {    if (width <= 0 or height <= 0) return;    if (width <= rule * 3) {        try commands.append(allocator, .{ .rect = .{ .x = 0, .y = y, .width = width, .height = rule } });        return;    }    const right = width - 1;    const middle = @divTrunc(right, 2);    const low = y + height - rule;    try commands.append(allocator, .{ .line = .{ .x0 = 0, .y0 = low, .x1 = middle, .y1 = y, .width = rule } });    try commands.append(allocator, .{ .line = .{ .x0 = middle, .y0 = y, .x1 = right, .y1 = low, .width = rule } });}fn appendWideCheck(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,) !void {    if (width <= 0 or height <= 0) return;    if (width <= rule * 3) {        try commands.append(allocator, .{ .rect = .{ .x = 0, .y = y, .width = width, .height = rule } });        return;    }    const right = width - 1;    const middle = @divTrunc(right, 2);    const low = y + height - rule;    try commands.append(allocator, .{ .line = .{ .x0 = 0, .y0 = y, .x1 = middle, .y1 = low, .width = rule } });    try commands.append(allocator, .{ .line = .{ .x0 = middle, .y0 = low, .x1 = right, .y1 = y, .width = rule } });}fn appendWideBreve(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,) !void {    if (width <= 0 or height <= 0) return;    const right = width - 1;    const quarter = @divTrunc(right, 4);    const middle = @divTrunc(right, 2);    const three_quarter = right - quarter;    const high = y;    const low = y + height - rule;    const segments = [_]Line{        .{ .x0 = 0, .y0 = high, .x1 = quarter, .y1 = low, .width = rule },        .{ .x0 = quarter, .y0 = low, .x1 = middle, .y1 = low, .width = rule },        .{ .x0 = middle, .y0 = low, .x1 = three_quarter, .y1 = low, .width = rule },        .{ .x0 = three_quarter, .y0 = low, .x1 = right, .y1 = high, .width = rule },    };    for (segments) |segment| try commands.append(allocator, .{ .line = segment });}fn appendWideTilde(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,) !void {    if (width <= 0 or height <= 0) return;    const right = width - 1;    const high = y;    const low = y + height - rule;    const mid = y + @divTrunc(height - rule, 2);    const step = @max(rule * 3, @divTrunc(width, 6));    var x: i32 = 0;    var current_y = mid;    var up = true;    while (x < right) {        const next_x = @min(right, x + step);        const next_y = if (up) high else low;        try commands.append(allocator, .{ .line = .{            .x0 = x,            .y0 = current_y,            .x1 = next_x,            .y1 = next_y,            .width = rule,        } });        x = next_x;        current_y = next_y;        up = !up;    }}fn appendWideArrowAccent(    allocator: std.mem.Allocator,    commands: *std.ArrayListUnmanaged(Command),    width: i32,    y: i32,    height: i32,    rule: i32,    left_head: bool,    right_head: bool,) !void {    if (width <= 0 or height <= 0) return;    const right = width - 1;    const axis = y + @divTrunc(height - rule, 2);    const head = @max(rule * 3, @min(@max(rule * 3, height - rule), @divTrunc(width, 4)));    const shaft_start = if (left_head) head else 0;    const shaft_end = if (right_head) @max(shaft_start, right - head) else right;    try appendStretchArrowShaft(allocator, commands, shaft_start, shaft_end, axis, rule);    if (right_head) {        try commands.append(allocator, .{ .line = .{ .x0 = right - head, .y0 = y, .x1 = right, .y1 = axis, .width = rule } });        try commands.append(allocator, .{ .line = .{ .x0 = right - head, .y0 = y + height - rule, .x1 = right, .y1 = axis, .width = rule } });    }    if (left_head) {        try commands.append(allocator, .{ .line = .{ .x0 = head, .y0 = y, .x1 = 0, .y1 = axis, .width = rule } });        try commands.append(allocator, .{ .line = .{ .x0 = head, .y0 = y + height - rule, .x1 = 0, .y1 = axis, .width = rule } });    }}fn scriptStyle(context: *const Context, style: Style) Style {    const percent = if (mathConstants(context)) |constants|        scriptPercent(constants.script_percent_scale_down)    else        70;    const scaled = @divTrunc(@as(i64, style.font_size) * percent + 50, 100);    return .{ .font_size = @max(8, @as(i32, @intCast(scaled))) };}fn scriptScriptStyle(context: *const Context, style: Style) Style {    const percent = if (mathConstants(context)) |constants|        scriptPercentOr(constants.script_script_percent_scale_down, 50)    else        50;    const scaled = @divTrunc(@as(i64, style.font_size) * percent + 50, 100);    return .{ .font_size = @max(8, @as(i32, @intCast(scaled))) };}fn strokeWidth(context: *const Context, style: Style) i32 {    return fractionRuleWidth(context, style);}fn horizontalBraceHeight(style: Style, rule: i32) i32 {    return @max(rule * 4 + 1, @divTrunc(style.font_size, 4));}fn wideAccentHeight(style: Style, rule: i32) i32 {    return @max(rule * 4 + 1, @divTrunc(style.font_size, 5));}fn wideAccentMinimumWidth(style: Style, rule: i32) i32 {    return @max(rule * 6, @divTrunc(style.font_size, 2));}fn defaultStrokeWidth(style: Style) i32 {    return @max(1, @divTrunc(style.font_size, 13));}fn fractionRuleWidth(context: *const Context, style: Style) i32 {    const fallback = defaultStrokeWidth(style);    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.fraction_rule_thickness, style, fallback);    return fallback;}fn radicalRuleWidth(context: *const Context, style: Style) i32 {    const fallback = fractionRuleWidth(context, style);    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.radical_rule_thickness, style, fallback);    return fallback;}fn overbarRuleWidth(context: *const Context, style: Style) i32 {    const fallback = strokeWidth(context, style);    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.overbar_rule_thickness, style, fallback);    return fallback;}fn underbarRuleWidth(context: *const Context, style: Style) i32 {    const fallback = strokeWidth(context, style);    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.underbar_rule_thickness, style, fallback);    return fallback;}fn fractionNumeratorGap(context: *const Context, style: Style, fraction_style: ast.FractionStyle) i32 {    const fallback = @max(2, @divTrunc(style.font_size, 6));    if (mathConstants(context)) |constants| {        const value = switch (fraction_style) {            .text => constants.fraction_numerator_gap_min,            .display => constants.fraction_num_display_style_gap_min,        };        return positiveMathValuePixels(context, value, style, fallback);    }    return fallback;}fn fractionDenominatorGap(context: *const Context, style: Style, fraction_style: ast.FractionStyle) i32 {    const fallback = @max(2, @divTrunc(style.font_size, 6));    if (mathConstants(context)) |constants| {        const value = switch (fraction_style) {            .text => constants.fraction_denominator_gap_min,            .display => constants.fraction_denom_display_style_gap_min,        };        return positiveMathValuePixels(context, value, style, fallback);    }    return fallback;}fn fractionNumeratorShiftUp(context: *const Context, style: Style, fraction_style: ast.FractionStyle) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| {        const value = switch (fraction_style) {            .text => constants.fraction_numerator_shift_up,            .display => constants.fraction_numerator_display_style_shift_up,        };        return positiveMathValuePixels(context, value, style, fallback);    }    return fallback;}fn fractionDenominatorShiftDown(context: *const Context, style: Style, fraction_style: ast.FractionStyle) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| {        const value = switch (fraction_style) {            .text => constants.fraction_denominator_shift_down,            .display => constants.fraction_denominator_display_style_shift_down,        };        return positiveMathValuePixels(context, value, style, fallback);    }    return fallback;}fn upperLimitGap(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.upper_limit_gap_min, style, fallback);    return fallback;}fn upperLimitBaselineRise(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.upper_limit_baseline_rise_min, style, fallback);    return fallback;}fn lowerLimitGap(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.lower_limit_gap_min, style, fallback);    return fallback;}fn lowerLimitBaselineDrop(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.lower_limit_baseline_drop_min, style, fallback);    return fallback;}fn scriptHorizontalGap(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.space_after_script, style, fallback);    return fallback;}fn subscriptShiftDown(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 3));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.subscript_shift_down, style, fallback);    return fallback;}fn subscriptTopMax(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size * 2, 5));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.subscript_top_max, style, fallback);    return fallback;}fn subscriptBaselineDropMin(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.subscript_baseline_drop_min, style, 0);    return 0;}fn superscriptShiftUp(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size * 2, 3));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.superscript_shift_up, style, fallback);    return fallback;}fn superscriptBottomMin(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 4));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.superscript_bottom_min, style, fallback);    return fallback;}fn superscriptBaselineDropMax(context: *const Context, style: Style) i32 {    const fallback = @max(0, @divTrunc(style.font_size, 4));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.superscript_baseline_drop_max, style, fallback);    return fallback;}fn subSuperscriptGap(context: *const Context, style: Style) i32 {    const fallback = strokeWidth(context, style) * 4;    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.sub_superscript_gap_min, style, fallback);    return fallback;}fn superscriptBottomMaxWithSubscript(context: *const Context, style: Style) i32 {    const fallback = @max(superscriptBottomMin(context, style), subscriptTopMax(context, style));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.superscript_bottom_max_with_subscript, style, fallback);    return fallback;}fn stretchStackTopShiftUp(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.stretch_stack_top_shift_up, style, fallback);    return fallback;}fn stretchStackBottomShiftDown(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 2));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.stretch_stack_bottom_shift_down, style, fallback);    return fallback;}fn stretchStackGapAbove(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.stretch_stack_gap_above_min, style, fallback);    return fallback;}fn stretchStackGapBelow(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.stretch_stack_gap_below_min, style, fallback);    return fallback;}fn stackTopShiftUp(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| {        const display = constants.stack_top_display_style_shift_up;        if (display.value > 0) return positiveMathValuePixels(context, display, style, 0);        return positiveMathValuePixels(context, constants.stack_top_shift_up, style, 0);    }    return 0;}fn stackBottomShiftDown(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| {        const display = constants.stack_bottom_display_style_shift_down;        if (display.value > 0) return positiveMathValuePixels(context, display, style, 0);        return positiveMathValuePixels(context, constants.stack_bottom_shift_down, style, 0);    }    return 0;}fn stackGap(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| {        const display = constants.stack_display_style_gap_min;        if (display.value > 0) return positiveMathValuePixels(context, display, style, fallback);        return positiveMathValuePixels(context, constants.stack_gap_min, style, fallback);    }    return fallback;}fn overbarGap(context: *const Context, style: Style) i32 {    const fallback = @max(2, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.overbar_vertical_gap, style, fallback);    return fallback;}fn underbarGap(context: *const Context, style: Style) i32 {    const fallback = @max(2, @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.underbar_vertical_gap, style, fallback);    return fallback;}fn overbarExtraAscender(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.overbar_extra_ascender, style, 0);    return 0;}fn underbarExtraDescender(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.underbar_extra_descender, style, 0);    return 0;}fn delimitedSubFormulaMinHeight(context: *const Context, style: Style) i32 {    if (mathConstants(context)) |constants| {        if (constants.delimited_sub_formula_min_height != 0) return @max(1, designUnitsToPixelsCeil(context, constants.delimited_sub_formula_min_height, style));    }    return 0;}fn displayOperatorMinHeight(context: *const Context, style: Style) ?i32 {    if (mathConstants(context)) |constants| {        if (constants.display_operator_min_height != 0) return @max(1, designUnitsToPixelsCeil(context, constants.display_operator_min_height, style));    }    return null;}fn radicalClearance(context: *const Context, style: Style) i32 {    const rule = radicalRuleWidth(context, style);    const fallback = @max(rule + @divTrunc(rule + 3, 4), @divTrunc(style.font_size, 8));    if (mathConstants(context)) |constants| {        const display = constants.radical_display_style_vertical_gap;        if (display.value != 0) return positiveMathValuePixels(context, display, style, fallback);        return positiveMathValuePixels(context, constants.radical_vertical_gap, style, fallback);    }    return fallback;}fn radicalExtraAscender(context: *const Context, style: Style) i32 {    const fallback = radicalRuleWidth(context, style);    if (mathConstants(context)) |constants| return positiveMathValuePixels(context, constants.radical_extra_ascender, style, fallback);    return fallback;}fn radicalKernBeforeDegree(context: *const Context, style: Style) i32 {    const fallback = @divTrunc(style.font_size * 5 + 9, 18);    if (mathConstants(context)) |constants| return scaleSigned(constants.radical_kern_before_degree.value, context.font.face.units_per_em, style.font_size);    return fallback;}fn radicalKernAfterDegree(context: *const Context, style: Style) i32 {    const fallback = -@divTrunc(style.font_size * 10 + 9, 18);    if (mathConstants(context)) |constants| return scaleSigned(constants.radical_kern_after_degree.value, context.font.face.units_per_em, style.font_size);    return fallback;}fn radicalDegreeBottomRaisePercent(context: *const Context) i64 {    if (mathConstants(context)) |constants| {        if (constants.radical_degree_bottom_raise_percent > 0) return constants.radical_degree_bottom_raise_percent;    }    return 60;}fn radicalWidth(context: *const Context, height: i32, style: Style) i32 {    return @max(@divTrunc(style.font_size, 2), @divTrunc(height, 4) + radicalRuleWidth(context, style) * 2);}fn mathAxisHeight(context: *const Context, style: Style) i32 {    const fallback = @max(1, @divTrunc(style.font_size, 4));    if (mathConstants(context)) |constants| {        if (constants.axis_height.value != 0) return @max(0, scaleSigned(constants.axis_height.value, context.font.face.units_per_em, style.font_size));    }    return fallback;}fn stretchArrowLabelPadding(style: Style) i32 {    return @max(6, @divTrunc(style.font_size, 2));}fn stretchArrowHeadOverlap(style: Style) i32 {    return @max(2, @divTrunc(style.font_size, 3));}fn stretchArrowMinimumWidth(left_head: ?Box, right_head: ?Box, style: Style) i32 {    var head_width: i32 = 0;    if (left_head) |box| head_width += box.width;    if (right_head) |box| head_width += box.width;    return @max(@max(style.font_size * 2, 12), head_width + @max(style.font_size, 8));}fn stretchArrowAxis(context: *const Context, style: Style, baseline: i32, height: i32, rule: i32) i32 {    const raw = baseline - mathAxisHeight(context, style);    const low = @max(0, @divTrunc(rule, 2));    const high = @max(low, height - @divTrunc(rule + 1, 2));    return @min(@max(raw, low), high);}fn accentMarkX(context: *Context, body_expr: *const ast.Expr, body: Box, mark_text: []const u8, mark: Box, body_style: Style, mark_style: Style) i32 {    const body_anchor = accentAttachmentX(context, singleGlyphId(context, body_expr), body, body_style);    const mark_anchor = accentAttachmentX(context, singleTextGlyphId(context, mark_text), mark, mark_style);    return body_anchor - mark_anchor;}fn accentAttachmentX(context: *const Context, glyph_id: ?u32, box: Box, style: Style) i32 {    if (glyph_id) |id| {        if (context.font.face.mathTopAccentAttachment(id)) |value| {            return scaleSigned(value.value, context.font.face.units_per_em, style.font_size);        }    }    return @divTrunc(box.width, 2);}fn mathConstants(context: *const Context) ?filigree.MathConstants {    return context.font.face.mathConstants();}fn positiveMathValuePixels(context: *const Context, value: filigree.MathValueRecord, style: Style, fallback: i32) i32 {    if (value.value <= 0) return fallback;    return @max(1, scaleSigned(value.value, context.font.face.units_per_em, style.font_size));}fn scriptPercent(value: i16) i64 {    return scriptPercentOr(value, 70);}fn scriptPercentOr(value: i16, fallback: i64) i64 {    if (value <= 0) return fallback;    if (value > 100) return 100;    return @intCast(value);}fn delimiterStyle(context: *Context, height: i32, style: Style) Style {    const metrics = context.fontMetrics(style.font_size);    if (height <= metrics.line_height) return style;    const extra = @max(2, @divTrunc(style.font_size, 6));    const desired = height + extra;    const scaled = @divTrunc(@as(i64, style.font_size) * desired + metrics.line_height - 1, metrics.line_height);    return .{ .font_size = @max(style.font_size, @as(i32, @intCast(scaled))) };}fn spacePixels(value: ast.Space, style: Style) i32 {    if (value.numerator == 0) return 0;    const numerator = @as(i64, style.font_size) * @as(i64, value.numerator);    const denominator: i64 = @intCast(value.denominator);    const rounded = if (numerator >= 0) numerator + @divTrunc(denominator, 2) else numerator - @divTrunc(denominator, 2);    const pixels = @as(i32, @intCast(@divTrunc(rounded, denominator)));    if (pixels == 0) return if (value.numerator > 0) 1 else -1;    return pixels;}fn mathClassSpace(children: []const Box, index: usize, style: Style) i32 {    const left = children[index - 1];    const right = children[index];    if (left.width == 0 or right.width == 0) return 0;    if (left.class == .spacing or right.class == .spacing) return 0;    if (left.class == .relation or right.class == .relation) return relationSpace(left.class, right.class, style);    if (right.class == .binary) return binaryBeforeSpace(left.class, style);    if (left.class == .binary) return binaryAfterSpace(children, index, right.class, style);    if (left.class == .punctuation) return muSpace(style, 3);    if (left.class == .operator and followsOperator(right.class)) return muSpace(style, 3);    return 0;}fn relationSpace(left: MathClass, right: MathClass, style: Style) i32 {    if (left == .open or right == .close or right == .punctuation) return 0;    return muSpace(style, 5);}fn binaryBeforeSpace(left: MathClass, style: Style) i32 {    if (precedesBinary(left)) return muSpace(style, 4);    return 0;}fn binaryAfterSpace(children: []const Box, index: usize, right: MathClass, style: Style) i32 {    if (index <= 1) return 0;    if (!precedesBinary(children[index - 2].class)) return 0;    if (followsBinary(right)) return muSpace(style, 4);    return 0;}fn precedesBinary(class: MathClass) bool {    return switch (class) {        .ordinary, .operator, .close, .inner => true,        .binary, .relation, .open, .punctuation, .spacing => false,    };}fn followsBinary(class: MathClass) bool {    return switch (class) {        .ordinary, .operator, .open, .inner => true,        .binary, .relation, .close, .punctuation, .spacing => false,    };}fn followsOperator(class: MathClass) bool {    return switch (class) {        .ordinary, .operator, .open, .inner => true,        .binary, .relation, .close, .punctuation, .spacing => false,    };}fn muSpace(style: Style, mu: i32) i32 {    return spacePixels(.{ .numerator = mu, .denominator = 18 }, style);}fn mathClassForText(value: []const u8) MathClass {    if (isBinaryText(value)) return .binary;    if (isRelationText(value)) return .relation;    if (operator.limitsText(value)) return .operator;    if (isOperatorText(value)) return .operator;    if (isOpenText(value)) return .open;    if (isCloseText(value)) return .close;    if (isPunctuationText(value)) return .punctuation;    return .ordinary;}fn isBinaryText(value: []const u8) bool {    inline for (.{        "+",        "-",        "*",        "/",        "±",        "∓",        "×",        "÷",        "·",        "∗",        "⋆",        "⋄",        "∘",        "∧",        "∨",        "∪",        "∩",        "∖",        "⊕",        "⊖",        "⊗",        "⊘",        "⊙",        "⊛",        "⊚",        "⊝",        "⊞",        "⊟",        "⊠",        "⊔",        "⊓",        "⊎",        "≀",        "∣",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn isRelationText(value: []const u8) bool {    inline for (.{        "=",        "<",        ">",        "≤",        "≰",        "≲",        "⪅",        "≪",        "⋘",        "≥",        "≱",        "≳",        "⪆",        "≫",        "≠",        "≮",        "≯",        "≐",        "≜",        "≔",        "≕",        "≡",        "≢",        "≈",        "≉",        "≍",        "∼",        "≁",        "≃",        "≄",        "≾",        "≿",        "≅",        "≇",        "∝",        "≺",        "≻",        "≼",        "≽",        "⊥",        "⊤",        "⋈",        "∈",        "∉",        "∋",        "∌",        "⊂",        "⊃",        "⊆",        "⊇",        "⊈",        "⊉",        "⊊",        "⊋",        "⊏",        "⊐",        "⊑",        "⊒",        "⋢",        "⋣",        "⊢",        "⊩",        "⊨",        "⊪",        "⊣",        "⊬",        "⊭",        "⊮",        "⊯",        "∥",        "∦",        "∤",        "→",        "⟶",        "←",        "⟵",        "↔",        "⟷",        "⇒",        "⟹",        "⇐",        "⟸",        "⇔",        "⟺",        "⇏",        "⇍",        "⇎",        "↦",        "⟼",        "⊸",        "↪",        "↩",        "↠",        "↞",        "↣",        "↢",        "↝",        "⇝",        "⇀",        "⇁",        "↼",        "↽",        "⇌",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn isOperatorText(value: []const u8) bool {    inline for (.{        "∑",        "⨁",        "⨂",        "⨀",        "∏",        "∐",        "∫",        "∬",        "∭",        "∮",        "⋀",        "⋁",        "⋃",        "⋂",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn isOpenText(value: []const u8) bool {    inline for (.{        "(",        "[",        "{",        "⟨",        "⟪",        "⟦",        "⌊",        "⌈",        "⌜",        "⌞",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn isCloseText(value: []const u8) bool {    inline for (.{        ")",        "]",        "}",        "⟩",        "⟫",        "⟧",        "⌋",        "⌉",        "⌝",        "⌟",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn isPunctuationText(value: []const u8) bool {    inline for (.{        ",",        ";",        "…",        "⋯",    }) |candidate| {        if (std.mem.eql(u8, value, candidate)) return true;    }    return false;}fn accentText(mark: ast.AccentMark) []const u8 {    return switch (mark) {        .hat => "^",        .vec => "→",        .dot => "˙",        .tilde => "~",        .check => "ˇ",        .breve => "˘",        .ddot => "¨",        .acute => "´",        .grave => "`",        .ring => "˚",        .overleft => "←",        .overleftright => "↔",        .bar, .underline => unreachable,    };}fn delimiterShapeText(shape: ast.DelimiterShape) []const u8 {    return switch (shape) {        .left_paren => "(",        .right_paren => ")",        .left_brace => "{",        .right_brace => "}",        .left_angle => "⟨",        .right_angle => "⟩",        .left_double_angle => "⟪",        .right_double_angle => "⟫",        .left_double_bracket => "⟦",        .right_double_bracket => "⟧",        .left_bracket, .right_bracket, .bar, .double_bar, .left_floor, .right_floor, .left_ceil, .right_ceil => "|",    };}fn delimiterShapeCodepoint(shape: ast.DelimiterShape) ?u21 {    return switch (shape) {        .left_paren => '(',        .right_paren => ')',        .left_bracket => '[',        .right_bracket => ']',        .left_brace => '{',        .right_brace => '}',        .left_angle => 0x27e8,        .right_angle => 0x27e9,        .left_double_angle => 0x27ea,        .right_double_angle => 0x27eb,        .left_double_bracket => 0x27e6,        .right_double_bracket => 0x27e7,        .left_floor => 0x230a,        .right_floor => 0x230b,        .left_ceil => 0x2308,        .right_ceil => 0x2309,        .bar, .double_bar => null,    };}fn gridLeftDelimiter(fence: ast.GridFence) ast.Delimiter {    return switch (fence) {        .none => .none,        .paren => .{ .shape = .left_paren },        .bracket => .{ .shape = .left_bracket },        .brace, .left_brace => .{ .shape = .left_brace },        .bar => .{ .shape = .bar },        .double_bar => .{ .shape = .double_bar },    };}fn gridRightDelimiter(fence: ast.GridFence) ast.Delimiter {    return switch (fence) {        .none, .left_brace => .none,        .paren => .{ .shape = .right_paren },        .bracket => .{ .shape = .right_bracket },        .brace => .{ .shape = .right_brace },        .bar => .{ .shape = .bar },        .double_bar => .{ .shape = .double_bar },    };}fn imageDimension(content: i32, padding: u32) !u32 {    const padded = content + @as(i32, @intCast(padding)) * 2;    if (padded <= 0) return 1;    return std.math.cast(u32, padded) orelse error.InvalidDimensions;}fn centered(width: i32, inner: i32) i32 {    if (inner >= width) return 0;    return @divTrunc(width - inner, 2);}fn scaleSigned(value: i32, units_per_em: u16, font_size: i32) i32 {    const numerator = @as(i64, value) * @as(i64, font_size);    const denominator: i64 = units_per_em;    const rounded = if (numerator >= 0) numerator + @divTrunc(denominator, 2) else numerator - @divTrunc(denominator, 2);    return @intCast(@divTrunc(rounded, denominator));}fn pixelHeightToDesignUnits(context: *const Context, height: i32, style: Style) u16 {    return pixelMeasurementToDesignUnits(context, height, style);}fn pixelWidthToDesignUnits(context: *const Context, width: i32, style: Style) u16 {    return pixelMeasurementToDesignUnits(context, width, style);}fn pixelMeasurementToDesignUnits(context: *const Context, measurement: i32, style: Style) u16 {    const pixels = @max(1, measurement);    const numerator = @as(i64, pixels) * @as(i64, context.font.face.units_per_em) + @divTrunc(style.font_size, 2);    const design = @divTrunc(numerator, style.font_size);    if (design <= 0) return 1;    if (design > std.math.maxInt(u16)) return std.math.maxInt(u16);    return @intCast(design);}fn designUnitsToPixelsFloor(context: *const Context, value: u16, style: Style) i32 {    return scaleFloor(value, context.font.face.units_per_em, style.font_size);}fn designUnitsToPixelsCeil(context: *const Context, value: u16, style: Style) i32 {    return scaleCeil(value, context.font.face.units_per_em, style.font_size);}fn glyphInkBounds(context: *Context, glyph_id: u32, style: Style) !?GlyphInkBounds {    var outline = filigree.glyphOutlineAlloc(context.scratch, context.font.face, glyph_id) catch |err| switch (err) {        error.OutOfMemory => return err,        else => return null,    };    defer outline.deinit(context.scratch);    if (outline.points.len == 0 or outline.bounds.x_max <= outline.bounds.x_min or outline.bounds.y_max <= outline.bounds.y_min) return null;    return .{        .left = scaleFloor(outline.bounds.x_min, context.font.face.units_per_em, style.font_size),        .top = scaleFloor(@as(i32, context.font.face.ascender) - outline.bounds.y_max, context.font.face.units_per_em, style.font_size),        .right = scaleCeil(outline.bounds.x_max, context.font.face.units_per_em, style.font_size),        .bottom = scaleCeil(@as(i32, context.font.face.ascender) - outline.bounds.y_min, context.font.face.units_per_em, style.font_size),    };}fn singleGlyphId(context: *Context, expr: *const ast.Expr) ?u32 {    const value = ast.textValue(expr) orelse return null;    return singleTextGlyphId(context, value);}fn singleTextGlyphId(context: *const Context, value: []const u8) ?u32 {    if (value.len == 0) return null;    const sequence_len = std.unicode.utf8ByteSequenceLength(value[0]) catch return null;    if (sequence_len != value.len) return null;    const codepoint = std.unicode.utf8Decode(value[0..sequence_len]) catch return null;    const glyph_id = context.font.face.glyphId(codepoint);    if (glyph_id == 0) return null;    return glyph_id;}fn scaleFloor(value: i32, units_per_em: u16, font_size: i32) i32 {    const numerator = @as(i64, value) * @as(i64, font_size);    return @intCast(divFloor(numerator, units_per_em));}fn scaleCeil(value: i32, units_per_em: u16, font_size: i32) i32 {    const numerator = @as(i64, value) * @as(i64, font_size);    return @intCast(-divFloor(-numerator, units_per_em));}fn divFloor(numerator: i64, denominator: i64) i64 {    var quotient = @divTrunc(numerator, denominator);    const remainder = @rem(numerator, denominator);    if (remainder != 0 and numerator < 0) quotient -= 1;    return quotient;}fn drawRect(canvas: filigree.render.Canvas, x: i32, y: i32, width: i32, height: i32, color: Color) void {    if (width <= 0 or height <= 0) return;    const x0 = @max(0, x);    const y0 = @max(0, y);    const x1 = @min(@as(i32, @intCast(canvas.width)), x + width);    const y1 = @min(@as(i32, @intCast(canvas.height)), y + height);    if (x0 >= x1 or y0 >= y1) return;    var py: usize = @intCast(y0);    while (py < @as(usize, @intCast(y1))) : (py += 1) {        var px: usize = @intCast(x0);        while (px < @as(usize, @intCast(x1))) : (px += 1) {            const offset = py * canvas.stride + px * 4;            canvas.pixels[offset] = color.r;            canvas.pixels[offset + 1] = color.g;            canvas.pixels[offset + 2] = color.b;            canvas.pixels[offset + 3] = color.a;        }    }}fn drawLine(canvas: filigree.render.Canvas, x0: i32, y0: i32, x1: i32, y1: i32, width: i32, color: Color) void {    if (width <= 0) return;    var x = x0;    var y = y0;    const dx = if (x1 >= x0) x1 - x0 else x0 - x1;    const dy = if (y1 >= y0) y1 - y0 else y0 - y1;    const sx: i32 = if (x0 < x1) 1 else -1;    const sy: i32 = if (y0 < y1) 1 else -1;    var err = dx - dy;    const offset = @divTrunc(width, 2);    while (true) {        drawRect(canvas, x - offset, y - offset, width, width, color);        if (x == x1 and y == y1) break;        const e2 = err * 2;        if (e2 > -dy) {            err -= dy;            x += sx;        }        if (e2 < dx) {            err += dx;            y += sy;        }    }}fn validateOptions(options: Options) !void {    if (options.font_bytes.len == 0) return error.InvalidFont;    if (options.font_size <= 0) return error.InvalidPixelSize;}fn loadFallbackFonts(allocator: std.mem.Allocator, fonts: []const []const u8) ![]filigree.Font {    const loaded = try allocator.alloc(filigree.Font, fonts.len);    errdefer allocator.free(loaded);    var index: usize = 0;    errdefer {        for (loaded[0..index]) |*font| font.deinit();    }    while (index < fonts.len) : (index += 1) {        const bytes = fonts[index];        loaded[index] = filigree.Font.initFromBytes(bytes.ptr, bytes.len) orelse return error.InvalidFont;    }    return loaded;}fn unloadFallbackFonts(allocator: std.mem.Allocator, fonts: []filigree.Font) void {    for (fonts) |*font| font.deinit();    allocator.free(fonts);}fn fill(pixels: []u8, width: u32, height: u32, stride: u32, color: Color) void {    var row: u32 = 0;    while (row < height) : (row += 1) {        var col: u32 = 0;        while (col < width) : (col += 1) {            const offset = @as(usize, row) * @as(usize, stride) + @as(usize, col) * 4;            pixels[offset] = color.r;            pixels[offset + 1] = color.g;            pixels[offset + 2] = color.b;            pixels[offset + 3] = color.a;        }    }}fn visiblePixels(pixels: []const u8) usize {    var count: usize = 0;    var index: usize = 3;    while (index < pixels.len) : (index += 4) {        if (pixels[index] != 0) count += 1;    }    return count;}fn visiblePixelsInRect(image: *const Image, x: u32, y: u32, width: u32, height: u32) usize {    const x1 = @min(image.width, x + width);    const y1 = @min(image.height, y + height);    var count: usize = 0;    var py = y;    while (py < y1) : (py += 1) {        var px = x;        while (px < x1) : (px += 1) {            const offset = @as(usize, py) * image.stride + @as(usize, px) * 4 + 3;            if (image.pixels[offset] != 0) count += 1;        }    }    return count;}test "image renderer draws parsed math into owned rgba pixels" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var rendered = try render(allocator, "A+B", .{        .font_bytes = bytes,        .font_size = 20,    });    defer rendered.deinit();    try std.testing.expect(rendered.width > 20);    try std.testing.expect(rendered.height > 20);    try std.testing.expect(visiblePixels(rendered.pixels) > 0);}test "image renderer lays out fractions as pixels instead of cells" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var rendered = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer rendered.deinit();    try std.testing.expect(rendered.width < 40);    try std.testing.expect(rendered.height > 40);    try std.testing.expect(rendered.baseline < rendered.height);    try std.testing.expect(visiblePixels(rendered.pixels) > 0);}test "image renderer scales delimiters around tall bodies" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var fraction = try render(allocator, "\\frac{A+B}{C+D}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fraction.deinit();    var delimited = try render(allocator, "\\left(\\frac{A+B}{C+D}\\right)", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer delimited.deinit();    try std.testing.expect(delimited.width > fraction.width);    try std.testing.expect(delimited.height > fraction.height);    try std.testing.expect(delimited.baseline < delimited.height);    try std.testing.expect(visiblePixels(delimited.pixels) > 0);}test "image renderer draws capped bracket fallbacks" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var rendered = try render(allocator, "\\left[\\frac{A}{B}\\right]", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer rendered.deinit();    try std.testing.expect(visiblePixelsInRect(&rendered, 0, 0, @max(1, rendered.width / 5), 4) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, rendered.height - @min(rendered.height, 4), @max(1, rendered.width / 5), @min(rendered.height, 4)) > 0);}test "image renderer draws double bracket fallbacks" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var fraction = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fraction.deinit();    var rendered = try render(allocator, "\\left\\llbracket\\frac{A}{B}\\right\\rrbracket", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer rendered.deinit();    const edge_width = @min(rendered.width, 16);    const half_edge = @max(1, edge_width / 2);    const band_height = @min(rendered.height, 6);    const right_x = rendered.width - edge_width;    try std.testing.expect(rendered.width > fraction.width);    try std.testing.expect(rendered.height >= fraction.height);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, 0, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, rendered.height - band_height, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, 0, half_edge, rendered.height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, half_edge, 0, edge_width - half_edge, rendered.height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, 0, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, rendered.height - band_height, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, 0, half_edge, rendered.height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x + half_edge, 0, edge_width - half_edge, rendered.height) > 0);}test "image renderer draws stroked brace fallbacks around tall bodies" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var fraction = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fraction.deinit();    var rendered = try render(allocator, "\\left\\{\\frac{A}{B}\\right\\}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer rendered.deinit();    const edge_width = @min(rendered.width, 14);    const band_height = @min(rendered.height, 6);    const middle_y = rendered.height / 2 - @min(rendered.height / 2, band_height / 2);    const right_x = rendered.width - edge_width;    try std.testing.expect(rendered.width > fraction.width);    try std.testing.expect(rendered.height > fraction.height);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, 0, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, middle_y, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, 0, rendered.height - band_height, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, 0, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, middle_y, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&rendered, right_x, rendered.height - band_height, edge_width, band_height) > 0);}test "image renderer uses MATH assemblies for tall delimiters" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const assembly_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(assembly_bytes);    var fallback = try render(allocator, "\\left(\\frac{A+B}{C+D}\\right)", .{        .font_bytes = fallback_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var assembled = try render(allocator, "\\left(\\frac{A+B}{C+D}\\right)", .{        .font_bytes = assembly_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer assembled.deinit();    const fallback_left = visiblePixelsInRect(&fallback, 0, 0, @min(fallback.width, 12), fallback.height);    const assembled_left = visiblePixelsInRect(&assembled, 0, 0, @min(assembled.width, 12), assembled.height);    try std.testing.expect(assembled_left > fallback_left * 2);}test "image renderer uses MATH minimum height for small delimiters" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\left(A\\right)", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\left(A\\right)", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height);    try std.testing.expect(math.baseline > fallback.baseline);    try std.testing.expect(visiblePixelsInRect(&math, 0, 0, @min(math.width, 12), math.height) > 0);}test "image renderer draws scalable native radicals" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var fraction = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fraction.deinit();    var rooted = try render(allocator, "\\sqrt{\\frac{A}{B}}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer rooted.deinit();    try std.testing.expect(rooted.width > fraction.width + 8);    try std.testing.expect(rooted.height > fraction.height);    try std.testing.expect(rooted.baseline > fraction.baseline);    try std.testing.expect(visiblePixelsInRect(&rooted, 0, rooted.height / 2, rooted.width / 3, rooted.height - rooted.height / 2) > 0);}test "image renderer uses MATH assemblies for radical signs" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const assembly_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(assembly_bytes);    var fallback = try render(allocator, "\\sqrt{\\frac{A+B}{C+D}}", .{        .font_bytes = fallback_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var assembled = try render(allocator, "\\sqrt{\\frac{A+B}{C+D}}", .{        .font_bytes = assembly_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer assembled.deinit();    const fallback_left = visiblePixelsInRect(&fallback, 0, 0, @min(fallback.width, 12), fallback.height);    const assembled_left = visiblePixelsInRect(&assembled, 0, 0, @min(assembled.width, 12), assembled.height);    try std.testing.expect(assembled.height > fallback.height);    try std.testing.expect(assembled_left > fallback_left * 2);}test "image renderer uses MATH display radical gap" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\sqrt{A}", .{        .font_bytes = fallback_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\sqrt{A}", .{        .font_bytes = math_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height + 16);    try std.testing.expect(math.baseline > fallback.baseline + 12);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH radical rule and ascender metrics" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\sqrt{A}", .{        .font_bytes = fallback_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\sqrt{A}", .{        .font_bytes = math_bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height + 22);    try std.testing.expect(math.baseline > fallback.baseline + 18);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer tucks radical degrees into the root sign" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var square = try render(allocator, "\\sqrt{\\frac{A+B}{C+D}}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer square.deinit();    var cube = try render(allocator, "\\sqrt[3]{\\frac{A+B}{C+D}}", .{        .font_bytes = bytes,        .font_size = 20,        .padding_x = 0,        .padding_y = 0,    });    defer cube.deinit();    try std.testing.expect(cube.width > square.width);    try std.testing.expect(cube.width < square.width + 12);}test "image renderer uses MATH script-script scale percentage for radical degrees" {    const allocator = std.testing.allocator;    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var square = try render(allocator, "\\sqrt{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer square.deinit();    var degree = try render(allocator, "\\sqrt[BBBBBBBB]{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer degree.deinit();    try std.testing.expect(degree.width > square.width + 56);    try std.testing.expect(visiblePixels(degree.pixels) > 0);}test "image renderer uses MATH radical degree metrics" {    const allocator = std.testing.allocator;    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var square = try render(allocator, "\\sqrt{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer square.deinit();    var degree = try render(allocator, "\\sqrt[3]{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer degree.deinit();    try std.testing.expect(degree.width > square.width + 26);    try std.testing.expect(degree.height > square.height + 4);    try std.testing.expect(degree.baseline > square.baseline + 4);    try std.testing.expect(visiblePixels(degree.pixels) > 0);}test "image renderer stacks large operator limits" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var limited = try render(allocator, "lim_{ABC}^{D}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer limited.deinit();    var side = try render(allocator, "x_{ABC}^{D}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer side.deinit();    try std.testing.expect(limited.width < side.width);    try std.testing.expect(limited.height > side.height);    try std.testing.expect(visiblePixels(limited.pixels) > 0);}test "image renderer uses MATH constants for operator limits" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "lim_{B}^{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "lim_{B}^{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height + 48);    try std.testing.expect(math.baseline > fallback.baseline + 32);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH display operator minimum height" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(bytes);    var ordinary = try render(allocator, "A", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer ordinary.deinit();    var sum = try render(allocator, "\\sum", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer sum.deinit();    var text_operator = try render(allocator, "lim", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer text_operator.deinit();    try std.testing.expect(sum.height > ordinary.height + 20);    try std.testing.expect(sum.height > text_operator.height + 20);    try std.testing.expect(visiblePixels(sum.pixels) > 0);}test "image renderer uses MATH constants for side scripts" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "A_{B}^{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "A_{B}^{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.baseline > fallback.baseline + 4);    try std.testing.expect(math.height > fallback.height);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH script scale percentage" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "A_{BBBBBBBB}^{BBBBBBBB}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "A_{BBBBBBBB}^{BBBBBBBB}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.width > fallback.width + 8);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH side script bounds" {    const allocator = std.testing.allocator;    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var base = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer base.deinit();    var scripted = try render(allocator, "\\frac{A}{B}_{B}^{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer scripted.deinit();    var crowded = try render(allocator, "A_{\\frac{B}{B}}^{\\frac{B}{B}}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer crowded.deinit();    try std.testing.expect(scripted.height > base.height + 50);    try std.testing.expect(scripted.baseline > base.baseline + 11);    try std.testing.expect(crowded.baseline > scripted.baseline + 22);    try std.testing.expect(visiblePixels(scripted.pixels) > 0);    try std.testing.expect(visiblePixels(crowded.pixels) > 0);}test "image renderer uses MATH constants for fraction shifts" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height);    try std.testing.expect(math.baseline > fallback.baseline);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH text fraction gap constants" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var text = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer text.deinit();    var display = try render(allocator, "\\dfrac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer display.deinit();    try std.testing.expect(text.height > fallback.height + 18);    try std.testing.expect(display.height > text.height);    try std.testing.expect(visiblePixels(text.pixels) > 0);}test "image renderer uses MATH fraction rule thickness" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height + 31);    try std.testing.expect(math.baseline > fallback.baseline + 32);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH axis height for fractions" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.baseline > fallback.baseline + 24);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer uses MATH display constants for display fractions" {    const allocator = std.testing.allocator;    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var text = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer text.deinit();    var text_alias = try render(allocator, "\\tfrac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer text_alias.deinit();    var display = try render(allocator, "\\dfrac{A}{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer display.deinit();    try std.testing.expectEqual(text.height, text_alias.height);    try std.testing.expectEqual(text.baseline, text_alias.baseline);    try std.testing.expect(display.height > text.height);    try std.testing.expect(display.baseline > text.baseline);    try std.testing.expect(visiblePixels(display.pixels) > 0);}test "image renderer stretches labeled arrows across wide labels" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const font_size: i32 = 24;    var right = try render(allocator, "\\xrightarrow{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer right.deinit();    var mapped = try render(allocator, "\\xmapsto{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer mapped.deinit();    const axis_drop: u32 = @intCast(@max(1, @divTrunc(font_size, 4)));    const band_margin = axis_drop + 2;    const band_y = if (right.baseline > band_margin) right.baseline - band_margin else 0;    const band_height = @min(right.height - band_y, 7);    const edge_width = @min(right.width, 12);    try std.testing.expect(visiblePixelsInRect(&right, right.width - edge_width, band_y, edge_width, band_height) > 0);    try std.testing.expect(visiblePixelsInRect(&mapped, 0, 0, @min(mapped.width, 4), mapped.height) > 0);}test "image renderer uses MATH assemblies for horizontal arrows" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const assembly_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(assembly_bytes);    var fallback = try render(allocator, "\\xrightarrow{ABCDEFGH}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var assembled = try render(allocator, "\\xrightarrow{ABCDEFGH}", .{        .font_bytes = assembly_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer assembled.deinit();    const fallback_base = visiblePixelsInRect(&fallback, 0, fallback.baseline / 2, fallback.width, @max(1, fallback.height - fallback.baseline / 2));    const assembled_base = visiblePixelsInRect(&assembled, 0, assembled.baseline / 2, assembled.width, @max(1, assembled.height - assembled.baseline / 2));    try std.testing.expect(assembled_base > fallback_base * 2);}test "image renderer uses MATH stretch stack constants for labeled arrows" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\xleftarrow[B]{B}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var math = try render(allocator, "\\xleftarrow[B]{B}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math.deinit();    try std.testing.expect(math.height > fallback.height + 48);    try std.testing.expect(math.baseline > fallback.baseline + 32);    try std.testing.expect(visiblePixels(math.pixels) > 0);}test "image renderer draws brace annotations as shaped strokes" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const font_size: i32 = 24;    var over = try render(allocator, "\\overbrace{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer over.deinit();    var under = try render(allocator, "\\underbrace{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer under.deinit();    const brace_depth: u32 = @intCast(@max(5, @divTrunc(font_size, 4)));    try std.testing.expect(visiblePixelsInRect(&over, 0, 2, over.width, @min(2, over.height - 2)) > 0);    const under_band_y = under.height - @min(under.height, brace_depth);    const under_middle_y = @min(under.height - 1, under_band_y + @min(2, brace_depth - 1));    try std.testing.expect(visiblePixelsInRect(&under, 0, under_middle_y, under.width, @min(2, under.height - under_middle_y)) > 0);}test "image renderer stretches wide accent strokes" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const font_size: i32 = 24;    var hat = try render(allocator, "\\widehat{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer hat.deinit();    var arrow = try render(allocator, "\\overrightarrow{ABCDEFGH}", .{        .font_bytes = bytes,        .font_size = font_size,        .padding_x = 0,        .padding_y = 0,    });    defer arrow.deinit();    const accent_depth: u32 = @intCast(@max(5, @divTrunc(font_size, 5)));    const edge_width = @min(hat.width, 12);    try std.testing.expect(visiblePixelsInRect(&hat, 0, 0, edge_width, @min(hat.height, accent_depth)) > 0);    try std.testing.expect(visiblePixelsInRect(&hat, hat.width - edge_width, 0, edge_width, @min(hat.height, accent_depth)) > 0);    try std.testing.expect(visiblePixelsInRect(&arrow, 0, 0, @min(arrow.width, edge_width), @min(arrow.height, accent_depth)) > 0);    try std.testing.expect(visiblePixelsInRect(&arrow, arrow.width - @min(arrow.width, edge_width), 0, @min(arrow.width, edge_width), @min(arrow.height, accent_depth)) > 0);}test "image renderer uses MATH top accent attachment" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback = try render(allocator, "\\grave{A}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback.deinit();    var attached = try render(allocator, "\\grave{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer attached.deinit();    try std.testing.expect(attached.width > fallback.width);    try std.testing.expect(visiblePixelsInRect(&attached, attached.width - @min(attached.width, 6), 0, @min(attached.width, 6), @min(attached.height, attached.baseline)) > 0);}test "image renderer uses MATH over and underbar metrics" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback_bar = try render(allocator, "\\bar{A}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback_bar.deinit();    var math_bar = try render(allocator, "\\bar{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math_bar.deinit();    var fallback_under = try render(allocator, "\\underline{A}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback_under.deinit();    var math_under = try render(allocator, "\\underline{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math_under.deinit();    try std.testing.expect(math_bar.height > fallback_bar.height + 24);    try std.testing.expect(math_bar.baseline > fallback_bar.baseline + 24);    try std.testing.expect(math_under.height > fallback_under.height + 22);    try std.testing.expectEqual(fallback_under.baseline, math_under.baseline);}test "image renderer uses MATH display stack constants for annotations" {    const allocator = std.testing.allocator;    const fallback_bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(fallback_bytes);    const math_bytes = try filigree.fixtures.createWithMathAssembly(allocator);    defer allocator.free(math_bytes);    var fallback_over = try render(allocator, "\\overset{B}{A}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback_over.deinit();    var math_over = try render(allocator, "\\overset{B}{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math_over.deinit();    var fallback_under = try render(allocator, "\\underset{B}{A}", .{        .font_bytes = fallback_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer fallback_under.deinit();    var math_under = try render(allocator, "\\underset{B}{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer math_under.deinit();    var tall_over = try render(allocator, "\\overset{\\frac{B}{B}}{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer tall_over.deinit();    var tall_under = try render(allocator, "\\underset{\\frac{B}{B}}{A}", .{        .font_bytes = math_bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer tall_under.deinit();    try std.testing.expect(math_over.baseline > fallback_over.baseline + 24);    try std.testing.expect(math_under.height > fallback_under.height + 24);    try std.testing.expect(tall_over.baseline > math_over.baseline + 40);    try std.testing.expect(tall_under.height > math_under.height + 40);    try std.testing.expect(visiblePixels(math_over.pixels) > 0);    try std.testing.expect(visiblePixels(math_under.pixels) > 0);    try std.testing.expect(visiblePixels(tall_over.pixels) > 0);    try std.testing.expect(visiblePixels(tall_under.pixels) > 0);}test "image renderer applies explicit operator limit policy" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var starred = try render(allocator, "\\operatorname*{argmin}_{ABC}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer starred.deinit();    var plain = try render(allocator, "\\operatorname{argmin}_{ABC}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer plain.deinit();    var automatic = try render(allocator, "lim_{ABC}^{D}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer automatic.deinit();    var nolimits = try render(allocator, "lim\\nolimits_{ABC}^{D}", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer nolimits.deinit();    try std.testing.expect(starred.width < plain.width);    try std.testing.expect(automatic.width < nolimits.width);    try std.testing.expect(automatic.height > nolimits.height);    try std.testing.expect(visiblePixels(starred.pixels) > 0);}test "image renderer keeps grid fences in pixel layout" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var rendered = try render(allocator, "\\begin{pmatrix}A&B\\\\C&D\\end{pmatrix}", .{        .font_bytes = bytes,        .font_size = 20,    });    defer rendered.deinit();    try std.testing.expect(rendered.width > 40);    try std.testing.expect(rendered.height > 35);    try std.testing.expect(rendered.baseline < rendered.height);    try std.testing.expect(visiblePixels(rendered.pixels) > 0);}test "image renderer scales explicit math spaces" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var tight = try render(allocator, "AB", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer tight.deinit();    var thin = try render(allocator, "A\\,B", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer thin.deinit();    var quad = try render(allocator, "A\\quad B", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer quad.deinit();    var negative = try render(allocator, "A\\!B", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer negative.deinit();    try std.testing.expect(thin.width > tight.width);    try std.testing.expect(quad.width > thin.width);    try std.testing.expect(negative.width < tight.width);}test "image renderer computes math class spacing" {    const style: Style = .{ .font_size = 36 };    const ordinary: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .ordinary };    const binary: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .binary };    const relation: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .relation };    const open: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .open };    const punctuation: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .punctuation };    const explicit: Box = .{ .width = 1, .height = 1, .baseline = 0, .commands = &.{}, .class = .spacing };    const plus = [_]Box{ ordinary, binary, ordinary };    try std.testing.expectEqual(@as(i32, 8), mathClassSpace(&plus, 1, style));    try std.testing.expectEqual(@as(i32, 8), mathClassSpace(&plus, 2, style));    const unary = [_]Box{ binary, ordinary };    try std.testing.expectEqual(@as(i32, 0), mathClassSpace(&unary, 1, style));    const equals = [_]Box{ ordinary, relation, ordinary };    try std.testing.expectEqual(@as(i32, 10), mathClassSpace(&equals, 1, style));    try std.testing.expectEqual(@as(i32, 10), mathClassSpace(&equals, 2, style));    const grouped = [_]Box{ open, binary, ordinary };    try std.testing.expectEqual(@as(i32, 0), mathClassSpace(&grouped, 1, style));    const comma = [_]Box{ ordinary, punctuation, ordinary };    try std.testing.expectEqual(@as(i32, 6), mathClassSpace(&comma, 2, style));    const manual = [_]Box{ ordinary, explicit, relation };    try std.testing.expectEqual(@as(i32, 0), mathClassSpace(&manual, 1, style));    try std.testing.expectEqual(@as(i32, 0), mathClassSpace(&manual, 2, style));    try std.testing.expectEqual(MathClass.operator, mathClassForText("lim"));    try std.testing.expectEqual(MathClass.operator, mathClassForText("max"));    try std.testing.expectEqual(MathClass.relation, mathClassForText("→"));}test "image renderer adds automatic binary and relation spacing" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    var automatic = try render(allocator, "A+B=C", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer automatic.deinit();    var tightened = try render(allocator, "A\\!+\\!B\\!=\\!C", .{        .font_bytes = bytes,        .font_size = 24,        .padding_x = 0,        .padding_y = 0,    });    defer tightened.deinit();    try std.testing.expect(automatic.width > tightened.width + 10);    try std.testing.expect(visiblePixels(automatic.pixels) > 0);}test "image renderer accepts fallback font bytes" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const fallback_fonts = [_][]const u8{bytes};    var rendered = try render(allocator, "\\mathbf{A}+B", .{        .font_bytes = bytes,        .fallback_font_bytes = &fallback_fonts,        .font_size = 20,    });    defer rendered.deinit();    try std.testing.expect(rendered.width > 20);    try std.testing.expect(rendered.height > 20);    try std.testing.expect(visiblePixels(rendered.pixels) > 0);}test "image renderer accepts CFF outline font bytes" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithCffOutlines(allocator);    defer allocator.free(bytes);    var rendered = try render(allocator, "\\frac{A}{B}", .{        .font_bytes = bytes,        .font_size = 20,    });    defer rendered.deinit();    try std.testing.expect(rendered.width > 20);    try std.testing.expect(rendered.height > 35);    try std.testing.expect(visiblePixels(rendered.pixels) > 0);}test "image renderer rejects invalid fallback font bytes" {    const allocator = std.testing.allocator;    const bytes = try filigree.fixtures.createWithOutlines(allocator);    defer allocator.free(bytes);    const fallback_fonts = [_][]const u8{&.{}};    try std.testing.expectError(error.InvalidFont, render(allocator, "A", .{        .font_bytes = bytes,        .fallback_font_bytes = &fallback_fonts,        .font_size = 20,    }));}

Source: lib/termtex/src/root.zig:12

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

Complete caller list for image.render

42 direct callers.

Audit

Definitions3
Public names3
Members0
Version26.7.0
Revisiondaab053ee433