From b8d1c7cf8791cc1f25fa44ef212379ebc7494405 Mon Sep 17 00:00:00 2001 From: Approximately Equal Date: Thu, 3 Apr 2025 22:02:17 -0700 Subject: [PATCH] feat(html): add meta tags for document authors and keywords --- crates/typst-html/src/lib.rs | 65 +++++++++++++++++++--- tests/ref/html/basic-table.html | 1 + tests/ref/html/block-html.html | 1 + tests/ref/html/box-html.html | 1 + tests/ref/html/col-gutter-table.html | 1 + tests/ref/html/col-row-gutter-table.html | 1 + tests/ref/html/enum-par.html | 1 + tests/ref/html/enum-start.html | 1 + tests/ref/html/heading-html-basic.html | 1 + tests/ref/html/link-basic.html | 1 + tests/ref/html/list-par.html | 1 + tests/ref/html/par-semantic-html.html | 1 + tests/ref/html/quote-attribution-link.html | 1 + tests/ref/html/quote-nesting-html.html | 1 + tests/ref/html/quote-plato.html | 1 + tests/ref/html/row-gutter-table.html | 1 + tests/ref/html/terms-par.html | 1 + 17 files changed, 73 insertions(+), 8 deletions(-) diff --git a/crates/typst-html/src/lib.rs b/crates/typst-html/src/lib.rs index aa769976e..0e3833400 100644 --- a/crates/typst-html/src/lib.rs +++ b/crates/typst-html/src/lib.rs @@ -5,9 +5,12 @@ mod encode; pub use self::encode::html; use comemo::{Track, Tracked, TrackedMut}; -use typst_library::diag::{bail, warning, At, SourceResult}; +pub use ecow::EcoVec; +use typst_library::diag::{bail, warning, At, SourceDiagnostic, SourceResult, StrResult}; use typst_library::engine::{Engine, Route, Sink, Traced}; -use typst_library::foundations::{Content, StyleChain, Target, TargetElem}; +use typst_library::foundations::{ + Content, Datetime, Smart, StyleChain, Target, TargetElem, +}; use typst_library::html::{ attr, tag, FrameElem, HtmlDocument, HtmlElem, HtmlElement, HtmlNode, }; @@ -84,7 +87,7 @@ fn html_document_impl( let output = handle_list(&mut engine, &mut locator, children.iter().copied())?; let introspector = Introspector::html(&output); - let root = root_element(output, &info)?; + let root = root_element(&mut engine, output, &info)?; Ok(HtmlDocument { info, root, introspector }) } @@ -262,18 +265,24 @@ fn handle( /// Wrap the nodes in `` and `` if they are not yet rooted, /// supplying a suitable ``. -fn root_element(output: Vec, info: &DocumentInfo) -> SourceResult { +fn root_element( + engine: &mut Engine, + output: Vec, + info: &DocumentInfo, +) -> SourceResult { + let head = head_element(engine, info).map_err(|err| { + EcoVec::from([SourceDiagnostic::warning(Span::detached(), err)]) + })?; let body = match classify_output(output)? { OutputKind::Html(element) => return Ok(element), OutputKind::Body(body) => body, OutputKind::Leafs(leafs) => HtmlElement::new(tag::body).with_children(leafs), }; - Ok(HtmlElement::new(tag::html) - .with_children(vec![head_element(info).into(), body.into()])) + Ok(HtmlElement::new(tag::html).with_children(vec![head.into(), body.into()])) } /// Generate a `` element. -fn head_element(info: &DocumentInfo) -> HtmlElement { +fn head_element(engine: &mut Engine, info: &DocumentInfo) -> StrResult { let mut children = vec![]; children.push(HtmlElement::new(tag::meta).with_attr(attr::charset, "utf-8").into()); @@ -302,7 +311,47 @@ fn head_element(info: &DocumentInfo) -> HtmlElement { ); } - HtmlElement::new(tag::head).with_children(children) + if !info.author.is_empty() { + children.push( + HtmlElement::new(tag::meta) + .with_attr(attr::name, "authors") + .with_attr(attr::content, info.author.join(", ")) + .into(), + ) + } + + if !info.keywords.is_empty() { + children.push( + HtmlElement::new(tag::meta) + .with_attr(attr::name, "keywords") + .with_attr(attr::content, info.keywords.join(", ")) + .into(), + ) + } + + match info.date { + Smart::Auto => children.push( + HtmlElement::new(tag::meta) + .with_attr(attr::name, "date") + .with_attr( + attr::content, + Datetime::today(engine, Smart::Auto)?.display(Smart::Auto)?, + ) + .into(), + ), + Smart::Custom(optional_date) => { + if let Some(date) = optional_date { + children.push( + HtmlElement::new(tag::meta) + .with_attr(attr::name, "date") + .with_attr(attr::content, date.display(Smart::Auto).unwrap()) + .into(), + ) + } + } + } + + Ok(HtmlElement::new(tag::head).with_children(children)) } /// Determine which kind of output the user generated. diff --git a/tests/ref/html/basic-table.html b/tests/ref/html/basic-table.html index 189a5b314..7d0bf1c50 100644 --- a/tests/ref/html/basic-table.html +++ b/tests/ref/html/basic-table.html @@ -3,6 +3,7 @@ + diff --git a/tests/ref/html/block-html.html b/tests/ref/html/block-html.html index d1716c6d7..93a792a08 100644 --- a/tests/ref/html/block-html.html +++ b/tests/ref/html/block-html.html @@ -3,6 +3,7 @@ +

Paragraph

diff --git a/tests/ref/html/box-html.html b/tests/ref/html/box-html.html index b2a26533b..0bedecd74 100644 --- a/tests/ref/html/box-html.html +++ b/tests/ref/html/box-html.html @@ -3,6 +3,7 @@ +

Text Span.

diff --git a/tests/ref/html/col-gutter-table.html b/tests/ref/html/col-gutter-table.html index 54170f534..d78ff99e1 100644 --- a/tests/ref/html/col-gutter-table.html +++ b/tests/ref/html/col-gutter-table.html @@ -3,6 +3,7 @@ +
diff --git a/tests/ref/html/col-row-gutter-table.html b/tests/ref/html/col-row-gutter-table.html index 54170f534..d78ff99e1 100644 --- a/tests/ref/html/col-row-gutter-table.html +++ b/tests/ref/html/col-row-gutter-table.html @@ -3,6 +3,7 @@ +
diff --git a/tests/ref/html/enum-par.html b/tests/ref/html/enum-par.html index 60d4592b7..25ebb1037 100644 --- a/tests/ref/html/enum-par.html +++ b/tests/ref/html/enum-par.html @@ -3,6 +3,7 @@ +
diff --git a/tests/ref/html/enum-start.html b/tests/ref/html/enum-start.html index fc9b3c061..b4abaa77b 100644 --- a/tests/ref/html/enum-start.html +++ b/tests/ref/html/enum-start.html @@ -3,6 +3,7 @@ +
    diff --git a/tests/ref/html/heading-html-basic.html b/tests/ref/html/heading-html-basic.html index 54a22faf4..7c1762789 100644 --- a/tests/ref/html/heading-html-basic.html +++ b/tests/ref/html/heading-html-basic.html @@ -3,6 +3,7 @@ +

    Level 1

    diff --git a/tests/ref/html/link-basic.html b/tests/ref/html/link-basic.html index 89cb54db5..6be58d33d 100644 --- a/tests/ref/html/link-basic.html +++ b/tests/ref/html/link-basic.html @@ -3,6 +3,7 @@ +

    https://example.com/

    diff --git a/tests/ref/html/list-par.html b/tests/ref/html/list-par.html index 7c747ff44..813562ee5 100644 --- a/tests/ref/html/list-par.html +++ b/tests/ref/html/list-par.html @@ -3,6 +3,7 @@ +
    diff --git a/tests/ref/html/par-semantic-html.html b/tests/ref/html/par-semantic-html.html index 09c7d2fd0..80dfcc70f 100644 --- a/tests/ref/html/par-semantic-html.html +++ b/tests/ref/html/par-semantic-html.html @@ -3,6 +3,7 @@ +

    Heading is no paragraph

    diff --git a/tests/ref/html/quote-attribution-link.html b/tests/ref/html/quote-attribution-link.html index c12d2ae2d..809fac6cc 100644 --- a/tests/ref/html/quote-attribution-link.html +++ b/tests/ref/html/quote-attribution-link.html @@ -3,6 +3,7 @@ +
    Compose papers faster
    diff --git a/tests/ref/html/quote-nesting-html.html b/tests/ref/html/quote-nesting-html.html index 6b05a94a0..9a9a383b8 100644 --- a/tests/ref/html/quote-nesting-html.html +++ b/tests/ref/html/quote-nesting-html.html @@ -3,6 +3,7 @@ +

    When you said that “he surely meant that ‘she intended to say “I'm sorry”’”, I was quite confused.

    diff --git a/tests/ref/html/quote-plato.html b/tests/ref/html/quote-plato.html index 039835082..a4fdcccff 100644 --- a/tests/ref/html/quote-plato.html +++ b/tests/ref/html/quote-plato.html @@ -3,6 +3,7 @@ +
    … ἔοικα γοῦν τούτου γε σμικρῷ τινι αὐτῷ τούτῳ σοφώτερος εἶναι, ὅτι ἃ μὴ οἶδα οὐδὲ οἴομαι εἰδέναι.
    diff --git a/tests/ref/html/row-gutter-table.html b/tests/ref/html/row-gutter-table.html index 54170f534..d78ff99e1 100644 --- a/tests/ref/html/row-gutter-table.html +++ b/tests/ref/html/row-gutter-table.html @@ -3,6 +3,7 @@ +
diff --git a/tests/ref/html/terms-par.html b/tests/ref/html/terms-par.html index 78bc5df16..a070aa919 100644 --- a/tests/ref/html/terms-par.html +++ b/tests/ref/html/terms-par.html @@ -3,6 +3,7 @@ +