tiny.termtex.alphabet
Defined in tiny.termtex.
API (3)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: lib/termtex/src/alphabet.zig
zig
const std = @import("std");const ast = @import("ast.zig");pub const Mode = enum { bold, italic, bold_italic, script, bold_script, fraktur, bold_fraktur, double_struck, double_struck_italic, sans, sans_bold, sans_italic, sans_bold_italic, monospace, small_caps,};const Command = struct { name: []const u8, mode: Mode,};pub fn command(name: []const u8) ?Mode { inline for (commands) |entry| { if (std.mem.eql(u8, name, entry.name)) return entry.mode; } return null;}pub fn apply(arena: std.mem.Allocator, mode: Mode, expr: *const ast.Expr) std.mem.Allocator.Error!*ast.Expr { return switch (expr.*) { .row => |items| block: { const out = try arena.alloc(*ast.Expr, items.len); for (items, 0..) |item, index| out[index] = try apply(arena, mode, item); break :block ast.node(arena, .{ .row = out }); }, .text => |value| mapText(arena, mode, value), .operator => |value| ast.node(arena, .{ .operator = .{ .body = try apply(arena, mode, value.body), .limit_policy = value.limit_policy, } }), .space => |value| ast.space(arena, value), .fraction => |value| ast.node(arena, .{ .fraction = .{ .numerator = try apply(arena, mode, value.numerator), .denominator = try apply(arena, mode, value.denominator), .style = value.style, } }), .sqrt => |value| ast.node(arena, .{ .sqrt = .{ .index = if (value.index) |index| try apply(arena, mode, index) else null, .body = try apply(arena, mode, value.body), } }), .scripts => |value| ast.node(arena, .{ .scripts = .{ .base = try apply(arena, mode, value.base), .sub = if (value.sub) |sub| try apply(arena, mode, sub) else null, .sup = if (value.sup) |sup| try apply(arena, mode, sup) else null, } }), .accent => |value| ast.node(arena, .{ .accent = .{ .mark = value.mark, .body = try apply(arena, mode, value.body), } }), .grid => |value| block: { const rows = try arena.alloc(ast.GridRow, value.rows.len); for (value.rows, 0..) |row, row_index| { const cells = try arena.alloc(*ast.Expr, row.cells.len); for (row.cells, 0..) |cell, cell_index| cells[cell_index] = try apply(arena, mode, cell); rows[row_index] = .{ .cells = cells }; } break :block ast.node(arena, .{ .grid = .{ .rows = rows, .fence = value.fence, .alignment = value.alignment, } }); }, .annotation => |value| ast.node(arena, .{ .annotation = .{ .base = try apply(arena, mode, value.base), .over = if (value.over) |over| try apply(arena, mode, over) else null, .under = if (value.under) |under| try apply(arena, mode, under) else null, .kind = value.kind, } }), .delimited => |value| ast.node(arena, .{ .delimited = .{ .left = value.left, .body = try apply(arena, mode, value.body), .right = value.right, } }), };}fn mapText(arena: std.mem.Allocator, mode: Mode, value: []const u8) std.mem.Allocator.Error!*ast.Expr { var out: std.ArrayListUnmanaged(u8) = .empty; errdefer out.deinit(arena); var index: usize = 0; while (index < value.len) { const len = std.unicode.utf8ByteSequenceLength(value[index]) catch 1; const end = index + @min(len, value.len - index); const glyph = value[index..end]; if (mappedGlyph(mode, glyph)) |mapped| { switch (mapped) { .scalar => |scalar| try appendScalar(arena, &out, scalar), .text => |text| try out.appendSlice(arena, text), } } else { try out.appendSlice(arena, glyph); } index = end; } return ast.node(arena, .{ .text = try out.toOwnedSlice(arena) });}const Glyph = union(enum) { scalar: u21, text: []const u8,};fn mappedGlyph(mode: Mode, glyph: []const u8) ?Glyph { if (glyph.len == 1) { if (asciiGlyph(mode, glyph[0])) |scalar| return .{ .scalar = scalar }; } if (greekGlyph(mode, glyph)) |scalar| return .{ .scalar = scalar }; return null;}fn asciiGlyph(mode: Mode, char: u8) ?u21 { return switch (mode) { .bold => rangedAscii(char, 0x1D400, 0x1D41A, 0x1D7CE), .italic => italicAscii(char), .bold_italic => rangedAscii(char, 0x1D468, 0x1D482, null), .script => scriptAscii(char), .bold_script => rangedAscii(char, 0x1D4D0, 0x1D4EA, null), .fraktur => frakturAscii(char), .bold_fraktur => rangedAscii(char, 0x1D56C, 0x1D586, null), .double_struck => doubleStruckAscii(char), .double_struck_italic => doubleStruckItalicAscii(char), .sans => rangedAscii(char, 0x1D5A0, 0x1D5BA, 0x1D7E2), .sans_bold => rangedAscii(char, 0x1D5D4, 0x1D5EE, 0x1D7EC), .sans_italic => rangedAscii(char, 0x1D608, 0x1D622, null), .sans_bold_italic => rangedAscii(char, 0x1D63C, 0x1D656, null), .monospace => rangedAscii(char, 0x1D670, 0x1D68A, 0x1D7F6), .small_caps => smallCapsAscii(char), };}fn rangedAscii(char: u8, upper: ?u21, lower: ?u21, digit: ?u21) ?u21 { if (char >= 'A' and char <= 'Z') { if (upper) |base| return base + offset(char, 'A'); } if (char >= 'a' and char <= 'z') { if (lower) |base| return base + offset(char, 'a'); } if (char >= '0' and char <= '9') { if (digit) |base| return base + offset(char, '0'); } return null;}fn italicAscii(char: u8) ?u21 { if (char == 'h') return 0x210E; return rangedAscii(char, 0x1D434, 0x1D44E, null);}fn scriptAscii(char: u8) ?u21 { return switch (char) { 'B' => 0x212C, 'E' => 0x2130, 'F' => 0x2131, 'H' => 0x210B, 'I' => 0x2110, 'L' => 0x2112, 'M' => 0x2133, 'R' => 0x211B, 'e' => 0x212F, 'g' => 0x210A, 'o' => 0x2134, else => rangedAscii(char, 0x1D49C, 0x1D4B6, null), };}fn frakturAscii(char: u8) ?u21 { return switch (char) { 'C' => 0x212D, 'H' => 0x210C, 'I' => 0x2111, 'R' => 0x211C, 'Z' => 0x2128, else => rangedAscii(char, 0x1D504, 0x1D51E, null), };}fn doubleStruckAscii(char: u8) ?u21 { return switch (char) { 'C' => 0x2102, 'H' => 0x210D, 'N' => 0x2115, 'P' => 0x2119, 'Q' => 0x211A, 'R' => 0x211D, 'Z' => 0x2124, else => rangedAscii(char, 0x1D538, 0x1D552, 0x1D7D8), };}fn doubleStruckItalicAscii(char: u8) ?u21 { return switch (char) { 'D' => 0x2145, 'd' => 0x2146, 'e' => 0x2147, 'i' => 0x2148, 'j' => 0x2149, else => null, };}fn smallCapsAscii(char: u8) ?u21 { return switch (std.ascii.toUpper(char)) { 'A' => 0x1D00, 'B' => 0x0299, 'C' => 0x1D04, 'D' => 0x1D05, 'E' => 0x1D07, 'F' => 0xA730, 'G' => 0x0262, 'H' => 0x029C, 'I' => 0x026A, 'J' => 0x1D0A, 'K' => 0x1D0B, 'L' => 0x029F, 'M' => 0x1D0D, 'N' => 0x0274, 'O' => 0x1D0F, 'P' => 0x1D18, 'Q' => 0xA7AF, 'R' => 0x0280, 'S' => 0xA731, 'T' => 0x1D1B, 'U' => 0x1D1C, 'V' => 0x1D20, 'W' => 0x1D21, 'X' => 'x', 'Y' => 0x028F, 'Z' => 0x1D22, else => null, };}fn greekGlyph(mode: Mode, glyph: []const u8) ?u21 { const set = switch (mode) { .bold => GreekSet{ .upper = 0x1D6A8, .nabla = 0x1D6C1, .lower = 0x1D6C2, .partial = 0x1D6DB, .symbol = 0x1D6DC }, .italic => GreekSet{ .upper = 0x1D6E2, .nabla = 0x1D6FB, .lower = 0x1D6FC, .partial = 0x1D715, .symbol = 0x1D716 }, .bold_italic => GreekSet{ .upper = 0x1D71C, .nabla = 0x1D735, .lower = 0x1D736, .partial = 0x1D74F, .symbol = 0x1D750 }, else => return null, }; inline for (greekUpper, 0..) |value, index| { if (std.mem.eql(u8, glyph, value)) return set.upper + @as(u21, @intCast(index)); } if (std.mem.eql(u8, glyph, "∇")) return set.nabla; inline for (greekLower, 0..) |value, index| { if (std.mem.eql(u8, glyph, value)) return set.lower + @as(u21, @intCast(index)); } if (std.mem.eql(u8, glyph, "∂")) return set.partial; inline for (greekSymbols, 0..) |value, index| { if (std.mem.eql(u8, glyph, value)) return set.symbol + @as(u21, @intCast(index)); } return null;}const GreekSet = struct { upper: u21, nabla: u21, lower: u21, partial: u21, symbol: u21,};fn appendScalar(arena: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), scalar: u21) std.mem.Allocator.Error!void { var buffer: [4]u8 = undefined; const len = std.unicode.utf8Encode(scalar, &buffer) catch unreachable; try out.appendSlice(arena, buffer[0..len]);}fn offset(char: u8, base: u8) u21 { return @intCast(char - base);}const commands = [_]Command{ .{ .name = "mathbf", .mode = .bold }, .{ .name = "textbf", .mode = .bold }, .{ .name = "mathbold", .mode = .bold }, .{ .name = "mathbfup", .mode = .bold }, .{ .name = "symbf", .mode = .bold }, .{ .name = "symbfup", .mode = .bold }, .{ .name = "mathit", .mode = .italic }, .{ .name = "textit", .mode = .italic }, .{ .name = "emph", .mode = .italic }, .{ .name = "symit", .mode = .italic }, .{ .name = "mathbfit", .mode = .bold_italic }, .{ .name = "boldsymbol", .mode = .bold_italic }, .{ .name = "bm", .mode = .bold_italic }, .{ .name = "symbfit", .mode = .bold_italic }, .{ .name = "mathcal", .mode = .script }, .{ .name = "mathscr", .mode = .script }, .{ .name = "symcal", .mode = .script }, .{ .name = "symscr", .mode = .script }, .{ .name = "mathbcal", .mode = .bold_script }, .{ .name = "mathbfcal", .mode = .bold_script }, .{ .name = "mathbfscr", .mode = .bold_script }, .{ .name = "symbfcal", .mode = .bold_script }, .{ .name = "symbfscr", .mode = .bold_script }, .{ .name = "mathfrak", .mode = .fraktur }, .{ .name = "symfrak", .mode = .fraktur }, .{ .name = "mathbffrak", .mode = .bold_fraktur }, .{ .name = "symbffrak", .mode = .bold_fraktur }, .{ .name = "mathbb", .mode = .double_struck }, .{ .name = "mathds", .mode = .double_struck }, .{ .name = "symbb", .mode = .double_struck }, .{ .name = "mathbbit", .mode = .double_struck_italic }, .{ .name = "symbbit", .mode = .double_struck_italic }, .{ .name = "mathsf", .mode = .sans }, .{ .name = "mathsfup", .mode = .sans }, .{ .name = "textsf", .mode = .sans }, .{ .name = "symsf", .mode = .sans }, .{ .name = "symsfup", .mode = .sans }, .{ .name = "mathbfsf", .mode = .sans_bold }, .{ .name = "mathbfsfup", .mode = .sans_bold }, .{ .name = "mathsfbf", .mode = .sans_bold }, .{ .name = "symbfsf", .mode = .sans_bold }, .{ .name = "symbfsfup", .mode = .sans_bold }, .{ .name = "symsfbf", .mode = .sans_bold }, .{ .name = "mathsfit", .mode = .sans_italic }, .{ .name = "symsfit", .mode = .sans_italic }, .{ .name = "mathbfsfit", .mode = .sans_bold_italic }, .{ .name = "mathsfbfit", .mode = .sans_bold_italic }, .{ .name = "symbfsfit", .mode = .sans_bold_italic }, .{ .name = "symsfbfit", .mode = .sans_bold_italic }, .{ .name = "mathtt", .mode = .monospace }, .{ .name = "texttt", .mode = .monospace }, .{ .name = "symtt", .mode = .monospace }, .{ .name = "textsc", .mode = .small_caps },};const greekUpper = [_][]const u8{ "Α", "Β", "Γ", "Δ", "Ε", "Ζ", "Η", "Θ", "Ι", "Κ", "Λ", "Μ", "Ν", "Ξ", "Ο", "Π", "Ρ", "ϴ", "Σ", "Τ", "Υ", "Φ", "Χ", "Ψ", "Ω",};const greekLower = [_][]const u8{ "α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "ο", "π", "ρ", "ς", "σ", "τ", "υ", "φ", "χ", "ψ", "ω",};const greekSymbols = [_][]const u8{ "ϵ", "ϑ", "ϰ", "ϕ", "ϱ", "ϖ",};test "alphabet maps common mathematical alphabets" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const expr = try ast.text(arena.allocator(), "Az09"); const bold = try apply(arena.allocator(), .bold, expr); try std.testing.expectEqualStrings("𝐀𝐳𝟎𝟗", bold.text); const blackboard = try apply(arena.allocator(), .double_struck, expr); try std.testing.expectEqualStrings("𝔸𝕫𝟘𝟡", blackboard.text);}test "alphabet maps unicode math aliases" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const bold = try apply(arena.allocator(), command("symbfup").?, try ast.text(arena.allocator(), "A")); try std.testing.expectEqualStrings("𝐀", bold.text); const sans = try apply(arena.allocator(), command("symsfup").?, try ast.text(arena.allocator(), "B")); try std.testing.expectEqualStrings("𝖡", sans.text); const bold_sans = try apply(arena.allocator(), command("symbfsfup").?, try ast.text(arena.allocator(), "C")); try std.testing.expectEqualStrings("𝗖", bold_sans.text); const bold_sans_italic = try apply(arena.allocator(), command("symbfsfit").?, try ast.text(arena.allocator(), "D")); try std.testing.expectEqualStrings("𝘿", bold_sans_italic.text); const blackboard_italic = try apply(arena.allocator(), command("symbbit").?, try ast.text(arena.allocator(), "Ddeijx")); try std.testing.expectEqualStrings("ⅅⅆⅇⅈⅉx", blackboard_italic.text);}test "alphabet maps script exceptions and bold Greek" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const script = try apply(arena.allocator(), .script, try ast.text(arena.allocator(), "BFeo")); try std.testing.expectEqualStrings("ℬℱℯℴ", script.text); const greek = try apply(arena.allocator(), .bold_italic, try ast.text(arena.allocator(), "αϕ∂")); try std.testing.expectEqualStrings("𝜶𝝓𝝏", greek.text); const small_caps = try apply(arena.allocator(), .small_caps, try ast.text(arena.allocator(), "E-App")); try std.testing.expectEqualStrings("ᴇ-ᴀᴘᴘ", small_caps.text);}Source: lib/termtex/src/root.zig:9
zig
pub const alphabet = @import("alphabet.zig");Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |