Skip to documentation
SLOP

tiny.termtex.layout

Reference tiny.termtex layout

Defined in tiny.termtex.

API (4)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsprivate sourcelib.choir.src.core.interfaces.entryInlineStorageprivate sourcelib.termtex.src.layoutlayoutAccentprivate sourcelib.termtex.src.layoutlayoutBoxedprivate sourcelib.termtex.src.layoutlayoutBraceAnnotationprivate sourcelib.termtex.src.layoutlayoutDelimited+8 moreprivate sourcelib.termtex.src.layoutlayoutAccentprivate sourcelib.termtex.src.layoutlayoutAnnotationprivate sourcelib.termtex.src.layoutlayoutDelimitedprivate sourcelib.termtex.src.layoutlayoutFractionprivate sourcelib.termtex.src.layoutlayoutGrid+5 morelayoutlayout
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsrendererrenderlayoutrows
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate sourcelib.termtex.src.layoutlayoutSpacelayoutspaceCells
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/termtex/src/layout.zig

zig
const std = @import("std");const ast = @import("ast.zig");const operator = @import("operator.zig");pub const Box = struct {    width: usize,    height: usize,    baseline: usize,    cells: []Cell,    pub fn at(self: Box, x: usize, y: usize) Cell {        return self.cells[y * self.width + x];    }    pub fn set(self: *Box, x: usize, y: usize, cell: Cell) void {        self.cells[y * self.width + x] = cell;    }};const Cell = []const u8;const space = " ";pub fn layout(arena: std.mem.Allocator, expr: *const ast.Expr) std.mem.Allocator.Error!Box {    return switch (expr.*) {        .row => |items| layoutRow(arena, items),        .text => |value| textBox(arena, value),        .operator => |value| layout(arena, value.body),        .space => |value| layoutSpace(arena, value),        .fraction => |value| layoutFraction(arena, value),        .sqrt => |value| layoutSqrt(arena, value),        .scripts => |value| layoutScripts(arena, value),        .accent => |value| layoutAccent(arena, value),        .grid => |value| layoutGrid(arena, value),        .annotation => |value| layoutAnnotation(arena, value),        .delimited => |value| layoutDelimited(arena, value),    };}pub fn rows(arena: std.mem.Allocator, value: Box) std.mem.Allocator.Error![]const []const u8 {    const out = try arena.alloc([]const u8, value.height);    for (0..value.height) |y| {        var end = value.width;        while (end > 0 and std.mem.eql(u8, value.at(end - 1, y), space)) end -= 1;        var line: std.ArrayListUnmanaged(u8) = .empty;        for (0..end) |x| try line.appendSlice(arena, value.at(x, y));        out[y] = try line.toOwnedSlice(arena);    }    return out;}fn layoutRow(arena: std.mem.Allocator, items: []const *ast.Expr) std.mem.Allocator.Error!Box {    if (items.len == 0) return makeBox(arena, 0, 1, 0);    const children = try arena.alloc(Box, items.len);    var width: usize = 0;    var baseline: usize = 0;    var descent: usize = 0;    for (items, 0..) |item, index| {        children[index] = try layout(arena, item);        width += children[index].width;        baseline = @max(baseline, children[index].baseline);        descent = @max(descent, children[index].height - children[index].baseline - 1);    }    var out = try makeBox(arena, width, baseline + descent + 1, baseline);    var x: usize = 0;    for (children) |child| {        putBox(&out, x, baseline - child.baseline, child);        x += child.width;    }    return out;}fn layoutSpace(arena: std.mem.Allocator, value: ast.Space) std.mem.Allocator.Error!Box {    return makeBox(arena, spaceCells(value), 1, 0);}fn layoutFraction(arena: std.mem.Allocator, value: ast.Fraction) std.mem.Allocator.Error!Box {    const numerator = try layout(arena, value.numerator);    const denominator = try layout(arena, value.denominator);    const width = @max(numerator.width, denominator.width) + 2;    var out = try makeBox(arena, width, numerator.height + denominator.height + 1, numerator.height);    putBox(&out, centered(width, numerator.width), 0, numerator);    for (0..width) |x| out.set(x, numerator.height, "─");    putBox(&out, centered(width, denominator.width), numerator.height + 1, denominator);    return out;}fn layoutSqrt(arena: std.mem.Allocator, value: ast.Radical) std.mem.Allocator.Error!Box {    const body = try layout(arena, value.body);    const index = if (value.index) |expr| try layout(arena, expr) else null;    const prefix = if (index) |idx| @max(idx.width, @as(usize, 1)) else 1;    const height = body.height + 1;    var out = try makeBox(arena, prefix + body.width + 1, height, body.baseline + 1);    if (index) |idx| putBox(&out, prefix - idx.width, 0, idx);    out.set(prefix - 1, out.baseline, "√");    for (0..body.width + 1) |x| out.set(prefix + x, 0, "─");    putBox(&out, prefix + 1, 1, body);    return out;}fn layoutScripts(arena: std.mem.Allocator, value: ast.Scripts) std.mem.Allocator.Error!Box {    if (operator.limitsBase(value.base)) return layoutLimits(arena, value);    const base = try layout(arena, value.base);    const sup = if (value.sup) |expr| try layout(arena, expr) else null;    const sub = if (value.sub) |expr| try layout(arena, expr) else null;    const script_width = @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0);    const top = if (sup) |box| box.height else 0;    const bottom = if (sub) |box| box.height else 0;    var out = try makeBox(arena, base.width + script_width, top + base.height + bottom, top + base.baseline);    if (sup) |box| putBox(&out, base.width, 0, box);    putBox(&out, 0, top, base);    if (sub) |box| putBox(&out, base.width, top + base.height, box);    return out;}fn layoutLimits(arena: std.mem.Allocator, value: ast.Scripts) std.mem.Allocator.Error!Box {    const base = try layout(arena, value.base);    const sup = if (value.sup) |expr| try layout(arena, expr) else null;    const sub = if (value.sub) |expr| try layout(arena, expr) else null;    const width = @max(base.width, @max(if (sup) |box| box.width else 0, if (sub) |box| box.width else 0));    const top = if (sup) |box| box.height else 0;    const bottom = if (sub) |box| box.height else 0;    var out = try makeBox(arena, width, top + base.height + bottom, top + base.baseline);    if (sup) |box| putBox(&out, centered(width, box.width), 0, box);    putBox(&out, centered(width, base.width), top, base);    if (sub) |box| putBox(&out, centered(width, box.width), top + base.height, box);    return out;}fn layoutAccent(arena: std.mem.Allocator, value: ast.Accent) std.mem.Allocator.Error!Box {    const body = try layout(arena, value.body);    if (value.mark == .underline) {        var out = try makeBox(arena, body.width, body.height + 1, body.baseline);        putBox(&out, 0, 0, body);        for (0..body.width) |x| out.set(x, body.height, "─");        return out;    }    var out = try makeBox(arena, body.width, body.height + 1, body.baseline + 1);    if (body.width != 0) {        switch (value.mark) {            .bar => for (0..body.width) |x| out.set(x, 0, "─"),            .hat => out.set(centered(body.width, 1), 0, "^"),            .vec => out.set(centered(body.width, 1), 0, "→"),            .dot => out.set(centered(body.width, 1), 0, "˙"),            .underline => unreachable,            .tilde => out.set(centered(body.width, 1), 0, "~"),            .check => out.set(centered(body.width, 1), 0, "ˇ"),            .breve => out.set(centered(body.width, 1), 0, "˘"),            .ddot => out.set(centered(body.width, 1), 0, "¨"),            .acute => out.set(centered(body.width, 1), 0, "´"),            .grave => out.set(centered(body.width, 1), 0, "`"),            .ring => out.set(centered(body.width, 1), 0, "˚"),            .overleft => out.set(centered(body.width, 1), 0, "←"),            .overleftright => out.set(centered(body.width, 1), 0, "↔"),        }    }    putBox(&out, 0, 1, body);    return out;}fn layoutAnnotation(arena: std.mem.Allocator, value: ast.Annotation) std.mem.Allocator.Error!Box {    return switch (value.kind) {        .plain => layoutStack(arena, value),        .overbrace => layoutBraceAnnotation(arena, value.base, .over),        .underbrace => layoutBraceAnnotation(arena, value.base, .under),        .boxed => layoutBoxed(arena, value.base),    };}const BraceSide = enum {    over,    under,};fn layoutStack(arena: std.mem.Allocator, value: ast.Annotation) std.mem.Allocator.Error!Box {    const base = try layout(arena, value.base);    const over = if (value.over) |expr| try layout(arena, expr) else null;    const under = if (value.under) |expr| try layout(arena, expr) else null;    const width = @max(base.width, @max(if (over) |box| box.width else 0, if (under) |box| box.width else 0));    const top = if (over) |box| box.height else 0;    const bottom = if (under) |box| box.height else 0;    var out = try makeBox(arena, width, top + base.height + bottom, top + base.baseline);    if (over) |box| putBox(&out, centered(width, box.width), 0, box);    putBox(&out, centered(width, base.width), top, base);    if (under) |box| putBox(&out, centered(width, box.width), top + base.height, box);    return out;}fn layoutBraceAnnotation(arena: std.mem.Allocator, body_expr: *ast.Expr, side: BraceSide) std.mem.Allocator.Error!Box {    const body = try layout(arena, body_expr);    var out = try makeBox(arena, body.width, body.height + 1, switch (side) {        .over => body.baseline + 1,        .under => body.baseline,    });    if (body.width != 0) {        switch (side) {            .over => for (0..body.width) |x| out.set(x, 0, "⏞"),            .under => for (0..body.width) |x| out.set(x, body.height, "⏟"),        }    }    switch (side) {        .over => putBox(&out, 0, 1, body),        .under => putBox(&out, 0, 0, body),    }    return out;}fn layoutBoxed(arena: std.mem.Allocator, body_expr: *ast.Expr) std.mem.Allocator.Error!Box {    const body = try layout(arena, body_expr);    var out = try makeBox(arena, body.width + 4, body.height + 2, body.baseline + 1);    out.set(0, 0, "┌");    for (1..out.width - 1) |x| out.set(x, 0, "─");    out.set(out.width - 1, 0, "┐");    for (1..out.height - 1) |y| {        out.set(0, y, "│");        out.set(out.width - 1, y, "│");    }    out.set(0, out.height - 1, "└");    for (1..out.width - 1) |x| out.set(x, out.height - 1, "─");    out.set(out.width - 1, out.height - 1, "┘");    putBox(&out, 2, 1, body);    return out;}fn layoutDelimited(arena: std.mem.Allocator, value: ast.Delimited) std.mem.Allocator.Error!Box {    const body = try layout(arena, value.body);    const left = try layoutDelimiter(arena, value.left, body.height, body.baseline);    const right = try layoutDelimiter(arena, value.right, body.height, body.baseline);    var out = try makeBox(arena, left.width + body.width + right.width, body.height, body.baseline);    putBox(&out, 0, 0, left);    putBox(&out, left.width, 0, body);    putBox(&out, left.width + body.width, 0, right);    return out;}fn layoutDelimiter(    arena: std.mem.Allocator,    delimiter: ast.Delimiter,    height: usize,    baseline: usize,) std.mem.Allocator.Error!Box {    return switch (delimiter) {        .none => makeBox(arena, 0, height, baseline),        .shape => |shape| block: {            var out = try makeBox(arena, 1, height, baseline);            for (0..height) |y| out.set(0, y, delimiterShapeGlyph(shape, y, height));            break :block out;        },        .text => |value| block: {            const text = try textBox(arena, value);            var out = try makeBox(arena, text.width, height, baseline);            putBox(&out, 0, @min(baseline, height - 1), text);            break :block out;        },    };}fn layoutGrid(arena: std.mem.Allocator, value: ast.Grid) std.mem.Allocator.Error!Box {    if (value.rows.len == 0) return makeBox(arena, 0, 1, 0);    var columns: usize = 0;    for (value.rows) |row| columns = @max(columns, row.cells.len);    if (columns == 0) return makeBox(arena, 0, 1, 0);    const column_widths = try arena.alloc(usize, columns);    for (column_widths) |*width| width.* = 0;    const row_baselines = try arena.alloc(usize, value.rows.len);    const row_heights = try arena.alloc(usize, value.rows.len);    const boxes = try arena.alloc([]Box, value.rows.len);    for (value.rows, 0..) |row, row_index| {        boxes[row_index] = try arena.alloc(Box, row.cells.len);        var baseline: usize = 0;        var descent: usize = 0;        for (row.cells, 0..) |cell, column_index| {            const box = try layout(arena, cell);            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 - 1);        }        row_baselines[row_index] = baseline;        row_heights[row_index] = baseline + descent + 1;    }    const gap: usize = if (columns > 1) 2 else 0;    var body_width: usize = gap * (columns - 1);    for (column_widths) |width| body_width += width;    var body_height: usize = 0;    for (row_heights) |height| body_height += height;    const left_width = if (leftFence(value.fence) != null) @as(usize, 1) else 0;    const right_width = if (rightFence(value.fence) != null) @as(usize, 1) else 0;    var out = try makeBox(arena, left_width + body_width + right_width, body_height, body_height / 2);    if (leftFence(value.fence)) |fence| drawFence(&out, 0, fence, .left);    if (rightFence(value.fence)) |fence| drawFence(&out, left_width + body_width, fence, .right);    var y: usize = 0;    for (value.rows, 0..) |row, row_index| {        var x = left_width;        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,                };                putBox(&out, x + offset, y + row_baselines[row_index] - box.baseline, box);            }            x += column_widths[column_index] + gap;        }        y += row_heights[row_index];    }    return out;}pub fn spaceCells(value: ast.Space) usize {    if (value.numerator <= 0) return 0;    const numerator: u64 = @intCast(value.numerator);    const denominator: u64 = value.denominator;    const cells: usize = @intCast(@divTrunc(numerator * 2 + denominator - 1, denominator));    return @max(@as(usize, 1), cells);}fn textBox(arena: std.mem.Allocator, value: []const u8) std.mem.Allocator.Error!Box {    var count: usize = 0;    var index: usize = 0;    while (index < value.len) {        const len = std.unicode.utf8ByteSequenceLength(value[index]) catch 1;        index += @min(len, value.len - index);        count += 1;    }    var out = try makeBox(arena, count, 1, 0);    index = 0;    var x: usize = 0;    while (index < value.len) : (x += 1) {        const len = std.unicode.utf8ByteSequenceLength(value[index]) catch 1;        const end = index + @min(len, value.len - index);        out.set(x, 0, value[index..end]);        index = end;    }    return out;}fn makeBox(arena: std.mem.Allocator, width: usize, height: usize, baseline: usize) std.mem.Allocator.Error!Box {    const cells = try arena.alloc(Cell, width * height);    for (cells) |*cell| cell.* = space;    return .{        .width = width,        .height = height,        .baseline = baseline,        .cells = cells,    };}fn putBox(out: *Box, x: usize, y: usize, child: Box) void {    for (0..child.height) |row| {        for (0..child.width) |col| {            out.set(x + col, y + row, child.at(col, row));        }    }}fn centered(width: usize, inner: usize) usize {    if (inner >= width) return 0;    return (width - inner) / 2;}const Fence = enum {    paren,    bracket,    brace,    bar,    double_bar,};const FenceSide = enum {    left,    right,};fn leftFence(fence: ast.GridFence) ?Fence {    return switch (fence) {        .none => null,        .paren => .paren,        .bracket => .bracket,        .brace => .brace,        .bar => .bar,        .double_bar => .double_bar,        .left_brace => .brace,    };}fn rightFence(fence: ast.GridFence) ?Fence {    return switch (fence) {        .none, .left_brace => null,        .paren => .paren,        .bracket => .bracket,        .brace => .brace,        .bar => .bar,        .double_bar => .double_bar,    };}fn drawFence(out: *Box, x: usize, fence: Fence, side: FenceSide) void {    for (0..out.height) |y| out.set(x, y, fenceGlyph(fence, side, y, out.height));}fn delimiterShapeGlyph(shape: ast.DelimiterShape, y: usize, height: usize) []const u8 {    return switch (shape) {        .left_paren => fenceGlyph(.paren, .left, y, height),        .right_paren => fenceGlyph(.paren, .right, y, height),        .left_bracket => fenceGlyph(.bracket, .left, y, height),        .right_bracket => fenceGlyph(.bracket, .right, y, height),        .left_brace => fenceGlyph(.brace, .left, y, height),        .right_brace => fenceGlyph(.brace, .right, y, height),        .bar => "|",        .double_bar => "‖",        .left_angle => "⟨",        .right_angle => "⟩",        .left_double_angle => "⟪",        .right_double_angle => "⟫",        .left_double_bracket => "⟦",        .right_double_bracket => "⟧",        .left_floor => if (height == 1 or y + 1 == height) "⌊" else "⎢",        .right_floor => if (height == 1 or y + 1 == height) "⌋" else "⎥",        .left_ceil => if (height == 1 or y == 0) "⌈" else "⎢",        .right_ceil => if (height == 1 or y == 0) "⌉" else "⎥",    };}fn fenceGlyph(fence: Fence, side: FenceSide, y: usize, height: usize) []const u8 {    return switch (fence) {        .bar => "|",        .double_bar => "‖",        .paren => if (height == 1)            switch (side) {                .left => "(",                .right => ")",            }        else if (y == 0)            switch (side) {                .left => "⎛",                .right => "⎞",            }        else if (y + 1 == height)            switch (side) {                .left => "⎝",                .right => "⎠",            }        else switch (side) {            .left => "⎜",            .right => "⎟",        },        .bracket => if (height == 1)            switch (side) {                .left => "[",                .right => "]",            }        else if (y == 0)            switch (side) {                .left => "⎡",                .right => "⎤",            }        else if (y + 1 == height)            switch (side) {                .left => "⎣",                .right => "⎦",            }        else switch (side) {            .left => "⎢",            .right => "⎥",        },        .brace => if (height == 1)            switch (side) {                .left => "{",                .right => "}",            }        else if (y == 0)            switch (side) {                .left => "⎧",                .right => "⎫",            }        else if (y + 1 == height)            switch (side) {                .left => "⎩",                .right => "⎭",            }        else if (y == height / 2)            switch (side) {                .left => "⎨",                .right => "⎬",            }        else switch (side) {            .left => "⎪",            .right => "⎪",        },    };}

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

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

Complete caller list for layout.layout

13 direct callers.

Complete call list for layout.layout

10 direct calls.

Audit

Definitions4
Public names4
Members0
Version26.7.0
Revisiondaab053ee433