Skip to main content

rustyfi_html/
lib.rs

1//! HTML output backend (`--format html`): one continuous, self-contained,
2//! semantic web document.
3//!
4//! The whole backend lives in the [`reflow`] submodule — see its doc comment
5//! for what the box stream becomes and why. This root module holds only what
6//! that submodule and its helpers share: the crate's error type, HTML
7//! escaping, and the `base64`/`fonts`/`image`/`svg` helper modules.
8//!
9//! The document is built from the flat block stream as it stood BEFORE page
10//! breaking (`DocumentValue::reflow_source` in `rustyfi-lang`), so there are
11//! no pages in it and nothing is cut at a page boundary; the browser does the
12//! line breaking. Nothing is fetched and nothing is executed — graphics and
13//! math are inline `<svg>` (`svg.rs`), images are data URIs (`image.rs`), and
14//! fonts are NAMED rather than embedded (`fonts.rs`'s `reflow_font_stack`).
15//!
16//! **Location.** This is its own `rustyfi-html` crate, a peer of
17//! `rustyfi-pdf`. It depends on `rustyfi-backend` for every box/geometry type
18//! it walks, plus `rustyfi-pdf` for [`rustyfi_pdf::TtfFontStore`] (the one
19//! type this crate reuses rather than re-implements — only its `pub`
20//! `file_index`/`file_family_name` accessors are used, so this is a plain
21//! one-way dependency, not a cycle: `rustyfi-pdf` does not depend on
22//! `rustyfi-html`). Nothing here touches `pdf_writer` or any other
23//! PDF-specific type, only `rustyfi_backend`/`rustyfi_pdf::TtfFontStore`
24//! types and `String` building.
25
26mod base64;
27mod fonts;
28mod image;
29mod reflow;
30mod svg;
31
32pub use reflow::{
33    render_html_reflow, render_html_reflow_ttf_with, render_html_reflow_ttf_with_decos,
34    render_html_reflow_with_decos,
35};
36
37/// Rendering is in practice infallible — every text run is valid
38/// UTF-8/HTML-escapable, and image/font handling reads from tables the
39/// compile step already validated. The `Result` return shape is kept anyway
40/// so the entry points are argument-for-argument (module signature, not
41/// module fallibility) with `rustyfi_pdf::render_pdf_with`, and so a future
42/// embedding step can surface a real error without a breaking signature
43/// change.
44#[derive(Debug, thiserror::Error)]
45pub enum HtmlError {
46    #[error(transparent)]
47    Io(#[from] std::io::Error),
48}
49
50/// Escape the five HTML/attribute-hostile characters. Emitted text is never
51/// re-parsed as markup, so this is the standard minimal set (no need for a
52/// full entity table).
53fn escape_html(s: &str) -> String {
54    let mut out = String::with_capacity(s.len());
55    for c in s.chars() {
56        match c {
57            '&' => out.push_str("&amp;"),
58            '<' => out.push_str("&lt;"),
59            '>' => out.push_str("&gt;"),
60            '"' => out.push_str("&quot;"),
61            '\'' => out.push_str("&#39;"),
62            _ => out.push(c),
63        }
64    }
65    out
66}