From f41fc219fe7c33fc2cbba228f87efef74558529a Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 16 Jan 2026 22:10:46 +0100 Subject: [PATCH 1/5] Adds mechanism to conditionally check EPUB (#68) * Updates git submodules to latest * Updates git submodules to latest * Returns "epub" in target() during EPUB compilation * Updates git submodules to latest * Extracts XHTML content from EPUB archives for testing * Saves EPUB XHTML content to reference files during test updates * Verifies EPUB XHTML content against reference files during tests * Tests target() function behavior in imported modules * Adds XHTML reference files for all EPUB tests * Updates EPUB module reference output to desired behavior * Injects target() override into all Typst files during EPUB compilation * Tests target() override in Typst Universe packages * Uses sys.inputs for EPUB target detection instead of target() override * Documents sys.inputs.rheo-target pattern for EPUB detection * Injects target() polyfill using sys.inputs for EPUB compilation * Documents sys.inputs.rheo-target for library developers * Updates git submodules to latest * Fixes lints * Updates git submodules to latest --- CLAUDE.md | 54 +++++ examples/blog_post/portable_epubs.typ | 2 +- examples/blog_site/content/index.typ | 2 +- examples/fcl_site | 2 +- examples/rheo_docs | 2 +- src/rs/formats/epub/mod.rs | 8 +- src/rs/formats/html/mod.rs | 12 +- src/rs/world.rs | 51 +++- src/typ/rheo.typ | 17 ++ tests/cases/target_function/main.typ | 24 ++ tests/cases/target_function/rheo.toml | 3 + .../lib/format_helper.typ | 19 ++ .../cases/target_function_in_module/main.typ | 16 ++ .../cases/target_function_in_module/rheo.toml | 6 + .../cases/target_function_in_package/main.typ | 35 +++ .../target_function_in_package/rheo.toml | 6 + tests/harness.rs | 3 + tests/helpers/comparison.rs | 96 ++++++++ tests/helpers/reference.rs | 19 +- .../blog_post/epub/xhtml/portable_epubs.xhtml | 222 ++++++++++++++++++ .../blog_site/epub/blog_site.metadata.json | 2 +- .../blog_site/epub/xhtml/severance-ep-1.xhtml | 45 ++++ .../blog_site/epub/xhtml/severance-ep-2.xhtml | 50 ++++ .../blog_site/epub/xhtml/severance-ep-3.xhtml | 70 ++++++ .../epub_inferred_spine/epub/xhtml/a.xhtml | 13 + .../epub_inferred_spine/epub/xhtml/b.xhtml | 13 + .../epub_inferred_spine/epub/xhtml/c.xhtml | 13 + .../fcl_site/epub/fcl_site.metadata.json | 2 +- .../examples/fcl_site/epub/xhtml/index.xhtml | 21 ++ tests/ref/examples/fcl_site/html/index.html | 7 +- .../fcl_site/pdf/fcl_site.metadata.json | 2 +- .../link_transformation/epub/xhtml/doc1.xhtml | 15 ++ .../link_transformation/epub/xhtml/doc2.xhtml | 15 ++ .../epub/xhtml/page1.xhtml | 16 ++ .../epub/xhtml/page2.xhtml | 20 ++ .../rheo_docs/epub/rheo_docs.metadata.json | 7 +- .../rheo_docs/epub/xhtml/build-dir.xhtml | 22 ++ .../rheo_docs/epub/xhtml/content-dir.xhtml | 18 ++ .../rheo_docs/epub/xhtml/custom-css.xhtml | 13 + .../examples/rheo_docs/epub/xhtml/faq.xhtml | 22 ++ .../rheo_docs/epub/xhtml/formats.xhtml | 26 ++ .../epub/xhtml/getting-started.xhtml | 59 +++++ .../examples/rheo_docs/epub/xhtml/index.xhtml | 33 +++ .../epub/xhtml/relative-linking.xhtml | 21 ++ .../rheo_docs/epub/xhtml/rheotoml.xhtml | 22 ++ .../rheo_docs/epub/xhtml/spines.xhtml | 26 ++ .../rheo_docs/epub/xhtml/why-is-rheo.xhtml | 37 +++ .../examples/rheo_docs/html/build-dir.html | 16 +- .../examples/rheo_docs/html/content-dir.html | 12 +- .../examples/rheo_docs/html/custom-css.html | 14 +- tests/ref/examples/rheo_docs/html/faq.html | 40 ++++ .../ref/examples/rheo_docs/html/formats.html | 10 +- .../rheo_docs/html/getting-started.html | 70 ++++-- tests/ref/examples/rheo_docs/html/index.html | 22 +- .../rheo_docs/html/relative-linking.html | 10 +- .../ref/examples/rheo_docs/html/rheotoml.html | 8 +- tests/ref/examples/rheo_docs/html/spines.html | 14 +- .../rheo_docs/html/style.metadata.json | 4 +- ...-and-why-is-rheo.html => why-is-rheo.html} | 16 +- .../rheo_docs/pdf/rheo_docs.metadata.json | 4 +- .../epub/target_function.metadata.json | 10 + .../target_function/epub/xhtml/main.xhtml | 15 ++ .../examples/target_function/html/main.html | 13 + .../target_function/html/style.metadata.json | 6 + .../target_function/pdf/main.metadata.json | 5 + .../target_function_in_module.metadata.json | 10 + .../epub/xhtml/main.xhtml | 17 ++ .../html/format_helper.html | 7 + .../target_function_in_module/html/main.html | 15 ++ .../html/style.metadata.json | 6 + .../pdf/format_helper.metadata.json | 5 + .../pdf/main.metadata.json | 5 + .../target_function_in_package.metadata.json | 10 + .../epub/xhtml/main.xhtml | 15 ++ .../target_function_in_package/html/main.html | 13 + .../html/style.metadata.json | 6 + 76 files changed, 1507 insertions(+), 100 deletions(-) create mode 100644 tests/cases/target_function/main.typ create mode 100644 tests/cases/target_function/rheo.toml create mode 100644 tests/cases/target_function_in_module/lib/format_helper.typ create mode 100644 tests/cases/target_function_in_module/main.typ create mode 100644 tests/cases/target_function_in_module/rheo.toml create mode 100644 tests/cases/target_function_in_package/main.typ create mode 100644 tests/cases/target_function_in_package/rheo.toml create mode 100644 tests/ref/examples/blog_post/epub/xhtml/portable_epubs.xhtml create mode 100644 tests/ref/examples/blog_site/epub/xhtml/severance-ep-1.xhtml create mode 100644 tests/ref/examples/blog_site/epub/xhtml/severance-ep-2.xhtml create mode 100644 tests/ref/examples/blog_site/epub/xhtml/severance-ep-3.xhtml create mode 100644 tests/ref/examples/epub_inferred_spine/epub/xhtml/a.xhtml create mode 100644 tests/ref/examples/epub_inferred_spine/epub/xhtml/b.xhtml create mode 100644 tests/ref/examples/epub_inferred_spine/epub/xhtml/c.xhtml create mode 100644 tests/ref/examples/fcl_site/epub/xhtml/index.xhtml create mode 100644 tests/ref/examples/link_transformation/epub/xhtml/doc1.xhtml create mode 100644 tests/ref/examples/link_transformation/epub/xhtml/doc2.xhtml create mode 100644 tests/ref/examples/links_with_fragments/epub/xhtml/page1.xhtml create mode 100644 tests/ref/examples/links_with_fragments/epub/xhtml/page2.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/build-dir.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/content-dir.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/custom-css.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/faq.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/formats.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/getting-started.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/index.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/relative-linking.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/rheotoml.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/spines.xhtml create mode 100644 tests/ref/examples/rheo_docs/epub/xhtml/why-is-rheo.xhtml rename tests/ref/examples/rheo_docs/html/{what-and-why-is-rheo.html => why-is-rheo.html} (74%) create mode 100644 tests/ref/examples/target_function/epub/target_function.metadata.json create mode 100644 tests/ref/examples/target_function/epub/xhtml/main.xhtml create mode 100644 tests/ref/examples/target_function/html/main.html create mode 100644 tests/ref/examples/target_function/html/style.metadata.json create mode 100644 tests/ref/examples/target_function/pdf/main.metadata.json create mode 100644 tests/ref/examples/target_function_in_module/epub/target_function_in_module.metadata.json create mode 100644 tests/ref/examples/target_function_in_module/epub/xhtml/main.xhtml create mode 100644 tests/ref/examples/target_function_in_module/html/format_helper.html create mode 100644 tests/ref/examples/target_function_in_module/html/main.html create mode 100644 tests/ref/examples/target_function_in_module/html/style.metadata.json create mode 100644 tests/ref/examples/target_function_in_module/pdf/format_helper.metadata.json create mode 100644 tests/ref/examples/target_function_in_module/pdf/main.metadata.json create mode 100644 tests/ref/examples/target_function_in_package/epub/target_function_in_package.metadata.json create mode 100644 tests/ref/examples/target_function_in_package/epub/xhtml/main.xhtml create mode 100644 tests/ref/examples/target_function_in_package/html/main.html create mode 100644 tests/ref/examples/target_function_in_package/html/style.metadata.json diff --git a/CLAUDE.md b/CLAUDE.md index 8935f46..12b0cb0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -315,6 +315,60 @@ cargo run -- compile my_project/ --epub **Note:** Existing projects with explicit `rheo.toml` configurations are not affected—explicit configs always take precedence over inferred defaults. +### Format Detection in Typst Code + +Rheo polyfills the `target()` function for EPUB compilation, so you can use standard Typst patterns: + +**Basic usage (recommended):** + +```typst +// target() returns "epub" for EPUB, "html" for HTML, "paged" for PDF +#context if target() == "epub" { + [EPUB-specific content] +} else if target() == "html" { + [HTML-specific content] +} else { + [PDF content] +} +``` + +**Helper Functions (available via rheo.typ injection):** + +```typst +// Explicit helpers for format checking +#if is-rheo-epub() { [EPUB-only content] } +#if is-rheo-html() { [HTML-only content] } +#if is-rheo-pdf() { [PDF-only content] } +``` + +**How it works:** +- Rheo sets `sys.inputs.rheo-target` to "epub", "html", or "pdf" +- For EPUB compilation, a `target()` polyfill is injected that checks `sys.inputs.rheo-target` +- This shadows the built-in `target()` so `target() == "epub"` works naturally +- The polyfill is syntactic sugar for user code convenience + +**For Typst library/package authors:** + +The `target()` polyfill only shadows the local function name. Packages that call `std.target()` (common practice to get the "real" target) will bypass the polyfill and see "html" for EPUB compilation. + +To properly support rheo's EPUB detection, library authors should check `sys.inputs.rheo-target` directly: + +```typst +// Recommended pattern for libraries +#let get-format() = { + if "rheo-target" in sys.inputs { + sys.inputs.rheo-target // "epub", "html", or "pdf" when compiled with rheo + } else { + target() // Fallback for vanilla Typst + } +} +``` + +This pattern: +- Returns the correct format when compiled with rheo +- Gracefully degrades to standard `target()` in vanilla Typst +- Works regardless of whether the package calls `target()` or `std.target()` + ### Incremental Compilation **Overview:** diff --git a/examples/blog_post/portable_epubs.typ b/examples/blog_post/portable_epubs.typ index fa70778..9f2ec39 100644 --- a/examples/blog_post/portable_epubs.typ +++ b/examples/blog_post/portable_epubs.typ @@ -3,7 +3,7 @@ // @rheo:description Long-form article with custom HTML elements and code examples #let html-element(body, name: "div", attrs: (:)) = context { - if target() == "html" { + if target() == "html" or target() == "epub" { html.elem(name, attrs: attrs, body) } else { block(body) diff --git a/examples/blog_site/content/index.typ b/examples/blog_site/content/index.typ index 80ba7c6..e80f8be 100644 --- a/examples/blog_site/content/index.typ +++ b/examples/blog_site/content/index.typ @@ -10,7 +10,7 @@ #let template(doc) = { doc - context if target() == "html" { + context if target() == "html" or target() == "epub" { div[ #br() #hr() diff --git a/examples/fcl_site b/examples/fcl_site index 32d0eed..ac3bb7c 160000 --- a/examples/fcl_site +++ b/examples/fcl_site @@ -1 +1 @@ -Subproject commit 32d0eedc1da172487d42e0e6079d269c5f01d330 +Subproject commit ac3bb7c83efc2de2645802a39d146fdacab4f0e0 diff --git a/examples/rheo_docs b/examples/rheo_docs index 8d67839..56ff65b 160000 --- a/examples/rheo_docs +++ b/examples/rheo_docs @@ -1 +1 @@ -Subproject commit 8d67839d38dc2ce5940418674f3c1bb403dbf8c5 +Subproject commit 56ff65b529ff54fc8a5e90933435e47044b4e982 diff --git a/src/rs/formats/epub/mod.rs b/src/rs/formats/epub/mod.rs index dc087d4..0483938 100644 --- a/src/rs/formats/epub/mod.rs +++ b/src/rs/formats/epub/mod.rs @@ -7,7 +7,7 @@ use xhtml::HtmlInfo; use crate::compile::RheoCompileOptions; use crate::config::{EpubConfig, EpubOptions}; use crate::reticulate::spine::RheoSpine; -use crate::{Result, RheoError}; +use crate::{OutputFormat, Result, RheoError}; use anyhow::Result as AnyhowResult; use chrono::{DateTime, Utc}; use iref::{IriRef, IriRefBuf, iri::Fragment}; @@ -328,7 +328,8 @@ fn text_to_id(s: &str) -> EcoString { impl EpubItem { pub fn create(path: PathBuf, root: &Path) -> AnyhowResult { info!(file = %path.display(), "compiling spine file"); - let document = crate::formats::html::compile_html_to_document(&path, root)?; + let document = + crate::formats::html::compile_html_to_document(&path, root, OutputFormat::Epub)?; let parent = path.parent().unwrap(); let bare_file = path.strip_prefix(parent).unwrap(); let href = IriRefBuf::new(bare_file.with_extension("xhtml").display().to_string())?; @@ -364,7 +365,8 @@ impl EpubItem { let temp_path = temp_file.path(); // Compile to HTML document - let document = crate::formats::html::compile_html_to_document(temp_path, root)?; + let document = + crate::formats::html::compile_html_to_document(temp_path, root, OutputFormat::Epub)?; let parent = path.parent().unwrap(); let bare_file = path.strip_prefix(parent).unwrap(); diff --git a/src/rs/formats/html/mod.rs b/src/rs/formats/html/mod.rs index c259e72..ccf6784 100644 --- a/src/rs/formats/html/mod.rs +++ b/src/rs/formats/html/mod.rs @@ -8,9 +8,13 @@ use std::path::Path; use tracing::{debug, info}; use typst_html::HtmlDocument; -pub fn compile_html_to_document(input: &Path, root: &Path) -> Result { - // Create the compilation world with HTML format for link transformations - let world = RheoWorld::new(root, input, Some(OutputFormat::Html))?; +pub fn compile_html_to_document( + input: &Path, + root: &Path, + output_format: OutputFormat, +) -> Result { + // Create the compilation world with specified format for link transformations + let world = RheoWorld::new(root, input, Some(output_format))?; // Compile the document to HtmlDocument info!(input = %input.display(), "compiling to HTML"); @@ -47,7 +51,7 @@ fn compile_html_impl_fresh( html_options: &HtmlOptions, ) -> Result<()> { // Compile to HTML document (transformations happen in RheoWorld) - let doc = compile_html_to_document(input, root)?; + let doc = compile_html_to_document(input, root, OutputFormat::Html)?; let html_string = compile_document_to_string(&doc)?; // Inject CSS and font links into diff --git a/src/rs/world.rs b/src/rs/world.rs index 0e26a76..f7e0429 100644 --- a/src/rs/world.rs +++ b/src/rs/world.rs @@ -7,7 +7,7 @@ use chrono::{Datelike, Local}; use codespan_reporting::files::{Error as CodespanError, Files}; use parking_lot::Mutex; use typst::diag::{FileError, FileResult}; -use typst::foundations::{Bytes, Datetime}; +use typst::foundations::{Bytes, Datetime, Dict, IntoValue}; use typst::syntax::{FileId, Lines, Source, VirtualPath}; use typst::text::{Font, FontBook}; use typst::utils::LazyHash; @@ -17,6 +17,25 @@ use typst_kit::fonts::{FontSlot, Fonts}; use typst_kit::package::PackageStorage; use typst_library::{Feature, Features}; +/// Build sys.inputs Dict for Typst compilation. +/// +/// This creates the dictionary that's accessible via `sys.inputs` in Typst code. +/// For EPUB/HTML/PDF compilation, we pass `{"rheo-target": "epub"|"html"|"pdf"}` +/// so user code can detect the output format using: +/// `if "rheo-target" in sys.inputs { sys.inputs.rheo-target }` +fn build_inputs(output_format: Option) -> Dict { + let mut dict = Dict::new(); + if let Some(format) = output_format { + let format_str = match format { + OutputFormat::Pdf => "pdf", + OutputFormat::Html => "html", + OutputFormat::Epub => "epub", + }; + dict.insert("rheo-target".into(), format_str.into_value()); + } + dict +} + /// A simple World implementation for rheo compilation. pub struct RheoWorld { /// The root directory for resolving imports (document directory). @@ -80,9 +99,13 @@ impl RheoWorld { })?; let main = FileId::new(None, main_vpath); - // Build library with HTML feature enabled + // Build library with HTML feature enabled and sys.inputs for format detection let features: Features = [Feature::Html].into_iter().collect(); - let library = Library::builder().with_features(features).build(); + let inputs = build_inputs(output_format); + let library = Library::builder() + .with_features(features) + .with_inputs(inputs) + .build(); // Search for fonts using typst-kit // Respect TYPST_IGNORE_SYSTEM_FONTS for test consistency @@ -287,12 +310,28 @@ impl World for RheoWorld { let path = self.path_for_id(id)?; let mut text = fs::read_to_string(&path).map_err(|e| FileError::from_io(e, &path))?; - // For the main file, inject the rheo.typ template automatically + // Inject target() polyfill into ALL .typ files for EPUB compilation + // This shadows the built-in target() to check sys.inputs.rheo-target first, + // allowing user code to use `if target() == "epub"` naturally. + // Packages can also adopt this pattern, or use sys.inputs directly. + let target_polyfill = if matches!(self.output_format, Some(OutputFormat::Epub)) { + "// Polyfill target() to return rheo's output format from sys.inputs\n\ + #let target() = if \"rheo-target\" in sys.inputs { sys.inputs.rheo-target } else { std.target() }\n\n" + } else { + "" + }; + + // For the main file, also inject the rheo.typ template if id == self.main { - // Embed rheo.typ content directly (it's small and avoids path issues) let rheo_content = include_str!("../typ/rheo.typ"); - let template_inject = format!("{}\n#show: rheo_template\n\n", rheo_content); + let template_inject = format!( + "{}{}\n#show: rheo_template\n\n", + target_polyfill, rheo_content + ); text = format!("{}{}", template_inject, text); + } else if !target_polyfill.is_empty() { + // For all other files (local modules and packages), just inject the target polyfill + text = format!("{}{}", target_polyfill, text); } // Apply link transformations for ALL .typ files if output format is set diff --git a/src/typ/rheo.typ b/src/typ/rheo.typ index a15a323..76f7db1 100644 --- a/src/typ/rheo.typ +++ b/src/typ/rheo.typ @@ -1,3 +1,20 @@ +// Get the rheo output format, with fallback to Typst's target() +// Returns: "epub", "html", "pdf" when compiled with rheo +// "html" or "paged" when compiled with vanilla Typst +#let rheo-target() = { + if "rheo-target" in sys.inputs { + sys.inputs.rheo-target + } else { + target() + } +} + +// Check if we're compiling for a specific rheo format +// Works in vanilla Typst (returns false when rheo-target not set) +#let is-rheo-epub() = "rheo-target" in sys.inputs and sys.inputs.rheo-target == "epub" +#let is-rheo-html() = "rheo-target" in sys.inputs and sys.inputs.rheo-target == "html" +#let is-rheo-pdf() = "rheo-target" in sys.inputs and sys.inputs.rheo-target == "pdf" + #let lemmacount = counter("lemmas") #let lemma(it) = block(inset: 8pt, [ #lemmacount.step() diff --git a/tests/cases/target_function/main.typ b/tests/cases/target_function/main.typ new file mode 100644 index 0000000..7486fea --- /dev/null +++ b/tests/cases/target_function/main.typ @@ -0,0 +1,24 @@ +// @rheo:test +// @rheo:formats html,pdf,epub +// @rheo:description Verifies target() function returns correct format string + += Target Function Test + +This test verifies that the `target()` function returns format-specific values. + +#context { + let format = target() + [Current format: *#format*] +} + +== Conditional Content + +#context if target() == "html" { + [HTML-specific content: This appears only in HTML output] +} else if target() == "pdf" or target() == "paged" { + [PDF-specific content: This appears only in PDF output] +} else if target() == "epub" { + [EPUB-specific content: This appears only in EPUB output] +} else { + [Unknown format detected] +} diff --git a/tests/cases/target_function/rheo.toml b/tests/cases/target_function/rheo.toml new file mode 100644 index 0000000..11de202 --- /dev/null +++ b/tests/cases/target_function/rheo.toml @@ -0,0 +1,3 @@ +version = "0.1.0" + +formats = ["html", "pdf", "epub"] diff --git a/tests/cases/target_function_in_module/lib/format_helper.typ b/tests/cases/target_function_in_module/lib/format_helper.typ new file mode 100644 index 0000000..1a0d06d --- /dev/null +++ b/tests/cases/target_function_in_module/lib/format_helper.typ @@ -0,0 +1,19 @@ +// Module that uses target() function +// Tests whether target() polyfill propagates to imported files + +#let get_format() = { + target() +} + +#let format_specific_content() = context { + let fmt = target() + if fmt == "epub" { + [Module: EPUB] + } else if fmt == "html" { + [Module: HTML] + } else if fmt == "pdf" or fmt == "paged" { + [Module: PDF] + } else { + [Module: Unknown (#fmt)] + } +} diff --git a/tests/cases/target_function_in_module/main.typ b/tests/cases/target_function_in_module/main.typ new file mode 100644 index 0000000..11ab260 --- /dev/null +++ b/tests/cases/target_function_in_module/main.typ @@ -0,0 +1,16 @@ +// @rheo:test +// @rheo:formats html,pdf,epub +// @rheo:description Verifies target() works in imported modules + +#import "lib/format_helper.typ": get_format, format_specific_content + += Target Function in Module + +== Main File +#context [Main: *#target()*] + +== Imported Module +#context [Module returns: *#get_format()*] + +== Module Conditional +#format_specific_content() diff --git a/tests/cases/target_function_in_module/rheo.toml b/tests/cases/target_function_in_module/rheo.toml new file mode 100644 index 0000000..cf4ad44 --- /dev/null +++ b/tests/cases/target_function_in_module/rheo.toml @@ -0,0 +1,6 @@ +version = "0.1.0" +formats = ["html", "pdf", "epub"] + +[epub.spine] +title = "Target Function in Module" +vertebrae = ["main.typ"] diff --git a/tests/cases/target_function_in_package/main.typ b/tests/cases/target_function_in_package/main.typ new file mode 100644 index 0000000..1901929 --- /dev/null +++ b/tests/cases/target_function_in_package/main.typ @@ -0,0 +1,35 @@ +// @rheo:test +// @rheo:formats html,epub +// @rheo:description Tests target() polyfill vs packages using std.target() +// +// This test demonstrates rheo's target() polyfill: +// +// - User code using target() sees "epub" for EPUB output (via polyfill) +// - Universe packages that call std.target() see "html" (the underlying compile target) +// +// Why packages see "html": +// - EPUB compilation uses Typst's HTML export internally +// - Packages like bullseye explicitly call std.target() to get the "real" target +// - This is expected behavior - std.target() returns the underlying format +// +// For package authors: +// - Packages can adopt rheo's pattern to detect rheo output format +// - The pattern: `if "rheo-target" in sys.inputs { sys.inputs.rheo-target } else { target() }` +// - This provides graceful degradation when compiled outside rheo + +#import "@preview/bullseye:0.1.0": on-target + += Target Function in Package + +== Using bullseye package + +// Expected: "html" in both HTML and EPUB modes (bullseye calls std.target()) +#context on-target( + html: [Package sees: *html*], + paged: [Package sees: *paged*], +) + +== Using target() + +// Expected: "html" for HTML, "epub" for EPUB (uses polyfill) +Main file target: #context [*#target()*] diff --git a/tests/cases/target_function_in_package/rheo.toml b/tests/cases/target_function_in_package/rheo.toml new file mode 100644 index 0000000..625fa7e --- /dev/null +++ b/tests/cases/target_function_in_package/rheo.toml @@ -0,0 +1,6 @@ +version = "0.1.0" +formats = ["html", "epub"] + +[epub.spine] +title = "Target Function in Package" +vertebrae = ["main.typ"] diff --git a/tests/harness.rs b/tests/harness.rs index d363f57..246d3c3 100644 --- a/tests/harness.rs +++ b/tests/harness.rs @@ -28,6 +28,9 @@ use std::path::PathBuf; #[test_case("tests/cases/multiple_links_inline.typ")] #[test_case("tests/cases/pdf_individual")] #[test_case("tests/cases/relative_path_links")] +#[test_case("tests/cases/target_function")] +#[test_case("tests/cases/target_function_in_module")] +#[test_case("tests/cases/target_function_in_package")] #[test_case("tests/cases/error_formatting/type_error.typ")] #[test_case("tests/cases/error_formatting/undefined_var.typ")] #[test_case("tests/cases/error_formatting/syntax_error.typ")] diff --git a/tests/helpers/comparison.rs b/tests/helpers/comparison.rs index f92c4e2..c14768e 100644 --- a/tests/helpers/comparison.rs +++ b/tests/helpers/comparison.rs @@ -1,4 +1,5 @@ use similar::{ChangeTag, TextDiff}; +use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; use std::env; use std::fs; @@ -510,6 +511,96 @@ pub fn extract_epub_metadata(epub_path: &Path) -> Result { }) } +/// Extract XHTML content files from an EPUB archive +pub fn extract_epub_xhtml(epub_path: &Path) -> Result, String> { + use std::io::Read; + use zip::ZipArchive; + + let file = fs::File::open(epub_path).map_err(|e| format!("Failed to open EPUB file: {}", e))?; + let mut archive = + ZipArchive::new(file).map_err(|e| format!("Failed to read EPUB archive: {}", e))?; + + let mut xhtml_files = HashMap::new(); + + for i in 0..archive.len() { + let mut file = archive + .by_index(i) + .map_err(|e| format!("Failed to read archive entry: {}", e))?; + + let name = file.name().to_string(); + + // Extract only XHTML content files (not nav.xhtml) + if name.starts_with("EPUB/") && name.ends_with(".xhtml") && !name.ends_with("nav.xhtml") { + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|e| format!("Failed to read XHTML content: {}", e))?; + + // Store with relative name (strip EPUB/ prefix) + let rel_name = name.strip_prefix("EPUB/").unwrap_or(&name); + xhtml_files.insert(rel_name.to_string(), contents); + } + } + + Ok(xhtml_files) +} + +/// Verify EPUB XHTML content against reference files +fn verify_epub_xhtml_content( + ref_dir: &Path, + actual_xhtml: &HashMap, + test_name: &str, +) -> Result<(), String> { + let xhtml_ref_dir = ref_dir.join("xhtml"); + + // If no xhtml reference directory exists, skip XHTML verification + if !xhtml_ref_dir.exists() { + return Ok(()); + } + + // Check all reference XHTML files exist in actual + for entry in WalkDir::new(&xhtml_ref_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|e| { + e.file_type().is_file() + && e.path() + .extension() + .map(|ext| ext == "xhtml") + .unwrap_or(false) + }) + { + let rel_path = entry + .path() + .strip_prefix(&xhtml_ref_dir) + .map_err(|e| format!("Failed to get relative path: {}", e))?; + let filename = rel_path.to_string_lossy().to_string(); + + let ref_content = fs::read_to_string(entry.path()) + .map_err(|e| format!("Failed to read reference XHTML: {}", e))?; + + let actual_content = actual_xhtml + .get(&filename) + .ok_or_else(|| format!("Missing XHTML file in EPUB: {}", filename))?; + + if ref_content != *actual_content { + let diff = compute_html_diff(&ref_content, actual_content); + + let test_name_sanitized = test_name + .replace('/', "_slash") + .replace('.', "_full_stop") + .replace(':', "_colon") + .replace('-', "_minus"); + + return Err(format!( + "EPUB XHTML content mismatch for {}\n\n{}\n\nTo update, run: UPDATE_REFERENCES=1 cargo test run_test_case_{}", + filename, diff, test_name_sanitized + )); + } + } + + Ok(()) +} + fn compare_pdf_metadata( reference: &BinaryFileMetadata, actual: &BinaryFileMetadata, @@ -817,6 +908,11 @@ pub fn verify_epub_output(test_name: &str, actual_dir: &Path) { extract_epub_metadata(entry.path()).expect("Failed to extract EPUB metadata"); compare_epub_metadata(&ref_metadata, &actual_metadata).expect("EPUB metadata mismatch"); + + // Verify XHTML content if reference files exist + let actual_xhtml = extract_epub_xhtml(entry.path()).expect("Failed to extract EPUB XHTML"); + verify_epub_xhtml_content(&ref_dir, &actual_xhtml, test_name) + .expect("EPUB XHTML content mismatch"); }); } diff --git a/tests/helpers/reference.rs b/tests/helpers/reference.rs index f982e01..22e4f79 100644 --- a/tests/helpers/reference.rs +++ b/tests/helpers/reference.rs @@ -175,7 +175,7 @@ pub fn update_pdf_references(test_name: &str, actual_dir: &Path) -> Result<(), S /// Update EPUB metadata references from test output pub fn update_epub_references(test_name: &str, actual_dir: &Path) -> Result<(), String> { - use crate::helpers::comparison::extract_epub_metadata; + use crate::helpers::comparison::{extract_epub_metadata, extract_epub_xhtml}; // Determine if this is a single-file test let ref_dir = if test_name.contains("_slash") @@ -262,6 +262,23 @@ pub fn update_epub_references(test_name: &str, actual_dir: &Path) -> Result<(), .map_err(|e| format!("Failed to write metadata: {}", e))?; println!("Updated EPUB metadata for {}", rel_path.display()); + + // Extract and save XHTML content files + let xhtml_content = extract_epub_xhtml(entry.path())?; + let xhtml_dir = ref_dir.join("xhtml"); + fs::create_dir_all(&xhtml_dir) + .map_err(|e| format!("Failed to create XHTML directory: {}", e))?; + + for (filename, content) in xhtml_content { + let xhtml_path = xhtml_dir.join(&filename); + // Create parent dirs if nested (e.g., chapters/ch1.xhtml) + if let Some(parent) = xhtml_path.parent() { + fs::create_dir_all(parent).ok(); + } + fs::write(&xhtml_path, &content) + .map_err(|e| format!("Failed to write XHTML: {}", e))?; + println!("Updated EPUB XHTML: {}", filename); + } } } diff --git a/tests/ref/examples/blog_post/epub/xhtml/portable_epubs.xhtml b/tests/ref/examples/blog_post/epub/xhtml/portable_epubs.xhtml new file mode 100644 index 0000000..e0386e8 --- /dev/null +++ b/tests/ref/examples/blog_post/epub/xhtml/portable_epubs.xhtml @@ -0,0 +1,222 @@ + + + + + + Portable EPUBs + +
+
+

Portable EPUBs

+ Will CrichtonBrown UniversityJanuary 25, 2024Despite decades of advances in document rendering technology, most of the world’s documents are stuck in the 1990s due to the limitations of PDF. Yet, modern document formats like HTML have yet to provide a competitive alternative to PDF. This post explores what prevents HTML documents from being portable, and I propose a way forward based on the EPUB format. To demonstrate my ideas, this post is presented using a prototype EPUB reading system. +
+

The Good and Bad of PDF

+

PDF is the de facto file format for reading and sharing digital documents like papers, textbooks, and flyers. People use the PDF format for several reasons:

+
    +
  • +

    PDFs are self-contained. A PDF is a single file that contains all the images, fonts, and other data needed to render it. It’s easy to pass around a PDF. A PDF is unlikely to be missing some critical dependency on your computer.

    +
  • +
  • +

    PDFs are rendered consistently. A PDF specifies precisely how it should be rendered, so a PDF author can be confident that a reader will see the same document under any conditions.

    +
  • +
  • +

    PDFs are stable over time. PDFs from decades ago still render the same today. PDFs have a relatively stable standard. PDFs cannot be easily edited.

    +
  • +
+

Yet, in the 32 years since the initial release of PDF, a lot has changed. People print out documents less and less. People use phones, tablets, and e-readers to read digital documents. The internet happened; web browsers now provide a platform for rendering rich documents. These changes have laid bare the limitations of PDF:

+
    +
  • +

    PDFs cannot easily adapt to different screen sizes. Most PDFs are designed to mimic 8.5x11″ paper (or worse, 145,161 km2). These PDFs are readable on a computer monitor, but they are less readable on a tablet, and far less readable on a phone.

    +
  • +
  • +

    PDFs cannot be easily understood by programs. A plain PDF is just a scattered sequence of lines and characters. For accessibility, screen readers may not know which order to read through the text. For data extraction, scraping tables out of a PDF is an open area of research.

    +
  • +
  • +

    PDFs cannot easily express interaction. PDFs were primarily designed as static documents that cannot react to user input beyond filling in forms.

    +
  • +
+

These pros and cons can be traced back to one key fact: the PDF representation of a document is fundamentally unstructured. A PDF consists of commands like:

+
+
Move the cursor to the right by 0.5 inches.

Set the current font color to black.

Draw the text "Hello World" at the current position.
+
+

PDF commands are unstructured because a document’s organization is only clear to a person looking at the rendered document, and not clear from the commands themselves. Reflowing, accessibility, data extraction, and interaction all rely on programmatically understanding the structure of a document. Hence, these aspects are not easy to integrate with PDFs.

+

This raises the question: how can we design digital documents with the benefits of PDFs but without the limitations?

+

Can’t We Just Fix PDF?

+

A simple answer is to improve the PDF format. After all, we already have billions of PDFs — why reinvent the wheel?

+

The designers of PDF are well aware of its limitations. I carefully hedged each bullet with “easily”, because PDF does make it possible to overcome each limitation, at least partially. PDFs can be annotated with their logical structure to create a tagged PDF. Most PDF exporters will not add tags automatically — the simplest option is to use Adobe’s subscription-only Acrobat Pro, which provides an “Automatically tag PDF” action. For example, here is a recent paper of mine with added tags:

+
+ +
Figure 1: A LaTeX-generated paper with automatically added tags.
+
+

If you squint, you can see that the logical structure closely resembles the HTML document model. The document has sections, headings, paragraphs, and links. Adobe characterizes the logical structure as an accessibility feature, but it has other benefits. You may be surprised to know that Adobe Acrobat allows you to reflow tagged PDFs at different screen sizes. You may be unsurprised to know that reflowing does not always work well. For example:

+
+
+ +
Figure 3: A section of the paper in its default fixed layout. Note that the second paragraph is wrapped around the code snippet.
+
+
+ +
Figure 4: The same section of the paper after reflowing to a smaller width. Note that the code is now interleaved with the second paragraph.
+
+
+

In theory, these issues could be fixed. If the world’s PDF exporters could be modified to include logical structure. If Adobe’s reflowing algorithm could be improved to fix its edge cases. If the reflowing algorithm could be specified, and if Adobe were willing to release it publicly, and if it were implemented in each PDF viewer. And that doesn’t even cover interaction! So in practice, I don’t think we can just fix the PDF format, at least within a reasonable time frame.

+

The Good and Bad of HTML

+

In the meantime, we already have a structured document format which can be flexibly and interactively rendered: HTML (and CSS and Javascript, but here just collectively referred to as HTML). The HTML format provides almost exactly the inverse advantages and disadvantages of PDF.

+
    +
  • HTML can more easily adapt to different screen sizes. Over the last 20 years, web developers and browser vendors have created a wide array of techniques for responsive design.
  • +
  • HTML can be more easily understood by a program. HTML provides both an inherent structure plus additional attributes to support accessibility tools.
  • +
  • HTML can more easily express interaction. People have used HTML to produce amazing interactive documents that would be impossible in PDF. Think: Distill.pub, Explorable Explanations, Bartosz Ciechanowski, and Bret Victor, just to name a few.
  • +
+

Again, these advantages are hedged with “more easily”. One can easily produce a convoluted or inaccessible HTML document. But on balance, these aspects are more true than not compared to PDF. However, HTML is lacking where PDF shines:

+
    +
  • HTML is not self-contained. HTML files may contain URL references to external files that may be hosted on a server. One can rarely download an HTML file and have it render correctly without an internet connection.
  • +
  • HTML is not always rendered consistently. HTML’s dynamic layout means that an author may not see the same document as a reader. Moreover, HTML layout is not fully specified, so browsers may differ in their implementation.
  • +
  • HTML is not fully stable over time. Browsers try to maintain backwards compatibility (come on and slam!), but the HTML format is still evolving. The HTML standard is a “living standard” due to the rapidly changing needs and feature sets of modern browsers.
  • +
+

So I’ve been thinking: how can we design HTML documents to gain the benefits of PDFs without losing the key strengths of HTML? The rest of this document will present some early prototypes and tentative proposals in this direction.

+

Self-Contained HTML with EPUB

+

First, how can we make HTML documents self-contained? This is an old problem with many potential solutions. WARC, webarchive, and MHTML are all file formats designed to contain all the resources needed to render a web page. But these formats are more designed for snapshotting an existing website, rather than serving as a single source of truth for a web document. From my research, the most sensible format for this purpose is EPUB.

+

EPUB is a “distribution and interchange format for digital publications and documents”, per the EPUB 3 Overview. Reductively, an EPUB is a ZIP archive of web files: HTML, CSS, JS, and assets like images and fonts. On a technical level, what distinguishes EPUB from archival formats is that EPUB includes well-specified files that describe metadata about a document. On a social level, EPUB appears to be the HTML publication format with the most adoption and momentum in 2024, compared to moribund formats like Mobi.

+

The EPUB spec has all the gory details, but to give you a rough sense, a sample EPUB might have the following file structure:

+
+
sample.epub

├── META-INF

│ └── container.xml

└── EPUB

├── package.opf

├── nav.xhtml

├── chapter1.xhtml

├── chapter2.xhtml

└── img

└── sample.jpg
+
+

An EPUB contains content documents (like chapter1.xhtml and chapter2.xhtml) which contain the core HTML content. Content documents can contain relative links to assets in the EPUB, like img/sample.jpg. The navigation document (nav.xhtml) provides a table of contents, and the package document (package.opf) provides metadata about the document. These files collectively define one “rendition” of the whole document, and the container file (container.xml) points to each rendition contained in the EPUB.

+

The EPUB format optimizes for machine-readable content and metadata. HTML content is required to be in XML format (hence, XHTML). Document metadata like the title and author is provided in structured form in the package document. The navigation document has a carefully prescribed tag structure so the TOC can be consistently extracted.

+

Overall, EPUB’s structured format makes it a solid candidate for a single-file HTML document container. However, EPUB is not a silver bullet. EPUB is quite permissive in what kinds of content can be put into a content document.

+

For example, a major issue for self-containment is that EPUB content can embed external assets. A content document can legally include an image or font file whose src is a URL to a hosted server. This is not hypothetical, either; as of the time of writing, Google Doc’s EPUB exporter will emit CSS that will @include external Google Fonts files. The problem is that such an EPUB will not render correctly without an internet connection, nor will it render correctly if Google changes the URLs of its font files.

+

Hence, I will propose a new format which I call a portable EPUB, which is an EPUB with additional requirements and recommendations to improve PDF-like portability. The first requirement is:

+
Local asset requirement: All assets (like images, scripts, and fonts) embedded in a content document of a portable EPUB must refer to local files included in the EPUB. Hyperlinks to external files are permissible.
+

Consistency vs. Flexibility in Rendering

+

There is a fundamental tension between consistency and flexibility in document rendering. A PDF is consistent because it is designed to render in one way: one layout, one choice of fonts, one choice of colors, one pagination, and so on. Consistency is desirable because an author can be confident that their document will look good for a reader (or at least, not look bad). Consistency has subtler benefits — because a PDF is chunked into a consistent set of pages, a passage can be cited by referring to the page containing the passage.

+

On the other hand, flexibility is desirable because people want to read documents under different conditions. Device conditions include screen size (from phone to monitor) and screen capabilities (E-ink vs. LCD). Some readers may prefer larger fonts or higher contrasts for visibility, alternative color schemes for color blindness, or alternative font faces for dyslexia. Sufficiently flexible documents can even permit readers to select a level of detail appropriate for their background (here’s an example).

+

Finding a balance between consistency and flexibility is arguably the most fundamental design challenge in attempting to replace PDF with EPUB. To navigate this trade-off, we first need to talk about EPUB reading systems, or the tools that render an EPUB for human consumption. To get a sense of variation between reading systems, I tried rendering this post as an EPUB (without any styling, just HTML) on four systems: Calibre, Adobe Digital Editions, Apple Books, and Amazon Kindle. This is how the first page looks on each system (omitting Calibre because it looked the same as Adobe Digital Editions):

+
+
+ +
Figure 6: Adobe Digital Editions
+
+
+ +
Figure 7: Apple Books
+
+
+ +
Figure 8: Amazon Kindle
+
+
+

Calibre and Adobe Digital Editions both render the document in a plain web view, as if you opened the HTML file directly in the browser. Apple Books applies some styling, using the New York font by default and changing link decorations. Amazon Kindle increases the line height and also uses my Kindle’s globally-configured default font, Bookerly.

+

As you can see, an EPUB may look quite different on different reading systems. The variation displayed above seems reasonable to me. But how different is too different? For instance, I was recently reading A History of Writing on my Kindle. Here’s an example of how a figure in the book renders on the Kindle:

+
+ +
Figure 9: A figure in the EPUB version of A History of Writing on my Kindle
+
+

When I read this page, I thought, “wow, this looks like crap.” The figure is way too small (although you can long-press the image and zoom), and the position of the figure seems nonsensical. I found a PDF version online, and indeed the PDF’s figure has a proper size in the right location:

+
+ +
Figure 10: A figure in the PDF version of A History of Writing on my Mac
+
+

This is not a fully fair comparison, but it nonetheless exemplifies an author’s reasonable concern today with EPUB: what if it makes my document looks like crap?

+

Principles for Consistent EPUB Rendering

+

I think the core solution for consistently rendering EPUBs comes down to this:

+
    +
  1. The document format (i.e., portable EPUB) needs to establish a subset of HTML (call it “portable HTML”) which could represent most, but not all, documents.
  2. +
  3. Reading systems need to guarantee that a document within the subset will always look reasonable under all reading conditions.
  4. +
  5. If a document uses features outside this subset, then the document author is responsible for ensuring the readability of the document.
  6. +
+

If someone wants to write a document such as this post, then that person need not be a frontend web developer to feel confident that their document will render reasonably. Conversely, if someone wants to stuff the entire Facebook interface into an EPUB, then fine, but it’s on them to ensure the document is responsive.

+

For instance, one simple version of portable HTML could be described by this grammar:

+
+
Document ::= <article> Block* </article>

Block ::= <p> Inline* </p> | <figure> Block* </figure>

Inline ::= text | <strong> Inline* </strong>
+
+

The EPUB spec already defines a comparable subset for navigation documents. I am essentially proposing to extend this idea for content documents, but as a soft constraint rather than a hard constraint. Finding the right subset of HTML will take some experimentation, so I can only gesture toward the broad solution here.

+
Portable HTML rendering requirement: if a document only uses features in the portable HTML subset, then a portable EPUB reading system must guarantee that the document will render reasonably.
+
Portable HTML generation principle: when possible, systems that generate portable EPUB should output portable HTML.
+

A related challenge is to define when a particular rendering is “good” or “reasonable”, so one could evaluate either a document or a reading system on its conformance to spec. For instance, if document content is accidentally rendered in an inaccesible location off-screen, then that would be a bad rendering. A more aggressive definition might say that any rendering which violates accessibility guidelines is a bad rendering. Again, finding the right standard for rendering quality will take some experimentation.

+

If an author is particularly concerned about providing a single “canonical” rendering of their document, one fallback option is to provide a fixed-layout rendition. The EPUB format permits a rendition to specify that it should be rendered in fixed viewport size and optionally a fixed pagination. A fixed-layout rendition could then manually position all content on the page, similar to a PDF. Of course, this loses the flexibility of a reflowable rendition. But an EPUB could in theory provide multiple renditions, offering users the choice of whichever best suits their reading conditions and aesthetic preferences.

+
Fixed-layout fallback principle: systems that generate portable EPUB can consider providing both a reflowable and fixed-layout rendition of a document.
+

It’s possible that the reading system, the document author, and the reader can each express preferences about how a document should render. If these preferences are conflicting, then the renderer should generally prioritize the reader over the author, and the author over the reading system. This is an ideal use case for the “cascading” aspect of CSS:

+
Cascading styles principle: both documents and reading systems should express stylistic preferences (such as font face, font size, and document width) as CSS styles which can be overriden (e.g., do not use !important). The reading system should load the CSS rules such that the priority order is reading system styles < document styles < reader styles.
+

A Lighter EPUB Reading System

+

The act of working with PDFs is relatively fluid. I can download a PDF, quickly open it in a PDF reading system like Preview, and keep or discard the PDF as needed. But EPUB reading systems feel comparatively clunky. Loading an EPUB into Apple Books or Calibre will import the EPUB into the application’s library, which both copies and potentially decompresses the file. Loading an EPUB on a Kindle requires waiting several minutes for the Send to Kindle service to complete.

+

Worse, EPUB reading systems often don’t give you appropriate control over rendering an EPUB. For example, to emulate the experience of reading a book, most reading systems will chunk an EPUB into pages. A reader cannot scroll the document but rather “turn” the page, meaning textually-adjacent content can be split up between pages. Whether a document is paginated or scrolled should be a reader’s choice, but 3/4 reading systems I tested would only permit pagination (Calibre being the exception).

+

Therefore I decided to build a lighter EPUB reading system, Bene. You’re using it right now. This document is an EPUB — you can download it by clicking the button in the top-right corner. The styling and icons are mostly borrowed from pdf.js. Bene is implemented in Tauri, so it can work as both a desktop app and a browser app. Please appreciate this picture of Bene running as a desktop app:

+
+ +
Figure 11: The Bene reading system running as a desktop app. Wow! It works!
+
+

Bene is designed to make opening and reading an EPUB feel fast and non-committal. The app is much quicker to open on my Macbook (<1sec) than other desktop apps. It decompresses files on-the-fly so no additional disk space is used. The backend is implemented in Rust and compiled to Wasm for the browser version.

+

The general design goal of Bene is to embody my ideals for a portable EPUB reader. That is, a utilitarian interface into an EPUB that satisfies my additional requirements for portability. Bene allows you to configure document rendering by changing the font size (try the +/- buttons in the top bar) and the viewer width (if you’re on desktop, move your mouse over the right edge of the document, and drag the handle). Long-term, I want Bene to also provide richer document interactions than a standard EPUB reader, which means we must discuss scripting.

+

Defensively Scripting EPUBs

+

To some people, the idea of code in their documents is unappealing. Last time one of my document-related projects was posted to Hacker News, the top comment was complaining about dynamic documents. The sentiment is understandable — concerns include:

+
    +
  • Bad code: your document shouldn’t crash or glitch due to a failure in a script.
  • +
  • Bad browsers: your document shouldn’t fail to render when a browser updates.
  • +
  • Bad actors: a malicious document shouldn’t be able to pwn your computer.
  • +
  • Bad interfaces: a script shouldn’t cause your document to become unreadable.
  • +
+

Yet, document scripting provides many opportunities for improving how we communicate information. For one example, if you haven’t yet, try hovering your mouse over any instance of the term portable EPUB (or long press it on a touch screen). You should see a tooltip appear with the term’s definition. The goal of these tooltips is to simplify reading a document that contains a lot of specialized notation or terminology. If you forget a definition, you can quickly look it up without having to jump around.

+

The key design challenge is how to permit useful scripting behaviors while limiting the downsides of scripting. One strategy is as follows:

+
Structure over scripts principle: documents should prefer structural annotations over scripts where possible. Documents should rely on reading systems to utilize structure where possible.
+

As an example of this principle, consider how the portable EPUB definition and references are expressed in this document:

+
+
+
<p><dfn-container>Hence, I will propose a new format which I call a <dfn id="portable-epub">portable EPUB</dfn>, which is an EPUB with additional requirements and recommendations to improve PDF-like portability.</dfn-container> The first requirement is:</p>
+
Listing 5: Creating a definition
+
+
+
For one example, if you haven't yet, try hovering your mouse over any instance of the term <a href="#portable-epub" data-target="dfn">portable EPUB</a> (or long press it on a touch screen).
+
Listing 6: Referencing a definition
+
+
+

The definition uses the <dfn> element wrapped in a custom <dfn-container> element to indicate the scope of the definition. The reference to the definition uses a standard anchor with an addition data-target attribute to emphasize that a definition is being linked. The document itself does not provide a script. The Bene reading system automatically detects these annotations and provides the tooltip interaction.

+

Encapsulating Scripts with Web Components

+

But what if a document wants to provide an interactive component that isn’t natively supported by the reading system? For instance, I have recently been working with The Rust Programming Language, a textbook that explains the different features of Rust. It contains a lot of passages like this one:

+
+
let x = 5;

let x = x + 1;

{

let x = x * 2;

println!("The value of x in the inner scope is: {x}");

}

println!("The value of x is: {x}");

}
+

