aboutsummaryrefslogtreecommitdiff
path: root/src/eval/math.zig
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2026-04-29 19:55:57 +0200
committerAnhgelus Morhtuuzh <william@herges.fr>2026-04-29 19:55:57 +0200
commitafd538d6af0baf8a1861ff2cdef881edbfb57000 (patch)
treedb41d566c318163cb41916defd7cd274b92a9a43 /src/eval/math.zig
parent0535fa152ae990a28d0b7b9a59e96911074118b8 (diff)
feat(): support math content
Diffstat (limited to 'src/eval/math.zig')
-rw-r--r--src/eval/math.zig108
1 files changed, 108 insertions, 0 deletions
diff --git a/src/eval/math.zig b/src/eval/math.zig
new file mode 100644
index 0000000..20d3fec
--- /dev/null
+++ b/src/eval/math.zig
@@ -0,0 +1,108 @@
+const std = @import("std");
+const typst = @cImport(@cInclude("typdown_typst.h"));
+const Allocator = std.mem.Allocator;
+const HTML = Element.HTML;
+const Element = @import("Element.zig");
+const Node = Element.Node;
+
+const content_template = @embedFile("template_math_content.typ");
+const block_template = @embedFile("template_math_block.typ");
+
+pub const Error = error{InvalidTypstTemplate} || Allocator.Error;
+
+fn typstInterop(alloc: Allocator, comptime f: fn ([*c]const u8) callconv(.c) [*c]const u8, content: []const u8) ![]const u8 {
+ const source = try alloc.dupeZ(u8, content);
+ defer alloc.free(source);
+ const raw_res = f(source);
+ const res = try alloc.dupe(u8, std.mem.span(raw_res));
+ defer typst.typst_freeString(raw_res);
+ return res;
+}
+
+fn generateSVG(alloc: Allocator, content: []const u8) ![]const u8 {
+ return try typstInterop(alloc, typst.typst_generateSVG, content);
+}
+
+fn escape(alloc: Allocator, content: []const u8) ![]const u8 {
+ return try typstInterop(alloc, typst.typst_escapeMath, content);
+}
+
+fn generateFile(alloc: Allocator, template: []const u8, content: []const u8) Error![]const u8 {
+ var iter = std.mem.splitSequence(u8, template, "!!");
+ const beg = iter.next() orelse return Error.InvalidTypstTemplate;
+ const end = iter.next() orelse return Error.InvalidTypstTemplate;
+ if (iter.next() != null) return Error.InvalidTypstTemplate;
+
+ var acc = try std.ArrayList(u8).initCapacity(alloc, beg.len + end.len + content.len);
+ try acc.appendSlice(alloc, beg);
+ try acc.appendSlice(alloc, content);
+ try acc.appendSlice(alloc, end);
+ return try acc.toOwnedSlice(alloc);
+}
+
+fn Math(comptime template: []const u8) type {
+ return struct {
+ content: ?[]const u8 = null,
+ node: Node,
+
+ const Self = @This();
+
+ pub fn init(alloc: Allocator) !*Self {
+ const v = try alloc.create(Self);
+ v.node = .{ .ptr = v, .vtable = .{ .element = fromNode } };
+ return v;
+ }
+
+ pub fn element(self: *Self) Element {
+ return .{ .ptr = self, .vtable = .{ .html = html, .node = getNode } };
+ }
+
+ fn getNode(context: *anyopaque) *Node {
+ const self: *Self = @ptrCast(@alignCast(context));
+ return &self.node;
+ }
+
+ fn fromNode(context: *anyopaque) Element {
+ const self: *Self = @ptrCast(@alignCast(context));
+ return self.element();
+ }
+
+ fn html(context: *anyopaque, alloc: Allocator) HTML.Error!HTML {
+ const self: *Self = @ptrCast(@alignCast(context));
+ const content = self.content orelse return (try HTML.Literal.init(alloc, "")).element();
+
+ var arena = std.heap.ArenaAllocator.init(alloc);
+ defer arena.deinit();
+ const escaped = try escape(arena.allocator(), content);
+ const file = generateFile(arena.allocator(), template, escaped) catch |err| switch (err) {
+ Error.InvalidTypstTemplate => @panic("invalid template"),
+ Error.OutOfMemory => return Error.OutOfMemory,
+ };
+ const svg = try generateSVG(arena.allocator(), file);
+ return (try HTML.Literal.initNoEscape(alloc, svg)).element();
+ }
+ };
+}
+
+pub const Content = Math(content_template);
+pub const Block = Math(block_template);
+
+fn doTest(alloc: Allocator, v: []const u8, r: []const u8) !void {
+ const escaped = try escape(alloc, v);
+ defer alloc.free(escaped);
+ std.testing.expect(std.mem.eql(u8, escaped, r)) catch |err| {
+ std.debug.print("{s}\n", .{escaped});
+ return err;
+ };
+}
+
+test "escape math" {
+ const alloc = std.testing.allocator;
+
+ try doTest(alloc, "hello", "hello");
+ try doTest(alloc, "hello $ world", "hello \\$ world");
+ try doTest(alloc,
+ \\hello
+ \\world
+ , "hello\\ world");
+}