summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-library/src/model/emph.rs17
-rw-r--r--crates/typst-library/src/model/enum.rs17
-rw-r--r--crates/typst-library/src/model/figure.rs35
-rw-r--r--crates/typst-library/src/model/heading.rs19
-rw-r--r--crates/typst-library/src/model/link.rs44
-rw-r--r--crates/typst-library/src/model/list.rs15
-rw-r--r--crates/typst-library/src/model/strong.rs18
-rw-r--r--crates/typst-library/src/model/terms.rs29
-rw-r--r--crates/typst-library/src/text/raw.rs11
9 files changed, 171 insertions, 34 deletions
diff --git a/crates/typst-library/src/model/emph.rs b/crates/typst-library/src/model/emph.rs
index e36e5ef7..45097b34 100644
--- a/crates/typst-library/src/model/emph.rs
+++ b/crates/typst-library/src/model/emph.rs
@@ -1,6 +1,9 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Show, StyleChain, TargetElem,
+};
+use crate::html::{tag, HtmlElem};
use crate::text::{ItalicToggle, TextElem};
/// Emphasizes content by toggling italics.
@@ -35,7 +38,15 @@ pub struct EmphElem {
impl Show for Packed<EmphElem> {
#[typst_macros::time(name = "emph", span = self.span())]
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
- Ok(self.body().clone().styled(TextElem::set_emph(ItalicToggle(true))))
+ fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let body = self.body.clone();
+ Ok(if TargetElem::target_in(styles).is_html() {
+ HtmlElem::new(tag::em)
+ .with_body(Some(body))
+ .pack()
+ .spanned(self.span())
+ } else {
+ body.styled(TextElem::set_emph(ItalicToggle(true)))
+ })
}
}
diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs
index bac792d3..e0121ba2 100644
--- a/crates/typst-library/src/model/enum.rs
+++ b/crates/typst-library/src/model/enum.rs
@@ -1,13 +1,15 @@
use std::str::FromStr;
+use ecow::eco_format;
use smallvec::SmallVec;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
- Styles,
+ Styles, TargetElem,
};
+use crate::html::{attr, tag, HtmlElem};
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
@@ -214,6 +216,19 @@ impl EnumElem {
impl Show for Packed<EnumElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::ol)
+ .with_body(Some(Content::sequence(self.children.iter().map(|item| {
+ let mut li = HtmlElem::new(tag::li);
+ if let Some(nr) = item.number(styles) {
+ li = li.with_attr(attr::value, eco_format!("{nr}"));
+ }
+ li.with_body(Some(item.body.clone())).pack().spanned(item.span())
+ }))))
+ .pack()
+ .spanned(self.span()));
+ }
+
let mut realized =
BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
.pack()
diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs
index 3e2777c1..e871fbeb 100644
--- a/crates/typst-library/src/model/figure.rs
+++ b/crates/typst-library/src/model/figure.rs
@@ -9,8 +9,9 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
- Show, ShowSet, Smart, StyleChain, Styles, Synthesize,
+ Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
};
+use crate::html::{tag, HtmlElem};
use crate::introspection::{
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
};
@@ -326,15 +327,30 @@ impl Synthesize for Packed<FigureElem> {
impl Show for Packed<FigureElem> {
#[typst_macros::time(name = "figure", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = self.body().clone();
+ let target = TargetElem::target_in(styles);
+ let mut realized = self.body.clone();
// Build the caption, if any.
if let Some(caption) = self.caption(styles) {
- let v = VElem::new(self.gap(styles).into()).with_weak(true).pack();
- realized = match caption.position(styles) {
- OuterVAlignment::Top => caption.pack() + v + realized,
- OuterVAlignment::Bottom => realized + v + caption.pack(),
+ let (first, second) = match caption.position(styles) {
+ OuterVAlignment::Top => (caption.pack(), realized),
+ OuterVAlignment::Bottom => (realized, caption.pack()),
};
+ let mut seq = Vec::with_capacity(3);
+ seq.push(first);
+ if !target.is_html() {
+ let v = VElem::new(self.gap(styles).into()).with_weak(true);
+ seq.push(v.pack().spanned(self.span()))
+ }
+ seq.push(second);
+ realized = Content::sequence(seq)
+ }
+
+ if target.is_html() {
+ return Ok(HtmlElem::new(tag::figure)
+ .with_body(Some(realized))
+ .pack()
+ .spanned(self.span()));
}
// Wrap the contents in a block.
@@ -607,6 +623,13 @@ impl Show for Packed<FigureCaption> {
realized = supplement + numbers + self.get_separator(styles) + realized;
}
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::figcaption)
+ .with_body(Some(realized))
+ .pack()
+ .spanned(self.span()));
+ }
+
Ok(realized)
}
}
diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs
index 269e95eb..eb3c5413 100644
--- a/crates/typst-library/src/model/heading.rs
+++ b/crates/typst-library/src/model/heading.rs
@@ -6,8 +6,9 @@ use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
- Styles, Synthesize,
+ Styles, Synthesize, TargetElem,
};
+use crate::html::{tag, HtmlElem};
use crate::introspection::{
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
};
@@ -216,6 +217,22 @@ impl Synthesize for Packed<HeadingElem> {
impl Show for Packed<HeadingElem> {
#[typst_macros::time(name = "heading", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ // HTML's h1 is closer to a title element. There should only be one.
+ // Meanwhile, a level 1 Typst heading is a section heading. For this
+ // reason, levels are offset by one: A Typst level 1 heading becomes
+ // a `<h2>`.
+ let level = self.resolve_level(styles);
+ let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level.get().min(5) - 1];
+
+ // TODO: Don't ignore the various non-body properties.
+ let body = self.body().clone();
+ return Ok(HtmlElem::new(t)
+ .with_body(Some(body))
+ .pack()
+ .spanned(self.span()));
+ }
+
const SPACING_TO_NUMBERING: Em = Em::new(0.3);
let span = self.span();
diff --git a/crates/typst-library/src/model/link.rs b/crates/typst-library/src/model/link.rs
index 31c65a1d..8ab129fd 100644
--- a/crates/typst-library/src/model/link.rs
+++ b/crates/typst-library/src/model/link.rs
@@ -3,11 +3,13 @@ use std::ops::Deref;
use ecow::{eco_format, EcoString};
use smallvec::SmallVec;
-use crate::diag::{bail, At, SourceResult, StrResult};
+use crate::diag::{bail, warning, At, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, Content, Label, Packed, Repr, Show, Smart, StyleChain,
+ cast, elem, Content, Label, NativeElement, Packed, Repr, Show, Smart, StyleChain,
+ TargetElem,
};
+use crate::html::{attr, tag, HtmlElem};
use crate::introspection::Location;
use crate::layout::Position;
use crate::text::{Hyphenate, TextElem};
@@ -99,18 +101,36 @@ impl LinkElem {
impl Show for Packed<LinkElem> {
#[typst_macros::time(name = "link", span = self.span())]
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let body = self.body().clone();
- let linked = match self.dest() {
- LinkTarget::Dest(dest) => body.linked(dest.clone()),
- LinkTarget::Label(label) => {
- let elem = engine.introspector.query_label(*label).at(self.span())?;
- let dest = Destination::Location(elem.location().unwrap());
- body.clone().linked(dest)
+ let dest = self.dest();
+
+ Ok(if TargetElem::target_in(styles).is_html() {
+ if let LinkTarget::Dest(Destination::Url(url)) = dest {
+ HtmlElem::new(tag::a)
+ .with_attr(attr::href, url.clone().into_inner())
+ .with_body(Some(body))
+ .pack()
+ .spanned(self.span())
+ } else {
+ engine.sink.warn(warning!(
+ self.span(),
+ "non-URL links are not yet supported by HTML export"
+ ));
+ body
}
- };
-
- Ok(linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))))
+ } else {
+ let linked = match self.dest() {
+ LinkTarget::Dest(dest) => body.linked(dest.clone()),
+ LinkTarget::Label(label) => {
+ let elem = engine.introspector.query_label(*label).at(self.span())?;
+ let dest = Destination::Location(elem.location().unwrap());
+ body.clone().linked(dest)
+ }
+ };
+
+ linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false))))
+ })
}
}
diff --git a/crates/typst-library/src/model/list.rs b/crates/typst-library/src/model/list.rs
index 18bddd10..1e369d54 100644
--- a/crates/typst-library/src/model/list.rs
+++ b/crates/typst-library/src/model/list.rs
@@ -4,8 +4,9 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
- Smart, StyleChain, Styles, Value,
+ Smart, StyleChain, Styles, TargetElem, Value,
};
+use crate::html::{tag, HtmlElem};
use crate::layout::{BlockElem, Em, Length, VElem};
use crate::model::ParElem;
use crate::text::TextElem;
@@ -140,6 +141,18 @@ impl ListElem {
impl Show for Packed<ListElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::ul)
+ .with_body(Some(Content::sequence(self.children.iter().map(|item| {
+ HtmlElem::new(tag::li)
+ .with_body(Some(item.body.clone()))
+ .pack()
+ .spanned(item.span())
+ }))))
+ .pack()
+ .spanned(self.span()));
+ }
+
let mut realized =
BlockElem::multi_layouter(self.clone(), engine.routines.layout_list)
.pack()
diff --git a/crates/typst-library/src/model/strong.rs b/crates/typst-library/src/model/strong.rs
index 0e23179e..16d04ba9 100644
--- a/crates/typst-library/src/model/strong.rs
+++ b/crates/typst-library/src/model/strong.rs
@@ -1,6 +1,9 @@
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Show, StyleChain, TargetElem,
+};
+use crate::html::{tag, HtmlElem};
use crate::text::{TextElem, WeightDelta};
/// Strongly emphasizes content by increasing the font weight.
@@ -40,9 +43,14 @@ pub struct StrongElem {
impl Show for Packed<StrongElem> {
#[typst_macros::time(name = "strong", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
- Ok(self
- .body()
- .clone()
- .styled(TextElem::set_delta(WeightDelta(self.delta(styles)))))
+ let body = self.body.clone();
+ Ok(if TargetElem::target_in(styles).is_html() {
+ HtmlElem::new(tag::strong)
+ .with_body(Some(body))
+ .pack()
+ .spanned(self.span())
+ } else {
+ body.styled(TextElem::set_delta(WeightDelta(self.delta(styles))))
+ })
}
}
diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs
index bbcb63fc..13aa8c6d 100644
--- a/crates/typst-library/src/model/terms.rs
+++ b/crates/typst-library/src/model/terms.rs
@@ -4,8 +4,9 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
- Styles,
+ Styles, TargetElem,
};
+use crate::html::{tag, HtmlElem};
use crate::layout::{Dir, Em, HElem, Length, Sides, StackChild, StackElem, VElem};
use crate::model::{ListItemLike, ListLike, ParElem};
use crate::text::TextElem;
@@ -114,6 +115,26 @@ impl TermsElem {
impl Show for Packed<TermsElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let span = self.span();
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::dl)
+ .with_body(Some(Content::sequence(self.children.iter().flat_map(
+ |item| {
+ [
+ HtmlElem::new(tag::dt)
+ .with_body(Some(item.term.clone()))
+ .pack()
+ .spanned(item.term.span()),
+ HtmlElem::new(tag::dd)
+ .with_body(Some(item.description.clone()))
+ .pack()
+ .spanned(item.description.span()),
+ ]
+ },
+ ))))
+ .pack());
+ }
+
let separator = self.separator(styles);
let indent = self.indent(styles);
let hanging_indent = self.hanging_indent(styles);
@@ -127,7 +148,7 @@ impl Show for Packed<TermsElem> {
let pad = hanging_indent + indent;
let unpad = (!hanging_indent.is_zero())
- .then(|| HElem::new((-hanging_indent).into()).pack().spanned(self.span()));
+ .then(|| HElem::new((-hanging_indent).into()).pack().spanned(span));
let mut children = vec![];
for child in self.children().iter() {
@@ -149,7 +170,7 @@ impl Show for Packed<TermsElem> {
let mut realized = StackElem::new(children)
.with_spacing(Some(gutter.into()))
.pack()
- .spanned(self.span())
+ .spanned(span)
.padded(padding);
if self.tight(styles) {
@@ -158,7 +179,7 @@ impl Show for Packed<TermsElem> {
.with_weak(true)
.with_attach(true)
.pack()
- .spanned(self.span());
+ .spanned(span);
realized = spacing + realized;
}
diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs
index f318f06e..10a7cfee 100644
--- a/crates/typst-library/src/text/raw.rs
+++ b/crates/typst-library/src/text/raw.rs
@@ -14,8 +14,9 @@ use crate::diag::{At, FileError, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Args, Array, Bytes, Content, Fold, NativeElement, Packed,
- PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, Value,
+ PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, Value,
};
+use crate::html::{tag, HtmlElem};
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
use crate::model::{Figurable, ParElem};
use crate::text::{
@@ -451,6 +452,14 @@ impl Show for Packed<RawElem> {
}
let mut realized = Content::sequence(seq);
+
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::pre)
+ .with_body(Some(realized))
+ .pack()
+ .spanned(self.span()));
+ }
+
if self.block(styles) {
// Align the text before inserting it into the block.
realized = realized.aligned(self.align(styles).into());