diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-12-02 14:19:52 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2024-12-04 10:12:07 +0100 |
| commit | e0122a5b509d151b7e0197d37a120fd965a055d5 (patch) | |
| tree | 1045c37c53dc2e08fedc8802cdfec1b244e10dd0 /crates/typst-library | |
| parent | 885c7d96eea73f478faea9877f0dbc40c00b0d7b (diff) | |
Add HTML export format
Diffstat (limited to 'crates/typst-library')
| -rw-r--r-- | crates/typst-library/src/engine.rs | 14 | ||||
| -rw-r--r-- | crates/typst-library/src/introspection/introspector.rs | 129 | ||||
| -rw-r--r-- | crates/typst-library/src/layout/frame.rs | 3 | ||||
| -rw-r--r-- | crates/typst-library/src/routines.rs | 18 |
4 files changed, 115 insertions, 49 deletions
diff --git a/crates/typst-library/src/engine.rs b/crates/typst-library/src/engine.rs index e532172e..cd25ec48 100644 --- a/crates/typst-library/src/engine.rs +++ b/crates/typst-library/src/engine.rs @@ -301,6 +301,9 @@ impl Route<'_> { /// The maximum layout nesting depth. const MAX_LAYOUT_DEPTH: usize = 72; + /// The maximum HTML nesting depth. + const MAX_HTML_DEPTH: usize = 72; + /// The maximum function call nesting depth. const MAX_CALL_DEPTH: usize = 80; @@ -326,6 +329,17 @@ impl Route<'_> { Ok(()) } + /// Ensures that we are within the maximum HTML depth. + pub fn check_html_depth(&self) -> HintedStrResult<()> { + if !self.within(Route::MAX_HTML_DEPTH) { + bail!( + "maximum HTML depth exceeded"; + hint: "try to reduce the amount of nesting of your HTML", + ); + } + Ok(()) + } + /// Ensures that we are within the maximum function call depth. pub fn check_call_depth(&self) -> StrResult<()> { if !self.within(Route::MAX_CALL_DEPTH) { diff --git a/crates/typst-library/src/introspection/introspector.rs b/crates/typst-library/src/introspection/introspector.rs index 388d1f00..8cbaea89 100644 --- a/crates/typst-library/src/introspection/introspector.rs +++ b/crates/typst-library/src/introspection/introspector.rs @@ -10,6 +10,7 @@ use typst_utils::NonZeroExt; use crate::diag::{bail, StrResult}; use crate::foundations::{Content, Label, Repr, Selector}; +use crate::html::{HtmlElement, HtmlNode}; use crate::introspection::{Location, Tag}; use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform}; use crate::model::Numbering; @@ -47,9 +48,15 @@ type Pair = (Content, Position); impl Introspector { /// Creates an introspector for a page list. - #[typst_macros::time(name = "introspect")] - pub fn new(pages: &[Page]) -> Self { - IntrospectorBuilder::new().build(pages) + #[typst_macros::time(name = "introspect pages")] + pub fn paged(pages: &[Page]) -> Self { + IntrospectorBuilder::new().build_paged(pages) + } + + /// Creates an introspector for HTML. + #[typst_macros::time(name = "introspect html")] + pub fn html(root: &HtmlElement) -> Self { + IntrospectorBuilder::new().build_html(root) } /// Iterates over all locatable elements. @@ -346,6 +353,7 @@ impl Clone for QueryCache { /// Builds the introspector. #[derive(Default)] struct IntrospectorBuilder { + pages: usize, page_numberings: Vec<Option<Numbering>>, page_supplements: Vec<Content>, seen: HashSet<Location>, @@ -361,46 +369,37 @@ impl IntrospectorBuilder { Self::default() } - /// Build the introspector. - fn build(mut self, pages: &[Page]) -> Introspector { + /// Build an introspector for a page list. + fn build_paged(mut self, pages: &[Page]) -> Introspector { + self.pages = pages.len(); self.page_numberings.reserve(pages.len()); self.page_supplements.reserve(pages.len()); // Discover all elements. - let mut root = Vec::new(); + let mut elems = Vec::new(); for (i, page) in pages.iter().enumerate() { self.page_numberings.push(page.numbering.clone()); self.page_supplements.push(page.supplement.clone()); - self.discover( - &mut root, + self.discover_in_frame( + &mut elems, &page.frame, NonZeroUsize::new(1 + i).unwrap(), Transform::identity(), ); } - self.locations.reserve(self.seen.len()); - - // Save all pairs and their descendants in the correct order. - let mut elems = Vec::with_capacity(self.seen.len()); - for pair in root { - self.visit(&mut elems, pair); - } + self.finalize(elems) + } - Introspector { - pages: pages.len(), - page_numberings: self.page_numberings, - page_supplements: self.page_supplements, - elems, - keys: self.keys, - locations: self.locations, - labels: self.labels, - queries: QueryCache::default(), - } + /// Build an introspector for an HTML document. + fn build_html(mut self, root: &HtmlElement) -> Introspector { + let mut elems = Vec::new(); + self.discover_in_html(&mut elems, root); + self.finalize(elems) } /// Processes the tags in the frame. - fn discover( + fn discover_in_frame( &mut self, sink: &mut Vec<Pair>, frame: &Frame, @@ -416,27 +415,83 @@ impl IntrospectorBuilder { if let Some(parent) = group.parent { let mut nested = vec![]; - self.discover(&mut nested, &group.frame, page, ts); + self.discover_in_frame(&mut nested, &group.frame, page, ts); self.insertions.insert(parent, nested); } else { - self.discover(sink, &group.frame, page, ts); + self.discover_in_frame(sink, &group.frame, page, ts); } } - FrameItem::Tag(Tag::Start(elem)) => { - let loc = elem.location().unwrap(); - if self.seen.insert(loc) { - let point = pos.transform(ts); - sink.push((elem.clone(), Position { page, point })); - } - } - FrameItem::Tag(Tag::End(loc, key)) => { - self.keys.insert(*key, *loc); + FrameItem::Tag(tag) => { + self.discover_in_tag( + sink, + tag, + Position { page, point: pos.transform(ts) }, + ); } _ => {} } } } + /// Processes the tags in the HTML element. + fn discover_in_html(&mut self, sink: &mut Vec<Pair>, elem: &HtmlElement) { + for child in &elem.children { + match child { + HtmlNode::Tag(tag) => self.discover_in_tag( + sink, + tag, + Position { page: NonZeroUsize::ONE, point: Point::zero() }, + ), + HtmlNode::Text(_, _) => {} + HtmlNode::Element(elem) => self.discover_in_html(sink, elem), + HtmlNode::Frame(frame) => self.discover_in_frame( + sink, + frame, + NonZeroUsize::ONE, + Transform::identity(), + ), + } + } + } + + /// Handle a tag. + fn discover_in_tag(&mut self, sink: &mut Vec<Pair>, tag: &Tag, position: Position) { + match tag { + Tag::Start(elem) => { + let loc = elem.location().unwrap(); + if self.seen.insert(loc) { + sink.push((elem.clone(), position)); + } + } + Tag::End(loc, key) => { + self.keys.insert(*key, *loc); + } + } + } + + /// Build a complete introspector with all acceleration structures from a + /// list of top-level pairs. + fn finalize(mut self, root: Vec<Pair>) -> Introspector { + self.locations.reserve(self.seen.len()); + + // Save all pairs and their descendants in the correct order. + let mut elems = Vec::with_capacity(self.seen.len()); + for pair in root { + self.visit(&mut elems, pair); + } + + Introspector { + pages: self.pages, + page_numberings: self.page_numberings, + page_supplements: self.page_supplements, + elems, + keys: self.keys, + locations: self.locations, + labels: self.labels, + queries: QueryCache::default(), + } + } + /// Saves a pair and all its descendants into `elems` and populates the /// acceleration structures. fn visit(&mut self, elems: &mut Vec<Pair>, pair: Pair) { diff --git a/crates/typst-library/src/layout/frame.rs b/crates/typst-library/src/layout/frame.rs index 204584fc..fc8634e8 100644 --- a/crates/typst-library/src/layout/frame.rs +++ b/crates/typst-library/src/layout/frame.rs @@ -520,8 +520,7 @@ pub enum FrameItem { Image(Image, Size, Span), /// An internal or external link to a destination. Link(Destination, Size), - /// An introspectable element that produced something within this frame - /// alongside its key. + /// An introspectable element that produced something within this frame. Tag(Tag), } diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs index 000b3bba..aa92012b 100644 --- a/crates/typst-library/src/routines.rs +++ b/crates/typst-library/src/routines.rs @@ -86,13 +86,6 @@ routines! { styles: StyleChain<'a>, ) -> SourceResult<Vec<Pair<'a>>> - /// Layout content into a document. - fn layout_document( - engine: &mut Engine, - content: &Content, - styles: StyleChain, - ) -> SourceResult<PagedDocument> - /// Lays out content into multiple regions. fn layout_fragment( engine: &mut Engine, @@ -343,11 +336,16 @@ pub enum EvalMode { /// Defines what kind of realization we are performing. pub enum RealizationKind<'a> { - /// This the root realization for the document. Requires a mutable reference + /// This the root realization for layout. Requires a mutable reference + /// to document metadata that will be filled from `set document` rules. + LayoutDocument(&'a mut DocumentInfo), + /// A nested realization in a container (e.g. a `block`). + LayoutFragment, + /// This the root realization for HTML. Requires a mutable reference /// to document metadata that will be filled from `set document` rules. - Root(&'a mut DocumentInfo), + HtmlDocument(&'a mut DocumentInfo), /// A nested realization in a container (e.g. a `block`). - Container, + HtmlFragment, /// A realization within math. Math, } |
