pub const HtmlRenderer = struct { out: OutputBuffer, allocator: Allocator, src_text: []const u8, image_nesting_level: u32 = 0, saved_img_title: []const u8 = "", tag_filter: bool = false, tag_filter_raw_depth: u32 = 0, autolink_headings: bool = false, heading_buf: std.ArrayListUnmanaged(u8) = .{}, heading_tracker: helpers.HeadingIdTracker = helpers.HeadingIdTracker.init(false), pub const OutputBuffer = struct { list: std.ArrayListUnmanaged(u8), allocator: Allocator, oom: bool, fn write(self: *OutputBuffer, data: []const u8) void { if (self.oom) return; self.list.appendSlice(self.allocator, data) catch { self.oom = true; }; } fn writeByte(self: *OutputBuffer, b: u8) void { if (self.oom) return; self.list.append(self.allocator, b) catch { self.oom = true; }; } }; pub fn init(allocator: Allocator, src_text: []const u8, render_opts: RenderOptions) HtmlRenderer { return .{ .out = .{ .list = .{}, .allocator = allocator, .oom = false }, .allocator = allocator, .src_text = src_text, .tag_filter = render_opts.tag_filter, .autolink_headings = render_opts.autolink_headings, .heading_tracker = helpers.HeadingIdTracker.init(render_opts.heading_ids), }; } pub fn deinit(self: *HtmlRenderer) void { self.out.list.deinit(self.allocator); self.heading_buf.deinit(self.allocator); self.heading_tracker.deinit(self.allocator); } pub fn toOwnedSlice(self: *HtmlRenderer) error{OutOfMemory}![]u8 { if (self.out.oom) return error.OutOfMemory; return self.out.list.toOwnedSlice(self.allocator); } pub fn renderer(self: *HtmlRenderer) Renderer { return .{ .ptr = self, .vtable = &vtable }; } pub const vtable: Renderer.VTable = .{ .enterBlock = enterBlockImpl, .leaveBlock = leaveBlockImpl, .enterSpan = enterSpanImpl, .leaveSpan = leaveSpanImpl, .text = textImpl, }; // ======================================== // VTable implementation functions // ======================================== fn enterBlockImpl(ptr: *anyopaque, block_type: BlockType, data: u32, flags: u32) bun.JSError!void { const self: *HtmlRenderer = @ptrCast(@alignCast(ptr)); self.enterBlock(block_type, data, flags); } fn leaveBlockImpl(ptr: *anyopaque, block_type: BlockType, data: u32) bun.JSError!void { const self: *HtmlRenderer = @ptrCast(@alignCast(ptr)); self.leaveBlock(block_type, data); } fn enterSpanImpl(ptr: *anyopaque, span_type: SpanType, detail: SpanDetail) bun.JSError!void { const self: *HtmlRenderer = @ptrCast(@alignCast(ptr)); self.enterSpan(span_type, detail); } fn leaveSpanImpl(ptr: *anyopaque, span_type: SpanType) bun.JSError!void { const self: *HtmlRenderer = @ptrCast(@alignCast(ptr)); self.leaveSpan(span_type); } fn textImpl(ptr: *anyopaque, text_type: TextType, content: []const u8) bun.JSError!void { const self: *HtmlRenderer = @ptrCast(@alignCast(ptr)); self.text(text_type, content); } // ======================================== // Block rendering // ======================================== pub fn enterBlock(self: *HtmlRenderer, block_type: BlockType, data: u32, flags: u32) void { switch (block_type) { .doc => {}, .quote => { self.ensureNewline(); self.write("
\n"); }, .ul => { self.ensureNewline(); self.write("