summaryrefslogtreecommitdiff
path: root/crates/typst-library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-12-02 14:19:52 +0100
committerLaurenz <laurmaedje@gmail.com>2024-12-04 10:12:07 +0100
commite0122a5b509d151b7e0197d37a120fd965a055d5 (patch)
tree1045c37c53dc2e08fedc8802cdfec1b244e10dd0 /crates/typst-library
parent885c7d96eea73f478faea9877f0dbc40c00b0d7b (diff)
Add HTML export format
Diffstat (limited to 'crates/typst-library')
-rw-r--r--crates/typst-library/src/engine.rs14
-rw-r--r--crates/typst-library/src/introspection/introspector.rs129
-rw-r--r--crates/typst-library/src/layout/frame.rs3
-rw-r--r--crates/typst-library/src/routines.rs18
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,
}