diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index a89b4953f..ae3c4a9a9 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -7,7 +7,7 @@ use std::fmt::Write; use self::tex::{layout_tex, Texify}; use crate::layout::BlockSpacing; use crate::prelude::*; -use crate::text::FontFamily; +use crate::text::{FallbackList, FontFamily, TextNode}; /// A piece of a mathematical formula. #[derive(Debug, Clone, Hash)] @@ -20,9 +20,6 @@ pub struct MathNode { #[node(Show, Finalize, LayoutInline, Texify)] impl MathNode { - /// The math font family. - #[property(referenced)] - pub const FAMILY: FontFamily = FontFamily::new("NewComputerModernMath"); /// The spacing above display math. #[property(resolve, shorthand(around))] pub const ABOVE: Option = Some(Ratio::one().into()); @@ -44,11 +41,7 @@ impl Show for MathNode { } fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(if self.display { - self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) - } else { - self.clone().pack() - }) + Ok(self.clone().pack()) } } @@ -57,13 +50,20 @@ impl Finalize for MathNode { &self, _: Tracked, styles: StyleChain, - realized: Content, + mut realized: Content, ) -> SourceResult { - Ok(if self.display { - realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) - } else { - realized - }) + realized = realized.styled( + TextNode::FAMILY, + FallbackList(vec![FontFamily::new("NewComputerModernMath")]), + ); + + if self.display { + realized = realized + .aligned(Axes::with_x(Some(Align::Center.into()))) + .spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)) + } + + Ok(realized) } } diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index 7de68d7bb..f924ebbeb 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -5,9 +5,8 @@ use rex::parser::color::RGBA; use rex::render::{Backend, Cursor, Renderer}; use typst::font::Font; -use super::MathNode; use crate::prelude::*; -use crate::text::{variant, LinebreakNode, SpaceNode, TextNode}; +use crate::text::{families, variant, LinebreakNode, SpaceNode, TextNode}; /// Turn a math node into TeX math code. #[capability] @@ -42,17 +41,21 @@ pub fn layout_tex( styles: StyleChain, ) -> SourceResult { // Load the font. - let font = world - .book() - .select(styles.get(MathNode::FAMILY).as_str(), variant(styles)) - .and_then(|id| world.font(id)) - .expect("failed to find math font"); + let variant = variant(styles); + let mut font = None; + for family in families(styles) { + font = world.book().select(family, variant).and_then(|id| world.font(id)); + if font.as_ref().map_or(false, |font| font.math().is_some()) { + break; + } + } // Prepare the font context. + let font = font.expect("failed to find suitable math font"); let ctx = font .math() .map(|math| FontContext::new(font.ttf(), math)) - .expect("font is not suitable for math"); + .expect("failed to create font context"); // Layout the formula. let em = styles.get(TextNode::SIZE); diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index f93be5d98..fe9b90133 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -1,6 +1,8 @@ +use typst::font::FontWeight; + use crate::layout::{BlockNode, BlockSpacing}; use crate::prelude::*; -use crate::text::{FontFamily, TextNode, TextSize}; +use crate::text::{TextNode, TextSize}; /// A section heading. #[derive(Debug, Hash)] @@ -14,32 +16,10 @@ pub struct HeadingNode { #[node(Show, Finalize)] impl HeadingNode { - /// The heading's font family. Just the normal text family if `auto`. - #[property(referenced)] - pub const FAMILY: Leveled> = Leveled::Value(Smart::Auto); - /// The color of text in the heading. Just the normal text color if `auto`. - #[property(referenced)] - pub const FILL: Leveled> = Leveled::Value(Smart::Auto); - /// The size of text in the heading. - #[property(referenced)] - pub const SIZE: Leveled = Leveled::Mapping(|level| { - let size = match level.get() { - 1 => 1.4, - 2 => 1.2, - _ => 1.0, - }; - TextSize(Em::new(size).into()) - }); - - /// Whether text in the heading is strengthend. - #[property(referenced)] - pub const STRONG: Leveled = Leveled::Value(true); - /// Whether text in the heading is emphasized. - #[property(referenced)] - pub const EMPH: Leveled = Leveled::Value(false); - /// Whether the heading is underlined. - #[property(referenced)] - pub const UNDERLINE: Leveled = Leveled::Value(false); + /// Whether the heading appears in the outline. + pub const OUTLINED: bool = true; + /// Whether the heading is numbered. + pub const NUMBERED: bool = true; /// The spacing above the heading. #[property(referenced, shorthand(around))] @@ -55,11 +35,6 @@ impl HeadingNode { pub const BELOW: Leveled> = Leveled::Value(Some(Ratio::new(0.55).into())); - /// Whether the heading appears in the outline. - pub const OUTLINED: bool = true; - /// Whether the heading is numbered. - pub const NUMBERED: bool = true; - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Self { body: args.expect("body")?, @@ -101,30 +76,17 @@ impl Finalize for HeadingNode { } let mut map = StyleMap::new(); - map.set(TextNode::SIZE, resolve!(Self::SIZE)); + map.set(TextNode::SIZE, { + let size = match self.level.get() { + 1 => 1.4, + 2 => 1.2, + _ => 1.0, + }; + TextSize(Em::new(size).into()) + }); + map.set(TextNode::WEIGHT, FontWeight::BOLD); - if let Smart::Custom(family) = resolve!(Self::FAMILY) { - map.set_family(family, styles); - } - - if let Smart::Custom(fill) = resolve!(Self::FILL) { - map.set(TextNode::FILL, fill); - } - - if resolve!(Self::STRONG) { - realized = realized.strong(); - } - - if resolve!(Self::EMPH) { - realized = realized.emph(); - } - - if resolve!(Self::UNDERLINE) { - realized = realized.underlined(); - } - - realized = realized.styled_with_map(map); - realized = realized.spaced( + realized = realized.styled_with_map(map).spaced( resolve!(Self::ABOVE).resolve(styles), resolve!(Self::BELOW).resolve(styles), ); diff --git a/library/src/text/link.rs b/library/src/text/link.rs index b74ca530d..acd37df67 100644 --- a/library/src/text/link.rs +++ b/library/src/text/link.rs @@ -7,31 +7,34 @@ pub struct LinkNode { /// The destination the link points to. pub dest: Destination, /// How the link is represented. - pub body: Option, + pub body: Content, } impl LinkNode { /// Create a link node from a URL with its bare text. pub fn from_url(url: EcoString) -> Self { - Self { dest: Destination::Url(url), body: None } + let mut text = url.as_str(); + for prefix in ["mailto:", "tel:"] { + text = text.trim_start_matches(prefix); + } + let shorter = text.len() < url.len(); + let body = TextNode::packed(if shorter { text.into() } else { url.clone() }); + Self { dest: Destination::Url(url), body } } } #[node(Show, Finalize)] impl LinkNode { - /// The fill color of text in the link. Just the surrounding text color - /// if `auto`. - pub const FILL: Smart = Smart::Auto; - /// Whether to underline the link. - pub const UNDERLINE: Smart = Smart::Auto; - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let dest = args.expect::("destination")?; - let body = match dest { - Destination::Url(_) => args.eat()?, - Destination::Internal(_) => Some(args.expect("body")?), - }; - Ok(Self { dest, body }.pack()) + Ok(match dest { + Destination::Url(url) => match args.eat()? { + Some(body) => Self { dest: Destination::Url(url), body }, + None => Self::from_url(url), + }, + Destination::Internal(_) => Self { dest, body: args.expect("body")? }, + } + .pack()) } fn field(&self, name: &str) -> Option { @@ -40,10 +43,7 @@ impl LinkNode { Destination::Url(url) => Value::Str(url.clone().into()), Destination::Internal(loc) => Value::Dict(loc.encode()), }), - "body" => Some(match &self.body { - Some(body) => Value::Content(body.clone()), - None => Value::None, - }), + "body" => Some(Value::Content(self.body.clone())), _ => None, } } @@ -53,23 +53,13 @@ impl Show for LinkNode { fn unguard_parts(&self, id: RecipeId) -> Content { Self { dest: self.dest.clone(), - body: self.body.as_ref().map(|body| body.unguard(id)), + body: self.body.unguard(id), } .pack() } fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.body.clone().unwrap_or_else(|| match &self.dest { - Destination::Url(url) => { - let mut text = url.as_str(); - for prefix in ["mailto:", "tel:"] { - text = text.trim_start_matches(prefix); - } - let shorter = text.len() < url.len(); - TextNode::packed(if shorter { text.into() } else { url.clone() }) - } - Destination::Internal(_) => Content::empty(), - })) + Ok(self.body.clone()) } } @@ -77,26 +67,9 @@ impl Finalize for LinkNode { fn finalize( &self, _: Tracked, - styles: StyleChain, - mut realized: Content, + _: StyleChain, + realized: Content, ) -> SourceResult { - let mut map = StyleMap::new(); - map.set(TextNode::LINK, Some(self.dest.clone())); - - if let Smart::Custom(fill) = styles.get(Self::FILL) { - map.set(TextNode::FILL, fill); - } - - if match styles.get(Self::UNDERLINE) { - Smart::Auto => match &self.dest { - Destination::Url(_) => true, - Destination::Internal(_) => false, - }, - Smart::Custom(underline) => underline, - } { - realized = realized.underlined(); - } - - Ok(realized.styled_with_map(map)) + Ok(realized.styled(TextNode::LINK, Some(self.dest.clone()))) } } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index c6229d599..2041b25e3 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -6,7 +6,7 @@ use syntect::highlighting::{ use syntect::parsing::SyntaxSet; use typst::syntax; -use super::{FontFamily, Hyphenate, LinebreakNode, TextNode}; +use super::{FallbackList, FontFamily, Hyphenate, LinebreakNode, TextNode}; use crate::layout::{BlockNode, BlockSpacing}; use crate::prelude::*; @@ -24,9 +24,6 @@ impl RawNode { /// The language to syntax-highlight in. #[property(referenced)] pub const LANG: Option = None; - /// The raw text's font family. - #[property(referenced)] - pub const FAMILY: FontFamily = FontFamily::new("IBM Plex Mono"); /// The spacing above block-level raw. #[property(resolve, shorthand(around))] pub const ABOVE: Option = Some(Ratio::one().into()); @@ -119,14 +116,16 @@ impl Finalize for RawNode { styles: StyleChain, mut realized: Content, ) -> SourceResult { - let mut map = StyleMap::new(); - map.set_family(styles.get(Self::FAMILY).clone(), styles); + realized = realized.styled( + TextNode::FAMILY, + FallbackList(vec![FontFamily::new("IBM Plex Mono")]), + ); if self.block { realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW)); } - Ok(realized.styled_with_map(map)) + Ok(realized) } } diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index b67ce411e..ac7218a03 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -552,7 +552,7 @@ pub fn variant(styles: StyleChain) -> FontVariant { } /// Resolve a prioritized iterator over the font families. -fn families(styles: StyleChain) -> impl Iterator + Clone { +pub fn families(styles: StyleChain) -> impl Iterator + Clone { const FALLBACKS: &[&str] = &[ "ibm plex sans", "twitter color emoji", diff --git a/tests/ref/text/link.png b/tests/ref/text/link.png index 59e62bea2..9d5cb0547 100644 Binary files a/tests/ref/text/link.png and b/tests/ref/text/link.png differ diff --git a/tests/typ/structure/heading.typ b/tests/typ/structure/heading.typ index d08b3687a..b552f6c3d 100644 --- a/tests/typ/structure/heading.typ +++ b/tests/typ/structure/heading.typ @@ -43,11 +43,10 @@ multiline. --- // Test styling. +#show heading.where(level: 5): it => { + text(family: "Roboto", fill: eastern, it.body + [!]) +} + = Heading - -#set heading(family: "Roboto", fill: eastern) -#show heading: it => it.body -#show strong: it => it.body + [!] - ===== Heading 🌍 #heading(level: 5)[Heading] diff --git a/tests/typ/style/closure.typ b/tests/typ/style/closure.typ deleted file mode 100644 index cd1f87df1..000000000 --- a/tests/typ/style/closure.typ +++ /dev/null @@ -1,49 +0,0 @@ -// Test styles with closure. - ---- -#set heading( - size: 10pt, - around: 0.65em, - fill: lvl => if even(lvl) { red } else { blue }, -) - -= Heading 1 -== Heading 2 -=== Heading 3 -==== Heading 4 - ---- -// Test in constructor. -#heading( - level: 3, - size: 10pt, - strong: lvl => { - assert(lvl == 3) - false - } -)[Level 3] - ---- -// Error: 22-26 expected string or auto or function, found length -#set heading(family: 10pt) -= Heading - ---- -// Error: 29-38 cannot add integer and string -#set heading(strong: lvl => lvl + "2") -= Heading - ---- -// Error: 22-34 expected string or auto, found boolean -#set heading(family: lvl => false) -= Heading - ---- -// Error: 22-37 missing argument: b -#set heading(family: (a, b) => a + b) -= Heading - ---- -// Error: 22-30 unexpected argument -#set heading(family: () => {}) -= Heading diff --git a/tests/typ/style/show-node.typ b/tests/typ/style/show-node.typ index e552038de..4b0542e51 100644 --- a/tests/typ/style/show-node.typ +++ b/tests/typ/style/show-node.typ @@ -13,8 +13,9 @@ --- // Test full reset. -#set heading(size: 1em, strong: false, around: none) +#set heading(around: none) #show heading: [B] +#show heading: text.with(size: 10pt, weight: 400) A [= Heading] C --- @@ -28,8 +29,8 @@ my heading? --- // Test integrated example. -#set heading(size: 1em) #show heading: it => { + set text(10pt) move(dy: -1pt)[📖] h(5pt) if it.level == 1 { diff --git a/tests/typ/text/indent.typ b/tests/typ/text/indent.typ index 92b121803..970441538 100644 --- a/tests/typ/text/indent.typ +++ b/tests/typ/text/indent.typ @@ -2,7 +2,8 @@ --- #set par(indent: 12pt, leading: 5pt) -#set heading(size: 10pt, above: 8pt) +#set heading(above: 8pt) +#show heading: text.with(size: 10pt) The first paragraph has no indent. diff --git a/tests/typ/text/link.typ b/tests/typ/text/link.typ index 8e7779563..8c3e6dddd 100644 --- a/tests/typ/text/link.typ +++ b/tests/typ/text/link.typ @@ -16,6 +16,7 @@ call #link("tel:123") for more information. --- // Test that the period is trimmed. +#show link: underline https://a.b.?q=%10#. \ Wahttp://link \ Nohttps:\//link \ @@ -23,20 +24,19 @@ Nohttp\://comment --- // Styled with underline and color. -#set link(fill: rgb("283663")) +#show link: it => underline(text(fill: rgb("283663"), it)) You could also make the #link("https://html5zombo.com/")[link look way more typical.] --- // Transformed link. #set page(height: 60pt) -#set link(underline: false) #let mylink = link("https://typst.org/")[LINK] My cool #move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))) --- // Link containing a block. -#link("https://example.com/", underline: false, block[ +#link("https://example.com/", block[ My cool rhino #move(dx: 10pt, image("/res/rhino.png", width: 1cm)) ])