This program first binds x to a value of 5. Then it creates a new variable x by repeating let x =, taking the original value and adding 1 so the value of x is then 6. Then, within an inner scope created with the curly brackets, the third let statement also shadows x and creates a new variable, multiplying the previous value by 2 to give x a value of 12. When that scope is over, the inner shadowing ends and x returns to being 6. When we run this program, it will output the following:

+
+

A challenge in reading this passage is finding the correspondences between the prose and the code. An interactive code reading component can help you track those correspondences, like this (try mousing-over or clicking-on each sentence):

+
+
fn main() { 
+  let x = 5
+  let x = x + 1
+  { 
+    let x = x * 2
+    println!(“The value of x in the inner scope is: {x}”);
+  }
+  println!(“The value of x is: {x}”);
+}
+

This program first binds x to a value of 5.Then it creates a new variable x by repeating let x =,taking the original value and adding 1 so the value of x is then 6.Then, within an inner scope created with the curly brackets,the third let statement also shadows x and creates a new variable,multiplying the previous value by 2 to give x a value of 12.When that scope is over, the inner shadowing ends and x returns to being 6.

+
+

The interactive code description component is used as follows:

+
+
<code-description>

<pre><code>fn main() {

let <span id="code-1">x</span> = <span id="code-2">5</span>;

<!-- rest of the code... -->

}</code></pre>

