tiny.termtex.layout
Defined in tiny.termtex.
API (4)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
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.
lib.choir.src.core.interfaces.entry.InlineStorage[function] — private source atlib/choir/src/core/interfaces/entry.zig:256in nearest public ownerlib.choir.src.core.interfaces.entrylib.termtex.src.layout.layoutAccent[function] — private source atlib/termtex/src/layout.zig:129in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutBoxed[function] — private source atlib/termtex/src/layout.zig:207in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutBraceAnnotation[function] — private source atlib/termtex/src/layout.zig:188in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutDelimited[function] — private source atlib/termtex/src/layout.zig:224in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutFraction[function] — private source atlib/termtex/src/layout.zig:76in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutGrid[function] — private source atlib/termtex/src/layout.zig:257in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutLimits[function] — private source atlib/termtex/src/layout.zig:115in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutRow[function] — private source atlib/termtex/src/layout.zig:51in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutScripts[function] — private source atlib/termtex/src/layout.zig:100in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutSqrt[function] — private source atlib/termtex/src/layout.zig:87in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutStack[function] — private source atlib/termtex/src/layout.zig:174in nearest public ownertiny.termtex.layouttiny.termtex.renderer.render[function] atlib/termtex/src/render.zig:33
Complete call list for layout.layout
10 direct calls.
lib.termtex.src.layout.layoutAccent[function] — private source atlib/termtex/src/layout.zig:129in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutAnnotation[function] — private source atlib/termtex/src/layout.zig:160in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutDelimited[function] — private source atlib/termtex/src/layout.zig:224in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutFraction[function] — private source atlib/termtex/src/layout.zig:76in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutGrid[function] — private source atlib/termtex/src/layout.zig:257in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutRow[function] — private source atlib/termtex/src/layout.zig:51in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutScripts[function] — private source atlib/termtex/src/layout.zig:100in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutSpace[function] — private source atlib/termtex/src/layout.zig:72in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.layoutSqrt[function] — private source atlib/termtex/src/layout.zig:87in nearest public ownertiny.termtex.layoutlib.termtex.src.layout.textBox[function] — private source atlib/termtex/src/layout.zig:323in nearest public ownertiny.termtex.layout
Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |