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

View File

@ -1,6 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use comemo::{Tracked, TrackedMut}; use comemo::{Tracked, TrackedMut};
use ecow::{EcoVec, eco_vec};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use typst_library::World; use typst_library::World;
use typst_library::diag::{SourceResult, bail}; 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, /// Wrap the nodes in `<html>` and `<body>` if they are not yet rooted,
/// supplying a suitable `<head>`. /// 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 head = head_element(info);
let body = match classify_output(output)? { let body = match classify_output(output)? {
OutputKind::Html(element) => return Ok(element), OutputKind::Html(element) => return Ok(element),
OutputKind::Body(body) => body, OutputKind::Body(body) => body,
OutputKind::Leafs(leafs) => HtmlElement::new(tag::body).with_children(leafs), 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. /// Generate a `<head>` element.
fn head_element(info: &DocumentInfo) -> HtmlElement { 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()); 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 { if let Some(title) = &info.title {
children.push( children.push(
HtmlElement::new(tag::title) HtmlElement::new(tag::title)
.with_children(vec![HtmlNode::Text(title.clone(), Span::detached())]) .with_children(eco_vec![HtmlNode::Text(title.clone(), Span::detached())])
.into(), .into(),
); );
} }
@ -203,9 +207,9 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
} }
/// Determine which kind of output the user generated. /// 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(); 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 HtmlNode::Element(elem) = node else { continue };
let tag = elem.tag; let tag = elem.tag;
let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html)); let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html));
@ -232,5 +236,5 @@ enum OutputKind {
/// one, but need supply the `<html>` element. /// one, but need supply the `<html>` element.
Body(HtmlElement), Body(HtmlElement),
/// The user generated leafs which we wrap in a `<body>` and `<html>`. /// 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. /// The element's attributes.
pub attrs: HtmlAttrs, pub attrs: HtmlAttrs,
/// The element's children. /// The element's children.
pub children: Vec<HtmlNode>, pub children: EcoVec<HtmlNode>,
/// The span from which the element originated, if any. /// The span from which the element originated, if any.
pub span: Span, pub span: Span,
} }
@ -68,7 +68,7 @@ impl HtmlElement {
Self { Self {
tag, tag,
attrs: HtmlAttrs::default(), attrs: HtmlAttrs::default(),
children: vec![], children: EcoVec::new(),
span: Span::detached(), span: Span::detached(),
} }
} }
@ -76,7 +76,7 @@ impl HtmlElement {
/// Attach children to the element. /// Attach children to the element.
/// ///
/// Note: This overwrites potential previous children. /// 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.children = children;
self self
} }
@ -337,7 +337,7 @@ pub struct HtmlFrame {
/// An ID to assign to the SVG itself. /// An ID to assign to the SVG itself.
pub id: Option<EcoString>, pub id: Option<EcoString>,
/// IDs to assign to destination jump points within the SVG. /// IDs to assign to destination jump points within the SVG.
pub link_points: Vec<(Point, EcoString)>, pub link_points: EcoVec<(Point, EcoString)>,
} }
impl HtmlFrame { impl HtmlFrame {
@ -347,7 +347,7 @@ impl HtmlFrame {
inner, inner,
text_size: styles.resolve(TextElem::size), text_size: styles.resolve(TextElem::size),
id: None, id: None,
link_points: vec![], link_points: EcoVec::new(),
} }
} }
} }

View File

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

View File

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