<p>

<code-step>This program first binds <a href="#code-1"><code>x</code></a> to a value of <a href="#code-2"><code>5</code></a>.</code-step>

<!-- rest of the prose... -->

</p>

</code-description>
+
+

Again, the document content contains no actual script. It contains a custom element <code-description>, and it contains a series of annotations as spans and anchors. The <code-description> element is implemented as a web component.

+

Web components are a programming model for writing encapsulated interactive fragments of HTML, CSS, and Javascript. Web components are one of many ways to write componentized HTML, such as React, Solid, Svelte, and Angular. I see web components as the most suitable as a framework for portable EPUBs because:

+
    +
  • Web components are a standardized technology. Its key features like custom elements (for specifying the behavior of novel elements) and shadow trees (for encapsulating a custom element from the rest of the document) are part of the official HTML and DOM specifications. This improves the likelihood that future browsers will maintain backwards compatibility with web components written today.
  • +
  • Web components are designed for tight encapusulation. The shadow tree mechanism ensures that styling applied within a custom component cannot accidentally affect other components on the page.
  • +
  • Web components have a decent ecosystem to leverage. As far as I can tell, web components are primarily used by Google, which has created notable frameworks like Lit.
  • +
  • Web components provide a clear fallback mechanism. If a renderer does not support Javascript, or if a renderer loses the ability to render web components, then an HTML renderer will simply ignore custom tags and render their contents.
  • +
+

Thus, I propose one principle and one requirement:

+
Encapsulated scripts principle: interactive components should be implemented as web components when possible, or otherwise be carefully designed to avoid conflicting with the base document or other components.
+
Components fallback requirement: interactive components must provide a fallback mechanism for rendering a reasonable substitute if Javascript is disabled.
+

Where To Go From Here?

+

Every time I have told someone “I want to replace PDF”, the statement has been met with extreme skepticism. Hopefully this document has convinced you that HTML-via-EPUB could potentially be a viable and desirable document format for the future.

+

My short-term goal is to implement a few more documents in the portable EPUB format, such as my PLDI paper. That will challenge both the file format and the reading system to be flexible enough to support each document type. In particular, each document should look good under a range of reading conditions (screen sizes, font sizes and faces, etc.).

+

My long-term goal is to design a document language that makes it easy to generate portable EPUBs. Writing XHTML by hand is not reasonable. I designed Nota before I was thinking about EPUBs, so its next iteration will be targeted at this new format.

+

If you have any thoughts about how to make this work or why I’m wrong, let me know by email or Twitter or Mastodon or wherever this gets posted. If you would like to help out, please reach out! This is just a passion project in my free time (for now…), so any programming or document authoring assistance could provide a lot of momentum to the project.

+

But What About…

+

A brief postscript for a few things I haven’t touched on.

+

…security? You might dislike the idea that document authors can run arbitrary Javascript on your personal computer. But then again, you presumably use both a PDF reader and a web browser on the daily, and those both run Javascript. What I’m proposing is not really any less secure than our current state of affairs. If anything, I’d hope that browsers are more battle-hardened than PDF viewers regarding code execution. Certainly the designers of EPUB reading systems should be careful to not give documents any additional capabilities beyond those already provided by the browser.

+

…privacy? Modern web sites use many kinds of telemetry and cookies to track user behavior. I strongly believe that EPUBs should not follow this trend. Telemetry must at least require the explicit consent of the user, and even that may be too generous. Companies will inevitably do things like offer discounts in exchange for requiring your consent to telemetry, similar to Amazon’s Kindle ads policy. Perhaps it is better to preempt this behavior by banning all tracking.

+

…aesthetics? People often intuit that LaTeX-generated PDFs look prettier than HTML documents, or even prettier than PDFs created by other software. This is because Donald Knuth took his job very seriously. In particular, the Knuth-Plass line-breaking algorithm tends to produce better-looking justified text than whatever algorithm is used by browsers.

+

There’s two ways to make progress here. One is for browsers to provide more typography tools. Allegedly, text-wrap: pretty is supposed to help, but in my brief testing it doesn’t seem to improve line-break quality. The other way is to pre-calculate line breaks, which would only work for fixed-layout renditions.

+

…page citations? I think we just have to give up on citing content by pages. Instead, we should mandate a consistent numbering scheme for block elements within a document, and have people cite using that scheme. (Allison Morrell points out this is already the standard in the Canadian legal system.) For example, Bene will auto-number all blocks. If you’re on a desktop, try hovering your mouse in the left column next to the top-right of any paragraph.

+

