summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2025-07-09 11:46:40 +0200
committerGitHub <noreply@github.com>2025-07-09 09:46:40 +0000
commit52a708b988cf7d13898194e886790acb7edd510f (patch)
tree54c5494377b272e032fb0fecfa7e3c03cc3517b1
parente71674f6b3db0768c3e9d6e0271628377f8c82d8 (diff)
Move `html` module to `typst-html` crate (#6577)
-rw-r--r--Cargo.lock4
-rw-r--r--crates/typst-html/Cargo.toml4
-rw-r--r--crates/typst-html/src/css.rs135
-rw-r--r--crates/typst-html/src/lib.rs18
-rw-r--r--crates/typst-html/src/typed.rs (renamed from crates/typst-library/src/html/typed.rs)166
-rw-r--r--crates/typst-library/src/html/mod.rs13
-rw-r--r--crates/typst-library/src/lib.rs12
-rw-r--r--crates/typst-library/src/routines.rs7
-rw-r--r--crates/typst/src/lib.rs1
9 files changed, 184 insertions, 176 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 550c4141..5526da48 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2971,8 +2971,12 @@ dependencies = [
name = "typst-html"
version = "0.13.1"
dependencies = [
+ "bumpalo",
"comemo",
"ecow",
+ "palette",
+ "time",
+ "typst-assets",
"typst-library",
"typst-macros",
"typst-svg",
diff --git a/crates/typst-html/Cargo.toml b/crates/typst-html/Cargo.toml
index 534848f9..54cad012 100644
--- a/crates/typst-html/Cargo.toml
+++ b/crates/typst-html/Cargo.toml
@@ -13,14 +13,18 @@ keywords = { workspace = true }
readme = { workspace = true }
[dependencies]
+typst-assets = { workspace = true }
typst-library = { workspace = true }
typst-macros = { workspace = true }
typst-syntax = { workspace = true }
typst-timing = { workspace = true }
typst-utils = { workspace = true }
typst-svg = { workspace = true }
+bumpalo = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true }
+palette = { workspace = true }
+time = { workspace = true }
[lints]
workspace = true
diff --git a/crates/typst-html/src/css.rs b/crates/typst-html/src/css.rs
new file mode 100644
index 00000000..2b659188
--- /dev/null
+++ b/crates/typst-html/src/css.rs
@@ -0,0 +1,135 @@
+//! Conversion from Typst data types into CSS data types.
+
+use std::fmt::{self, Display};
+
+use typst_library::layout::Length;
+use typst_library::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
+use typst_utils::Numeric;
+
+pub fn length(length: Length) -> impl Display {
+ typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
+ (false, false) => {
+ write!(f, "calc({}pt + {}em)", length.abs.to_pt(), length.em.get())
+ }
+ (true, false) => write!(f, "{}em", length.em.get()),
+ (_, true) => write!(f, "{}pt", length.abs.to_pt()),
+ })
+}
+
+pub fn color(color: Color) -> impl Display {
+ typst_utils::display(move |f| match color {
+ Color::Rgb(_) | Color::Cmyk(_) | Color::Luma(_) => rgb(f, color.to_rgb()),
+ Color::Oklab(v) => oklab(f, v),
+ Color::Oklch(v) => oklch(f, v),
+ Color::LinearRgb(v) => linear_rgb(f, v),
+ Color::Hsl(_) | Color::Hsv(_) => hsl(f, color.to_hsl()),
+ })
+}
+
+fn oklab(f: &mut fmt::Formatter<'_>, v: Oklab) -> fmt::Result {
+ write!(f, "oklab({} {} {}{})", percent(v.l), number(v.a), number(v.b), alpha(v.alpha))
+}
+
+fn oklch(f: &mut fmt::Formatter<'_>, v: Oklch) -> fmt::Result {
+ write!(
+ f,
+ "oklch({} {} {}deg{})",
+ percent(v.l),
+ number(v.chroma),
+ number(v.hue.into_degrees()),
+ alpha(v.alpha)
+ )
+}
+
+fn rgb(f: &mut fmt::Formatter<'_>, v: Rgb) -> fmt::Result {
+ if let Some(v) = rgb_to_8_bit_lossless(v) {
+ let (r, g, b, a) = v.into_components();
+ write!(f, "#{r:02x}{g:02x}{b:02x}")?;
+ if a != u8::MAX {
+ write!(f, "{a:02x}")?;
+ }
+ Ok(())
+ } else {
+ write!(
+ f,
+ "rgb({} {} {}{})",
+ percent(v.red),
+ percent(v.green),
+ percent(v.blue),
+ alpha(v.alpha)
+ )
+ }
+}
+
+/// Converts an f32 RGBA color to its 8-bit representation if the result is
+/// [very close](is_very_close) to the original.
+fn rgb_to_8_bit_lossless(
+ v: Rgb,
+) -> Option<palette::rgb::Rgba<palette::encoding::Srgb, u8>> {
+ let l = v.into_format::<u8, u8>();
+ let h = l.into_format::<f32, f32>();
+ (is_very_close(v.red, h.red)
+ && is_very_close(v.blue, h.blue)
+ && is_very_close(v.green, h.green)
+ && is_very_close(v.alpha, h.alpha))
+ .then_some(l)
+}
+
+fn linear_rgb(f: &mut fmt::Formatter<'_>, v: LinearRgb) -> fmt::Result {
+ write!(
+ f,
+ "color(srgb-linear {} {} {}{})",
+ percent(v.red),
+ percent(v.green),
+ percent(v.blue),
+ alpha(v.alpha),
+ )
+}
+
+fn hsl(f: &mut fmt::Formatter<'_>, v: Hsl) -> fmt::Result {
+ write!(
+ f,
+ "hsl({}deg {} {}{})",
+ number(v.hue.into_degrees()),
+ percent(v.saturation),
+ percent(v.lightness),
+ alpha(v.alpha),
+ )
+}
+
+/// Displays an alpha component if it not 1.
+fn alpha(value: f32) -> impl Display {
+ typst_utils::display(move |f| {
+ if !is_very_close(value, 1.0) {
+ write!(f, " / {}", percent(value))?;
+ }
+ Ok(())
+ })
+}
+
+/// Displays a rounded percentage.
+///
+/// For a percentage, two significant digits after the comma gives us a
+/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
+fn percent(ratio: f32) -> impl Display {
+ typst_utils::display(move |f| {
+ write!(f, "{}%", typst_utils::round_with_precision(ratio as f64 * 100.0, 2))
+ })
+}
+
+/// Rounds a number for display.
+///
+/// For a number between 0 and 1, four significant digits give us a
+/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
+fn number(value: f32) -> impl Display {
+ typst_utils::round_with_precision(value as f64, 4)
+}
+
+/// Whether two component values are close enough that there is no
+/// difference when encoding them with 12-bit. 12 bit is the highest
+/// reasonable color bit depth found in the industry.
+fn is_very_close(a: f32, b: f32) -> bool {
+ const MAX_BIT_DEPTH: u32 = 12;
+ const EPS: f32 = 0.5 / 2_i32.pow(MAX_BIT_DEPTH) as f32;
+ (a - b).abs() < EPS
+}
diff --git a/crates/typst-html/src/lib.rs b/crates/typst-html/src/lib.rs
index 19eb1446..7063931b 100644
--- a/crates/typst-html/src/lib.rs
+++ b/crates/typst-html/src/lib.rs
@@ -1,7 +1,9 @@
//! Typst's HTML exporter.
+mod css;
mod encode;
mod rules;
+mod typed;
pub use self::encode::html;
pub use self::rules::register;
@@ -9,7 +11,9 @@ pub use self::rules::register;
use comemo::{Track, Tracked, TrackedMut};
use typst_library::diag::{bail, warning, At, SourceResult};
use typst_library::engine::{Engine, Route, Sink, Traced};
-use typst_library::foundations::{Content, StyleChain, Target, TargetElem};
+use typst_library::foundations::{
+ Content, Module, Scope, StyleChain, Target, TargetElem,
+};
use typst_library::html::{
attr, tag, FrameElem, HtmlDocument, HtmlElem, HtmlElement, HtmlFrame, HtmlNode,
};
@@ -20,9 +24,19 @@ use typst_library::layout::{Abs, Axes, BlockBody, BlockElem, BoxElem, Region, Si
use typst_library::model::{DocumentInfo, ParElem};
use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind, Routines};
use typst_library::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
-use typst_library::World;
+use typst_library::{Category, World};
use typst_syntax::Span;
+/// Create a module with all HTML definitions.
+pub fn module() -> Module {
+ let mut html = Scope::deduplicating();
+ html.start_category(Category::Html);
+ html.define_elem::<HtmlElem>();
+ html.define_elem::<FrameElem>();
+ crate::typed::define(&mut html);
+ Module::new("html", html)
+}
+
/// Produce an HTML document from content.
///
/// This first performs root-level realization and then turns the resulting
diff --git a/crates/typst-library/src/html/typed.rs b/crates/typst-html/src/typed.rs
index 8240b296..4b794bbb 100644
--- a/crates/typst-library/src/html/typed.rs
+++ b/crates/typst-html/src/typed.rs
@@ -11,19 +11,20 @@ use bumpalo::Bump;
use comemo::Tracked;
use ecow::{eco_format, eco_vec, EcoString};
use typst_assets::html as data;
-use typst_macros::cast;
-
-use crate::diag::{bail, At, Hint, HintedStrResult, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
+use typst_library::diag::{bail, At, Hint, HintedStrResult, SourceResult};
+use typst_library::engine::Engine;
+use typst_library::foundations::{
Args, Array, AutoValue, CastInfo, Content, Context, Datetime, Dict, Duration,
FromValue, IntoValue, NativeFuncData, NativeFuncPtr, NoneValue, ParamInfo,
PositiveF64, Reflect, Scope, Str, Type, Value,
};
-use crate::html::tag;
-use crate::html::{HtmlAttr, HtmlAttrs, HtmlElem, HtmlTag};
-use crate::layout::{Axes, Axis, Dir, Length};
-use crate::visualize::Color;
+use typst_library::html::tag;
+use typst_library::html::{HtmlAttr, HtmlAttrs, HtmlElem, HtmlTag};
+use typst_library::layout::{Axes, Axis, Dir, Length};
+use typst_library::visualize::Color;
+use typst_macros::cast;
+
+use crate::css;
/// Hook up all typed HTML definitions.
pub(super) fn define(html: &mut Scope) {
@@ -705,153 +706,6 @@ impl IntoAttr for SourceSize {
}
}
-/// Conversion from Typst data types into CSS data types.
-///
-/// This can be moved elsewhere once we start supporting more CSS stuff.
-mod css {
- use std::fmt::{self, Display};
-
- use typst_utils::Numeric;
-
- use crate::layout::Length;
- use crate::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
-
- pub fn length(length: Length) -> impl Display {
- typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
- (false, false) => {
- write!(f, "calc({}pt + {}em)", length.abs.to_pt(), length.em.get())
- }
- (true, false) => write!(f, "{}em", length.em.get()),
- (_, true) => write!(f, "{}pt", length.abs.to_pt()),
- })
- }
-
- pub fn color(color: Color) -> impl Display {
- typst_utils::display(move |f| match color {
- Color::Rgb(_) | Color::Cmyk(_) | Color::Luma(_) => rgb(f, color.to_rgb()),
- Color::Oklab(v) => oklab(f, v),
- Color::Oklch(v) => oklch(f, v),
- Color::LinearRgb(v) => linear_rgb(f, v),
- Color::Hsl(_) | Color::Hsv(_) => hsl(f, color.to_hsl()),
- })
- }
-
- fn oklab(f: &mut fmt::Formatter<'_>, v: Oklab) -> fmt::Result {
- write!(
- f,
- "oklab({} {} {}{})",
- percent(v.l),
- number(v.a),
- number(v.b),
- alpha(v.alpha)
- )
- }
-
- fn oklch(f: &mut fmt::Formatter<'_>, v: Oklch) -> fmt::Result {
- write!(
- f,
- "oklch({} {} {}deg{})",
- percent(v.l),
- number(v.chroma),
- number(v.hue.into_degrees()),
- alpha(v.alpha)
- )
- }
-
- fn rgb(f: &mut fmt::Formatter<'_>, v: Rgb) -> fmt::Result {
- if let Some(v) = rgb_to_8_bit_lossless(v) {
- let (r, g, b, a) = v.into_components();
- write!(f, "#{r:02x}{g:02x}{b:02x}")?;
- if a != u8::MAX {
- write!(f, "{a:02x}")?;
- }
- Ok(())
- } else {
- write!(
- f,
- "rgb({} {} {}{})",
- percent(v.red),
- percent(v.green),
- percent(v.blue),
- alpha(v.alpha)
- )
- }
- }
-
- /// Converts an f32 RGBA color to its 8-bit representation if the result is
- /// [very close](is_very_close) to the original.
- fn rgb_to_8_bit_lossless(
- v: Rgb,
- ) -> Option<palette::rgb::Rgba<palette::encoding::Srgb, u8>> {
- let l = v.into_format::<u8, u8>();
- let h = l.into_format::<f32, f32>();
- (is_very_close(v.red, h.red)
- && is_very_close(v.blue, h.blue)
- && is_very_close(v.green, h.green)
- && is_very_close(v.alpha, h.alpha))
- .then_some(l)
- }
-
- fn linear_rgb(f: &mut fmt::Formatter<'_>, v: LinearRgb) -> fmt::Result {
- write!(
- f,
- "color(srgb-linear {} {} {}{})",
- percent(v.red),
- percent(v.green),
- percent(v.blue),
- alpha(v.alpha),
- )
- }
-
- fn hsl(f: &mut fmt::Formatter<'_>, v: Hsl) -> fmt::Result {
- write!(
- f,
- "hsl({}deg {} {}{})",
- number(v.hue.into_degrees()),
- percent(v.saturation),
- percent(v.lightness),
- alpha(v.alpha),
- )
- }
-
- /// Displays an alpha component if it not 1.
- fn alpha(value: f32) -> impl Display {
- typst_utils::display(move |f| {
- if !is_very_close(value, 1.0) {
- write!(f, " / {}", percent(value))?;
- }
- Ok(())
- })
- }
-
- /// Displays a rounded percentage.
- ///
- /// For a percentage, two significant digits after the comma gives us a
- /// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
- fn percent(ratio: f32) -> impl Display {
- typst_utils::display(move |f| {
- write!(f, "{}%", typst_utils::round_with_precision(ratio as f64 * 100.0, 2))
- })
- }
-
- /// Rounds a number for display.
- ///
- /// For a number between 0 and 1, four significant digits give us a
- /// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
- fn number(value: f32) -> impl Display {
- typst_utils::round_with_precision(value as f64, 4)
- }
-
- /// Whether two component values are close enough that there is no
- /// difference when encoding them with 12-bit. 12 bit is the highest
- /// reasonable color bit depth found in the industry.
- fn is_very_close(a: f32, b: f32) -> bool {
- const MAX_BIT_DEPTH: u32 = 12;
- const EPS: f32 = 0.5 / 2_i32.pow(MAX_BIT_DEPTH) as f32;
- (a - b).abs() < EPS
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/typst-library/src/html/mod.rs b/crates/typst-library/src/html/mod.rs
index f9835206..ca2cc031 100644
--- a/crates/typst-library/src/html/mod.rs
+++ b/crates/typst-library/src/html/mod.rs
@@ -1,23 +1,12 @@
//! HTML output.
mod dom;
-mod typed;
pub use self::dom::*;
use ecow::EcoString;
-use crate::foundations::{elem, Content, Module, Scope};
-
-/// Create a module with all HTML definitions.
-pub fn module() -> Module {
- let mut html = Scope::deduplicating();
- html.start_category(crate::Category::Html);
- html.define_elem::<HtmlElem>();
- html.define_elem::<FrameElem>();
- self::typed::define(&mut html);
- Module::new("html", html)
-}
+use crate::foundations::{elem, Content};
/// An HTML element that can contain Typst content.
///
diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs
index 3e2ce99e..5d047570 100644
--- a/crates/typst-library/src/lib.rs
+++ b/crates/typst-library/src/lib.rs
@@ -165,7 +165,6 @@ pub struct Library {
/// Constructed via the `LibraryExt` trait.
#[derive(Debug, Clone)]
pub struct LibraryBuilder {
- #[expect(unused, reason = "will be used in the future")]
routines: &'static Routines,
inputs: Option<Dict>,
features: Features,
@@ -200,7 +199,7 @@ impl LibraryBuilder {
pub fn build(self) -> Library {
let math = math::module();
let inputs = self.inputs.unwrap_or_default();
- let global = global(math.clone(), inputs, &self.features);
+ let global = global(self.routines, math.clone(), inputs, &self.features);
Library {
global: global.clone(),
math,
@@ -282,7 +281,12 @@ impl Category {
}
/// Construct the module with global definitions.
-fn global(math: Module, inputs: Dict, features: &Features) -> Module {
+fn global(
+ routines: &Routines,
+ math: Module,
+ inputs: Dict,
+ features: &Features,
+) -> Module {
let mut global = Scope::deduplicating();
self::foundations::define(&mut global, inputs, features);
@@ -297,7 +301,7 @@ fn global(math: Module, inputs: Dict, features: &Features) -> Module {
global.define("math", math);
global.define("pdf", self::pdf::module());
if features.is_enabled(Feature::Html) {
- global.define("html", self::html::module());
+ global.define("html", (routines.html_module)());
}
prelude(&mut global);
diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs
index 6db99ba5..a81806fd 100644
--- a/crates/typst-library/src/routines.rs
+++ b/crates/typst-library/src/routines.rs
@@ -8,8 +8,8 @@ use typst_utils::LazyHash;
use crate::diag::SourceResult;
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
- Args, Closure, Content, Context, Func, NativeRuleMap, Scope, StyleChain, Styles,
- Value,
+ Args, Closure, Content, Context, Func, Module, NativeRuleMap, Scope, StyleChain,
+ Styles, Value,
};
use crate::introspection::{Introspector, Locator, SplitLocator};
use crate::layout::{Frame, Region};
@@ -92,6 +92,9 @@ routines! {
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>
+
+ /// Constructs the `html` module.
+ fn html_module() -> Module
}
/// Defines what kind of realization we are performing.
diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs
index 11d5c9e0..591e5a9b 100644
--- a/crates/typst/src/lib.rs
+++ b/crates/typst/src/lib.rs
@@ -357,4 +357,5 @@ pub static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
eval_closure: typst_eval::eval_closure,
realize: typst_realize::realize,
layout_frame: typst_layout::layout_frame,
+ html_module: typst_html::module,
});