mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Less style properties
This commit is contained in:
parent
0a41844cc4
commit
a7a4cae294
@ -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<BlockSpacing> = Some(Ratio::one().into());
|
||||
@ -44,11 +41,7 @@ impl Show for MathNode {
|
||||
}
|
||||
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
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<dyn World>,
|
||||
styles: StyleChain,
|
||||
realized: Content,
|
||||
mut realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Frame> {
|
||||
// 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);
|
||||
|
@ -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<Smart<FontFamily>> = Leveled::Value(Smart::Auto);
|
||||
/// The color of text in the heading. Just the normal text color if `auto`.
|
||||
#[property(referenced)]
|
||||
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
|
||||
/// The size of text in the heading.
|
||||
#[property(referenced)]
|
||||
pub const SIZE: Leveled<TextSize> = 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<bool> = Leveled::Value(true);
|
||||
/// Whether text in the heading is emphasized.
|
||||
#[property(referenced)]
|
||||
pub const EMPH: Leveled<bool> = Leveled::Value(false);
|
||||
/// Whether the heading is underlined.
|
||||
#[property(referenced)]
|
||||
pub const UNDERLINE: Leveled<bool> = 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<Option<BlockSpacing>> =
|
||||
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<Content> {
|
||||
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),
|
||||
);
|
||||
|
@ -7,31 +7,34 @@ pub struct LinkNode {
|
||||
/// The destination the link points to.
|
||||
pub dest: Destination,
|
||||
/// How the link is represented.
|
||||
pub body: Option<Content>,
|
||||
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<Paint> = Smart::Auto;
|
||||
/// Whether to underline the link.
|
||||
pub const UNDERLINE: Smart<bool> = Smart::Auto;
|
||||
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let dest = args.expect::<Destination>("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<Value> {
|
||||
@ -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<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
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<dyn World>,
|
||||
styles: StyleChain,
|
||||
mut realized: Content,
|
||||
_: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
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())))
|
||||
}
|
||||
}
|
||||
|
@ -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<EcoString> = 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<BlockSpacing> = Some(Ratio::one().into());
|
||||
@ -119,14 +116,16 @@ impl Finalize for RawNode {
|
||||
styles: StyleChain,
|
||||
mut realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,7 +552,7 @@ pub fn variant(styles: StyleChain) -> FontVariant {
|
||||
}
|
||||
|
||||
/// Resolve a prioritized iterator over the font families.
|
||||
fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||
pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||
const FALLBACKS: &[&str] = &[
|
||||
"ibm plex sans",
|
||||
"twitter color emoji",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@ -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]
|
||||
|
@ -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
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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))
|
||||
])
|
||||
|
Loading…
x
Reference in New Issue
Block a user