…annotations? Ideally it should be as easy to mark up an EPUB as a PDF. The Web Annotations specification seems to be a good starting point for annotating EPUBs. Web Annotations seem designed for annotations on “targetable” objects, like a labeled element or a range of text. It’s not yet clear how to deal with free-hand annotations, especially on reflowable documents.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/blog_site/epub/blog_site.metadata.json b/tests/ref/examples/blog_site/epub/blog_site.metadata.json index 30cff5b..3d246ea 100644 --- a/tests/ref/examples/blog_site/epub/blog_site.metadata.json +++ b/tests/ref/examples/blog_site/epub/blog_site.metadata.json @@ -1,6 +1,6 @@ { "filetype": "epub", - "file_size": 4665717, + "file_size": 4665720, "title": "Screening the Subject | Severance", "language": "en", "spine_files": [ diff --git a/tests/ref/examples/blog_site/epub/xhtml/severance-ep-1.xhtml b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-1.xhtml new file mode 100644 index 0000000..3e63eaf --- /dev/null +++ b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-1.xhtml @@ -0,0 +1,45 @@ + + + + + + Good news about hell - Severance [s1/e1] + +
+

Good news about hell - Severance [s1/e1]

+ +

The first thing to notice is the colour palette. She is dressed in blue, but her hair is chestnut red. It spills out for the frame of her figure into the table around it, blockaded at its border by chairs and a carpet clad in green, yellow, then green again; then gray. The establishing shot is a bird’s eye view of an unknown woman who is soon revealed to have been put in the board room by someone else’s design, who learns about her predicament only by a man’s voice that emanates from the little device that rests on the table along with the woman, arranged so that it aims directly at her head.

+

This opening image is a graph of the subject’s predicament on the severed floor at Lumon. Blue is the company colour. Employees are almost invariably dressed in shades of it– navy, midnight, Prussian, Oxford, cobalt– and more reliably so as we work our way up the hierarchy. Red is unruly passion, the tone of tempers that itch to tear off the straitjacket directives, to disregulate the business-as-usual in which there is no obvious place for illicit activities. Green is the accent of Macro Data Refinement, the division of Lumon in which the show’s protagonists are employed. The device directs a man’s voice at a woman’s body in an attempt to keep her tempers in check, to ensure her firecraft does not smoke out the staid edifice of personality management, to order her “perceptual chronologies” accordingly. (Later in the episode, we learn that she almost manages to “break in” on the control room during that opening sequence: the solidity of its enclosure is threatened from the very first.)

+

It is instructive to attempt to articulate the dynamics that this graph indexes before we start talking about other scenes in the show. Graphs are not at one with what they represent, for in the decision to render ‘data’ in the very act of a representation, we both lose and gain distinction of the dynamics in question. The voice that opens Helly R up to the world of Lumon’s severed floor begins: “Who are you?” This question is a mistake. We retroactively learn, in a later scene, that Mark S was in fact supposed to begin with a less interrogative, more perfunctory: “Hi there, you on the table. I wonder if you’d mind taking a brief survey.” As Irving puts it: “You [Mark S] skipped the preamble”. Helly R is thrust, by this accident, immediately into questioning not only herself, but also the self-assurance of the voice that interrogates her. Does this voice in my head [she could be thinking] really know what it is doing? Or is it just a role of similarly confused actors struggling to stick to a badly written script?

+

This episode-length recap of the first episode names this graph ‘the Helly incident’, a poorly executed orientation of Helly’s newfound subjectivity that can be blamed at one level on Mark S (for starting with the wrong part of the manual), at another on Mr. Milchick (for misguiding Mark while he was distracted setting up the visual feed), on Ms. Cobel (for giving Mark Petey K’s old manual without redacting his obscurely scribbled notes and paper bookmarks), or even on Irving (for neglecting to intervene and clarify how Mark should begin being the more senior refiner in the situation: “Irving will be there to shadow. Just stick to the flowchart and escalate properly depending on dialectics.”). Wherever to place blame, there is doubtless a misconfiguration that takes place. Helly’s instinctual reaction seems to be to try to kill the voice pointed at her head, rather than to befriend it as Mark states he did (where Petey was Mark). (Helly will eventually have sex with the source of the voice, rather than murdering or fraternizing with it.) In this episode, however, Mark (the voice’s source) is physically assaulted by Helly, dented in his temple by the same vocalization device that mediated their first communication.

+ +

So this is the Macro Data refiner’s situation. On the one hand, she is affronted with a voice that compels her to abide by the rules and permits her to enjoy some small reliefs (egress from a locked room) if she concedes to it. On the other, she is always teeming and thus flirting with red, considering escape routes that involve drawing blood, setting off alarms, or removing clothes.

+

This unruly red is what Macro Data Refinement’s greening procedures are supposed to contain to produce a completely controlled and scripted blot of blue. Perhaps this is why the glipse of the vacant desks planned for the severed floor’s expansion are draped in purple, for that shade of subjectivity would better incorporate the contrasting contours into a unified and taskable tone. The red that threatens Lumon’s corporate, calm, and collected blue (the Lumon logo is a water droplet that suspiciously resembles a camera) is splattered across scenes in the episode. It is, for example, the envelope that Petey slips Mark at the company-owned restaurant Pip’s with the suggestion that he should read it if he wants to know “what’s going on down there”. It is the sweater Mark wears to his sister’s dinnerless dinner party, punctuated by red place mats (“what a lot of people overlook, I think, is that life is not food”), where the ontological substance of his innie is called into question, and where we learn about the passions he has lost– the history of World War II, educating, whiskey– the last of which seems to have given way to an indiscriminate consumption of beer, wine, anything that will drown out the clarity of sober consciousness. It is the general hue of his sister’s house, which consisently wants him to question that placid blue of his company-subsidized housing at Baird Creek Manor.

+

This dinner tells us something more about the subject in question in Severance. Just as Helly’s outie had alerted us to the basic principle in the video her innie was shown in curiously lo-fi resolution to conclude her innie’s orientation– “perceptual chronologies… surgically split”– Mark’s predicament is comparably explained to him by another more or less ignorant (we can’t help but imagine) third party: “One’s memories are bifurcated, so when you’re at work, you have no recollection of what it is you do there.” As pretentious as they are, the dinner’s guests do seem to be attuned to an important dimension of the meaning of life, which is that it can’t only be about satiating biological needs such as food. What each individual ‘needs’ is a disharmonious melange of needs and demands, openings of desire that emerge not only through a graph of bare necessities– food, water, shelter– but also through capricious carapaces that emerge from more ambiguous pinings in the social sphere– company, care, love. The real question of Lumon’s smooth functioning is whether it will be able to effectively plug up these pinings, the incidental moments at work where one wonders what one is really doing with one’s life, whether the company can really manage its employees’ unsanctioned thoughts and the way in which those illicit ideas seep into the daily practice of their workerhood. More on the plasticity of our needs and drives to satisfy them in later posts.

+ +

Ms. Cobel, in contrast to Helly’s and Mark’s doubtful and doubting red, is a stormy and icy blue. (We must wait until season two to uncover the historical and psychological depth of this colour for Harmony Cobel.) She is the figure with a body that seems to be the most in charge, of those we meet in this episode. Though Ms. Cobel is not a master in herself, it seems, for she too is subjected to a disembodied voice-via-device, ‘the board’, albeit which only appears evidently as an ear so far (“The board won’t be contributing to this meeting vocally”). Cobel is responsible for keeping the severed floor’s uncertainty in check, the ‘head’ that sits atop the variegated limbs of its disobedient body.

+

When Cobel reprimands Mark for his derailing of Helly’s orientation, she recalls an obscure and theological aspect of her parentage:

+
You know, my mother was an atheist. She used to say that there was good news and bad news about hell. The good news is, hell is just the product of a morbid human imagination. The bad news is, whatever humans can imagine, they can usually create.
+

At the close of the episode, just before Mark’s senile neighbor Mrs. Selvig (who we have only heard about through Mark’s voice thus far, when he is on the phone with her) visually reveals herself to be the same woman as Ms. Cobel, she gives a slightly different account of her heritage:

+
You know, my mother was a Catholic. She used to say it takes the saints eight hours to bless a sleeping child. I hope you aren’t rushing the saints.
+

It’s unclear at this point whether Cobel is a severed worker like Mark, or whether there is some other reason for her (strange, almost senseless) duplicity. Why lie about the religious leanings of one’s mother? Or maybe ‘mother’ is actually a name for something else, a kind of interim authority that gives synthetic weight to some hearsay, rumor, or idle phrase. (The other cameo of an ambiguously defined mother in this episode is in question five of Helly’s orientation survey: “To the best of your memory, what is or was the color of your mother’s eyes?”) Perhaps it is that, severed or not, atheist or Catholic, Cobel’s subjectivity is structured by a comparable split in her perceptual chronologies, whereby some memories (of her mother) get more airtime in her conscious experience of herself than others.

+

Severance flirts with this idea extensively, that the innie/outie dyad is analagous to the unconscious/conscious experience that we, as subjects, have of ourselves. Mark’s sister Devon hints at the psycho-logical reading of the severed condition in her diagnosis of Mark’s morose (outie) predicament as a state of failed therapy in response to mourning for his late wife: “I just feel like forgetting about her for eight hours a day isn’t the same thing as healing.” As with not-mothers and the plasticity of the drive, we will address the psychoanalytic implications here in later posts; but to finish I want to bring our attention to the imaging of time at work in just this first episode.

+

The fascinating details of failed synchronisation between all the watchfaces we see are enumerated in this Reddit thread. Many of the watch hands appear to be stalled, and the crossover from each to the next– as when Mark Scout switches his wrist watch in preparation for his elevator descent into the workday of innie Mark S– doesn’t match with our experience of the actors on screen. One of the few things we do know about the severance procedure is that it ‘alters perceptual chronologies’, and that this messing with a subject’s sense of time is thought to

+
    +
  1. make them more adequate or productive in a certain kind of work (for why else would Lumon go to the necessary lengths to sever some employees)
  2. +
  3. supposes to section off innie memories and experience from outie memories and experience
  4. +
+

So the subject’s subjectivity is marked by its sense of time, and Lumon’s success (profitability?) hinges in some way on altering their employees’ stable sense of it while in the space of the severed floor.

+

Mark S’s temporal predicament here has been explained by a man whose last name we get by speeding up the saying of his own, Karl Marx (Mar-k-S). Logically speaking, Marx argues, there is an amount of time that goes missing in the worker’s employment by way of a wage, when he advances some portion of his time to the capitalist in exchange for a pay-check one or more weeks later. I refer the reader interested in the details to chapter 20 of Capital Vol. I: but the essential point here is that it is through an obfuscation of the real value of a worker’s time that the capitalist manages to produce surplus-value. The production of this kind of time-distorted surplus-value is the engine of capitalism as a social relation that appears, on the surface, to be equally fair to capitalist and worker alike. So the project of controlling ‘perceptual chronologies’ with which Lumon seems to be so concerned is perhaps not as esoteric and inessential as it might at first seem. Perhaps it is an embodiment of the core ingredient of the company’s success as a company, of its incorporation as an entity that ought to be sustained even at the expense of its members’ happiness, their health, and their livelihoods.

+
+



+
+ +
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/blog_site/epub/xhtml/severance-ep-2.xhtml b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-2.xhtml new file mode 100644 index 0000000..58f2971 --- /dev/null +++ b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-2.xhtml @@ -0,0 +1,50 @@ + + + + + + Half Loop - Severance [s1/e2] + +
+

Half Loop - Severance [s1/e2]

+

In the first episode, we were introduced to the two-sided subject at Lumon. On the one hand, there is Mark S, the innie, who is screened for the first and major part of the episode. On the other, Mark Scout, the outie, to whose predicament we are introduced in the concluding scenes. S1E2 opens with a rewind on how innie Helly R came to be: how Milchick handed her flowers at end of her first day (which we glimpsed in S1E1 when Mark almost ran her over), a glimpse of her confidence gliding into the operating room on a higher floor of the same Lumon complex we saw Mark leave, a stereoscopic view of the implant procedure by which she becomes an android whose existence is “spatially dictated” by Lumon’s mysterious machinations.

+

Lumon Industries

+ +

Lumon is a corporate pastiche, and not only of technology companies. Lumon seems to have its hands in surgical hardware (the operating room equipment), digital technology (‘Macrodata Refinement’), and medicines and topical salves (as discussed at the dinner party in S1E1 - “What don't they make?”). It is a quintessentially American jack of all trades, a global power in its own right cohered by a family dynasty—the Eagans—recalling the Du Ponts or the Rockefellers.

+

The more obvious comparison to make, however, is between Lumon and Apple, perhaps in part because the show screens on Apple TV Plus. The style of the computers on the severed floor recalls the dawn of the era of personal computing in the 1970s and 80s, an aesthetic imaginary in which Apple plays an important role.

+

Indeed, the aura of Lumon as a futuristic computing corporation from the late 70s is reinforced by the fact that its headquarters are shot at Bell Labs in New Jersey, a building that has now been renovated as a mixed-use office for high-tech startup companies as Bell Works. Bell Labs is the quasi-mythological source in the contemporary corporate technology culture (Silicon Valley) of the idea that a certain kind of research freedom characterized by open-ended product delivery timelines and serendipitous encounters in open office plans can cultivate ground-breaking technology. (Mark Zuckerberg recommended a book on Bell Labs as one of his “important books” of 2015.) The irony of this setting, of course, both in Severance and in the technology companies it parodies in the American landscape in 2025, is that the workplace has never been more saturated with surveillance and micro-management. The overhead shot of Helly R that opened the series is indicative here again, as is the complementary overhead of MDR’s desks we get in this episode: there is always something watching from above, it seems, even if what it captures of the actual activity is a flattened and at times misrepresentative image.

+

There are also evocations of Microsoft and IBM in Lumon, such as the Clippy-like guide on the manual handed to Helly in the episode, or the apparent requirement of suits on the severed floor echoing IBM’s infamous strict dress code. Lumon is a melange of imaginary pasts, presents, and futures in American innovation. It is futuristic in the framing of its bio-technological project of perceptual management—and in the “data smuggling” detectors that are installed in the elevators to the severed floor, about which more soon—but retrofitted in its aesthetic, in its management style, and in its outdated repertoire of daily devices. Recall, for example, Milchick’s handheld camcorder, and the tube-activated (vacuum-tube?) camera he uses to snap the official photo of the new group of refiners.

+ +

The overhead of Lumon Industries itself depicts a sketchy graph of a brain, one can’t help but think. Its upper floors all operate above board with normally conscious workers, whereas underground there is something sensitive enough happening so as to require extra precautions. In S1E1′s analysis, we introduced the idea that Lumon’s interest in severing workers has to do with the mechanics of capital, in that surplus value can only ever be produced (in Marx’s account) through the structural theft of time from its laborers.1 Lumon’s spatial layout suggests that there might also be a psychoanalytic metaphor at stake in severance as an operation, where the happenings that occur in the business brain’s basement are essential to what it really is, why it does what it does.

+

Though Freud’s theory has been popularized as a topographical notion, wherein the unconscious is the submerged part of the mind’s iceberg of which we only see the tip, there is good reason to believe that this spatial description misrepresents how the unconscious should be properly understood. Lacan thus preferred topological descriptors to suggest that, if the unconscious is a ‘place’ or ‘site’, it contradicts any over-simplistic understanding of spaces that are distinctly separable. The relationship between the conscious and the unconscious in a psychoanalytic theory of the subject, I would suggest, is better understood through the figure of a coin with two inseparable sides. The meaning of any one side (‘heads’) derives from the meaning of its opposite (‘tails’); and it is thus insensible to imagine separating one part from the other without repressing something fundamental about the structure of the subject as a whole.

+

Lumon, though, seems to want desperately to keep innies from being in contact with their outies. Indeed, the very project of severance seems to have something fundamental to do with managing repression effectively, with renovating the worker into a perfectly divided self that cannot complain about the conditions of her labor through the fact of not knowing anything about them. (When Mark is given a dinner coupon on account of his head injury in S1E1, the real cause of the scar—Helly R’s riotous attempt to escape the orientation room—is not revealed to outie Mark.) The subject in Severance is split and maintained as such. The ‘unconscious’ of one’s home life should not affect one’s ‘conscious’ ability to perform at work.

+

The vice-versa is also true. Outies cannot suffer the ‘unconscious’ of their innies, either. Mark Scout’s decision to sever himself seems to be an attempt to repress the devastating effect of his experience of his wife’s death for some part of the day, given that he admits he was unable to continue his job as a history teacher due to alcoholism. At Lumon, however, Mark’s alcoholism is brutally functional; as his innie must suffer what (lack of) energy he is given by outie Mark’s actions the night before (“I find it helps to focus on the effects of sleep since we don’t actually get to experience it”).

+

The intellectual impoverishment of Lumon’s severed workers is further exposed in this episode as Dylan tries to convey to Helly the substance of what there is to live for as a severed innie: his “embarrassment of wealth” that consists of finger traps, a caricature portrait, and the hope that there might be a “waffle party” on the horizon. The sad satisfactions that severed workers aspire to reinvigorate the sense of the phrase “wage slavery”, an important formulation that in fact has solid footing in Marx’s analysis of capital. For Marx, it is worth comparing the wage worker’s predicament to the slave’s; for both must labor not for themselves, for their own ends and aspirations, but for an external master that appropriates their efforts. The important distinction is that, while in actual slavery the slave’s enthrallment to the master is explicit and explicitly enforced by means of force, in wage slavery the figure of the master is more diffuse, and hierarchical distinctions are ‘justified’ in the discursive suggestion of their being fairly and freely established. The proletariat (wage laborer) is free to choose her own master on the market, selling her labor power to whomever she chooses. But she is not free to refuse to sell her labor as labor-power; as this “wage slavery” is the generalized means of her reproduction and ability to go on living. So the proletariat is enslaved to a structure, not a person, and that structure is characterized by the reduction of labor in its multifarious forms to labor-power, a measurement of labor in time that thus becomes exchangeable on the market. In capitalism, in other words, freedom is structurally reduced to the freedom to choose to whom one sells one labor-power: which is not the same thing as freedom tout court. Thus is the wage laborer unfree in a way that is comparable, though not equivalent, to the slave.

+

Death at Lumon

+

The death culture at Lumon should also be doubly refracted through Marx’s analysis of how capital reduces its workers to shadows of themselves on the one hand, and a psychoanalytic understanding of the subject on the other. When Mark gets emotional about Petey’s disappearance during the game of office introductions (which tellingly involves passing around a brignt red ball), Milchick reprimands him with the following explanation:

+
I think this is a good time to remind ourselves that things like deaths happen outside of here. Not here. A life at Lumon is protected from such things. And I think a great potential response to that from all of you is gratitude.
+

Severed workers are insulated from death because the very structure of their subjectivity distances the meaning of its concept. Innies symbolically ‘die’ when their outies do not come back to work, but this event does not necessarily coincide with their physical death, which as Milchick suggests should only be imagined to take place in the world of their outies. There is a contradiction here, though, as a physical accident at work would propagate through to an innie’s outie. So Milchick’s repression of the notion of death must be recognized as just that: a repression of a certain moment in or dimension of logic (a moment that is too dangerous or frightening to imagine saying out loud), and not as an explication of the necessary consequences of a thorough logic of life.

+

Milchick’s philosophizing also points to something more sinister in the structure of the severed subject. The severed worker is protected from death, perhaps, because there is a sense in which he is already undead. Doomed to exist in the artificial enclosure of Lumon’s basement and placated only by the pathetic enjoyments of finger traps, company coffee, ideological art, and the odd waffle party, what is there, really, to live for at Lumon? The motto briefly shown on the implant hardware in Helly’s operating room scene at the episode’s opening has a morbid resonance here: “Don’t live to work. Work to live.”

+

There is a stronger psychoanalytic sense in which we might make sense of Milchick’s discourse on death that is worth mentioning here, too. Lacan articulates a distinction between two kinds of death in his theory of the subject, a first death that is biological and a second death that is symbolic. I will explicate this theory later in S1, when Milchick’s foreshadowing of death’s importance in the show bubbles clearly to the surface in a later episode.

+

Capturing and controlling the symbolic

+

Let’s talk about the “symbol detectors” in the elevators, which are introduced in this episode. These are the real basis of how Lumon separates innies from outies, as they supposedly ensure that no notes, no language, is passed between the two kinds of self. In S1E1, we saw outie Mark put the tissue he had been crying into in his car in his pocket; and we then saw innie Mark confidently strolling out of the elevator on the severed floor, quizzically discovering the tissue in his pocket, and tossing it into a bin on his walk down the hall to MDR. So the suggestion has already been planted in our (the viewers’) mind that it is possible to traffic objects across the boundary. The other clear evidence of this is offered here in S1E2, where Irving similarly, quizzically, observes the black sooty substance underneath his fingernails during the distraction of the melon party.

+

Yet Helly’s note to herself triggers the alarms, resulting in the elevator doors refusing to close and a screen washed out with red alert. So they do seem to have some power to detect ‘symbols’. But what marks the boundary between a symbol and a non-symbol for this technology? It is not only explicit language in the form of written or spoken words that make meaning for us as human animals. We are affected by a frightening range of other things; colors, tactile memories, qualities of our past selves that seep into our present (such as too much alcohol drunk the night before). So it is hard to imagine, knowing the complexities of our selves as we all do, that Lumon could really effectively police the boundary between innies and outies, even with its back-to-the-future technological prowess.

+

Indeed, the audio recording that innie/outie Petey shows outie Mark in his hideout at the greenhouse reveals the insecurity of symbol detection at Lumon. In order to get a recording of what he was subjected to in the Break Room, he must have been able to get that retro handheld device back up into the ‘real’ world. So either the elevators weren’t able to pick it up, or there is some other way for innies to move between the supposedly demarcated spaces. Either way, the symbol policing at the innie/outie border seems to have some shortcomings.

+

A brief note on Petey’s dishevelled greenhouse to conclude, as this episode is where we are first introduced to much of the geography that will become important in the series: the break room, wellness, MDR, optics and design, Mark’s basement, the company restaurant (where Mark has his insufferably awkward date), the elevator, the MDR kitchen, the operating room, the Lumon foyer. Petey’s greenhouse, like many of the spaces in Severance, is a graph that both embodies and reflects a psycho-social moment of the show. Green like Macrodata Refinement, but much less put-together, the greenhouse reveals the underside of Lumon’s apparent glaem, the unconscious damage that its project of perfection wreaks on its workers psychologically and physically. Petey shows us that the worker, like so many words and things in the show, is not simply what it seems, but consists also of an excess signification that inevitably creeps into its conspicuous comportment. Mark is a depressed drunkard on the outside, and Irving (it seems) has his fingers in some hellish kind of black pie, a color that takes over his desk as he dozes off when he lets the distinction between his waking and unconscious self slip, we might say, when the reality of sleep threatens the security of being awake. There is, as the imagery in the poster of the ‘Whole Mind Collective’ that motivates Mark to bunk off and follow up on Petey’s enigmatic red letter suggests, a real revolution of sorts brewing beneath the surface of a fantasy of symbolic control.

+
+



+
+ +
+
+
    +
  1. 1Boštjan Nedoh has evocatively called this operation “theft without a thief”.
  2. +
+
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/blog_site/epub/xhtml/severance-ep-3.xhtml b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-3.xhtml new file mode 100644 index 0000000..3326795 --- /dev/null +++ b/tests/ref/examples/blog_site/epub/xhtml/severance-ep-3.xhtml @@ -0,0 +1,70 @@ + + + + + + In Perpetuity - Severance [s1/e3] + +
+

In Perpetuity - Severance [s1/e3]

+ +

We need to talk about Ms. Cobel

+

As we noted in analysis of S1E1, she typically storms the screen with an icy blue, a temper (the significance of this word we shall unpack shortly) that seeks to quell the fiery red that flickers in and out of the consciousness of workers on the severed floor. The ominous ending to that first episode intimated that, while her wintery business has its office underground, it also warrants her prying into Mark’s outie’s personal life in Baird Creek’s subsidized Lumon housing. Indeed, it seems that Miss Cobel lives in Mark’s housing complex, too. From the state of her fridge, though, which we see in the foreground of a shot that implies surreptitious surveillance at work in her intimate space– a sense that has already been produced in Mark’s home with objects littered in the frame’s foreground– it doesn’t appear that she spends very much time making a home there. (Not too unlike Mark, perhaps.)

+

Ms. Cobel is a kaleidoscopic vector of strange femininity in the show. She is at once old widow next door, a girl-boss superior on the severed floor, and a little girl prone to tantrums. As Mrs. Selvig, the hare-brained widow next door, she offers Mark unwanted company and cruddy cookies. Yet we know by now that this is apparanetly a ruse, a senile disguise through which the conniving Harmony Cobel can keep an eye on her employee, Mark, beyond the bounds of his time at work. At Baird Creek, she is a middle-aged executive in the clothing of an older and less cognitively composed character.

+

But even if Ms. Cobel is the ‘real’ Mrs. Selvig, there is still something anile about her character. She can be both comandeering and childish, as we see in her encounter with (innie) Mark S when he arrives unannounced at her office to request a kind of permission to take Hellie to the perpetuity wing in S1E3. Commandering, because she accosts Mark with bureaucratic demands in her role as his boss (“And have you filled out a common-reservation slip?”). Childish, because she literally throws a mug at him out of a petty frustration that is unbefitting of a mature manager. Cobel rationalizes her childish temper as follows:

+
What I just did was something I knew that you could handle and grow from. It was very painful for me. I hope that you’ll let it help you.
+

This outburst locates something undecided within Ms. Cobel, a moment in relation to Mark where she lets her personal anger supersede her role as his manager. This mug-throwing episode demonstrates that Cobel, too, is capable of breaking character as head of the severed floor and allowing some other aspect of her self to seep in, despite the pretense of a calm composure. The thrown-mug, in other words, is the wish fulfillment heralded by Cobel’s stunningly funny, inappropriate remark to Hellie during her orientation in S1E1; “I’ve wanted to pummel Mark myself, but I am his employer.” Even Cobel, who is supposed to be more in charge of herself than the MDR employees who are her inferiors– her breaking into Mark’s house while he isn’t there implies is that she is unsevered, and thus more ‘responsible’– harbours desires that exceed and contradict the prescribed role she is supposed to play.

+

The image of Cobel above confirms her as childish in some respects. Notice that here, at ‘home’, she wears her hair in pigtails rather than loosely around her shoulders. But it also paints her as a scopophilic and overbearing mother. Whatever she is doing creating excuses to talk to Mark’s outie as Mrs. Selvig, it becomes clear in this episode that there is a convoluted kind of care at stake in her creepy and overcurious work. Peering at him as he wanders up from the basement (Cobel doesn’t seem to know that Petey is also down there at this point, though her break-in later in the episode suggests that she suspects something is awry), she murmurs to herself, “Oh, Mark. Are you all right?”

+

This is a strange exhibition of affection, coming from the same woman who will throw a mug at Markfor his failure to “get MDR to its numbers” as department chief, who knowingly subjects him to the break room– which we observe on screen for the first time later in the episode– and who steals the book left by his brother-in-law as a gift at his doorstep. Despite these mistreatments, Cobel does still seem to hold some perverted penchant for and attachment to Mark. As HaxDogma notes in his review of this episode, it is hard to see Mark’s promotion to department chief after Petey disappears as anything other than a nepotistic appointment, given that Irving is clearly the more experienced refiner in a number of respects (orientation procedure, group photo protocol, number of years spent on the severed floor, to name a few). Cobel’s overinquisitive manner on display in this episode is perhap best described as motherly, even as she is certainly not a paradigmatically good mother.

+

There is also something undoubtedly sexual about Cobel’s relationship to Mark. Her lingering at the door in S1E2 waiting to be invited in, her awkward and suggestive mention of her late husband’s building an apartment in the back of their abode in heaven “in case I found a new man before I got there”, her creating an excuse to talk to him by pretending to de-ice her stoop; and, naturally, her peeping at him through the window. She is either a stalker by-the-book, or (more charitably) a lonely woman who is searching for some missing satisfaction. Most likely, she is an inextricable concoction of the two. Cobel wants to have Mark’s cake and eat it too; to be at once his mother, his corporate superior, and (we can’t help but suspect) his lover. Like many put in positions of power, she has trouble setting her more inapproriate desires aside so as to simply ‘do her job’.

+

Primal father figures

+

Cobel’s mother energy is arguably muted and mixed up in her Sphinxesque triplicity. But the father energy on display in this episode is, by contrast, loudly and proudly pronounced in at least three different figures: Petey, Irving, and, of course, Kier Eagan.1 Before tackling these fathers one by one, it is instructive to straightforwardly and schematically lay out the Oedipus complex, an ‘absolute fiction’ that nonetheless, Freud claims, depicts something foundational about the graph of the speaking subject, the graph in which we took interest in our analysis of S1E1.

+

The Oedipus complex is so-named because it takes its architecture from the figure of Oedipus as he appears in the ancient Greek playwright Sophocles’ trilogy, which consists of the plays Oedipus Rex, Oedipus at Colonus, and Antigone. (Oedipus’ tragic tale is drawn from a mythology that predates these plays, but the story is nonetheless usually traced to its Sophoclean production.) Oedipus is well-known to students of psychoanalysis because of Freud’s making him into a complex, which is generally (mis)understood as ‘every person wants to kill their father and fuck their mother’. Famously, Oedipus killed his father– at a crossroads, thinking he was simply a threatening stranger at the time– and married his mother– not understanding that relation in the moment of the act, either.

+

Jacques Lacan rendered the Oedipus complex more philosophically significant than this overblown and crude Freudian telling. For Lacan, the Oedipus complex designates an abstract account of how desire is produced by the speaking subject in relation to the formative figures with which it is in relation. As he notes in one of his 1938 text, The Family Complexes:

+
our criticism since Freud presents this psychological entity [the Oedipus complex] as the specific form of the human family and subordinates all social variations of the family to it. (Lacan 2002, p.35)
+

The Oedipus complex is not so much a diagnosis of a particular perversion that is presumed universal, in the sense that everyone consciously suffers by repressing these secret dual desires to kill (my father) and to fuck (my mother). It is rather an important part of how he architects a philosophy of the subject’s relation to itself (and others) by way of a “triangular conflict” (Lacan 2002, p.41) between three figures: one’s self, the Mother, and the Father.

+

The Mother is the subject’s first known object that is seen as separable from one’s sense of self. We can imagine this through the process of weaning, of a mother teaching her baby that sustenance ought to be sought in solid foods rather than directly from her teat. Originally, a baby does not have a firm enough sense of itself to recognize that the Mother’s teat is separated from its own body. When it wants nourishment, it cries, and a breast brimming with milk appears (assuming a good mother, here). The breast seems almost part and parcel of the baby, from its perspective, as what reason does it have to think otherwise? (We are assuming here that the separation between a baby’s sense of its own body and the world is not ingrained at birth, but rather learned, acculturated.) It is only when the baby’s crying stops precipitating a breast that it should start to doubt this part of itself, to think that perhaps my Mother’s breast is not part of me as subject but rather its own kind of thing, a separate object. Thus the Mother is, in this developmental sense, the subject’s first proper object. The Mother (and her breast), the baby subject thinks, is both mine and not mine, as though there is some relation that my Mother has to me, she is not (quite) the same as me.

+

The Father, on the other hand, incorporates (into) the baby subject’s sense of self differently. It is not considered, as the Mother is, a part of the subject that was at some point taken away, but rather represents the source of that action of taking away. If the Mother ought (in the terms of the baby subject’s nascent ethics) to be a part of me, the Father is the force and figure responsible for taking her away. This stature of the Father is better understood, perhaps, with reference to the myth of the Primal Father, which Lacan reinterprets from its presentation in Freud as originally depicted in the fourth and final chapter of Totem and Taboo (Freud 1919). Like the Oedipus complex, the myth of the Primal Father is a narrativization that helps to understand the structure of the subject. Suppose a primal horde, Freud offers, at the helm of which exists a Primal Father who monopolizes all women. All women in the horde, in other words, are sexually subject to this single male; no other male gets to enjoy anything of them. A band of brothers, resentful of the Father’s monopoly on enjoyment, conspire to escape the ban on sexual enjoyment through a plot to murder him.2 They do so through what could be called an original jealousy, a feeling that the Father is enjoying in a way that is prohibited (by virtue of the Father’s taboo) for each of them.

+

Freud offers this as an “historic explanation… [of] the origin of incest” (Freud 1919, p.207), as the Primal Father’s taboo on enjoyment is what, Freud suggests, drives exogamy, wherein each of the band of brothers leaves that original tribe to start their own in which they can (finally) enjoy the women for themselves. That this is an historic explanation does not mean that Freud believes that it represents an actual state of affairs in some distant past. Indeed, he states the opposite, that “primal state of society has nowhere been observed.” (Freud 1919, p.233) The parable of the Primal Father is historic rather in the sense that narrates to us an important aspect of the structure of the subject, much like Oedipus’ tragedy.

+

Daddy issues at work

+

Okay: we now return from this Freudian digression to the stuff of Severance. What bearing do the Oedipus complex and the myth of the Primal Father have on the structure of the subject on display in the show? Let’s go now to the scene in S1E3 at the crossroads, where MDR runs into two employees in Optics and Design (O&D).

+ +

The composition of this shot puts the reflective axis down the center, and the encounter is suggestively Oedipean in its structure (at a crossroads, unknowing of the Other at play). Note that Irving is compositionally mirrored by Burt, played by Christopher Walken, and we will explore this suggestive symmetry in detail in later episodes. The two departments (MDR and O&D) know of each other, we surmise from the dialogue that follows. But Irving isn’t supposed to know Burt by name, as he accidentally happened upon him in S1E2 on the way to a Wellness session. (Burt was coming from his Wellness session.)

+

While Irving greets Burt on the back of this previous encounter with gentle and flirtatious warmth, Dylan’s hostility towards O&D is clear. In place of the camaraderie that one might have hoped for between the two factions given their shared plight as severed workers, there appears to be an enmity built on a mythology (what Irving calls an “absolute fiction”) of otherness:

+
Kier sorted the departments by virtue. Macrodats are clever and true, while O&D’s more cruelty-centered…. O&D tried a violent coup on the others decades ago, and that’s why they reduced them down to two. And that’s why they keep us all so far apart now.
+

Kier is evidently the Primal Father of the severed floor, responsible for instituting the symbolic system of rules, regulations, and affects in the various ‘bands of brothers’ which reside there. The tour of the perfect replica of Kier’s house later in the episode reinforces his architectural status as Primal Father. Irving chides Mark for his lack of reverence in deigning to turn the tour of the Perpetuity Wing into Eagan Bingo, and is aghast when he almost happens to “bed sit” on the facsimile in his duplicate chambers. (Thou shall not lie in Kier Eagan’s bed.) Kier and the lineage of Eagans more generally constitute the law of the father, the signifier of authority that keeps the severed floor’s social order intact, the symbolic source from which both rules and the forbidden temptations of their being broken, taboos, sprout. Irving fosters this authority during the tour, standing in for the absent caregivers, existential (Kier, the Eagans) and material (Cobel and Milchick as superintendents who seem to be letting the kids take care of themselves for a short period).

+

Another paternal authority whose absence has haunted and structured Mark since the show’s opening is Petey, the man whose shoes he stepped into as MDR’s department chief. As per his exchange to Cobel in the mug-throwing scene, Mark lionizes Petey as a tone-setter, often acting through an ethics refracted by the subordinate conjunctive, ‘if Petey were here’, or the preface ‘Petey used to say’. Mark’s innie is steered more by an imagined sense of what Petey would do, rather than what Kier would.

+

Thus while it is Cobel who is explicitly in charge, the spectral presence of these father figures– Kier, Petey, Irving– correlatively structures the subject on the severed floor. There is, in other words, an Oedipal triangular conflict at work in relation the ethical imperative of a severed worker. The four members of MDR, as orientations to the structure of this subject, suffer different relationships to the positions of Mother and Father. Mark S is a momma’s boy, sired more by Petey’s radical rejection of company policy than by Kier. Dylan, though impertinent to the minutiae in the structure of Law at times, is ultimately his Father’s son, acquiring satisfaction by accumulating accolades, and apparently driven by the impending idea of another finger trap or a waffle party. Irving seems at this point the most mature of the children, looking reverentailly to Kier. Yet recall that he has been chided by Milchick already for falling asleep on the job, so not all is perfect in paradise. Hellie has no time for Cobel’s authority, yet we will see in due course that her relationship with a Father is a deep lineament in her personality, too.

+

Taming tempers

+

The count of four in the members of MDR mirrors the exact amount of tempers that we learn about from Kier Eagan’s wax simulacrum speaking during the tour of the Perpetuity Wing. These tempers are crucial as coordinates of the Eaganic attempt to coherently quantify the subject, and Kier’s pronouncement is deeply significant for our investigation of the subject’s distorted structure on the severed floor:

+
I know that death is near upon me, because people have begun to ask what I see as my life’s great achievement. They wish to know how they should remember me as I rot. In my life, I have identified four components, which I call tempers, from which are derived every human soul. Woe. Frolic. Dread. Malice. Each man’s character is defined by the precise ratio that resides in him. I walked into the cave of my own mind, and there I tamed them. Should you tame the tempers as I did mine, then the world shall become but your appendage. It is this great and consecrated power that I hope to pass on to all of you, my children.
+

If there was any doubt that Kier Eagan embodies the Freudian Primal Father, the foundational component of absolute fiction on which the edifice of Law (the rules and taboos by which a subject is bound to abide) is constructed, the quotation above should put it to bed. Kier’s ‘philosophy’ seeks to conquer death by quantifying life, sorting its myriadic nature into a “precise ratio” of character that can be counted (completely, it seems) in four distinct tempers. Indeed, we saw the pictorial representation of this taming in s1e2, in the scene where Irving meets Burt:

+ +

In the post-Platonic cave of his own mind, Kier is the master of his passions. He admits no unconscious contours that sneak up on him unbeknowst in Freudian slips of the tongue or unwanted symptoms. Indeed, the Eaganesque fantasy of the subject is one in which the necessary excess of language that psychoanalysis discovered does not exist. Words are detected (via sensors in the elevator, say), controlled, managed. Any psychoanalytic excess is, in Kier’s project of a precisely rationalized subject, beaten out of language. Excess meaning is ‘tamed’ as if it were a wild animal by a clear-headed, upstanding, divinely radiant visonary. (As we will see, the position of primal power that Kier occupies here is sexually overbearing, too, as we might suspect from the Freudian analogy.)

+

This episode ends with two scenes depicting the dark and bloody underside of Kier’s waxen vision of the precisely quantified human subject. The first is Helly’s harrowing experience in the break room, a space where the unruly distance between words as they are uttered and the meaning they convey is thought to be stamped out, suffocated by the drudgery of debilitating repetition. A subject will not exceed its authorized symbolization, the break room seems to want to claim. The worker’s unconscious will be tamed and ultimately made beholden to a regime of conscious rationality. The second, and the closing scene of the epsiode, is Petey’s psychotic demise at the convenience store, where he yells at wit’s end: “I need tokens so I can eat!” Ravaged by the failure of his complete quantification inside Lumon, Petey seems no longer to have a firm footing in either his innie’s or outie’s reality. Mark looks on from a distance as he collapses outside the store, escorted by police, attempting (it seems) to account for his disintegration.

+
+

Bibliography

+
    +
  • Freud, Sigmund. 1919. “Totem and Taboo: Resemblances Between the Psychic Lives of Savages and Neurotics.” Translated by A.A Brill. Moffat, Yard and Company 50 (1): 94–95.
  • +
  • Lacan, Jacques. 2002. “Family Complexes in the Formation of the Individual.” Antony Rowe London,.
  • +
  • McGowan, Todd. 2021. “The Distribution of Enjoyment.” European Journal of Psychoanalysis 8 (1).
  • +
+
+
+



+
+ +
+
+
    +
  1. 1 There is foreshadowing, too, of a fourth father figure in Rickon, Mark’s brother-in-law. While reading his confiscated book, Milchick quietly remarks to himself a thought that will become an important refrain for many other characters with respect to Rickon later in the season: “This is… Jesus.”
  2. +
  3. 2 There has been much written on Freud’s mythos of the Primal Father. For a relatively recent use of the concept that serves as a reasonable introduction to Lacan’s reading of Totem and Taboo, see (McGowan 2021).
  4. +
+
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/epub_inferred_spine/epub/xhtml/a.xhtml b/tests/ref/examples/epub_inferred_spine/epub/xhtml/a.xhtml new file mode 100644 index 0000000..a3f0bb8 --- /dev/null +++ b/tests/ref/examples/epub_inferred_spine/epub/xhtml/a.xhtml @@ -0,0 +1,13 @@ + + + + + + Part A + +
+

Part A

+

This is the first part of the document.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/epub_inferred_spine/epub/xhtml/b.xhtml b/tests/ref/examples/epub_inferred_spine/epub/xhtml/b.xhtml new file mode 100644 index 0000000..95a51a8 --- /dev/null +++ b/tests/ref/examples/epub_inferred_spine/epub/xhtml/b.xhtml @@ -0,0 +1,13 @@ + + + + + + Part B + +
+

Part B

+

This is the second part of the document.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/epub_inferred_spine/epub/xhtml/c.xhtml b/tests/ref/examples/epub_inferred_spine/epub/xhtml/c.xhtml new file mode 100644 index 0000000..14650d0 --- /dev/null +++ b/tests/ref/examples/epub_inferred_spine/epub/xhtml/c.xhtml @@ -0,0 +1,13 @@ + + + + + + Part C + +
+

Part C

+

This is the third part of the document.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/fcl_site/epub/fcl_site.metadata.json b/tests/ref/examples/fcl_site/epub/fcl_site.metadata.json index 94677e2..ce795f4 100644 --- a/tests/ref/examples/fcl_site/epub/fcl_site.metadata.json +++ b/tests/ref/examples/fcl_site/epub/fcl_site.metadata.json @@ -1,6 +1,6 @@ { "filetype": "epub", - "file_size": 1307334, + "file_size": 1307566, "title": "The Free Computing Lab", "language": "en", "spine_files": [ diff --git a/tests/ref/examples/fcl_site/epub/xhtml/index.xhtml b/tests/ref/examples/fcl_site/epub/xhtml/index.xhtml new file mode 100644 index 0000000..2b6cb5e --- /dev/null +++ b/tests/ref/examples/fcl_site/epub/xhtml/index.xhtml @@ -0,0 +1,21 @@ + + + + + + +
+ +

The free computing lab researches the nature of computing freedom. Our research aims to demonstrate the role that the computer could and should play in a society where freedom is flourishing rather than deprecated, and to cultivate a critically grounded practice of software production, development, and maintenance.

+

See our GitHub page for code and more information. You can also join our Zulip to ask questions and follow our research, or drop us a note at hi@ohrg.org.

+

Our projects include:

+ +
People
+ +



+ + +
\ No newline at end of file diff --git a/tests/ref/examples/fcl_site/html/index.html b/tests/ref/examples/fcl_site/html/index.html index 60daabd..3d880a4 100644 --- a/tests/ref/examples/fcl_site/html/index.html +++ b/tests/ref/examples/fcl_site/html/index.html @@ -4,15 +4,16 @@ -

The free computing lab researches the nature of computing freedom. Our research aims to demonstrate the role that the computer could and should play in a society where freedom is flourishing rather than deprecated, and to cultivate a critically grounded practice of software production, development, and maintenance. See our GitHub page for code and more information.

-

Our research projects include:

+

The free computing lab researches the nature of computing freedom. Our research aims to demonstrate the role that the computer could and should play in a society where freedom is flourishing rather than deprecated, and to cultivate a critically grounded practice of software production, development, and maintenance.

+

See our GitHub page for code and more information. You can also join our Zulip to ask questions and follow our research, or drop us a note at hi@ohrg.org.

+

Our projects include:

People


-

If you’re interested in what we do, you can drop us a note at hi@ohrg.org.

\ No newline at end of file diff --git a/tests/ref/examples/fcl_site/pdf/fcl_site.metadata.json b/tests/ref/examples/fcl_site/pdf/fcl_site.metadata.json index 9e1595d..42f05aa 100644 --- a/tests/ref/examples/fcl_site/pdf/fcl_site.metadata.json +++ b/tests/ref/examples/fcl_site/pdf/fcl_site.metadata.json @@ -1,5 +1,5 @@ { "filetype": "pdf", - "file_size": 4606, + "file_size": 5802, "page_count": 1 } \ No newline at end of file diff --git a/tests/ref/examples/link_transformation/epub/xhtml/doc1.xhtml b/tests/ref/examples/link_transformation/epub/xhtml/doc1.xhtml new file mode 100644 index 0000000..f063a99 --- /dev/null +++ b/tests/ref/examples/link_transformation/epub/xhtml/doc1.xhtml @@ -0,0 +1,15 @@ + + + + + + +
+

Document 1

+

This is the first document.

+

You can navigate to See Doc 2 for more information.

+

Section in Doc 1

+

More content here.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/link_transformation/epub/xhtml/doc2.xhtml b/tests/ref/examples/link_transformation/epub/xhtml/doc2.xhtml new file mode 100644 index 0000000..31c8c84 --- /dev/null +++ b/tests/ref/examples/link_transformation/epub/xhtml/doc2.xhtml @@ -0,0 +1,15 @@ + + + + + + +
+

Document 2

+

This is the second document.

+

Go Back to Doc 1 to see the first document.

+

Another Section

+

Additional content in document 2.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/links_with_fragments/epub/xhtml/page1.xhtml b/tests/ref/examples/links_with_fragments/epub/xhtml/page1.xhtml new file mode 100644 index 0000000..e09a2a1 --- /dev/null +++ b/tests/ref/examples/links_with_fragments/epub/xhtml/page1.xhtml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/tests/ref/examples/links_with_fragments/epub/xhtml/page2.xhtml b/tests/ref/examples/links_with_fragments/epub/xhtml/page2.xhtml new file mode 100644 index 0000000..5a697ff --- /dev/null +++ b/tests/ref/examples/links_with_fragments/epub/xhtml/page2.xhtml @@ -0,0 +1,20 @@ + + + + + + +
+

Page 2

+

This is the second page.

+

Introduction

+

This is the introduction section.

+

It has some content that the first page links to.

+

Middle Section

+

Some middle content.

+

Conclusion

+

This is the conclusion section.

+

Referenced from page 1.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/rheo_docs.metadata.json b/tests/ref/examples/rheo_docs/epub/rheo_docs.metadata.json index 06d1d12..86c6b67 100644 --- a/tests/ref/examples/rheo_docs/epub/rheo_docs.metadata.json +++ b/tests/ref/examples/rheo_docs/epub/rheo_docs.metadata.json @@ -1,12 +1,11 @@ { "filetype": "epub", - "file_size": 109804, + "file_size": 53115, "title": "Rheo Manual", "language": "en", "spine_files": [ - "frontmatter.xhtml", "index.xhtml", - "what-and-why-is-rheo.xhtml", + "why-is-rheo.xhtml", "getting-started.xhtml", "relative-linking.xhtml", "rheotoml.xhtml", @@ -15,7 +14,7 @@ "formats.xhtml", "spines.xhtml", "custom-css.xhtml", - "endmatter.xhtml" + "faq.xhtml" ], "has_nav": true } \ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/build-dir.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/build-dir.xhtml new file mode 100644 index 0000000..3c847e0 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/build-dir.xhtml @@ -0,0 +1,22 @@ + + + + + + Build directory + +
+

Build directory

+

Rheo produces outputs in a simple directory structure with one subdirectory for each kind of output. By default, Rheo produces all outputs (PDF, HTML, and EPUB) in a build directory instide the project directory:

+
build/

├── epub

│   └── blog_post.epub

├── html

│   ├── portable_epubs.html

│   └── style.css

└── pdf

└── portable_epubs.pdf
+

The build directory path is calculated relative to the content directory. This is important, as if you change the content directory, then your build directory path will become relevant to that directory.

+

Configuration

+

CLI flag

+

You can specify a build directory with either the compile and watch commands:

+
rheo compile path/to/project --build-dir path/to/build
+

rheo.toml

+

The build directory is specified at the top level of the rheo.toml:

+
build_dir = "custom_build_directory"
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/content-dir.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/content-dir.xhtml new file mode 100644 index 0000000..7137d80 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/content-dir.xhtml @@ -0,0 +1,18 @@ + + + + + + Content directory + +
+

Content directory

+

By default, Rheo will search your entire project directory for Typst documents. You can, however, indicate a specific subdirectory that Rheo should use if you prefer. This can be helpful for structuring projects, as it allows you, for example, to keep a separate drafts folder that Rheo will not compile.

+

The default content directory is the same path as the Rheo project directory. It is important to note that if you specify a custom content directory, all other configuration such as build_dir and spine globs will operate relative to the content directory.

+

Configuration

+

rheo.toml

+

A custom content directory can be specified at the top level of the rheo.toml. The path is calculated relative to the project directory:

+
content_dir = "pages"
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/custom-css.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/custom-css.xhtml new file mode 100644 index 0000000..c8c9b4b --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/custom-css.xhtml @@ -0,0 +1,13 @@ + + + + + + Custom CSS + +
+

When Rheo generates HTML, it injects a default stylesheet into the generated static site for a simple, modern, and mobile-friendly aesthetic. ‘Screening the subject’ is a website generated with the default Rheo stylesheet for reference.

+

You can fully customize the stylesheet by adding a style.css at the root of your project directory. Note that if your project contains a custom style.css, none of the styles in the default stylesheet will be applied. If you want to build on the default styles, copy and paste the default stylesheet into the style.css file in your project directory.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/faq.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/faq.xhtml new file mode 100644 index 0000000..61fe642 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/faq.xhtml @@ -0,0 +1,22 @@ + + + + + + Rheo + +
+

Frequently Asked Questions

+

What is the difference between Typst and Rheo?

+

Typst is a markup/programming language that provides its own toolchain which includes a CLI. You can use the Typst CLI to compile one Typst document to one kind of output file:

+
typst compile source.typ # compile to PDF

typst compile --features html --format html source.typ # compile to HTML
+

Rheo compiles a project folder to three outputs—PDF, HTML, and EPUB—concurrently. It allows you to configure how certain source files should be merged (to produce a ‘combined’ EPUB or PDF file, for example, via spines), and also allows you to enrich certain outputs (such as HTML via custom CSS) with non-Typst content. Rheo supports EPUB natively, which is not currently supported by the upstream Typst CLI (though it is on the roadmap). In summary, Rheo is a opinionated way to manage writing projects with Typst.

+

How do I read EPUBs on my system?

+

Mileage varies greatly on EPUB reading niceness across systems! If you’re interested to learn more, we have written more about this here. If you don’t have a good EPUB reading experience currently, we recommend trying bene, an EPUB reading system that we are developing.

+

Who maintains Rheo?

+

Rheo is developed by the Free Computing Lab, an academic research consortium that researches the nature of computing freedom. If you’re interested to learn more or get involved, you can join our Zulip.

+

Can I contribute to Rheo?

+

Yes! Rheo is written in Rust and developed in public through Github. You can track development and submit issues or requests for features through that platform. While in principle we welcome community pull requests, it’s best to join our Zulip and ask about it first, to confirm that your work will not go to waste.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/formats.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/formats.xhtml new file mode 100644 index 0000000..1b4a38b --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/formats.xhtml @@ -0,0 +1,26 @@ + + + + + + Formats + +
+

Formats

+

By default, Rheo produces three different output formats simultaneously: PDF, HTML, and EPUB. There are cases in which you may only want to produce one of these formats, however, or to exclude one format because your project either cannot support or does not require it.

+

PDF

+

Typst, the programming language and compilation toolchain that underwrites Rheo, natively and fully supports PDF.

+

HTML

+

Typst experimentally supports HTML. This means that not all Typst syntax will translate to a meaningful HTML structure. The most common features in everyday prose are all supported, however, such as text markup, links, headings, footnotes, and citations. For more information on which features are currently supported in Typst’s HTML export, refer to the HTML export tracking issue.

+

EPUB

+

Typst does not yet support EPUB, but it is supported in Rheo. As EPUB export is on Typst’s roadmap, Rheo will track this feature closely and look to integrate with it when it lands in the future.

+

Configuration

+

CLI flag

+

You can constrain Rheo to producing one or more formats by passing one or more of the following flags to compile or watch:

+
rheo compile path/to/project --pdf

rheo compile path/to/project --html

rheo compile path/to/project --epub
+

rheo.toml

+

You can also specify formats at the top level of rheo.toml in an array that contains one or more formats. The default, if formats is not specified, is an array with all three formats:

+
formats = ["pdf", "html", "epub"] 
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/getting-started.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/getting-started.xhtml new file mode 100644 index 0000000..86ebfe4 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/getting-started.xhtml @@ -0,0 +1,59 @@ + + + + + + Getting started + +
+

Installation

+

The easiest way to install Rheo is from crates.io, the Rust language’s package manager. If you don’t already have Rust/cargo, you will need to install those first. Open your terminal, and run the following command:

+
cargo install rheo --locked
+

Rheo is packaged as a standalone binary, and doesn’t require any version of Typst on your system. (Note that even if you already have Typst on your system, Rheo will use its own embedded version of the compiler.) Refer to Rheo’s source code for more information and installation options.

+

Firing up

+

With Rheo we can produce a static site, a PDF, and an EPUB from a Typst document. Let’s create a directory with a single Typst file in it:

+
mkdir project_uno

touch project_uno/index.typ
+

As one of Rheo’s outputs is a static site, the landing page will default to one named ‘index’. (If this file doesn’t exist, Rheo will present you with a basic listing of all the other files in the site.) Let’s put some Typst in the index.typ file:

+

project_uno/index.typ

+
= Project uno



Project uno is a writing project.
+

Rheo aims to keep out of your way as much as possible, and doesn’t require that you add any special syntax or metadata to your files to work. This single Typst file we need to get started. Provided you’ve already installed Rheo on your system, we can compile the project. You can tell Rheo to compile a folder by pointing the compile command at it:

+
rheo compile project_uno 
+

This command produces a build subdirectory inside the base directory which contains a PDF, an EPUB, and a static site (HTML and CSS). Your project folder should now look like so:

+
.

├── build

│   ├── epub

│   │   └── project_uno.epub

│   ├── html

│   │   ├── index.html

│   │   └── style.css

│   └── pdf

│   └── index.pdf

└── index.typ

+

It’s a little tiring to have to run the compile command every time we make a change, though. Let’s spin up a development server to see live changes across all output formats as we edit the source:

+
rheo watch project_uno --open
+

The --open flag here indicates that we’d like to open the output using our system’s default applications. Provided you have an EPUB reader on your system (if you don’t, we recommend installing bene), you should now have a PDF, an EPUB, and a website in front of you. As simple as that!

+

Scaling up

+

Let’s kill that process (with Ctrl-C). Rheo compiles documents from across your project directory towards EPUB, PDF, and HTML simultaneously, whereas the Typst compiler typically takes just one Typst file and produces one kind of output.1 Let’s add a couple of files to our project and link between them:

+

project_uno/about.typ

+
= About



Project uno is an incredible writing project that will transform the way we understand the world.

If you want to be involved, see the #link("./contact.typ")[Contact page].
+

project_uno/contact.typ

+
#let email = "myemail@mydomain.com"

= Contact



To learn more about project uno, email me at #link("mailto:" + email)[#email]
+

And let’s also link to the two new pages on the index page:

+

project_uno/index.typ

+
= Project uno



Project uno is a writing project.



- #link("./about.typ")[About]

- #link("./contact.typ")[Contact]
+

Now let’s run Rheo again, but this time let’s only build the HTML and EPUB outputs:

+
rheo watch project_uno --html --epub --open
+

Note how the relative links are working across both the EPUB and the PDF. Relative linking is one of the key features in Rheo that enables you to build richer static sites and EPUBs beyond using just Typst. All of Typst’s other features such as variables are fair game, too, as Rheo just uses Typst’s compiler under the hood.

+

Adding a config

+

One issue with the EPUB that is currently being produced is that the index.typ section shows up last, after the about.typ and contact.typ, as Rheo orders files lexicographically by default. This is probably not what we want, as the index page acts as a sort of table of contents in our writing project currently.

+

To sophisticate the way that Rheo produces outputs, we can add a rheo.toml config at the base of the project directory:

+

project_uno/rheo.toml

+
version = "0.1.0"



[epub.spine]

title = "Project Uno"

vertebrae = ["index.typ", "about.typ", "contact.typ"]
+

This config uses the notion of a spine to indicate a custom order for the sections. We’ll learn more about these later on in this documentation.

+

Let’s run the watch command again, this time with all outputs like the first time:

+
rheo watch project_uno --open
+

Great! The EPUB order is fixed. We now, however, have three distinct PDFs that are being created: one for each page. This is because Rheo defaults to producing one PDF per file in the project directory. We can configure Rheo to merge files together into a single PDF output by specifying a PDF spine, as we did with EPUB, and setting the merge attribute to true:

+

project_uno/rheo.toml

+
version = "0.1.0"



[epub.spine]

title = "Project Uno"

vertebrae = ["index.typ", "about.typ", "contact.typ"]



[pdf.spine]

title = "Project Uno"

vertebrae = ["index.typ", "about.typ", "contact.typ"]

merge = true
+

Before we run this again, let’s also clean the outputs in the build directory, as we don’t need those individual PDFs that we produced anymore:

+
rheo clean project_uno

rheo watch project_uno --open
+

Now we have a fully featured writing project, with nice-looking and orderly outputs in PDF, EPUB, and in HTML!

+
+
    +
  1. 1Typst allows you to break up your projects using modules, but still requires one entrypoint. Rheo, by contrast, enables multiple entrypoint files, corresponding to multiple standalone pages in a static site.
  2. +
+
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/index.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/index.xhtml new file mode 100644 index 0000000..7c36f2e --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/index.xhtml @@ -0,0 +1,33 @@ + + + + + + Introduction + +
+

What is Rheo?

+

The simple answer is that Rheo (ree-oh) is a new and more flexible way to produce and publish digital documents. The less simple answer is that Rheo is a typesetting and static site engine based on Typst. This guide explains both how to use Rheo, and why it might be for you.

+

Rheo allows you to produce a website, a fixed-size document, and an adaptive document from a single set of source Typst files. It allows you to do something similar to LaTeX—except that Typst is much simpler to write, and we can produce a greater number of formats with it. The documentation that you are reading now, for example, was typeset with Rheo. As a result, you can read it as:

+
    +
  • HTML - as a website for browsers.
  • +
  • PDF - as a fixed-size document for printing.
  • +
  • EPUB - as an adaptive document for e-readers.
  • +
+

Who should use Rheo?

+

If you write anything as simple as a blog or as complex as a dissertation or monograph in Typst, Rheo enables you to publish it in multiple formats. If you are willing to learn a little bit of syntax, you can turn a piece of writing into a website, an adaptive document, and/or a printable document.

+

Some of the things you can write and publish with Rheo include:

+
    +
  • A blog
  • +
  • A paper
  • +
  • A dissertation
  • +
  • A book manuscript
  • +
  • A novel
  • +
  • A textbook
  • +
  • Technical documentation
  • +
+

Rheo is for anyone who has ever spent regrettable hours formatting citatons, fighting with LaTeX, who has experienced the limitations of Markdown, or who wants to benefit from the richer writing experience that Typst makes possible (more on this in the next section). It is for students and teachers, humanists and scientists, bloggers and novelists.

+

If you have only ever used Microsoft Word to author text, or haven’t heard the phrase ‘markup language’ before, we recommend first familiarizing yourself with Typst via the excellent tutorial. This should give you a good intuition for what Typst is—a markup language similar to but also more powerful than Markdown—and why you might want to use Rheo to typeset your documents.

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/relative-linking.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/relative-linking.xhtml new file mode 100644 index 0000000..3bef6c0 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/relative-linking.xhtml @@ -0,0 +1,21 @@ + + + + + + Relative linking + +
+

Rheo allows you to write documents in plain Typst without requiring any additional syntax or metadata. Because Rheo can combine multiple files into unified outputs, however, we need a way to reference other files in the same Rheo project.

+

The syntax for these relative links in Rheo should be familiar, as they look just like regular Typst links, but reference a .typ file in the same directory as its target:

+
#link("./another-section.typ")[Another section]
+

When you compile a project with Rheo, relative links to other Typst documents in the same directory will be resolved and transformed according to the output format. What a relative link transforms to depends on both the output format and your Rheo configuration, as using features such as spines affects the control flow between your source Typst and output formats.

+
    +
  • In HTML, relative links become <a> tags that point to the relevant html page.
  • +
  • In PDF, relative links either become plain text (if input Typst is not combined, and thus produces one PDF per source document), or links to the relevant sections in the output document (if your config specifies a spine with the merge attribute set).
  • +
  • In EPUB, relative links become links to the relevant sections in the EPUB.
  • +
+

Relative linking is what allows Rheo to produce fully functional static sites. It is also a feature that you can use to help you organize large writing projects. (Note that the Typst import keyword works as you would expect in Rheo, and so can also/still be used as a mechanism to modularize projects.)

+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/rheotoml.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/rheotoml.xhtml new file mode 100644 index 0000000..5e85411 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/rheotoml.xhtml @@ -0,0 +1,22 @@ + + + + + + Rheo.toml + +
+

Rheo is a CLI that produces PDF, HTML, and EPUB simultaneously from a directory of Typst source documents. The directory that contains your Typst is called the project directory, and you can compile it like so:

+
rheo compile path/to/projectdirectory
+

In general, there are two ways to configure Rheo:

+
    +
  1. By passing flags directly to the CLI command.
  2. +
  3. By specifying configuration in a rheo.toml file at the root of the project directory.
  4. +
+

If you compile a Rheo project directory without a rheo.toml file, the following default settings will be applied to compile your project.

+
version = "0.1.0"

content_dir = "./"

build_dir = "build"

formats = ["pdf", "html", "epub"]



[epub.spine]

vertebrae = ["**/*.typ"]

title = "[project directory name]"
+

To point Rheo to a rheo.toml file that is not at the root of the project directory, specify it directly via the CLI:

+
rheo compile path/to/project --config path/to/config 
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/spines.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/spines.xhtml new file mode 100644 index 0000000..811a9e5 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/spines.xhtml @@ -0,0 +1,26 @@ + + + + + + Spines + +
+

Spines

+

A spine in Rheo is the backbone or ‘table of contents’ of Typst source files that should be compiled to an output format. It takes its name from the epub specification, in which the spine articulates—or reticulates— the set and order of chapters included.

+

You can specify a spine’s vertebrae for any output format using an array of glob strings in rheo.toml:

+
[epub.spine]

title = "My epub"

vertebrae = ["intro.typ", "*.typ"]
+

Notice how the first entry intro.typ is a specific file, whereas the second *.typ captures a range of files. When a glob string captures a range of source files, they will be ordered lexicographically in the spine.

+

EPUB

+

An EPUB must have a spine in order to be valid. By default, Rheo will infer the following spine if not specified:

+
[epub.spine]

title = "[project folder name]"

vertebrae = ["**/*.typ"]
+

PDF

+

By default, Rheo generates one PDF per Typst source file. You can specify a spine for the PDF format in order to reticulate multiple source documents into a single output PDF by indicating the vertebrae and setting merge to true:

+
[pdf.spine]

title = "My reticulated pdf"

vertebrae = ["intro.typ", "*.typ"]

merge = true
+

In a PDF generated in this way, relative links will resolve to internal document links that point to the relevant section.

+

HTML

+

Rheo does not currently support customizing HTML spines. The default spine uses all Typst files:

+
[html.spine]

title = "[project folder name]"

vertebrae = ["**/*.typ"]
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/epub/xhtml/why-is-rheo.xhtml b/tests/ref/examples/rheo_docs/epub/xhtml/why-is-rheo.xhtml new file mode 100644 index 0000000..4315359 --- /dev/null +++ b/tests/ref/examples/rheo_docs/epub/xhtml/why-is-rheo.xhtml @@ -0,0 +1,37 @@ + + + + + + Why Rheo? + +
+

Why do we need Rheo?

+

Rheo (ree-oh) is an open source typesetting and static site engine for Typst. It is a typesetting engine because it produces typeset digital documents such as PDF and EPUB, and a static site engine because it produces websites that don’t require communication with a custom backend server, but rather are self-sufficient sets of files that can be natively opened a browser (static sites). Most static site engines these days employ Markdown, a markup format that is approachable and pretty generic, allowing folks who are not familiar with or otherwise don’t want to deal directly with the required file formats of the web— HTML, CSS, and Javascript— to write blog posts and other content which can then be pumped into a static site.

+

As useful as it is, Markdown has its ambiguities. For one, there isn’t a standardized syntax for citations or footnotes. Though extensions exist that can produce these, they are not supported in the core Markdown syntax, meaning that it’s not really Markdown and can’t be relied upon to work in all contexts that support Markdown. Markdown is great when using hypertext (hyperlinks, images, etc). It’s not so great when it comes to things like tables, figures, and math.

+

Typst is a markup language that integrates with plain text, like Markdown, making it easy to adopt and joyful to write. Unlike Markdown, however, it is also a Turing-complete programming language with a modern type system, meaning that it is possible (though not necessary) to express sophisticated conditional logic controlling where and how text is rendered. Typst has a concrete and concise syntax for footnotes and citations, and can express visual constructs such as tables, figures, colors, and mathematical formulas. It was developed as a modern alternative to LaTeX, Leslie Lamport’s legendary 1980s addition to Donald Knuth’s original ‘78 Tex typesetting system. For the past 40 years, LaTeX has been the most expressive way to produce PDF documents, rendering it the de facto standard for academic and scientific publication. In the past few years, Typst has become the most promising and powerful alternative to LaTeX due to its maintainers’ effort to build out a reliable PDF compilation toolchain.

+

In 2025, Typst added experimental support for HTML compilation. Though there are still many features in Typst that will only produce meaningful output in the PDF toolchain, the HTML toolchain now supports all of the essential features for academic documents in the humanities: text decoration, headings, hyperlinks, footnotes, and citations.1 This makes it an extremely good replacement for Markdown as a markup language in a static site engine, and so: enter Rheo.

+

Rheo is a CLI (command line interface) that produces PDF, HTML, and EPUB simultaneously from a folder of Typst documents. It is a static site engine because it can produce a fully valid website: all it needs is a folder containing valid Typst. Rheo also provides mechanisms to combine multiple Typst files into a unified EPUB or PDF, making it a tool that improves the experience of writing books, dissertations, or any other long-form text in Typst. On the other side of the same coin, Rheo allows you to produce an offline version of a website such as a blog written in Typst through its PDF/EPUB toolchain.

+

Rheo allows you to compile multiple Typst files that link to each other into a single output, adding what is needed (relative linking) in order to make Typst an ideal markup language for writing static sites. Typst is the most elegant and flexible way to typeset PDF documents today; Rheo extends Typst’s capabilities, allowing you to additionally typeset EPUBs and generate static sites from the same source.2 Naturally, this blog post was written in Typst, and this site was made with Rheo. If you’re already convinced, feel free to jump ahead to Getting Started to download Rheo on your system and start writing.

+

The philosophy of Rheo

+

Rheo is a prefix or combining form in English that originates from the Greek word rheos (ῥέος), meaning flow, stream, or current. Rheo flows Typst documents into a number of concurrent output formats in PDF, HTML, and EPUB. But other meanings lurk beneath the surface of this basic idea. Sarah Pourciau has argued that the oceanic is a deep-rooted metaphor in computing, as all computation at some level seeks solid space in a sea of digital noise (Pourciau 2022). From Alan Turing’s partial solution to David Hilbert’s Entscheidungsproblem in the universal machine, to Claude Shannon’s information theory, to Leslie Lamport’s ordering of events in a distributed system, the key issue at hand is how to carve out clarity from uncertainty and confusion. Writing has played a magisterial role in calming the storm of imprecise thought. Long before computation arrived on the scene, the written word has served as the steward of reason, in the Western world and beyond, from Mesopotamian cunieform to Twitter. Nota bene (‘Take note’): that writing can also herald chaos and confusion doesn’t invalidate its capacity for spreading sensibility.

+

Rheo is a tool that facilitates the production and publication of documents following from the original vision of the Internet as a mechanism for lively and reasonably unfettered academic exchange, rather than the densely commercial space of platform capitalism that it has become. It should not be so difficult, given the extraordinary capacities of software and hardware today, to make a piece of writing publically available in a plain and pleasant format. That there exist digital humanities initiatives measured in months and years to bring books to the web as basic websites is a clear sign that something has gone awry.3

+

Rheo aims to enable the publication of more books, blog posts, and papers without the necessary capitalist ceremony of creating an account on Substack, Medium, or Squarespace. A website without any interactive elements such as forms, online marketplaces, or comments should be simple to set up, as it isn’t rocket science in 2025 thanks to all the hard work that folks have put into Internet protocols and web standards. It should be simple to create a PDF or EPUB for sharing with colleagues or collaborators—as simple as it is to send an email.

+

This is the vision of the world to which we at the free computing lab aspire, and in search of which we have built Rheo. Rheo is the first installment in a larger set of writing tools we aim to build, which will include processes for collaboratively drafting documents, constructing and working with digital libraries, and more.

+

Bibliography

+
+
    +
  • Pourciau, Sarah. 2022. “On the Digital Ocean.” Critical Inquiry 48 (2): 233–61. https://doi.org/10.1086/717319.
  • +
+
+

Footnotes

+
+
    +
  1. 1 I qualify this with ‘in the humanities’ as scientific papers often require tables, figures, and mathematical markup. These features are on Typst’s roadmap for html, but are not yet available at time of writing (January 2026).
  2. +
  3. 2EPUB is on Typst’s roadmap, but is not yet natively supported.
  4. +
  5. 3 There are many exciting and experimental ways of presenting text and other conent in the digital humanities. But we think that it should be easier that it currently is to publish and distribute books digitally, simply and straightforwardly.
  6. +
+
+ + +
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/html/build-dir.html b/tests/ref/examples/rheo_docs/html/build-dir.html index 8f242d6..1dd80db 100644 --- a/tests/ref/examples/rheo_docs/html/build-dir.html +++ b/tests/ref/examples/rheo_docs/html/build-dir.html @@ -1,17 +1,18 @@ + Build directory
-
+

Rheo

Build directory

-

Rheo produces outputs a simple directory structure with one subdirectory for each kind of output. By default, Rheo produces all outputs (PDF, HTML, and EPUB) in a build directory instide the project directory:

+

Rheo produces outputs in a simple directory structure with one subdirectory for each kind of output. By default, Rheo produces all outputs (PDF, HTML, and EPUB) in a build directory instide the project directory:

build/
├── epub
│   └── blog_post.epub
├── html
│   ├── portable_epubs.html
│   └── style.css
└── pdf
└── portable_epubs.pdf

The build directory path is calculated relative to the content directory. This is important, as if you change the content directory, then your build directory path will become relevant to that directory.

-

Configuration

-

CLI flag

+

Configuration

+

CLI flag

You can specify a build directory with either the compile and watch commands:

rheo compile path/to/project --build-dir path/to/build
-

rheo.toml

+

rheo.toml

The build directory is specified at the top level of the rheo.toml:

build_dir = "custom_build_directory"
diff --git a/tests/ref/examples/rheo_docs/html/content-dir.html b/tests/ref/examples/rheo_docs/html/content-dir.html index ed07d59..b0b3fc6 100644 --- a/tests/ref/examples/rheo_docs/html/content-dir.html +++ b/tests/ref/examples/rheo_docs/html/content-dir.html @@ -1,17 +1,18 @@ + Content directory
-
+

Rheo

Content directory

By default, Rheo will search your entire project directory for Typst documents. You can, however, indicate a specific subdirectory that Rheo should use if you prefer. This can be helpful for structuring projects, as it allows you, for example, to keep a separate drafts folder that Rheo will not compile.

The default content directory is the same path as the Rheo project directory. It is important to note that if you specify a custom content directory, all other configuration such as build_dir and spine globs will operate relative to the content directory.

-

Configuration

-

rheo.toml

+

Configuration

+

rheo.toml

A custom content directory can be specified at the top level of the rheo.toml. The path is calculated relative to the project directory:

content_dir = "pages"
diff --git a/tests/ref/examples/rheo_docs/html/custom-css.html b/tests/ref/examples/rheo_docs/html/custom-css.html index 9294624..56d11a9 100644 --- a/tests/ref/examples/rheo_docs/html/custom-css.html +++ b/tests/ref/examples/rheo_docs/html/custom-css.html @@ -1,17 +1,18 @@ + Custom CSS
-
+

Rheo

-

When rheo generates HTML, it injects a default stylesheet into the generated static site for a simple, modern, and mobile-friendly aesthetic. ‘Screening the subject’ is a website generated with the default rheo stylesheet for reference.

+

When Rheo generates HTML, it injects a default stylesheet into the generated static site for a simple, modern, and mobile-friendly aesthetic. ‘Screening the subject’ is a website generated with the default Rheo stylesheet for reference.

You can fully customize the stylesheet by adding a style.css at the root of your project directory. Note that if your project contains a custom style.css, none of the styles in the default stylesheet will be applied. If you want to build on the default styles, copy and paste the default stylesheet into the style.css file in your project directory.

- - + + diff --git a/tests/ref/examples/rheo_docs/html/faq.html b/tests/ref/examples/rheo_docs/html/faq.html index fcf9151..24c828a 100644 --- a/tests/ref/examples/rheo_docs/html/faq.html +++ b/tests/ref/examples/rheo_docs/html/faq.html @@ -1,7 +1,47 @@ + Rheo +
+ +

Rheo

+
+ +
+

Frequently Asked Questions

+

What is the difference between Typst and Rheo?

+

Typst is a markup/programming language that provides its own toolchain which includes a CLI. You can use the Typst CLI to compile one Typst document to one kind of output file:

+
typst compile source.typ # compile to PDF
typst compile --features html --format html source.typ # compile to HTML
+

Rheo compiles a project folder to three outputs—PDF, HTML, and EPUB—concurrently. It allows you to configure how certain source files should be merged (to produce a ‘combined’ EPUB or PDF file, for example, via spines), and also allows you to enrich certain outputs (such as HTML via custom CSS) with non-Typst content. Rheo supports EPUB natively, which is not currently supported by the upstream Typst CLI (though it is on the roadmap). In summary, Rheo is a opinionated way to manage writing projects with Typst.

+

How do I read EPUBs on my system?

+

Mileage varies greatly on EPUB reading niceness across systems! If you’re interested to learn more, we have written more about this here. If you don’t have a good EPUB reading experience currently, we recommend trying bene, an EPUB reading system that we are developing.

+

Who maintains Rheo?

+

Rheo is developed by the Free Computing Lab, an academic research consortium that researches the nature of computing freedom. If you’re interested to learn more or get involved, you can join our Zulip.

+

Can I contribute to Rheo?

+

Yes! Rheo is written in Rust and developed in public through Github. You can track development and submit issues or requests for features through that platform. While in principle we welcome community pull requests, it’s best to join our Zulip and ask about it first, to confirm that your work will not go to waste.

+
+ + + + \ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/html/formats.html b/tests/ref/examples/rheo_docs/html/formats.html index 8e6068c..a7e9641 100644 --- a/tests/ref/examples/rheo_docs/html/formats.html +++ b/tests/ref/examples/rheo_docs/html/formats.html @@ -1,17 +1,18 @@ + Formats
-
+

Rheo

Formats

-

By default, rheo produces three different output formats simultaneously: PDF, HTML, and EPUB. There are cases in which you may only want to produce one of these formats, however, or to exclude one format because your project either cannot support or does not require it.

+

By default, Rheo produces three different output formats simultaneously: PDF, HTML, and EPUB. There are cases in which you may only want to produce one of these formats, however, or to exclude one format because your project either cannot support or does not require it.

PDF

Typst, the programming language and compilation toolchain that underwrites Rheo, natively and fully supports PDF.

HTML

diff --git a/tests/ref/examples/rheo_docs/html/getting-started.html b/tests/ref/examples/rheo_docs/html/getting-started.html index 664c4c3..6a4f8ab 100644 --- a/tests/ref/examples/rheo_docs/html/getting-started.html +++ b/tests/ref/examples/rheo_docs/html/getting-started.html @@ -1,17 +1,18 @@ + Getting started
-
+

Rheo

-

Installation

+

Installation

The easiest way to install Rheo is from crates.io, the Rust language’s package manager. If you don’t already have Rust/cargo, you will need to install those first. Open your terminal, and run the following command:

-
cargo install rheo
+
cargo install rheo --locked

Rheo is packaged as a standalone binary, and doesn’t require any version of Typst on your system. (Note that even if you already have Typst on your system, Rheo will use its own embedded version of the compiler.) Refer to Rheo’s source code for more information and installation options.

-

Firing up

-

Rheo’s documentation is a written in Typst. With Rheo we can produce a static site, a PDF, and an EPUB from it. Let’s clone the documentation source to see how Rheo works:

-

git clone https://github.com/freecomputinglab/rheo.ohrg.org

-

Take a quick look at the structure of the source code. Besides some configuration files at the top level, it’s almost entirely just regular Typst. Rheo aims to keep out of your way as much as possible, and doesn’t require that you add any special syntax or metadata to your files to work.

-

Now that we have some Typst on our system, we can see Rheo at work:

-
rheo compile rheo.ohrg.org
-

This command produces a build subdirectory inside the base directory which contains a PDF, an EPUB, and a static site (HTML and CSS). It’s a little tiring to have to run the compile command every time we make a change, though. Let’s spin up a development server to see live changes across all output formats as we edit the source:

-
rheo watch rheo.ohrg.org --open
-

The --open flag here indicates that we’d like to open the output using our system’s default applications. Provided you have an EPUB reader on your system (if you don’t, we recommend installing bene), you should now have a PDF, an EPUB, and a website in front of you. Huzzah!

+

Firing up

+

With Rheo we can produce a static site, a PDF, and an EPUB from a Typst document. Let’s create a directory with a single Typst file in it:

+
mkdir project_uno
touch project_uno/index.typ
+

As one of Rheo’s outputs is a static site, the landing page will default to one named ‘index’. (If this file doesn’t exist, Rheo will present you with a basic listing of all the other files in the site.) Let’s put some Typst in the index.typ file:

+

project_uno/index.typ

+
= Project uno

Project uno is a writing project.
+

Rheo aims to keep out of your way as much as possible, and doesn’t require that you add any special syntax or metadata to your files to work. This single Typst file we need to get started. Provided you’ve already installed Rheo on your system, we can compile the project. You can tell Rheo to compile a folder by pointing the compile command at it:

+
rheo compile project_uno 
+

This command produces a build subdirectory inside the base directory which contains a PDF, an EPUB, and a static site (HTML and CSS). Your project folder should now look like so:

+
.
├── build
│   ├── epub
│   │   └── project_uno.epub
│   ├── html
│   │   ├── index.html
│   │   └── style.css
│   └── pdf
│   └── index.pdf
└── index.typ
+

It’s a little tiring to have to run the compile command every time we make a change, though. Let’s spin up a development server to see live changes across all output formats as we edit the source:

+
rheo watch project_uno --open
+

The --open flag here indicates that we’d like to open the output using our system’s default applications. Provided you have an EPUB reader on your system (if you don’t, we recommend installing bene), you should now have a PDF, an EPUB, and a website in front of you. As simple as that!

+

Scaling up

+

Let’s kill that process (with Ctrl-C). Rheo compiles documents from across your project directory towards EPUB, PDF, and HTML simultaneously, whereas the Typst compiler typically takes just one Typst file and produces one kind of output.1 Let’s add a couple of files to our project and link between them:

+

project_uno/about.typ

+
= About

Project uno is an incredible writing project that will transform the way we understand the world.
If you want to be involved, see the #link("./contact.typ")[Contact page].
+

project_uno/contact.typ

+
#let email = "myemail@mydomain.com"
= Contact

To learn more about project uno, email me at #link("mailto:" + email)[#email]
+

And let’s also link to the two new pages on the index page:

+

project_uno/index.typ

+
= Project uno

Project uno is a writing project.

- #link("./about.typ")[About]
- #link("./contact.typ")[Contact]
+

Now let’s run Rheo again, but this time let’s only build the HTML and EPUB outputs:

+
rheo watch project_uno --html --epub --open
+

Note how the relative links are working across both the EPUB and the PDF. Relative linking is one of the key features in Rheo that enables you to build richer static sites and EPUBs beyond using just Typst. All of Typst’s other features such as variables are fair game, too, as Rheo just uses Typst’s compiler under the hood.

+

Adding a config

+

One issue with the EPUB that is currently being produced is that the index.typ section shows up last, after the about.typ and contact.typ, as Rheo orders files lexicographically by default. This is probably not what we want, as the index page acts as a sort of table of contents in our writing project currently.

+

To sophisticate the way that Rheo produces outputs, we can add a rheo.toml config at the base of the project directory:

+

project_uno/rheo.toml

+
version = "0.1.0"

[epub.spine]
title = "Project Uno"
vertebrae = ["index.typ", "about.typ", "contact.typ"]
+

This config uses the notion of a spine to indicate a custom order for the sections. We’ll learn more about these later on in this documentation.

+

Let’s run the watch command again, this time with all outputs like the first time:

+
rheo watch project_uno --open
+

Great! The EPUB order is fixed. We now, however, have three distinct PDFs that are being created: one for each page. This is because Rheo defaults to producing one PDF per file in the project directory. We can configure Rheo to merge files together into a single PDF output by specifying a PDF spine, as we did with EPUB, and setting the merge attribute to true:

+

project_uno/rheo.toml

+
version = "0.1.0"

[epub.spine]
title = "Project Uno"
vertebrae = ["index.typ", "about.typ", "contact.typ"]

[pdf.spine]
title = "Project Uno"
vertebrae = ["index.typ", "about.typ", "contact.typ"]
merge = true
+

Before we run this again, let’s also clean the outputs in the build directory, as we don’t need those individual PDFs that we produced anymore:

+
rheo clean project_uno
rheo watch project_uno --open
+

Now we have a fully featured writing project, with nice-looking and orderly outputs in PDF, EPUB, and in HTML!

+

Footnotes

- - + + +
+
    +
  1. 1Typst allows you to break up your projects using modules, but still requires one entrypoint. Rheo, by contrast, enables multiple entrypoint files, corresponding to multiple standalone pages in a static site.
  2. +
+
\ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/html/index.html b/tests/ref/examples/rheo_docs/html/index.html index d330b84..954d8d0 100644 --- a/tests/ref/examples/rheo_docs/html/index.html +++ b/tests/ref/examples/rheo_docs/html/index.html @@ -1,17 +1,18 @@ + Introduction
-
+

Rheo

-

What is Rheo?

+

What is Rheo?

The simple answer is that Rheo (ree-oh) is a new and more flexible way to produce and publish digital documents. The less simple answer is that Rheo is a typesetting and static site engine based on Typst. This guide explains both how to use Rheo, and why it might be for you.

Rheo allows you to produce a website, a fixed-size document, and an adaptive document from a single set of source Typst files. It allows you to do something similar to LaTeX—except that Typst is much simpler to write, and we can produce a greater number of formats with it. The documentation that you are reading now, for example, was typeset with Rheo. As a result, you can read it as:

  • HTML - as a website for browsers.
  • PDF - as a fixed-size document for printing.
  • -
  • EPUB - as an adaptive document for e-readers.
  • +
  • EPUB - as an adaptive document for e-readers.
-

Who should use Rheo?

+

Who should use Rheo?

If you write anything as simple as a blog or as complex as a dissertation or monograph in Typst, Rheo enables you to publish it in multiple formats. If you are willing to learn a little bit of syntax, you can turn a piece of writing into a website, an adaptive document, and/or a printable document.

Some of the things you can write and publish with Rheo include:

    @@ -40,14 +42,14 @@

    Who should use Rheo?

  • A dissertation
  • A book manuscript
  • A novel
  • -
  • A text book
  • +
  • A textbook
  • Technical documentation

Rheo is for anyone who has ever spent regrettable hours formatting citatons, fighting with LaTeX, who has experienced the limitations of Markdown, or who wants to benefit from the richer writing experience that Typst makes possible (more on this in the next section). It is for students and teachers, humanists and scientists, bloggers and novelists.

-

If you have only ever used Microsoft Word to author text, or haven’t heard the phrase ‘markup language’ before, we recommend first familiarizing yourself with Markdown via the beginner’s tutorial. This should give you a good intuition for what Typst is—a markup language similar to but also more powerful than Markdown—and why you might want to use Rheo to typeset your documents.

+

If you have only ever used Microsoft Word to author text, or haven’t heard the phrase ‘markup language’ before, we recommend first familiarizing yourself with Typst via the excellent tutorial. This should give you a good intuition for what Typst is—a markup language similar to but also more powerful than Markdown—and why you might want to use Rheo to typeset your documents.

- - + + diff --git a/tests/ref/examples/rheo_docs/html/relative-linking.html b/tests/ref/examples/rheo_docs/html/relative-linking.html index e2ee964..6f48870 100644 --- a/tests/ref/examples/rheo_docs/html/relative-linking.html +++ b/tests/ref/examples/rheo_docs/html/relative-linking.html @@ -1,17 +1,18 @@ + Relative linking
-
+

Rheo

@@ -32,7 +34,7 @@
  • In PDF, relative links either become plain text (if input Typst is not combined, and thus produces one PDF per source document), or links to the relevant sections in the output document (if your config specifies a spine with the merge attribute set).
  • In EPUB, relative links become links to the relevant sections in the EPUB.
  • -

    Relative linking is what allows rheo to produce fully functinoal static sites. It is also a feature that you can use to help you organize large writing projects. (Note that the Typst import keyword works as you would expect in Rheo, and so can also/still be used as a mechanism to modularize projects.)

    +

    Relative linking is what allows Rheo to produce fully functional static sites. It is also a feature that you can use to help you organize large writing projects. (Note that the Typst import keyword works as you would expect in Rheo, and so can also/still be used as a mechanism to modularize projects.)

    diff --git a/tests/ref/examples/rheo_docs/html/rheotoml.html b/tests/ref/examples/rheo_docs/html/rheotoml.html index 959bc46..16f987b 100644 --- a/tests/ref/examples/rheo_docs/html/rheotoml.html +++ b/tests/ref/examples/rheo_docs/html/rheotoml.html @@ -1,17 +1,18 @@ + Rheo.toml
    -
    +

    Rheo

    diff --git a/tests/ref/examples/rheo_docs/html/spines.html b/tests/ref/examples/rheo_docs/html/spines.html index 0496094..dcca598 100644 --- a/tests/ref/examples/rheo_docs/html/spines.html +++ b/tests/ref/examples/rheo_docs/html/spines.html @@ -1,17 +1,18 @@ + Spines
    -
    +

    Rheo

    Spines

    -

    A spine in rheo is the backbone or ‘table of contents’ of Typst source files that should be compiled to an output format. It takes its name from the epub specification, in which the spine articulates—or reticulates— the set and order of chapters included.

    +

    A spine in Rheo is the backbone or ‘table of contents’ of Typst source files that should be compiled to an output format. It takes its name from the epub specification, in which the spine articulates—or reticulates— the set and order of chapters included.

    You can specify a spine’s vertebrae for any output format using an array of glob strings in rheo.toml:

    [epub.spine]
    title = "My epub"
    vertebrae = ["intro.typ", "*.typ"]

    Notice how the first entry intro.typ is a specific file, whereas the second *.typ captures a range of files. When a glob string captures a range of source files, they will be ordered lexicographically in the spine.

    EPUB

    -

    An EPUB must have a spine in order to be valid. By default, rheo will infer the following spine if not specified:

    +

    An EPUB must have a spine in order to be valid. By default, Rheo will infer the following spine if not specified:

    [epub.spine]
    title = "[project folder name]"
    vertebrae = ["**/*.typ"]

    PDF

    -

    By default, rheo generates one PDF per Typst source file. You can specify a spine for the PDF format in order to reticulate multiple source documents into a single output PDF by indicating the vertebrae and setting merge to true:

    +

    By default, Rheo generates one PDF per Typst source file. You can specify a spine for the PDF format in order to reticulate multiple source documents into a single output PDF by indicating the vertebrae and setting merge to true:

    [pdf.spine]
    title = "My reticulated pdf"
    vertebrae = ["intro.typ", "*.typ"]
    merge = true

    In a PDF generated in this way, relative links will resolve to internal document links that point to the relevant section.

    HTML

    diff --git a/tests/ref/examples/rheo_docs/html/style.metadata.json b/tests/ref/examples/rheo_docs/html/style.metadata.json index 77508c4..7cfcff7 100644 --- a/tests/ref/examples/rheo_docs/html/style.metadata.json +++ b/tests/ref/examples/rheo_docs/html/style.metadata.json @@ -1,6 +1,6 @@ { "filetype": "css", - "file_size": 10205, + "file_size": 10339, "path": "src/css/style.css", - "hash": "baf8566046bbdeac383ecc05d7baa4f1891dfe776a3c95e02f583d87d45025e4" + "hash": "941a9bc8b4b6f672fa89dcfa54f2426ea7a804d5c9dc4023cdfeeefe9102eb8d" } \ No newline at end of file diff --git a/tests/ref/examples/rheo_docs/html/what-and-why-is-rheo.html b/tests/ref/examples/rheo_docs/html/why-is-rheo.html similarity index 74% rename from tests/ref/examples/rheo_docs/html/what-and-why-is-rheo.html rename to tests/ref/examples/rheo_docs/html/why-is-rheo.html index f34da33..8779dab 100644 --- a/tests/ref/examples/rheo_docs/html/what-and-why-is-rheo.html +++ b/tests/ref/examples/rheo_docs/html/why-is-rheo.html @@ -1,17 +1,18 @@ + Why Rheo?
    -
    +

    Rheo

    -

    What and why is Rheo?

    +

    Why do we need Rheo?

    Rheo (ree-oh) is an open source typesetting and static site engine for Typst. It is a typesetting engine because it produces typeset digital documents such as PDF and EPUB, and a static site engine because it produces websites that don’t require communication with a custom backend server, but rather are self-sufficient sets of files that can be natively opened a browser (static sites). Most static site engines these days employ Markdown, a markup format that is approachable and pretty generic, allowing folks who are not familiar with or otherwise don’t want to deal directly with the required file formats of the web— HTML, CSS, and Javascript— to write blog posts and other content which can then be pumped into a static site.

    As useful as it is, Markdown has its ambiguities. For one, there isn’t a standardized syntax for citations or footnotes. Though extensions exist that can produce these, they are not supported in the core Markdown syntax, meaning that it’s not really Markdown and can’t be relied upon to work in all contexts that support Markdown. Markdown is great when using hypertext (hyperlinks, images, etc). It’s not so great when it comes to things like tables, figures, and math.

    -

    Typst is a markup language that integrates with plain text, like Markdown, making it easy to adopt and joyful to write. Unlike Markdown, however, it is also a Turing-complete programming language with a modern type system, meaning that it is possible (though not necessary) to express sophisticated conditional logic controlling where and how text is rendered. Typst has a concrete and concise syntax for footnotes and citations, and can express visual constructs such as tables, figures, colors, and mathematical formulas. It was developed as a modern alternative to LaTeX, Leslie Lamport’s legendary 1980s addition to Donald Knuth’s original ‘78 Tex typesetting system. For the past 40 years, LaTeX has been the most expressive way to produce pdf documents, rendering it the de facto standard for academic and scientific publication. In the past few years, Typst has become the most promising and powerful alternative to LaTeX due to its maintainers’ effort to build out a reliable PDF compilation toolchain.

    +

    Typst is a markup language that integrates with plain text, like Markdown, making it easy to adopt and joyful to write. Unlike Markdown, however, it is also a Turing-complete programming language with a modern type system, meaning that it is possible (though not necessary) to express sophisticated conditional logic controlling where and how text is rendered. Typst has a concrete and concise syntax for footnotes and citations, and can express visual constructs such as tables, figures, colors, and mathematical formulas. It was developed as a modern alternative to LaTeX, Leslie Lamport’s legendary 1980s addition to Donald Knuth’s original ‘78 Tex typesetting system. For the past 40 years, LaTeX has been the most expressive way to produce PDF documents, rendering it the de facto standard for academic and scientific publication. In the past few years, Typst has become the most promising and powerful alternative to LaTeX due to its maintainers’ effort to build out a reliable PDF compilation toolchain.

    In 2025, Typst added experimental support for HTML compilation. Though there are still many features in Typst that will only produce meaningful output in the PDF toolchain, the HTML toolchain now supports all of the essential features for academic documents in the humanities: text decoration, headings, hyperlinks, footnotes, and citations.1 This makes it an extremely good replacement for Markdown as a markup language in a static site engine, and so: enter Rheo.

    Rheo is a CLI (command line interface) that produces PDF, HTML, and EPUB simultaneously from a folder of Typst documents. It is a static site engine because it can produce a fully valid website: all it needs is a folder containing valid Typst. Rheo also provides mechanisms to combine multiple Typst files into a unified EPUB or PDF, making it a tool that improves the experience of writing books, dissertations, or any other long-form text in Typst. On the other side of the same coin, Rheo allows you to produce an offline version of a website such as a blog written in Typst through its PDF/EPUB toolchain.

    -

    Rheo allows you to compile multiple Typst files that link to each other into a single output, adding what is needed (relative linking) in order to make Typst an ideal markup language for writing static sites. Typst is the most elegant and flexible way to typeset PDF documents today; Rheo extends Typst’s capabilities, allowing you to additionally typeset EPUBs and generate static sites from the same source.2 Naturally, this blog post was written with Typst, and this site was made with Rheo. If you’re already convinced, feel free to jump ahead to Getting Started to download Rheo on your system and get building.

    +

    Rheo allows you to compile multiple Typst files that link to each other into a single output, adding what is needed (relative linking) in order to make Typst an ideal markup language for writing static sites. Typst is the most elegant and flexible way to typeset PDF documents today; Rheo extends Typst’s capabilities, allowing you to additionally typeset EPUBs and generate static sites from the same source.2 Naturally, this blog post was written in Typst, and this site was made with Rheo. If you’re already convinced, feel free to jump ahead to Getting Started to download Rheo on your system and start writing.

    The philosophy of Rheo

    Rheo is a prefix or combining form in English that originates from the Greek word rheos (ῥέος), meaning flow, stream, or current. Rheo flows Typst documents into a number of concurrent output formats in PDF, HTML, and EPUB. But other meanings lurk beneath the surface of this basic idea. Sarah Pourciau has argued that the oceanic is a deep-rooted metaphor in computing, as all computation at some level seeks solid space in a sea of digital noise (Pourciau 2022). From Alan Turing’s partial solution to David Hilbert’s Entscheidungsproblem in the universal machine, to Claude Shannon’s information theory, to Leslie Lamport’s ordering of events in a distributed system, the key issue at hand is how to carve out clarity from uncertainty and confusion. Writing has played a magisterial role in calming the storm of imprecise thought. Long before computation arrived on the scene, the written word has served as the steward of reason, in the Western world and beyond, from Mesopotamian cunieform to Twitter. Nota bene (‘Take note’): that writing can also herald chaos and confusion doesn’t invalidate its capacity for spreading sensibility.

    Rheo is a tool that facilitates the production and publication of documents following from the original vision of the Internet as a mechanism for lively and reasonably unfettered academic exchange, rather than the densely commercial space of platform capitalism that it has become. It should not be so difficult, given the extraordinary capacities of software and hardware today, to make a piece of writing publically available in a plain and pleasant format. That there exist digital humanities initiatives measured in months and years to bring books to the web as basic websites is a clear sign that something has gone awry.3

    @@ -43,7 +45,7 @@

    Bibliography

    Footnotes

    - +