typst_html/
lib.rs

1//! Typst's HTML exporter.
2
3mod attr;
4mod charsets;
5mod convert;
6mod css;
7mod document;
8mod dom;
9mod encode;
10mod fragment;
11mod link;
12mod rules;
13mod tag;
14mod typed;
15
16pub use self::document::html_document;
17pub use self::dom::*;
18pub use self::encode::html;
19pub use self::rules::{html_span_filled, register};
20
21use ecow::EcoString;
22use typst_library::Category;
23use typst_library::foundations::{Content, Module, Scope};
24use typst_library::introspection::Location;
25use typst_macros::elem;
26
27/// Creates the module with all HTML definitions.
28pub fn module() -> Module {
29    let mut html = Scope::deduplicating();
30    html.start_category(Category::Html);
31    html.define_elem::<HtmlElem>();
32    html.define_elem::<FrameElem>();
33    crate::typed::define(&mut html);
34    Module::new("html", html)
35}
36
37/// An HTML element that can contain Typst content.
38///
39/// Typst's HTML export automatically generates the appropriate tags for most
40/// elements. However, sometimes, it is desirable to retain more control. For
41/// example, when using Typst to generate your blog, you could use this function
42/// to wrap each article in an `<article>` tag.
43///
44/// Typst is aware of what is valid HTML. A tag and its attributes must form
45/// syntactically valid HTML. Some tags, like `meta` do not accept content.
46/// Hence, you must not provide a body for them. We may add more checks in the
47/// future, so be sure that you are generating valid HTML when using this
48/// function.
49///
50/// Normally, Typst will generate `html`, `head`, and `body` tags for you. If
51/// you instead create them with this function, Typst will omit its own tags.
52///
53/// ```typ
54/// #html.elem("div", attrs: (style: "background: aqua"))[
55///   A div with _Typst content_ inside!
56/// ]
57/// ```
58#[elem(name = "elem")]
59pub struct HtmlElem {
60    /// The element's tag.
61    #[required]
62    pub tag: HtmlTag,
63
64    /// The element's HTML attributes.
65    pub attrs: HtmlAttrs,
66
67    /// The contents of the HTML element.
68    ///
69    /// The body can be arbitrary Typst content.
70    #[positional]
71    pub body: Option<Content>,
72
73    /// The element's logical parent, if any.
74    #[internal]
75    #[synthesized]
76    pub parent: Location,
77
78    /// A role that should be applied to the top-level styled HTML element, but
79    /// not its descendants. If we ever get set rules that apply to a specific
80    /// element instead of a subtree, they could supplant this. If we need the
81    /// same mechanism for things like `class`, this could potentially also be
82    /// extended to arbitrary attributes. It's minimal for now.
83    ///
84    /// This is ignored for `<p>` elements as it otherwise tends to
85    /// unintentionally attach to paragraphs resulting from grouping of a single
86    /// element instead of attaching to that element. This is a bit of a hack,
87    /// but good enough for now as the `role` property is purely internal and
88    /// we control what it is used for.
89    #[internal]
90    #[ghost]
91    pub role: Option<EcoString>,
92}
93
94impl HtmlElem {
95    /// Add an attribute to the element.
96    pub fn with_attr(mut self, attr: HtmlAttr, value: impl Into<EcoString>) -> Self {
97        self.attrs
98            .as_option_mut()
99            .get_or_insert_with(Default::default)
100            .push(attr, value);
101        self
102    }
103
104    /// Adds the attribute to the element if value is not `None`.
105    pub fn with_optional_attr(
106        self,
107        attr: HtmlAttr,
108        value: Option<impl Into<EcoString>>,
109    ) -> Self {
110        if let Some(value) = value { self.with_attr(attr, value) } else { self }
111    }
112
113    /// Adds CSS styles to an element.
114    fn with_styles(self, properties: css::Properties) -> Self {
115        if let Some(value) = properties.into_inline_styles() {
116            self.with_attr(attr::style, value)
117        } else {
118            self
119        }
120    }
121
122    /// Checks whether the given element is an inline-level HTML element.
123    fn is_inline(elem: &Content) -> bool {
124        elem.to_packed::<HtmlElem>()
125            .is_some_and(|elem| tag::is_inline_by_default(elem.tag))
126    }
127}
128
129/// An element that lays out its content as an inline SVG.
130///
131/// Sometimes, converting Typst content to HTML is not desirable. This can be
132/// the case for plots and other content that relies on positioning and styling
133/// to convey its message.
134///
135/// This function allows you to use the Typst layout engine that would also be
136/// used for PDF, SVG, and PNG export to render a part of your document exactly
137/// how it would appear when exported in one of these formats. It embeds the
138/// content as an inline SVG.
139#[elem]
140pub struct FrameElem {
141    /// The content that shall be laid out.
142    #[positional]
143    #[required]
144    pub body: Content,
145}