mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
A few basic HTML show rules
These are very incomplete and ignore various properties. They are just to get things started.
This commit is contained in:
parent
0ef97c104a
commit
885c7d96ee
@ -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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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(linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))))
|
||||
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
|
||||
}
|
||||
} 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))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user