Make HTML data structures cheaper to clone (#6708)

This commit is contained in:
Laurenz 2025-08-06 11:30:45 +02:00 committed by GitHub
parent d0026093d4
commit 15d5591a42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 29 additions and 23 deletions

View File

@ -1,3 +1,4 @@
use ecow::EcoVec;
use typst_library::diag::{SourceResult, warning};
use typst_library::engine::Engine;
use typst_library::foundations::{Content, StyleChain, Target, TargetElem};
@ -15,8 +16,8 @@ pub fn convert_to_nodes<'a>(
engine: &mut Engine,
locator: &mut SplitLocator,
children: impl IntoIterator<Item = Pair<'a>>,
) -> SourceResult<Vec<HtmlNode>> {
let mut output = Vec::new();
) -> SourceResult<EcoVec<HtmlNode>> {
let mut output = EcoVec::new();
for (child, styles) in children {
handle(engine, child, locator, styles, &mut output)?;
}
@ -29,12 +30,12 @@ fn handle(
child: &Content,
locator: &mut SplitLocator,
styles: StyleChain,
output: &mut Vec<HtmlNode>,
output: &mut EcoVec<HtmlNode>,
) -> SourceResult<()> {
if let Some(elem) = child.to_packed::<TagElem>() {
output.push(HtmlNode::Tag(elem.tag.clone()));
} else if let Some(elem) = child.to_packed::<HtmlElem>() {
let mut children = vec![];
let mut children = EcoVec::new();
if let Some(body) = elem.body.get_ref(styles) {
children = html_fragment(engine, body, locator.next(&elem.span()), styles)?;
}

View File

@ -1,6 +1,7 @@
use std::num::NonZeroUsize;
use comemo::{Tracked, TrackedMut};
use ecow::{EcoVec, eco_vec};
use rustc_hash::FxHashSet;
use typst_library::World;
use typst_library::diag::{SourceResult, bail};
@ -141,19 +142,22 @@ fn introspect_html(
/// Wrap the nodes in `<html>` and `<body>` if they are not yet rooted,
/// supplying a suitable `<head>`.
fn root_element(output: Vec<HtmlNode>, info: &DocumentInfo) -> SourceResult<HtmlElement> {
fn root_element(
output: EcoVec<HtmlNode>,
info: &DocumentInfo,
) -> SourceResult<HtmlElement> {
let head = head_element(info);
let body = match classify_output(output)? {
OutputKind::Html(element) => return Ok(element),
OutputKind::Body(body) => body,
OutputKind::Leafs(leafs) => HtmlElement::new(tag::body).with_children(leafs),
};
Ok(HtmlElement::new(tag::html).with_children(vec![head.into(), body.into()]))
Ok(HtmlElement::new(tag::html).with_children(eco_vec![head.into(), body.into()]))
}
/// Generate a `<head>` element.
fn head_element(info: &DocumentInfo) -> HtmlElement {
let mut children = vec![];
let mut children = EcoVec::new();
children.push(HtmlElement::new(tag::meta).with_attr(attr::charset, "utf-8").into());
@ -167,7 +171,7 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
if let Some(title) = &info.title {
children.push(
HtmlElement::new(tag::title)
.with_children(vec![HtmlNode::Text(title.clone(), Span::detached())])
.with_children(eco_vec![HtmlNode::Text(title.clone(), Span::detached())])
.into(),
);
}
@ -203,9 +207,9 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
}
/// Determine which kind of output the user generated.
fn classify_output(mut output: Vec<HtmlNode>) -> SourceResult<OutputKind> {
fn classify_output(mut output: EcoVec<HtmlNode>) -> SourceResult<OutputKind> {
let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count();
for node in &mut output {
for node in output.make_mut() {
let HtmlNode::Element(elem) = node else { continue };
let tag = elem.tag;
let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html));
@ -232,5 +236,5 @@ enum OutputKind {
/// one, but need supply the `<html>` element.
Body(HtmlElement),
/// The user generated leafs which we wrap in a `<body>` and `<html>`.
Leafs(Vec<HtmlNode>),
Leafs(EcoVec<HtmlNode>),
}

View File

@ -57,7 +57,7 @@ pub struct HtmlElement {
/// The element's attributes.
pub attrs: HtmlAttrs,
/// The element's children.
pub children: Vec<HtmlNode>,
pub children: EcoVec<HtmlNode>,
/// The span from which the element originated, if any.
pub span: Span,
}
@ -68,7 +68,7 @@ impl HtmlElement {
Self {
tag,
attrs: HtmlAttrs::default(),
children: vec![],
children: EcoVec::new(),
span: Span::detached(),
}
}
@ -76,7 +76,7 @@ impl HtmlElement {
/// Attach children to the element.
///
/// Note: This overwrites potential previous children.
pub fn with_children(mut self, children: Vec<HtmlNode>) -> Self {
pub fn with_children(mut self, children: EcoVec<HtmlNode>) -> Self {
self.children = children;
self
}
@ -337,7 +337,7 @@ pub struct HtmlFrame {
/// An ID to assign to the SVG itself.
pub id: Option<EcoString>,
/// IDs to assign to destination jump points within the SVG.
pub link_points: Vec<(Point, EcoString)>,
pub link_points: EcoVec<(Point, EcoString)>,
}
impl HtmlFrame {
@ -347,7 +347,7 @@ impl HtmlFrame {
inner,
text_size: styles.resolve(TextElem::size),
id: None,
link_points: vec![],
link_points: EcoVec::new(),
}
}
}

View File

@ -1,4 +1,5 @@
use comemo::{Track, Tracked, TrackedMut};
use ecow::EcoVec;
use typst_library::diag::{At, SourceResult};
use typst_library::engine::{Engine, Route, Sink, Traced};
use typst_library::foundations::{Content, StyleChain};
@ -16,7 +17,7 @@ pub fn html_fragment(
content: &Content,
locator: Locator,
styles: StyleChain,
) -> SourceResult<Vec<HtmlNode>> {
) -> SourceResult<EcoVec<HtmlNode>> {
html_fragment_impl(
engine.routines,
engine.world,
@ -43,7 +44,7 @@ fn html_fragment_impl(
content: &Content,
locator: Tracked<Locator>,
styles: StyleChain,
) -> SourceResult<Vec<HtmlNode>> {
) -> SourceResult<EcoVec<HtmlNode>> {
let link = LocatorLink::new(locator);
let mut locator = Locator::link(&link).split();
let mut engine = Engine {

View File

@ -1,7 +1,7 @@
use std::collections::VecDeque;
use comemo::Track;
use ecow::{EcoString, eco_format};
use ecow::{EcoString, EcoVec, eco_format, eco_vec};
use rustc_hash::{FxHashMap, FxHashSet};
use typst_library::foundations::{Label, NativeElement};
use typst_library::introspection::{Introspector, Location, Tag};
@ -75,11 +75,11 @@ fn traverse(
work: &mut Work,
targets: &FxHashSet<Location>,
identificator: &mut Identificator<'_>,
nodes: &mut Vec<HtmlNode>,
nodes: &mut EcoVec<HtmlNode>,
) {
let mut i = 0;
while i < nodes.len() {
let node = &mut nodes[i];
let node = &mut nodes.make_mut()[i];
match node {
// When visiting a start tag, we check whether the element needs an
// ID and if so, add it to the queue, so that its first child node
@ -115,7 +115,7 @@ fn traverse(
HtmlNode::Text(..) => {
work.drain(|label| {
let mut element =
HtmlElement::new(tag::span).with_children(vec![node.clone()]);
HtmlElement::new(tag::span).with_children(eco_vec![node.clone()]);
let id = identificator.assign(&mut element, label);
*node = HtmlNode::Element(element);
id
@ -148,7 +148,7 @@ fn traverse_frame(
targets: &FxHashSet<Location>,
identificator: &mut Identificator<'_>,
frame: &Frame,
link_points: &mut Vec<(Point, EcoString)>,
link_points: &mut EcoVec<(Point, EcoString)>,
) {
for (_, item) in frame.items() {
match item {