mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
markdown: simplify rendering logic and state management
This commit is contained in:
@@ -36,7 +36,6 @@ const State = struct {
|
|||||||
|
|
||||||
list_depth: usize = 0,
|
list_depth: usize = 0,
|
||||||
list_stack: [32]ListState = undefined,
|
list_stack: [32]ListState = undefined,
|
||||||
in_pre: bool = false,
|
|
||||||
pre_node: ?*Node = null,
|
pre_node: ?*Node = null,
|
||||||
in_code: bool = false,
|
in_code: bool = false,
|
||||||
in_table: bool = false,
|
in_table: bool = false,
|
||||||
@@ -112,13 +111,12 @@ fn isAllWhitespace(text: []const u8) bool {
|
|||||||
|
|
||||||
fn hasBlockDescendant(node: *Node) bool {
|
fn hasBlockDescendant(node: *Node) bool {
|
||||||
var it = node.childrenIterator();
|
var it = node.childrenIterator();
|
||||||
while (it.next()) |child| {
|
return while (it.next()) |child| {
|
||||||
if (child.is(Element)) |el| {
|
if (child.is(Element)) |el| {
|
||||||
if (isBlock(el.getTag())) return true;
|
if (isBlock(el.getTag())) break true;
|
||||||
if (hasBlockDescendant(child)) return true;
|
if (hasBlockDescendant(child)) break true;
|
||||||
}
|
}
|
||||||
}
|
} else false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensureNewline(state: *State, writer: *std.Io.Writer) !void {
|
fn ensureNewline(state: *State, writer: *std.Io.Writer) !void {
|
||||||
@@ -148,17 +146,15 @@ fn render(node: *Node, state: *State, writer: *std.Io.Writer, page: *Page) error
|
|||||||
.cdata => |cd| {
|
.cdata => |cd| {
|
||||||
if (node.is(Node.CData.Text)) |_| {
|
if (node.is(Node.CData.Text)) |_| {
|
||||||
var text = cd.getData();
|
var text = cd.getData();
|
||||||
if (state.in_pre) {
|
if (state.pre_node) |pre| {
|
||||||
if (state.pre_node) |pre| {
|
if (node.parentNode() == pre and node.nextSibling() == null) {
|
||||||
if (node.parentNode() == pre and node.nextSibling() == null) {
|
text = std.mem.trimRight(u8, text, " \t\r\n");
|
||||||
text = std.mem.trimRight(u8, text, " \t\r\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try renderText(text, state, writer);
|
try renderText(text, state, writer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {}, // Ignore other node types
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,19 +168,15 @@ fn renderChildren(parent: *Node, state: *State, writer: *std.Io.Writer, page: *P
|
|||||||
fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Page) !void {
|
fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Page) !void {
|
||||||
const tag = el.getTag();
|
const tag = el.getTag();
|
||||||
|
|
||||||
// Skip hidden/metadata elements
|
|
||||||
if (!isVisibleElement(el)) return;
|
if (!isVisibleElement(el)) return;
|
||||||
|
|
||||||
// --- Opening Tag Logic ---
|
// --- Opening Tag Logic ---
|
||||||
|
|
||||||
// Ensure block elements start on a new line (double newline for paragraphs etc)
|
// Ensure block elements start on a new line (double newline for paragraphs etc)
|
||||||
if (isBlock(tag)) {
|
if (isBlock(tag) and !state.in_table) {
|
||||||
if (!state.in_table) {
|
try ensureNewline(state, writer);
|
||||||
try ensureNewline(state, writer);
|
if (shouldAddSpacing(tag)) {
|
||||||
if (shouldAddSpacing(tag)) {
|
try writer.writeByte('\n');
|
||||||
// Add an extra newline for spacing between blocks
|
|
||||||
try writer.writeByte('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (tag == .li or tag == .tr) {
|
} else if (tag == .li or tag == .tr) {
|
||||||
try ensureNewline(state, writer);
|
try ensureNewline(state, writer);
|
||||||
@@ -214,14 +206,10 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
const indent = if (state.list_depth > 0) state.list_depth - 1 else 0;
|
const indent = if (state.list_depth > 0) state.list_depth - 1 else 0;
|
||||||
for (0..indent) |_| try writer.writeAll(" ");
|
for (0..indent) |_| try writer.writeAll(" ");
|
||||||
|
|
||||||
if (state.list_depth > 0) {
|
if (state.list_depth > 0 and state.list_stack[state.list_depth - 1].type == .ordered) {
|
||||||
const current_list = &state.list_stack[state.list_depth - 1];
|
const current_list = &state.list_stack[state.list_depth - 1];
|
||||||
if (current_list.type == .ordered) {
|
try writer.print("{d}. ", .{current_list.index});
|
||||||
try writer.print("{d}. ", .{current_list.index});
|
current_list.index += 1;
|
||||||
current_list.index += 1;
|
|
||||||
} else {
|
|
||||||
try writer.writeAll("- ");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try writer.writeAll("- ");
|
try writer.writeAll("- ");
|
||||||
}
|
}
|
||||||
@@ -247,12 +235,11 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
},
|
},
|
||||||
.pre => {
|
.pre => {
|
||||||
try writer.writeAll("```\n");
|
try writer.writeAll("```\n");
|
||||||
state.in_pre = true;
|
|
||||||
state.pre_node = el.asNode();
|
state.pre_node = el.asNode();
|
||||||
state.last_char_was_newline = true;
|
state.last_char_was_newline = true;
|
||||||
},
|
},
|
||||||
.code => {
|
.code => {
|
||||||
if (!state.in_pre) {
|
if (state.pre_node == null) {
|
||||||
try writer.writeByte('`');
|
try writer.writeByte('`');
|
||||||
state.in_code = true;
|
state.in_code = true;
|
||||||
state.last_char_was_newline = false;
|
state.last_char_was_newline = false;
|
||||||
@@ -273,7 +260,7 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
.hr => {
|
.hr => {
|
||||||
try writer.writeAll("---\n");
|
try writer.writeAll("---\n");
|
||||||
state.last_char_was_newline = true;
|
state.last_char_was_newline = true;
|
||||||
return; // Void element
|
return;
|
||||||
},
|
},
|
||||||
.br => {
|
.br => {
|
||||||
if (state.in_table) {
|
if (state.in_table) {
|
||||||
@@ -282,7 +269,7 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
try writer.writeByte('\n');
|
try writer.writeByte('\n');
|
||||||
state.last_char_was_newline = true;
|
state.last_char_was_newline = true;
|
||||||
}
|
}
|
||||||
return; // Void element
|
return;
|
||||||
},
|
},
|
||||||
.img => {
|
.img => {
|
||||||
try writer.writeAll("![");
|
try writer.writeAll("![");
|
||||||
@@ -295,7 +282,7 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
}
|
}
|
||||||
try writer.writeAll(")");
|
try writer.writeAll(")");
|
||||||
state.last_char_was_newline = false;
|
state.last_char_was_newline = false;
|
||||||
return; // Treat as void
|
return;
|
||||||
},
|
},
|
||||||
.anchor => {
|
.anchor => {
|
||||||
const has_block = hasBlockDescendant(el.asNode());
|
const has_block = hasBlockDescendant(el.asNode());
|
||||||
@@ -335,15 +322,11 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
.input => {
|
.input => {
|
||||||
if (el.getAttributeSafe(comptime .wrap("type"))) |type_attr| {
|
const type_attr = el.getAttributeSafe(comptime .wrap("type")) orelse return;
|
||||||
if (std.ascii.eqlIgnoreCase(type_attr, "checkbox")) {
|
if (std.ascii.eqlIgnoreCase(type_attr, "checkbox")) {
|
||||||
if (el.getAttributeSafe(comptime .wrap("checked"))) |_| {
|
const checked = el.getAttributeSafe(comptime .wrap("checked")) != null;
|
||||||
try writer.writeAll("[x] ");
|
try writer.writeAll(if (checked) "[x] " else "[ ] ");
|
||||||
} else {
|
state.last_char_was_newline = false;
|
||||||
try writer.writeAll("[ ] ");
|
|
||||||
}
|
|
||||||
state.last_char_was_newline = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
@@ -362,12 +345,11 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
try writer.writeByte('\n');
|
try writer.writeByte('\n');
|
||||||
}
|
}
|
||||||
try writer.writeAll("```\n");
|
try writer.writeAll("```\n");
|
||||||
state.in_pre = false;
|
|
||||||
state.pre_node = null;
|
state.pre_node = null;
|
||||||
state.last_char_was_newline = true;
|
state.last_char_was_newline = true;
|
||||||
},
|
},
|
||||||
.code => {
|
.code => {
|
||||||
if (!state.in_pre) {
|
if (state.pre_node == null) {
|
||||||
try writer.writeByte('`');
|
try writer.writeByte('`');
|
||||||
state.in_code = false;
|
state.in_code = false;
|
||||||
state.last_char_was_newline = false;
|
state.last_char_was_newline = false;
|
||||||
@@ -396,8 +378,7 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
try writer.writeByte('\n');
|
try writer.writeByte('\n');
|
||||||
if (state.table_row_index == 0) {
|
if (state.table_row_index == 0) {
|
||||||
try writer.writeByte('|');
|
try writer.writeByte('|');
|
||||||
var i: usize = 0;
|
for (0..state.table_col_count) |_| {
|
||||||
while (i < state.table_col_count) : (i += 1) {
|
|
||||||
try writer.writeAll("---|");
|
try writer.writeAll("---|");
|
||||||
}
|
}
|
||||||
try writer.writeByte('\n');
|
try writer.writeByte('\n');
|
||||||
@@ -414,32 +395,22 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Post-block newlines
|
// Post-block newlines
|
||||||
if (isBlock(tag)) {
|
if (isBlock(tag) and !state.in_table) {
|
||||||
if (!state.in_table) {
|
try ensureNewline(state, writer);
|
||||||
try ensureNewline(state, writer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderText(text: []const u8, state: *State, writer: *std.Io.Writer) !void {
|
fn renderText(text: []const u8, state: *State, writer: *std.Io.Writer) !void {
|
||||||
if (text.len == 0) return;
|
if (text.len == 0) return;
|
||||||
|
|
||||||
if (state.in_pre) {
|
if (state.pre_node) |_| {
|
||||||
try writer.writeAll(text);
|
try writer.writeAll(text);
|
||||||
if (text.len > 0 and text[text.len - 1] == '\n') {
|
state.last_char_was_newline = text[text.len - 1] == '\n';
|
||||||
state.last_char_was_newline = true;
|
|
||||||
} else {
|
|
||||||
state.last_char_was_newline = false;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for pure whitespace
|
// Check for pure whitespace
|
||||||
const is_all_whitespace = for (text) |c| {
|
if (isAllWhitespace(text)) {
|
||||||
if (!std.ascii.isWhitespace(c)) break false;
|
|
||||||
} else true;
|
|
||||||
|
|
||||||
if (is_all_whitespace) {
|
|
||||||
if (!state.last_char_was_newline) {
|
if (!state.last_char_was_newline) {
|
||||||
try writer.writeByte(' ');
|
try writer.writeByte(' ');
|
||||||
}
|
}
|
||||||
@@ -450,13 +421,7 @@ fn renderText(text: []const u8, state: *State, writer: *std.Io.Writer) !void {
|
|||||||
var it = std.mem.tokenizeAny(u8, text, " \t\n\r");
|
var it = std.mem.tokenizeAny(u8, text, " \t\n\r");
|
||||||
var first = true;
|
var first = true;
|
||||||
while (it.next()) |word| {
|
while (it.next()) |word| {
|
||||||
if (first) {
|
if (!first or (!state.last_char_was_newline and std.ascii.isWhitespace(text[0]))) {
|
||||||
if (!state.last_char_was_newline) {
|
|
||||||
if (text.len > 0 and std.ascii.isWhitespace(text[0])) {
|
|
||||||
try writer.writeByte(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try writer.writeByte(' ');
|
try writer.writeByte(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,10 +431,8 @@ fn renderText(text: []const u8, state: *State, writer: *std.Io.Writer) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle trailing whitespace from the original text
|
// Handle trailing whitespace from the original text
|
||||||
if (!first and !state.last_char_was_newline) {
|
if (!first and !state.last_char_was_newline and std.ascii.isWhitespace(text[text.len - 1])) {
|
||||||
if (text.len > 0 and std.ascii.isWhitespace(text[text.len - 1])) {
|
try writer.writeByte(' ');
|
||||||
try writer.writeByte(' ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user