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}