Rework style chain access

This commit is contained in:
Laurenz 2023-03-08 13:02:41 +01:00
parent e5eab73374
commit d7a65fa26d
45 changed files with 537 additions and 702 deletions

View File

@ -39,10 +39,12 @@ static FONTS: Lazy<(Prehashed<FontBook>, Vec<Font>)> = Lazy::new(|| {
static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| { static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
let mut lib = typst_library::build(); let mut lib = typst_library::build();
lib.styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(240.0).into()));
lib.styles.set(PageNode::HEIGHT, Smart::Auto);
lib.styles lib.styles
.set(PageNode::MARGIN, Sides::splat(Some(Smart::Custom(Abs::pt(15.0).into())))); .set(PageNode::set_width(Smart::Custom(Abs::pt(240.0).into())));
lib.styles.set(PageNode::set_height(Smart::Auto));
lib.styles.set(PageNode::set_margin(Sides::splat(Some(Smart::Custom(
Abs::pt(15.0).into(),
)))));
typst::eval::set_lang_items(lib.items.clone()); typst::eval::set_lang_items(lib.items.clone());
Prehashed::new(lib) Prehashed::new(lib)
}); });

View File

@ -16,8 +16,7 @@ use crate::prelude::*;
/// Category: layout /// Category: layout
#[node(Show)] #[node(Show)]
#[set({ #[set({
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default(); styles.set(Self::set_alignment(args.find()?.unwrap_or_default()));
styles.set(Self::ALIGNMENT, aligns);
})] })]
pub struct AlignNode { pub struct AlignNode {
/// The alignment along both axes. /// The alignment along both axes.

View File

@ -68,7 +68,7 @@ impl Layout for ColumnsNode {
// Determine the width of the gutter and each column. // Determine the width of the gutter and each column.
let columns = self.count().get(); let columns = self.count().get();
let gutter = styles.get(Self::GUTTER).relative_to(regions.base().x); let gutter = Self::gutter_in(styles).relative_to(regions.base().x);
let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64; let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
let backlog: Vec<_> = std::iter::once(&regions.size.y) let backlog: Vec<_> = std::iter::once(&regions.size.y)
@ -90,7 +90,7 @@ impl Layout for ColumnsNode {
let mut frames = body.layout(vt, styles, pod)?.into_iter(); let mut frames = body.layout(vt, styles, pod)?.into_iter();
let mut finished = vec![]; let mut finished = vec![];
let dir = styles.get(TextNode::DIR); let dir = TextNode::dir_in(styles);
let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
// Stitch together the columns for each region. // Stitch together the columns for each region.

View File

@ -134,7 +134,7 @@ impl Layout for BoxNode {
// Apply inset. // Apply inset.
let mut body = self.body().unwrap_or_default(); let mut body = self.body().unwrap_or_default();
let inset = styles.get(Self::INSET); let inset = Self::inset_in(styles);
if inset.iter().any(|v| !v.is_zero()) { if inset.iter().any(|v| !v.is_zero()) {
body = body.padded(inset.map(|side| side.map(Length::from))); body = body.padded(inset.map(|side| side.map(Length::from)));
} }
@ -145,21 +145,20 @@ impl Layout for BoxNode {
let mut frame = body.layout(vt, styles, pod)?.into_frame(); let mut frame = body.layout(vt, styles, pod)?.into_frame();
// Apply baseline shift. // Apply baseline shift.
let shift = styles.get(Self::BASELINE).relative_to(frame.height()); let shift = Self::baseline_in(styles).relative_to(frame.height());
if !shift.is_zero() { if !shift.is_zero() {
frame.set_baseline(frame.baseline() - shift); frame.set_baseline(frame.baseline() - shift);
} }
// Prepare fill and stroke. // Prepare fill and stroke.
let fill = styles.get(Self::FILL); let fill = Self::fill_in(styles);
let stroke = styles let stroke =
.get(Self::STROKE) Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
.map(|s| s.map(PartialStroke::unwrap_or_default));
// Add fill and/or stroke. // Add fill and/or stroke.
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
let outset = styles.get(Self::OUTSET); let outset = Self::outset_in(styles);
let radius = styles.get(Self::RADIUS); let radius = Self::radius_in(styles);
frame.fill_and_stroke(fill, stroke, outset, radius); frame.fill_and_stroke(fill, stroke, outset, radius);
} }
@ -220,16 +219,16 @@ impl Layout for BoxNode {
#[set({ #[set({
let spacing = args.named("spacing")?; let spacing = args.named("spacing")?;
styles.set_opt( styles.set_opt(
Self::ABOVE,
args.named("above")? args.named("above")?
.map(VNode::block_around) .map(VNode::block_around)
.or_else(|| spacing.map(VNode::block_spacing)), .or_else(|| spacing.map(VNode::block_spacing))
.map(Self::set_above),
); );
styles.set_opt( styles.set_opt(
Self::BELOW,
args.named("below")? args.named("below")?
.map(VNode::block_around) .map(VNode::block_around)
.or_else(|| spacing.map(VNode::block_spacing)), .or_else(|| spacing.map(VNode::block_spacing))
.map(Self::set_below),
); );
})] })]
pub struct BlockNode { pub struct BlockNode {
@ -361,7 +360,7 @@ impl Layout for BlockNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Apply inset. // Apply inset.
let mut body = self.body().unwrap_or_default(); let mut body = self.body().unwrap_or_default();
let inset = styles.get(Self::INSET); let inset = Self::inset_in(styles);
if inset.iter().any(|v| !v.is_zero()) { if inset.iter().any(|v| !v.is_zero()) {
body = body.clone().padded(inset.map(|side| side.map(Length::from))); body = body.clone().padded(inset.map(|side| side.map(Length::from)));
} }
@ -376,7 +375,7 @@ impl Layout for BlockNode {
.unwrap_or(regions.base()); .unwrap_or(regions.base());
// Layout the child. // Layout the child.
let mut frames = if styles.get(Self::BREAKABLE) { let mut frames = if Self::breakable_in(styles) {
// Measure to ensure frames for all regions have the same width. // Measure to ensure frames for all regions have the same width.
if sizing.x == Smart::Auto { if sizing.x == Smart::Auto {
let pod = Regions::one(size, Axes::splat(false)); let pod = Regions::one(size, Axes::splat(false));
@ -414,10 +413,9 @@ impl Layout for BlockNode {
}; };
// Prepare fill and stroke. // Prepare fill and stroke.
let fill = styles.get(Self::FILL); let fill = Self::fill_in(styles);
let stroke = styles let stroke =
.get(Self::STROKE) Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
.map(|s| s.map(PartialStroke::unwrap_or_default));
// Add fill and/or stroke. // Add fill and/or stroke.
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
@ -426,8 +424,8 @@ impl Layout for BlockNode {
skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
} }
let outset = styles.get(Self::OUTSET); let outset = Self::outset_in(styles);
let radius = styles.get(Self::RADIUS); let radius = Self::radius_in(styles);
for frame in frames.iter_mut().skip(skip as usize) { for frame in frames.iter_mut().skip(skip as usize) {
frame.fill_and_stroke(fill, stroke, outset, radius); frame.fill_and_stroke(fill, stroke, outset, radius);
} }

View File

@ -187,21 +187,20 @@ impl Layout for EnumNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let numbering = styles.get(Self::NUMBERING); let numbering = Self::numbering_in(styles);
let indent = styles.get(Self::INDENT); let indent = Self::indent_in(styles);
let body_indent = styles.get(Self::BODY_INDENT); let body_indent = Self::body_indent_in(styles);
let gutter = if self.tight() { let gutter = if self.tight() {
styles.get(ParNode::LEADING).into() ParNode::leading_in(styles).into()
} else { } else {
styles Self::spacing_in(styles)
.get(Self::SPACING) .unwrap_or_else(|| BlockNode::below_in(styles).amount())
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
}; };
let mut cells = vec![]; let mut cells = vec![];
let mut number = NonZeroUsize::new(1).unwrap(); let mut number = NonZeroUsize::new(1).unwrap();
let mut parents = styles.get(Self::PARENTS); let mut parents = Self::parents_in(styles);
let full = styles.get(Self::FULL); let full = Self::full_in(styles);
for item in self.children() { for item in self.children() {
number = item.number().unwrap_or(number); number = item.number().unwrap_or(number);
@ -223,7 +222,7 @@ impl Layout for EnumNode {
cells.push(Content::empty()); cells.push(Content::empty());
cells.push(resolved); cells.push(resolved);
cells.push(Content::empty()); cells.push(Content::empty());
cells.push(item.body().styled(Self::PARENTS, Parent(number))); cells.push(item.body().styled(Self::set_parents(Parent(number))));
number = number.saturating_add(1); number = number.saturating_add(1);
} }

View File

@ -134,8 +134,8 @@ impl<'a> FlowLayouter<'a> {
par: &ParNode, par: &ParNode,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles); let aligns = AlignNode::alignment_in(styles).resolve(styles);
let leading = styles.get(ParNode::LEADING); let leading = ParNode::leading_in(styles);
let consecutive = self.last_was_par; let consecutive = self.last_was_par;
let frames = par let frames = par
.layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)? .layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)?
@ -180,8 +180,8 @@ impl<'a> FlowLayouter<'a> {
content: &Content, content: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles); let aligns = AlignNode::alignment_in(styles).resolve(styles);
let sticky = styles.get(BlockNode::STICKY); let sticky = BlockNode::sticky_in(styles);
let pod = Regions::one(self.regions.base(), Axes::splat(false)); let pod = Regions::one(self.regions.base(), Axes::splat(false));
let layoutable = content.with::<dyn Layout>().unwrap(); let layoutable = content.with::<dyn Layout>().unwrap();
let frame = layoutable.layout(vt, styles, pod)?.into_frame(); let frame = layoutable.layout(vt, styles, pod)?.into_frame();
@ -208,10 +208,10 @@ impl<'a> FlowLayouter<'a> {
} }
// How to align the block. // How to align the block.
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles); let aligns = AlignNode::alignment_in(styles).resolve(styles);
// Layout the block itself. // Layout the block itself.
let sticky = styles.get(BlockNode::STICKY); let sticky = BlockNode::sticky_in(styles);
let fragment = block.layout(vt, styles, self.regions)?; let fragment = block.layout(vt, styles, self.regions)?;
for (i, frame) in fragment.into_iter().enumerate() { for (i, frame) in fragment.into_iter().enumerate() {
if i > 0 { if i > 0 {

View File

@ -262,7 +262,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
} }
// Reverse for RTL. // Reverse for RTL.
let is_rtl = styles.get(TextNode::DIR) == Dir::RTL; let is_rtl = TextNode::dir_in(styles) == Dir::RTL;
if is_rtl { if is_rtl {
cols.reverse(); cols.reverse();
} }

View File

@ -25,6 +25,6 @@ pub struct HideNode {
impl Show for HideNode { impl Show for HideNode {
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled(MetaNode::DATA, vec![Meta::Hidden])) Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hidden])))
} }
} }

View File

@ -127,25 +127,24 @@ impl Layout for ListNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let indent = styles.get(Self::INDENT); let indent = Self::indent_in(styles);
let body_indent = styles.get(Self::BODY_INDENT); let body_indent = Self::body_indent_in(styles);
let gutter = if self.tight() { let gutter = if self.tight() {
styles.get(ParNode::LEADING).into() ParNode::leading_in(styles).into()
} else { } else {
styles Self::spacing_in(styles)
.get(Self::SPACING) .unwrap_or_else(|| BlockNode::below_in(styles).amount())
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
}; };
let depth = styles.get(Self::DEPTH); let depth = Self::depth_in(styles);
let marker = styles.get(Self::MARKER).resolve(vt.world(), depth)?; let marker = Self::marker_in(styles).resolve(vt.world(), depth)?;
let mut cells = vec![]; let mut cells = vec![];
for item in self.children() { for item in self.children() {
cells.push(Content::empty()); cells.push(Content::empty());
cells.push(marker.clone()); cells.push(marker.clone());
cells.push(Content::empty()); cells.push(Content::empty());
cells.push(item.body().styled(Self::DEPTH, Depth)); cells.push(item.body().styled(Self::set_depth(Depth)));
} }
let layouter = GridLayouter::new( let layouter = GridLayouter::new(

View File

@ -450,13 +450,13 @@ impl<'a> FlowBuilder<'a> {
}; };
if !last_was_parbreak && is_tight_list { if !last_was_parbreak && is_tight_list {
let leading = styles.get(ParNode::LEADING); let leading = ParNode::leading_in(styles);
let spacing = VNode::list_attach(leading.into()); let spacing = VNode::list_attach(leading.into());
self.0.push(spacing.pack(), styles); self.0.push(spacing.pack(), styles);
} }
let above = styles.get(BlockNode::ABOVE); let above = BlockNode::above_in(styles);
let below = styles.get(BlockNode::BELOW); let below = BlockNode::below_in(styles);
self.0.push(above.clone().pack(), styles); self.0.push(above.clone().pack(), styles);
self.0.push(content.clone(), styles); self.0.push(content.clone(), styles);
self.0.push(below.clone().pack(), styles); self.0.push(below.clone().pack(), styles);

View File

@ -30,8 +30,8 @@ use crate::prelude::*;
#[node] #[node]
#[set({ #[set({
if let Some(paper) = args.named_or_find::<Paper>("paper")? { if let Some(paper) = args.named_or_find::<Paper>("paper")? {
styles.set(Self::WIDTH, Smart::Custom(paper.width().into())); styles.set(Self::set_width(Smart::Custom(paper.width().into())));
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into())); styles.set(Self::set_height(Smart::Custom(paper.height().into())));
} }
})] })]
pub struct PageNode { pub struct PageNode {
@ -260,10 +260,10 @@ impl PageNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// When one of the lengths is infinite the page fits its content along // When one of the lengths is infinite the page fits its content along
// that axis. // that axis.
let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf()); let width = Self::width_in(styles).unwrap_or(Abs::inf());
let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf()); let height = Self::height_in(styles).unwrap_or(Abs::inf());
let mut size = Size::new(width, height); let mut size = Size::new(width, height);
if styles.get(Self::FLIPPED) { if Self::flipped_in(styles) {
std::mem::swap(&mut size.x, &mut size.y); std::mem::swap(&mut size.x, &mut size.y);
} }
@ -274,12 +274,12 @@ impl PageNode {
// Determine the margins. // Determine the margins.
let default = Rel::from(0.1190 * min); let default = Rel::from(0.1190 * min);
let padding = styles.get(Self::MARGIN).map(|side| side.unwrap_or(default)); let padding = Self::margin_in(styles).map(|side| side.unwrap_or(default));
let mut child = self.body(); let mut child = self.body();
// Realize columns. // Realize columns.
let columns = styles.get(Self::COLUMNS); let columns = Self::columns_in(styles);
if columns.get() > 1 { if columns.get() > 1 {
child = ColumnsNode::new(columns, child).pack(); child = ColumnsNode::new(columns, child).pack();
} }
@ -291,11 +291,11 @@ impl PageNode {
let regions = Regions::repeat(size, size.map(Abs::is_finite)); let regions = Regions::repeat(size, size.map(Abs::is_finite));
let mut fragment = child.layout(vt, styles, regions)?; let mut fragment = child.layout(vt, styles, regions)?;
let fill = styles.get(Self::FILL); let fill = Self::fill_in(styles);
let header = styles.get(Self::HEADER); let header = Self::header_in(styles);
let footer = styles.get(Self::FOOTER); let footer = Self::footer_in(styles);
let foreground = styles.get(Self::FOREGROUND); let foreground = Self::foreground_in(styles);
let background = styles.get(Self::BACKGROUND); let background = Self::background_in(styles);
// Realize overlays. // Realize overlays.
for frame in &mut fragment { for frame in &mut fragment {

View File

@ -2,7 +2,7 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
use unicode_script::{Script, UnicodeScript}; use unicode_script::{Script, UnicodeScript};
use xi_unicode::LineBreakIterator; use xi_unicode::LineBreakIterator;
use typst::model::{Key, StyledNode}; use typst::model::StyledNode;
use super::{BoxNode, HNode, Sizing, Spacing}; use super::{BoxNode, HNode, Sizing, Spacing};
use crate::layout::AlignNode; use crate::layout::AlignNode;
@ -495,7 +495,7 @@ fn collect<'a>(
let mut iter = children.iter().peekable(); let mut iter = children.iter().peekable();
if consecutive { if consecutive {
let indent = styles.get(ParNode::INDENT); let indent = ParNode::indent_in(*styles);
if !indent.is_zero() if !indent.is_zero()
&& children && children
.iter() .iter()
@ -530,7 +530,7 @@ fn collect<'a>(
Segment::Text(1) Segment::Text(1)
} else if let Some(node) = child.to::<TextNode>() { } else if let Some(node) = child.to::<TextNode>() {
let prev = full.len(); let prev = full.len();
if let Some(case) = styles.get(TextNode::CASE) { if let Some(case) = TextNode::case_in(styles) {
full.push_str(&case.apply(&node.text())); full.push_str(&case.apply(&node.text()));
} else { } else {
full.push_str(&node.text()); full.push_str(&node.text());
@ -545,9 +545,9 @@ fn collect<'a>(
Segment::Text(c.len_utf8()) Segment::Text(c.len_utf8())
} else if let Some(node) = child.to::<SmartQuoteNode>() { } else if let Some(node) = child.to::<SmartQuoteNode>() {
let prev = full.len(); let prev = full.len();
if styles.get(SmartQuoteNode::ENABLED) { if SmartQuoteNode::enabled_in(styles) {
let lang = styles.get(TextNode::LANG); let lang = TextNode::lang_in(styles);
let region = styles.get(TextNode::REGION); let region = TextNode::region_in(styles);
let quotes = Quotes::from_lang(lang, region); let quotes = Quotes::from_lang(lang, region);
let peeked = iter.peek().and_then(|child| { let peeked = iter.peek().and_then(|child| {
if let Some(node) = child.to::<TextNode>() { if let Some(node) = child.to::<TextNode>() {
@ -613,7 +613,7 @@ fn prepare<'a>(
) -> SourceResult<Preparation<'a>> { ) -> SourceResult<Preparation<'a>> {
let bidi = BidiInfo::new( let bidi = BidiInfo::new(
text, text,
match styles.get(TextNode::DIR) { match TextNode::dir_in(styles) {
Dir::LTR => Some(BidiLevel::ltr()), Dir::LTR => Some(BidiLevel::ltr()),
Dir::RTL => Some(BidiLevel::rtl()), Dir::RTL => Some(BidiLevel::rtl()),
_ => None, _ => None,
@ -642,7 +642,7 @@ fn prepare<'a>(
Segment::Formula(formula) => { Segment::Formula(formula) => {
let pod = Regions::one(region, Axes::splat(false)); let pod = Regions::one(region, Axes::splat(false));
let mut frame = formula.layout(vt, styles, pod)?.into_frame(); let mut frame = formula.layout(vt, styles, pod)?.into_frame();
frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.translate(Point::with_y(TextNode::baseline_in(styles)));
items.push(Item::Frame(frame)); items.push(Item::Frame(frame));
} }
Segment::Box(node) => { Segment::Box(node) => {
@ -651,7 +651,7 @@ fn prepare<'a>(
} else { } else {
let pod = Regions::one(region, Axes::splat(false)); let pod = Regions::one(region, Axes::splat(false));
let mut frame = node.layout(vt, styles, pod)?.into_frame(); let mut frame = node.layout(vt, styles, pod)?.into_frame();
frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.translate(Point::with_y(TextNode::baseline_in(styles)));
items.push(Item::Frame(frame)); items.push(Item::Frame(frame));
} }
} }
@ -664,10 +664,10 @@ fn prepare<'a>(
bidi, bidi,
items, items,
styles, styles,
hyphenate: shared_get(styles, children, TextNode::HYPHENATE), hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
lang: shared_get(styles, children, TextNode::LANG), lang: shared_get(styles, children, TextNode::lang_in),
align: styles.get(AlignNode::ALIGNMENT).x.resolve(styles), align: AlignNode::alignment_in(styles).x.resolve(styles),
justify: styles.get(ParNode::JUSTIFY), justify: ParNode::justify_in(styles),
}) })
} }
@ -727,22 +727,23 @@ fn is_compatible(a: Script, b: Script) -> bool {
/// Get a style property, but only if it is the same for all children of the /// Get a style property, but only if it is the same for all children of the
/// paragraph. /// paragraph.
fn shared_get<'a, K: Key>( fn shared_get<'a, T: PartialEq>(
styles: StyleChain<'a>, styles: StyleChain<'a>,
children: &[Content], children: &[Content],
key: K, getter: fn(StyleChain) -> T,
) -> Option<K::Output> { ) -> Option<T> {
let value = getter(styles);
children children
.iter() .iter()
.filter_map(|child| child.to::<StyledNode>()) .filter_map(|child| child.to::<StyledNode>())
.all(|node| !node.map().contains(key)) .all(|node| getter(styles.chain(&node.map())) == value)
.then(|| styles.get(key)) .then(|| value)
} }
/// Find suitable linebreaks. /// Find suitable linebreaks.
fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| { let linebreaks = ParNode::linebreaks_in(p.styles).unwrap_or_else(|| {
if p.styles.get(ParNode::JUSTIFY) { if ParNode::justify_in(p.styles) {
Linebreaks::Optimized Linebreaks::Optimized
} else { } else {
Linebreaks::Simple Linebreaks::Simple
@ -840,7 +841,7 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
line: line(vt, p, 0..0, false, false), line: line(vt, p, 0..0, false, false),
}]; }];
let em = p.styles.get(TextNode::SIZE); let em = TextNode::size_in(p.styles);
for (end, mandatory, hyphen) in breakpoints(p) { for (end, mandatory, hyphen) in breakpoints(p) {
let k = table.len(); let k = table.len();
@ -1005,7 +1006,7 @@ impl Breakpoints<'_> {
.hyphenate .hyphenate
.or_else(|| { .or_else(|| {
let shaped = self.p.find(offset)?.text()?; let shaped = self.p.find(offset)?.text()?;
Some(shaped.styles.get(TextNode::HYPHENATE)) Some(TextNode::hyphenate_in(shaped.styles))
}) })
.unwrap_or(false) .unwrap_or(false)
} }
@ -1014,7 +1015,7 @@ impl Breakpoints<'_> {
fn lang(&self, offset: usize) -> Option<hypher::Lang> { fn lang(&self, offset: usize) -> Option<hypher::Lang> {
let lang = self.p.lang.or_else(|| { let lang = self.p.lang.or_else(|| {
let shaped = self.p.find(offset)?.text()?; let shaped = self.p.find(offset)?.text()?;
Some(shaped.styles.get(TextNode::LANG)) Some(TextNode::lang_in(shaped.styles))
})?; })?;
let bytes = lang.as_str().as_bytes().try_into().ok()?; let bytes = lang.as_str().as_bytes().try_into().ok()?;
@ -1155,7 +1156,7 @@ fn finalize(
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
// Prevent orphans. // Prevent orphans.
let leading = p.styles.get(ParNode::LEADING); let leading = ParNode::leading_in(p.styles);
if frames.len() >= 2 && !frames[1].is_empty() { if frames.len() >= 2 && !frames[1].is_empty() {
let second = frames.remove(1); let second = frames.remove(1);
let first = &mut frames[0]; let first = &mut frames[0];
@ -1199,7 +1200,7 @@ fn commit(
if let Some(Item::Text(text)) = reordered.first() { if let Some(Item::Text(text)) = reordered.first() {
if let Some(glyph) = text.glyphs.first() { if let Some(glyph) = text.glyphs.first() {
if !text.dir.is_positive() if !text.dir.is_positive()
&& text.styles.get(TextNode::OVERHANG) && TextNode::overhang_in(text.styles)
&& (reordered.len() > 1 || text.glyphs.len() > 1) && (reordered.len() > 1 || text.glyphs.len() > 1)
{ {
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
@ -1213,7 +1214,7 @@ fn commit(
if let Some(Item::Text(text)) = reordered.last() { if let Some(Item::Text(text)) = reordered.last() {
if let Some(glyph) = text.glyphs.last() { if let Some(glyph) = text.glyphs.last() {
if text.dir.is_positive() if text.dir.is_positive()
&& text.styles.get(TextNode::OVERHANG) && TextNode::overhang_in(text.styles)
&& (reordered.len() > 1 || text.glyphs.len() > 1) && (reordered.len() > 1 || text.glyphs.len() > 1)
{ {
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size); let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
@ -1257,7 +1258,7 @@ fn commit(
let region = Size::new(amount, full); let region = Size::new(amount, full);
let pod = Regions::one(region, Axes::new(true, false)); let pod = Regions::one(region, Axes::new(true, false));
let mut frame = node.layout(vt, *styles, pod)?.into_frame(); let mut frame = node.layout(vt, *styles, pod)?.into_frame();
frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.translate(Point::with_y(TextNode::baseline_in(*styles)));
push(&mut offset, frame); push(&mut offset, frame);
} else { } else {
offset += amount; offset += amount;

View File

@ -40,7 +40,7 @@ impl Layout for RepeatNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.size, Axes::new(false, false)); let pod = Regions::one(regions.size, Axes::new(false, false));
let piece = self.body().layout(vt, styles, pod)?.into_frame(); let piece = self.body().layout(vt, styles, pod)?.into_frame();
let align = styles.get(AlignNode::ALIGNMENT).x.resolve(styles); let align = AlignNode::alignment_in(styles).x.resolve(styles);
let fill = regions.size.x; let fill = regions.size.x;
let width = piece.width(); let width = piece.width();

View File

@ -201,10 +201,9 @@ impl<'a> StackLayouter<'a> {
// Block-axis alignment of the `AlignNode` is respected // Block-axis alignment of the `AlignNode` is respected
// by the stack node. // by the stack node.
let aligns = if let Some(styled) = block.to::<StyledNode>() { let aligns = match block.to::<StyledNode>() {
styles.chain(&styled.map()).get(AlignNode::ALIGNMENT) Some(styled) => AlignNode::alignment_in(styles.chain(&styled.map())),
} else { None => AlignNode::alignment_in(styles),
styles.get(AlignNode::ALIGNMENT)
}; };
let aligns = aligns.resolve(styles); let aligns = aligns.resolve(styles);

View File

@ -128,8 +128,8 @@ impl Layout for TableNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let inset = styles.get(Self::INSET); let inset = Self::inset_in(styles);
let align = styles.get(Self::ALIGN); let align = Self::align_in(styles);
let tracks = Axes::new(self.columns().0, self.rows().0); let tracks = Axes::new(self.columns().0, self.rows().0);
let gutter = Axes::new(self.column_gutter().0, self.row_gutter().0); let gutter = Axes::new(self.column_gutter().0, self.row_gutter().0);
@ -144,15 +144,15 @@ impl Layout for TableNode {
let x = i % cols; let x = i % cols;
let y = i / cols; let y = i / cols;
if let Smart::Custom(alignment) = align.resolve(vt, x, y)? { if let Smart::Custom(alignment) = align.resolve(vt, x, y)? {
child = child.styled(AlignNode::ALIGNMENT, alignment) child = child.styled(AlignNode::set_alignment(alignment));
} }
Ok(child) Ok(child)
}) })
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?;
let fill = styles.get(Self::FILL); let fill = Self::fill_in(styles);
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); let stroke = Self::stroke_in(styles).map(PartialStroke::unwrap_or_default);
// Prepare grid layout by unifying content and gutter tracks. // Prepare grid layout by unifying content and gutter tracks.
let layouter = GridLayouter::new( let layouter = GridLayouter::new(

View File

@ -90,14 +90,13 @@ impl Layout for TermsNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let indent = styles.get(Self::INDENT); let indent = Self::indent_in(styles);
let body_indent = styles.get(Self::HANGING_INDENT); let body_indent = Self::hanging_indent_in(styles);
let gutter = if self.tight() { let gutter = if self.tight() {
styles.get(ParNode::LEADING).into() ParNode::leading_in(styles).into()
} else { } else {
styles Self::spacing_in(styles)
.get(Self::SPACING) .unwrap_or_else(|| BlockNode::below_in(styles).amount())
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
}; };
let mut cells = vec![]; let mut cells = vec![];

View File

@ -122,7 +122,7 @@ impl Layout for RotateNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let ts = Transform::translate(x, y) let ts = Transform::translate(x, y)
.pre_concat(Transform::rotate(self.angle())) .pre_concat(Transform::rotate(self.angle()))
@ -199,7 +199,7 @@ impl Layout for ScaleNode {
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON);
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y) let transform = Transform::translate(x, y)
.pre_concat(Transform::scale(self.x(), self.y())) .pre_concat(Transform::scale(self.x(), self.y()))

View File

@ -164,8 +164,8 @@ fn styles() -> StyleMap {
fn items() -> LangItems { fn items() -> LangItems {
LangItems { LangItems {
layout: |world, content, styles| content.layout_root(world, styles), layout: |world, content, styles| content.layout_root(world, styles),
em: |styles| styles.get(text::TextNode::SIZE), em: text::TextNode::size_in,
dir: |styles| styles.get(text::TextNode::DIR), dir: text::TextNode::dir_in,
space: || text::SpaceNode::new().pack(), space: || text::SpaceNode::new().pack(),
linebreak: || text::LinebreakNode::new().pack(), linebreak: || text::LinebreakNode::new().pack(),
text: |text| text::TextNode::new(text).pack(), text: |text| text::TextNode::new(text).pack(),
@ -178,7 +178,7 @@ fn items() -> LangItems {
raw: |text, lang, block| { raw: |text, lang, block| {
let content = text::RawNode::new(text).with_block(block).pack(); let content = text::RawNode::new(text).with_block(block).pack();
match lang { match lang {
Some(_) => content.styled(text::RawNode::LANG, lang), Some(_) => content.styled(text::RawNode::set_lang(lang)),
None => content, None => content,
} }
}, },

View File

@ -49,7 +49,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
) -> Self { ) -> Self {
let table = font.ttf().tables().math.unwrap(); let table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap(); let constants = table.constants.unwrap();
let size = styles.get(TextNode::SIZE); let size = TextNode::size_in(styles);
let ttf = font.ttf(); let ttf = font.ttf();
let space_width = ttf let space_width = ttf
.glyph_index(' ') .glyph_index(' ')
@ -175,21 +175,20 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
pub fn style(&mut self, style: MathStyle) { pub fn style(&mut self, style: MathStyle) {
self.style_stack.push((self.style, self.size)); self.style_stack.push((self.style, self.size));
let base_size = self.styles().get(TextNode::SIZE) / self.style.size.factor(self); let base_size = TextNode::size_in(self.styles()) / self.style.size.factor(self);
self.size = base_size * style.size.factor(self); self.size = base_size * style.size.factor(self);
self.map.set(TextNode::SIZE, TextSize(self.size.into())); self.map.set(TextNode::set_size(TextSize(self.size.into())));
self.map.set( self.map
TextNode::STYLE, .set(TextNode::set_style(if style.italic == Smart::Custom(true) {
if style.italic == Smart::Custom(true) {
FontStyle::Italic FontStyle::Italic
} else { } else {
FontStyle::Normal FontStyle::Normal
}, }));
); self.map.set(TextNode::set_weight(if style.bold {
self.map.set( FontWeight::BOLD
TextNode::WEIGHT, } else {
if style.bold { FontWeight::BOLD } else { FontWeight::REGULAR }, FontWeight::REGULAR
); }));
self.style = style; self.style = style;
} }

View File

@ -133,7 +133,7 @@ fn layout(
line_pos, line_pos,
Element::Shape( Element::Shape(
Geometry::Line(Point::with_x(line_width)).stroked(Stroke { Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
paint: ctx.styles().get(TextNode::FILL), paint: TextNode::fill_in(ctx.styles()),
thickness, thickness,
}), }),
), ),

View File

@ -180,8 +180,8 @@ impl GlyphFragment {
id, id,
c, c,
font: ctx.font.clone(), font: ctx.font.clone(),
lang: ctx.styles().get(TextNode::LANG), lang: TextNode::lang_in(ctx.styles()),
fill: ctx.styles().get(TextNode::FILL), fill: TextNode::fill_in(ctx.styles()),
style: ctx.style, style: ctx.style,
font_size: ctx.size, font_size: ctx.size,
width, width,

View File

@ -35,7 +35,7 @@ pub struct VecNode {
impl LayoutMath for VecNode { impl LayoutMath for VecNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = ctx.styles().get(Self::DELIM); let delim = Self::delim_in(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
} }
@ -115,7 +115,7 @@ impl Construct for MatNode {
impl LayoutMath for MatNode { impl LayoutMath for MatNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = ctx.styles().get(Self::DELIM); let delim = Self::delim_in(ctx.styles());
let frame = layout_mat_body(ctx, &self.rows())?; let frame = layout_mat_body(ctx, &self.rows())?;
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
} }
@ -156,7 +156,7 @@ pub struct CasesNode {
impl LayoutMath for CasesNode { impl LayoutMath for CasesNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = ctx.styles().get(Self::DELIM); let delim = Self::delim_in(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
layout_delimiters(ctx, frame, Some(delim.open()), None) layout_delimiters(ctx, frame, Some(delim.open()), None)
} }

View File

@ -158,11 +158,10 @@ impl Show for FormulaNode {
impl Finalize for FormulaNode { impl Finalize for FormulaNode {
fn finalize(&self, realized: Content) -> Content { fn finalize(&self, realized: Content) -> Content {
realized realized
.styled(TextNode::WEIGHT, FontWeight::from_number(450)) .styled(TextNode::set_weight(FontWeight::from_number(450)))
.styled( .styled(TextNode::set_font(FontList(vec![FontFamily::new(
TextNode::FONT, "New Computer Modern Math",
FontList(vec![FontFamily::new("New Computer Modern Math")]), )])))
)
} }
} }
@ -196,10 +195,10 @@ impl Layout for FormulaNode {
let mut frame = ctx.layout_frame(self)?; let mut frame = ctx.layout_frame(self)?;
if !block { if !block {
let slack = styles.get(ParNode::LEADING) * 0.7; let slack = ParNode::leading_in(styles) * 0.7;
let top_edge = styles.get(TextNode::TOP_EDGE).resolve(styles, font.metrics()); let top_edge = TextNode::top_edge_in(styles).resolve(styles, font.metrics());
let bottom_edge = let bottom_edge =
-styles.get(TextNode::BOTTOM_EDGE).resolve(styles, font.metrics()); -TextNode::bottom_edge_in(styles).resolve(styles, font.metrics());
let ascent = top_edge.max(frame.ascent() - slack); let ascent = top_edge.max(frame.ascent() - slack);
let descent = bottom_edge.max(frame.descent() - slack); let descent = bottom_edge.max(frame.descent() - slack);
@ -232,7 +231,9 @@ impl LayoutMath for Content {
if let Some(styled) = self.to::<StyledNode>() { if let Some(styled) = self.to::<StyledNode>() {
let map = styled.map(); let map = styled.map();
if map.contains(TextNode::FONT) { if TextNode::font_in(ctx.styles().chain(&map))
!= TextNode::font_in(ctx.styles())
{
let frame = ctx.layout_content(self)?; let frame = ctx.layout_content(self)?;
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true)); ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
return Ok(()); return Ok(());
@ -241,7 +242,7 @@ impl LayoutMath for Content {
let prev_map = std::mem::replace(&mut ctx.map, map); let prev_map = std::mem::replace(&mut ctx.map, map);
let prev_size = ctx.size; let prev_size = ctx.size;
ctx.map.apply(prev_map.clone()); ctx.map.apply(prev_map.clone());
ctx.size = ctx.styles().get(TextNode::SIZE); ctx.size = TextNode::size_in(ctx.styles());
styled.body().layout_math(ctx)?; styled.body().layout_math(ctx)?;
ctx.size = prev_size; ctx.size = prev_size;
ctx.map = prev_map; ctx.map = prev_map;

View File

@ -128,7 +128,7 @@ fn layout(
line_pos, line_pos,
Element::Shape( Element::Shape(
Geometry::Line(Point::with_x(radicand.width())) Geometry::Line(Point::with_x(radicand.width()))
.stroked(Stroke { paint: ctx.styles().get(TextNode::FILL), thickness }), .stroked(Stroke { paint: TextNode::fill_in(ctx.styles()), thickness }),
), ),
); );

View File

@ -103,7 +103,7 @@ impl MathRow {
pub fn to_frame(self, ctx: &MathContext) -> Frame { pub fn to_frame(self, ctx: &MathContext) -> Frame {
let styles = ctx.styles(); let styles = ctx.styles();
let align = styles.get(AlignNode::ALIGNMENT).x.resolve(styles); let align = AlignNode::alignment_in(styles).x.resolve(styles);
self.to_aligned_frame(ctx, &[], align) self.to_aligned_frame(ctx, &[], align)
} }
@ -124,7 +124,7 @@ impl MathRow {
if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
let fragments: Vec<_> = std::mem::take(&mut self.0); let fragments: Vec<_> = std::mem::take(&mut self.0);
let leading = if ctx.style.size >= MathSize::Text { let leading = if ctx.style.size >= MathSize::Text {
ctx.styles().get(ParNode::LEADING) ParNode::leading_in(ctx.styles())
} else { } else {
TIGHT_LEADING.scaled(ctx) TIGHT_LEADING.scaled(ctx)
}; };

View File

@ -58,8 +58,8 @@ impl LayoutRoot for DocumentNode {
Ok(Document { Ok(Document {
pages, pages,
title: styles.get(Self::TITLE).clone(), title: Self::title_in(styles),
author: styles.get(Self::AUTHOR).0.clone(), author: Self::author_in(styles).0,
}) })
} }
} }

View File

@ -106,15 +106,15 @@ impl Prepare for HeadingNode {
} }
let mut numbers = Value::None; let mut numbers = Value::None;
if let Some(numbering) = styles.get(Self::NUMBERING) { if let Some(numbering) = Self::numbering_in(styles) {
numbers = numbering.apply(vt.world(), counter.advance(self))?; numbers = numbering.apply(vt.world(), counter.advance(self))?;
} }
this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); this.push_field("outlined", Value::Bool(Self::outlined_in(styles)));
this.push_field("numbers", numbers); this.push_field("numbers", numbers);
let meta = Meta::Node(my_id, this.clone()); let meta = Meta::Node(my_id, this.clone());
Ok(this.styled(MetaNode::DATA, vec![meta])) Ok(this.styled(MetaNode::set_data(vec![meta])))
} }
} }
@ -145,11 +145,11 @@ impl Finalize for HeadingNode {
let below = Em::new(0.75) / scale; let below = Em::new(0.75) / scale;
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set(TextNode::SIZE, TextSize(size.into())); map.set(TextNode::set_size(TextSize(size.into())));
map.set(TextNode::WEIGHT, FontWeight::BOLD); map.set(TextNode::set_weight(FontWeight::BOLD));
map.set(BlockNode::ABOVE, VNode::block_around(above.into())); map.set(BlockNode::set_above(VNode::block_around(above.into())));
map.set(BlockNode::BELOW, VNode::block_around(below.into())); map.set(BlockNode::set_below(VNode::block_around(below.into())));
map.set(BlockNode::STICKY, true); map.set(BlockNode::set_sticky(true));
realized.styled_with_map(map) realized.styled_with_map(map)
} }
} }

View File

@ -91,8 +91,8 @@ impl Show for LinkNode {
impl Finalize for LinkNode { impl Finalize for LinkNode {
fn finalize(&self, realized: Content) -> Content { fn finalize(&self, realized: Content) -> Content {
realized realized
.styled(MetaNode::DATA, vec![Meta::Link(self.dest())]) .styled(MetaNode::set_data(vec![Meta::Link(self.dest())]))
.styled(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false))) .styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
} }
} }

View File

@ -102,9 +102,9 @@ impl Show for OutlineNode {
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
let mut seq = vec![ParbreakNode::new().pack()]; let mut seq = vec![ParbreakNode::new().pack()];
if let Some(title) = styles.get(Self::TITLE) { if let Some(title) = Self::title_in(styles) {
let title = title.clone().unwrap_or_else(|| { let title = title.clone().unwrap_or_else(|| {
TextNode::packed(match styles.get(TextNode::LANG) { TextNode::packed(match TextNode::lang_in(styles) {
Lang::GERMAN => "Inhaltsverzeichnis", Lang::GERMAN => "Inhaltsverzeichnis",
Lang::ENGLISH | _ => "Contents", Lang::ENGLISH | _ => "Contents",
}) })
@ -113,13 +113,13 @@ impl Show for OutlineNode {
seq.push( seq.push(
HeadingNode::new(title) HeadingNode::new(title)
.pack() .pack()
.styled(HeadingNode::NUMBERING, None) .styled(HeadingNode::set_numbering(None))
.styled(HeadingNode::OUTLINED, false), .styled(HeadingNode::set_outlined(false)),
); );
} }
let indent = styles.get(Self::INDENT); let indent = Self::indent_in(styles);
let depth = styles.get(Self::DEPTH); let depth = Self::depth_in(styles);
let mut ancestors: Vec<&Content> = vec![]; let mut ancestors: Vec<&Content> = vec![];
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) { for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
@ -171,7 +171,7 @@ impl Show for OutlineNode {
seq.push(start.linked(Destination::Internal(loc))); seq.push(start.linked(Destination::Internal(loc)));
// Add filler symbols between the section name and page number. // Add filler symbols between the section name and page number.
if let Some(filler) = styles.get(Self::FILL) { if let Some(filler) = Self::fill_in(styles) {
seq.push(SpaceNode::new().pack()); seq.push(SpaceNode::new().pack());
seq.push( seq.push(
BoxNode::new() BoxNode::new()

View File

@ -1,6 +1,8 @@
//! Extension traits. //! Extension traits.
use crate::layout::{AlignNode, MoveNode, PadNode};
use crate::prelude::*; use crate::prelude::*;
use crate::text::{EmphNode, FontFamily, FontList, StrongNode, TextNode, UnderlineNode};
/// Additional methods on content. /// Additional methods on content.
pub trait ContentExt { pub trait ContentExt {
@ -28,27 +30,27 @@ pub trait ContentExt {
impl ContentExt for Content { impl ContentExt for Content {
fn strong(self) -> Self { fn strong(self) -> Self {
crate::text::StrongNode::new(self).pack() StrongNode::new(self).pack()
} }
fn emph(self) -> Self { fn emph(self) -> Self {
crate::text::EmphNode::new(self).pack() EmphNode::new(self).pack()
} }
fn underlined(self) -> Self { fn underlined(self) -> Self {
crate::text::UnderlineNode::new(self).pack() UnderlineNode::new(self).pack()
} }
fn linked(self, dest: Destination) -> Self { fn linked(self, dest: Destination) -> Self {
self.styled(MetaNode::DATA, vec![Meta::Link(dest.clone())]) self.styled(MetaNode::set_data(vec![Meta::Link(dest.clone())]))
} }
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self { fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {
self.styled(crate::layout::AlignNode::ALIGNMENT, aligns) self.styled(AlignNode::set_alignment(aligns))
} }
fn padded(self, padding: Sides<Rel<Length>>) -> Self { fn padded(self, padding: Sides<Rel<Length>>) -> Self {
crate::layout::PadNode::new(self) PadNode::new(self)
.with_left(padding.left) .with_left(padding.left)
.with_top(padding.top) .with_top(padding.top)
.with_right(padding.right) .with_right(padding.right)
@ -57,10 +59,7 @@ impl ContentExt for Content {
} }
fn moved(self, delta: Axes<Rel<Length>>) -> Self { fn moved(self, delta: Axes<Rel<Length>>) -> Self {
crate::layout::MoveNode::new(self) MoveNode::new(self).with_dx(delta.x).with_dy(delta.y).pack()
.with_dx(delta.x)
.with_dy(delta.y)
.pack()
} }
} }
@ -68,18 +67,15 @@ impl ContentExt for Content {
pub trait StyleMapExt { pub trait StyleMapExt {
/// Set a font family composed of a preferred family and existing families /// Set a font family composed of a preferred family and existing families
/// from a style chain. /// from a style chain.
fn set_family(&mut self, preferred: crate::text::FontFamily, existing: StyleChain); fn set_family(&mut self, preferred: FontFamily, existing: StyleChain);
} }
impl StyleMapExt for StyleMap { impl StyleMapExt for StyleMap {
fn set_family(&mut self, preferred: crate::text::FontFamily, existing: StyleChain) { fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
self.set( self.set(TextNode::set_font(FontList(
crate::text::TextNode::FONT, std::iter::once(preferred)
crate::text::FontList( .chain(TextNode::font_in(existing))
std::iter::once(preferred) .collect(),
.chain(existing.get(crate::text::TextNode::FONT).0.iter().cloned()) )));
.collect(),
),
);
} }
} }

View File

@ -75,16 +75,13 @@ pub struct UnderlineNode {
impl Show for UnderlineNode { impl Show for UnderlineNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled( Ok(self.body().styled(TextNode::set_deco(Decoration {
TextNode::DECO, line: DecoLine::Underline,
Decoration { stroke: Self::stroke_in(styles).unwrap_or_default(),
line: DecoLine::Underline, offset: Self::offset_in(styles),
stroke: styles.get(Self::STROKE).unwrap_or_default(), extent: Self::extent_in(styles),
offset: styles.get(Self::OFFSET), evade: Self::evade_in(styles),
extent: styles.get(Self::EXTENT), })))
evade: styles.get(Self::EVADE),
},
))
} }
} }
@ -165,16 +162,13 @@ pub struct OverlineNode {
impl Show for OverlineNode { impl Show for OverlineNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled( Ok(self.body().styled(TextNode::set_deco(Decoration {
TextNode::DECO, line: DecoLine::Overline,
Decoration { stroke: Self::stroke_in(styles).unwrap_or_default(),
line: DecoLine::Overline, offset: Self::offset_in(styles),
stroke: styles.get(Self::STROKE).unwrap_or_default(), extent: Self::extent_in(styles),
offset: styles.get(Self::OFFSET), evade: Self::evade_in(styles),
extent: styles.get(Self::EXTENT), })))
evade: styles.get(Self::EVADE),
},
))
} }
} }
@ -239,16 +233,13 @@ pub struct StrikeNode {
impl Show for StrikeNode { impl Show for StrikeNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled( Ok(self.body().styled(TextNode::set_deco(Decoration {
TextNode::DECO, line: DecoLine::Strikethrough,
Decoration { stroke: Self::stroke_in(styles).unwrap_or_default(),
line: DecoLine::Strikethrough, offset: Self::offset_in(styles),
stroke: styles.get(Self::STROKE).unwrap_or_default(), extent: Self::extent_in(styles),
offset: styles.get(Self::OFFSET), evade: false,
extent: styles.get(Self::EXTENT), })))
evade: false,
},
))
} }
} }

View File

@ -103,7 +103,7 @@ pub struct StrongNode {
impl Show for StrongNode { impl Show for StrongNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA)))) Ok(self.body().styled(TextNode::set_delta(Delta(Self::delta_in(styles)))))
} }
} }
@ -164,7 +164,7 @@ pub struct EmphNode {
impl Show for EmphNode { impl Show for EmphNode {
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
Ok(self.body().styled(TextNode::EMPH, Toggle)) Ok(self.body().styled(TextNode::set_emph(Toggle)))
} }
} }
@ -233,7 +233,7 @@ pub fn upper(args: &mut Args) -> SourceResult<Value> {
fn case(case: Case, args: &mut Args) -> SourceResult<Value> { fn case(case: Case, args: &mut Args) -> SourceResult<Value> {
Ok(match args.expect("string or content")? { Ok(match args.expect("string or content")? {
ToCase::Str(v) => Value::Str(case.apply(&v).into()), ToCase::Str(v) => Value::Str(case.apply(&v).into()),
ToCase::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))),
}) })
} }
@ -313,7 +313,7 @@ cast_to_value! {
#[func] #[func]
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> { pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
let body: Content = args.expect("content")?; let body: Content = args.expect("content")?;
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) Ok(Value::Content(body.styled(TextNode::set_smallcaps(true))))
} }
/// Create blind text. /// Create blind text.

View File

@ -578,6 +578,15 @@ cast_to_value! {
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct FontList(pub Vec<FontFamily>); pub struct FontList(pub Vec<FontFamily>);
impl IntoIterator for FontList {
type IntoIter = std::vec::IntoIter<FontFamily>;
type Item = FontFamily;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
cast_from_value! { cast_from_value! {
FontList, FontList,
family: FontFamily => Self(vec![family]), family: FontFamily => Self(vec![family]),
@ -664,7 +673,7 @@ impl Resolve for HorizontalDir {
fn resolve(self, styles: StyleChain) -> Self::Output { fn resolve(self, styles: StyleChain) -> Self::Output {
match self.0 { match self.0 {
Smart::Auto => styles.get(TextNode::LANG).dir(), Smart::Auto => TextNode::lang_in(styles).dir(),
Smart::Custom(dir) => dir, Smart::Custom(dir) => dir,
} }
} }
@ -688,7 +697,7 @@ impl Resolve for Hyphenate {
fn resolve(self, styles: StyleChain) -> Self::Output { fn resolve(self, styles: StyleChain) -> Self::Output {
match self.0 { match self.0 {
Smart::Auto => styles.get(ParNode::JUSTIFY), Smart::Auto => ParNode::justify_in(styles),
Smart::Custom(v) => v, Smart::Custom(v) => v,
} }
} }

View File

@ -114,7 +114,7 @@ impl Prepare for RawNode {
mut this: Content, mut this: Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
this.push_field("lang", styles.get(Self::LANG).clone()); this.push_field("lang", Self::lang_in(styles).clone());
Ok(this) Ok(this)
} }
} }
@ -122,7 +122,7 @@ impl Prepare for RawNode {
impl Show for RawNode { impl Show for RawNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
let text = self.text(); let text = self.text();
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let lang = Self::lang_in(styles).as_ref().map(|s| s.to_lowercase());
let foreground = THEME let foreground = THEME
.settings .settings
.foreground .foreground
@ -181,11 +181,11 @@ impl Show for RawNode {
impl Finalize for RawNode { impl Finalize for RawNode {
fn finalize(&self, realized: Content) -> Content { fn finalize(&self, realized: Content) -> Content {
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set(TextNode::OVERHANG, false); map.set(TextNode::set_overhang(false));
map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false))); map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))));
map.set(TextNode::SIZE, TextSize(Em::new(0.8).into())); map.set(TextNode::set_size(TextSize(Em::new(0.8).into())));
map.set(TextNode::FONT, FontList(vec![FontFamily::new("DejaVu Sans Mono")])); map.set(TextNode::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
map.set(SmartQuoteNode::ENABLED, false); map.set(SmartQuoteNode::set_enabled(false));
realized.styled_with_map(map) realized.styled_with_map(map)
} }
} }
@ -221,7 +221,7 @@ fn styled(piece: &str, foreground: Paint, style: synt::Style) -> Content {
let paint = to_typst(style.foreground).into(); let paint = to_typst(style.foreground).into();
if paint != foreground { if paint != foreground {
body = body.styled(TextNode::FILL, paint); body = body.styled(TextNode::set_fill(paint));
} }
if style.font_style.contains(synt::FontStyle::BOLD) { if style.font_style.contains(synt::FontStyle::BOLD) {

View File

@ -88,10 +88,10 @@ impl<'a> ShapedText<'a> {
let mut frame = Frame::new(size); let mut frame = Frame::new(size);
frame.set_baseline(top); frame.set_baseline(top);
let shift = self.styles.get(TextNode::BASELINE); let shift = TextNode::baseline_in(self.styles);
let lang = self.styles.get(TextNode::LANG); let lang = TextNode::lang_in(self.styles);
let decos = self.styles.get(TextNode::DECO); let decos = TextNode::deco_in(self.styles);
let fill = self.styles.get(TextNode::FILL); let fill = TextNode::fill_in(self.styles);
for ((font, y_offset), group) in for ((font, y_offset), group) in
self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset)) self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset))
@ -137,8 +137,8 @@ impl<'a> ShapedText<'a> {
let mut top = Abs::zero(); let mut top = Abs::zero();
let mut bottom = Abs::zero(); let mut bottom = Abs::zero();
let top_edge = self.styles.get(TextNode::TOP_EDGE); let top_edge = TextNode::top_edge_in(self.styles);
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE); let bottom_edge = TextNode::bottom_edge_in(self.styles);
// Expand top and bottom by reading the font's vertical metrics. // Expand top and bottom by reading the font's vertical metrics.
let mut expand = |font: &Font| { let mut expand = |font: &Font| {
@ -315,8 +315,7 @@ pub fn shape<'a>(
styles: StyleChain<'a>, styles: StyleChain<'a>,
dir: Dir, dir: Dir,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let size = styles.get(TextNode::SIZE); let size = TextNode::size_in(styles);
let mut ctx = ShapingContext { let mut ctx = ShapingContext {
vt, vt,
size, size,
@ -325,7 +324,7 @@ pub fn shape<'a>(
styles, styles,
variant: variant(styles), variant: variant(styles),
tags: tags(styles), tags: tags(styles),
fallback: styles.get(TextNode::FALLBACK), fallback: TextNode::fallback_in(styles),
dir, dir,
}; };
@ -494,11 +493,9 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
/// Apply tracking and spacing to the shaped glyphs. /// Apply tracking and spacing to the shaped glyphs.
fn track_and_space(ctx: &mut ShapingContext) { fn track_and_space(ctx: &mut ShapingContext) {
let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), ctx.size); let tracking = Em::from_length(TextNode::tracking_in(ctx.styles), ctx.size);
let spacing = ctx let spacing =
.styles TextNode::spacing_in(ctx.styles).map(|abs| Em::from_length(abs, ctx.size));
.get(TextNode::SPACING)
.map(|abs| Em::from_length(abs, ctx.size));
let mut glyphs = ctx.glyphs.iter_mut().peekable(); let mut glyphs = ctx.glyphs.iter_mut().peekable();
while let Some(glyph) = glyphs.next() { while let Some(glyph) = glyphs.next() {
@ -527,17 +524,17 @@ fn nbsp_delta(font: &Font) -> Option<Em> {
/// Resolve the font variant. /// Resolve the font variant.
pub fn variant(styles: StyleChain) -> FontVariant { pub fn variant(styles: StyleChain) -> FontVariant {
let mut variant = FontVariant::new( let mut variant = FontVariant::new(
styles.get(TextNode::STYLE), TextNode::style_in(styles),
styles.get(TextNode::WEIGHT), TextNode::weight_in(styles),
styles.get(TextNode::STRETCH), TextNode::stretch_in(styles),
); );
let delta = styles.get(TextNode::DELTA); let delta = TextNode::delta_in(styles);
variant.weight = variant variant.weight = variant
.weight .weight
.thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); .thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16);
if styles.get(TextNode::EMPH) { if TextNode::emph_in(styles) {
variant.style = match variant.style { variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic, FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal, FontStyle::Italic => FontStyle::Normal,
@ -558,10 +555,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
"segoe ui emoji", "segoe ui emoji",
]; ];
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] }; let tail = if TextNode::fallback_in(styles) { FALLBACKS } else { &[] };
styles TextNode::font_in(styles)
.get(TextNode::FONT)
.0
.into_iter() .into_iter()
.chain(tail.iter().copied().map(FontFamily::new)) .chain(tail.iter().copied().map(FontFamily::new))
} }
@ -574,59 +569,59 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
}; };
// Features that are on by default in Harfbuzz are only added if disabled. // Features that are on by default in Harfbuzz are only added if disabled.
if !styles.get(TextNode::KERNING) { if !TextNode::kerning_in(styles) {
feat(b"kern", 0); feat(b"kern", 0);
} }
// Features that are off by default in Harfbuzz are only added if enabled. // Features that are off by default in Harfbuzz are only added if enabled.
if styles.get(TextNode::SMALLCAPS) { if TextNode::smallcaps_in(styles) {
feat(b"smcp", 1); feat(b"smcp", 1);
} }
if styles.get(TextNode::ALTERNATES) { if TextNode::alternates_in(styles) {
feat(b"salt", 1); feat(b"salt", 1);
} }
let storage; let storage;
if let Some(set) = styles.get(TextNode::STYLISTIC_SET) { if let Some(set) = TextNode::stylistic_set_in(styles) {
storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10]; storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10];
feat(&storage, 1); feat(&storage, 1);
} }
if !styles.get(TextNode::LIGATURES) { if !TextNode::ligatures_in(styles) {
feat(b"liga", 0); feat(b"liga", 0);
feat(b"clig", 0); feat(b"clig", 0);
} }
if styles.get(TextNode::DISCRETIONARY_LIGATURES) { if TextNode::discretionary_ligatures_in(styles) {
feat(b"dlig", 1); feat(b"dlig", 1);
} }
if styles.get(TextNode::HISTORICAL_LIGATURES) { if TextNode::historical_ligatures_in(styles) {
feat(b"hilg", 1); feat(b"hilg", 1);
} }
match styles.get(TextNode::NUMBER_TYPE) { match TextNode::number_type_in(styles) {
Smart::Auto => {} Smart::Auto => {}
Smart::Custom(NumberType::Lining) => feat(b"lnum", 1), Smart::Custom(NumberType::Lining) => feat(b"lnum", 1),
Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1), Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1),
} }
match styles.get(TextNode::NUMBER_WIDTH) { match TextNode::number_width_in(styles) {
Smart::Auto => {} Smart::Auto => {}
Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1), Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1),
Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1), Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1),
} }
if styles.get(TextNode::SLASHED_ZERO) { if TextNode::slashed_zero_in(styles) {
feat(b"zero", 1); feat(b"zero", 1);
} }
if styles.get(TextNode::FRACTIONS) { if TextNode::fractions_in(styles) {
feat(b"frac", 1); feat(b"frac", 1);
} }
for (tag, value) in styles.get(TextNode::FEATURES).0 { for (tag, value) in TextNode::features_in(styles).0 {
tags.push(Feature::new(tag, value, ..)) tags.push(Feature::new(tag, value, ..))
} }
@ -636,8 +631,8 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
/// Process the language and and region of a style chain into a /// Process the language and and region of a style chain into a
/// rustybuzz-compatible BCP 47 language. /// rustybuzz-compatible BCP 47 language.
fn language(styles: StyleChain) -> rustybuzz::Language { fn language(styles: StyleChain) -> rustybuzz::Language {
let mut bcp: EcoString = styles.get(TextNode::LANG).as_str().into(); let mut bcp: EcoString = TextNode::lang_in(styles).as_str().into();
if let Some(region) = styles.get(TextNode::REGION) { if let Some(region) = TextNode::region_in(styles) {
bcp.push('-'); bcp.push('-');
bcp.push_str(region.as_str()); bcp.push_str(region.as_str());
} }

View File

@ -59,7 +59,7 @@ impl Show for SubNode {
) -> SourceResult<Content> { ) -> SourceResult<Content> {
let body = self.body(); let body = self.body();
let mut transformed = None; let mut transformed = None;
if styles.get(Self::TYPOGRAPHIC) { if Self::typographic_in(styles) {
if let Some(text) = search_text(&body, true) { if let Some(text) = search_text(&body, true) {
if is_shapable(vt, &text, styles) { if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text)); transformed = Some(TextNode::packed(text));
@ -68,10 +68,8 @@ impl Show for SubNode {
}; };
Ok(transformed.unwrap_or_else(|| { Ok(transformed.unwrap_or_else(|| {
let mut map = StyleMap::new(); body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
map.set(TextNode::BASELINE, styles.get(Self::BASELINE)); .styled(TextNode::set_size(Self::size_in(styles)))
map.set(TextNode::SIZE, styles.get(Self::SIZE));
body.styled_with_map(map)
})) }))
} }
} }
@ -132,7 +130,7 @@ impl Show for SuperNode {
) -> SourceResult<Content> { ) -> SourceResult<Content> {
let body = self.body(); let body = self.body();
let mut transformed = None; let mut transformed = None;
if styles.get(Self::TYPOGRAPHIC) { if Self::typographic_in(styles) {
if let Some(text) = search_text(&body, false) { if let Some(text) = search_text(&body, false) {
if is_shapable(vt, &text, styles) { if is_shapable(vt, &text, styles) {
transformed = Some(TextNode::packed(text)); transformed = Some(TextNode::packed(text));
@ -141,10 +139,8 @@ impl Show for SuperNode {
}; };
Ok(transformed.unwrap_or_else(|| { Ok(transformed.unwrap_or_else(|| {
let mut map = StyleMap::new(); body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
map.set(TextNode::BASELINE, styles.get(Self::BASELINE)); .styled(TextNode::set_size(Self::size_in(styles)))
map.set(TextNode::SIZE, styles.get(Self::SIZE));
body.styled_with_map(map)
})) }))
} }
} }
@ -174,7 +170,7 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
/// given string. /// given string.
fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool { fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool {
let world = vt.world(); let world = vt.world();
for family in styles.get(TextNode::FONT).0.iter() { for family in TextNode::font_in(styles) {
if let Some(font) = world if let Some(font) = world
.book() .book()
.select(family.as_str(), variant(styles)) .select(family.as_str(), variant(styles))

View File

@ -90,7 +90,7 @@ impl Layout for ImageNode {
}; };
// Compute the actual size of the fitted image. // Compute the actual size of the fitted image.
let fit = styles.get(Self::FIT); let fit = Self::fit_in(styles);
let fitted = match fit { let fitted = match fit {
ImageFit::Cover | ImageFit::Contain => { ImageFit::Cover | ImageFit::Contain => {
if wide == (fit == ImageFit::Contain) { if wide == (fit == ImageFit::Contain) {

View File

@ -84,7 +84,7 @@ impl Layout for LineNode {
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let stroke = styles.get(Self::STROKE).unwrap_or_default(); let stroke = Self::stroke_in(styles).unwrap_or_default();
let origin = self let origin = self
.start() .start()

View File

@ -167,11 +167,11 @@ impl Layout for RectNode {
ShapeKind::Rect, ShapeKind::Rect,
&self.body(), &self.body(),
Axes::new(self.width(), self.height()), Axes::new(self.width(), self.height()),
styles.get(Self::FILL), Self::fill_in(styles),
styles.get(Self::STROKE), Self::stroke_in(styles),
styles.get(Self::INSET), Self::inset_in(styles),
styles.get(Self::OUTSET), Self::outset_in(styles),
styles.get(Self::RADIUS), Self::radius_in(styles),
) )
} }
} }
@ -301,11 +301,11 @@ impl Layout for SquareNode {
ShapeKind::Square, ShapeKind::Square,
&self.body(), &self.body(),
Axes::new(self.width(), self.height()), Axes::new(self.width(), self.height()),
styles.get(Self::FILL), Self::fill_in(styles),
styles.get(Self::STROKE), Self::stroke_in(styles),
styles.get(Self::INSET), Self::inset_in(styles),
styles.get(Self::OUTSET), Self::outset_in(styles),
styles.get(Self::RADIUS), Self::radius_in(styles),
) )
} }
} }
@ -394,10 +394,10 @@ impl Layout for EllipseNode {
ShapeKind::Ellipse, ShapeKind::Ellipse,
&self.body(), &self.body(),
Axes::new(self.width(), self.height()), Axes::new(self.width(), self.height()),
styles.get(Self::FILL), Self::fill_in(styles),
styles.get(Self::STROKE).map(Sides::splat), Self::stroke_in(styles).map(Sides::splat),
styles.get(Self::INSET), Self::inset_in(styles),
styles.get(Self::OUTSET), Self::outset_in(styles),
Corners::splat(Rel::zero()), Corners::splat(Rel::zero()),
) )
} }
@ -522,10 +522,10 @@ impl Layout for CircleNode {
ShapeKind::Circle, ShapeKind::Circle,
&self.body(), &self.body(),
Axes::new(self.width(), self.height()), Axes::new(self.width(), self.height()),
styles.get(Self::FILL), Self::fill_in(styles),
styles.get(Self::STROKE).map(Sides::splat), Self::stroke_in(styles).map(Sides::splat),
styles.get(Self::INSET), Self::inset_in(styles),
styles.get(Self::OUTSET), Self::outset_in(styles),
Corners::splat(Rel::zero()), Corners::splat(Rel::zero()),
) )
} }

View File

@ -20,7 +20,9 @@ struct Field {
attrs: Vec<syn::Attribute>, attrs: Vec<syn::Attribute>,
vis: syn::Visibility, vis: syn::Visibility,
ident: Ident, ident: Ident,
ident_in: Ident,
with_ident: Ident, with_ident: Ident,
set_ident: Ident,
name: String, name: String,
positional: bool, positional: bool,
@ -36,6 +38,7 @@ struct Field {
skip: bool, skip: bool,
ty: syn::Type, ty: syn::Type,
output: syn::Type,
default: Option<syn::Expr>, default: Option<syn::Expr>,
} }
@ -62,20 +65,17 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
let mut fields = vec![]; let mut fields = vec![];
for field in &named.named { for field in &named.named {
let Some(mut ident) = field.ident.clone() else { let Some(ident) = field.ident.clone() else {
bail!(field, "expected named field"); bail!(field, "expected named field");
}; };
let mut attrs = field.attrs.clone(); let mut attrs = field.attrs.clone();
let settable = has_attr(&mut attrs, "settable"); let mut field = Field {
if settable {
ident = Ident::new(&ident.to_string().to_uppercase(), ident.span());
}
let field = Field {
vis: field.vis.clone(), vis: field.vis.clone(),
ident: ident.clone(), ident: ident.clone(),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
with_ident: Ident::new(&format!("with_{}", ident), ident.span()), with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
name: kebab_case(&ident), name: kebab_case(&ident),
positional: has_attr(&mut attrs, "positional"), positional: has_attr(&mut attrs, "positional"),
@ -88,12 +88,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
Some(ident) => Shorthand::Named(ident), Some(ident) => Shorthand::Named(ident),
}), }),
settable, settable: has_attr(&mut attrs, "settable"),
fold: has_attr(&mut attrs, "fold"), fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"), resolve: has_attr(&mut attrs, "resolve"),
skip: has_attr(&mut attrs, "skip"), skip: has_attr(&mut attrs, "skip"),
ty: field.ty.clone(), ty: field.ty.clone(),
output: field.ty.clone(),
default: parse_attr(&mut attrs, "default")?.map(|opt| { default: parse_attr(&mut attrs, "default")?.map(|opt| {
opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }) opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() })
}), }),
@ -104,6 +105,15 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
}, },
}; };
if field.resolve {
let output = &field.output;
field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output };
}
if field.fold {
let output = &field.output;
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
}
if !field.positional && !field.named && !field.variadic && !field.settable { if !field.positional && !field.named && !field.variadic && !field.settable {
bail!(ident, "expected positional, named, variadic, or settable"); bail!(ident, "expected positional, named, variadic, or settable");
} }
@ -140,34 +150,22 @@ fn create(node: &Node) -> TokenStream {
let attrs = &node.attrs; let attrs = &node.attrs;
let vis = &node.vis; let vis = &node.vis;
let ident = &node.ident; let ident = &node.ident;
let name = &node.name;
// Inherent methods and functions.
let new = create_new_func(node); let new = create_new_func(node);
let field_methods = node.inherent().map(create_field_method);
let with_fields_methods = node.inherent().map(create_with_field_method);
let field_in_methods = node.settable().map(create_field_in_method);
let field_style_methods = node.settable().map(create_field_style_method);
// Trait implementations.
let construct = node let construct = node
.capable .capable
.iter() .iter()
.all(|capability| capability != "Construct") .all(|capability| capability != "Construct")
.then(|| create_construct_impl(node)); .then(|| create_construct_impl(node));
let set = create_set_impl(node); let set = create_set_impl(node);
let builders = node.inherent().map(create_builder_method); let node = create_node_impl(node);
let accessors = node.inherent().map(create_accessor_method);
let vtable = create_vtable(node);
let mut modules = vec![];
let mut items = vec![];
let scope = quote::format_ident!("__{}_keys", ident);
for field in node.settable() {
let ident = &field.ident;
let attrs = &field.attrs;
let vis = &field.vis;
let ty = &field.ty;
modules.push(create_field_module(node, field));
items.push(quote! {
#(#attrs)*
#vis const #ident: #scope::#ident::Key<#ty>
= #scope::#ident::Key(::std::marker::PhantomData);
});
}
quote! { quote! {
#(#attrs)* #(#attrs)*
@ -178,7 +176,10 @@ fn create(node: &Node) -> TokenStream {
impl #ident { impl #ident {
#new #new
#(#builders)* #(#field_methods)*
#(#with_fields_methods)*
#(#field_in_methods)*
#(#field_style_methods)*
/// The node's span. /// The node's span.
pub fn span(&self) -> Option<::typst::syntax::Span> { pub fn span(&self) -> Option<::typst::syntax::Span> {
@ -186,25 +187,7 @@ fn create(node: &Node) -> TokenStream {
} }
} }
impl #ident { #node
#(#accessors)*
#(#items)*
}
impl ::typst::model::Node for #ident {
fn id() -> ::typst::model::NodeId {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
name: #name,
vtable: #vtable,
};
::typst::model::NodeId::from_meta(&META)
}
fn pack(self) -> ::typst::model::Content {
self.0
}
}
#construct #construct
#set #set
@ -213,12 +196,6 @@ fn create(node: &Node) -> TokenStream {
value.0.into() value.0.into()
} }
} }
#[allow(non_snake_case)]
mod #scope {
use super::*;
#(#modules)*
}
} }
} }
@ -252,20 +229,8 @@ fn create_new_func(node: &Node) -> TokenStream {
} }
} }
/// Create a builder pattern method for a field.
fn create_builder_method(field: &Field) -> TokenStream {
let Field { with_ident, ident, name, ty, .. } = field;
let doc = format!("Set the [`{}`](Self::{}) field.", name, ident);
quote! {
#[doc = #doc]
pub fn #with_ident(mut self, #ident: #ty) -> Self {
Self(self.0.with_field(#name, #ident))
}
}
}
/// Create an accessor methods for a field. /// Create an accessor methods for a field.
fn create_accessor_method(field: &Field) -> TokenStream { fn create_field_method(field: &Field) -> TokenStream {
let Field { attrs, vis, ident, name, ty, .. } = field; let Field { attrs, vis, ident, name, ty, .. } = field;
quote! { quote! {
#(#attrs)* #(#attrs)*
@ -275,6 +240,145 @@ fn create_accessor_method(field: &Field) -> TokenStream {
} }
} }
/// Create a builder pattern method for a field.
fn create_with_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, with_ident, name, ty, .. } = field;
let doc = format!("Set the [`{}`](Self::{}) field.", name, ident);
quote! {
#[doc = #doc]
#vis fn #with_ident(mut self, #ident: #ty) -> Self {
Self(self.0.with_field(#name, #ident))
}
}
}
/// Create a style chain access method for a field.
fn create_field_in_method(field: &Field) -> TokenStream {
let Field { vis, ident_in, name, ty, output, default, .. } = field;
let doc = format!("Access the `{}` field in the given style chain.", name);
let args = quote! { ::typst::model::NodeId::of::<Self>(), #name };
let body = if field.fold && field.resolve {
quote! {
fn next(
mut values: impl ::std::iter::Iterator<Item = #ty>,
styles: ::typst::model::StyleChain,
) -> #output {
values
.next()
.map(|value| {
::typst::model::Fold::fold(
::typst::model::Resolve::resolve(value, styles),
next(values, styles),
)
})
.unwrap_or_else(|| #default)
}
next(styles.properties(#args), styles)
}
} else if field.fold {
quote! {
fn next(
mut values: impl ::std::iter::Iterator<Item = #ty>,
styles: ::typst::model::StyleChain,
) -> #output {
values
.next()
.map(|value| {
::typst::model::Fold::fold(value, next(values, styles))
})
.unwrap_or_else(|| #default)
}
next(styles.properties(#args), styles)
}
} else if field.resolve {
quote! {
::typst::model::Resolve::resolve(
styles.property::<#ty>(#args).unwrap_or_else(|| #default),
styles
)
}
} else {
quote! {
styles.property(#args).unwrap_or_else(|| #default)
}
};
quote! {
#[doc = #doc]
#[allow(unused)]
#vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
#body
}
}
}
/// Create a style creation method for a field.
fn create_field_style_method(field: &Field) -> TokenStream {
let Field { vis, ident, set_ident, name, ty, .. } = field;
let doc = format!("Create a style property for the `{}` field.", name);
quote! {
#[doc = #doc]
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Property {
::typst::model::Property::new(
::typst::model::NodeId::of::<Self>(),
#name.into(),
#ident.into()
)
}
}
}
/// Create the node's `Node` implementation.
fn create_node_impl(node: &Node) -> TokenStream {
let ident = &node.ident;
let name = &node.name;
let vtable_func = create_vtable_func(node);
quote! {
impl ::typst::model::Node for #ident {
fn id() -> ::typst::model::NodeId {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
name: #name,
vtable: #vtable_func,
};
::typst::model::NodeId::from_meta(&META)
}
fn pack(self) -> ::typst::model::Content {
self.0
}
}
}
}
/// Create the node's metadata vtable.
fn create_vtable_func(node: &Node) -> TokenStream {
let ident = &node.ident;
let checks =
node.capable
.iter()
.filter(|&ident| ident != "Construct")
.map(|capability| {
quote! {
if id == ::std::any::TypeId::of::<dyn #capability>() {
return Some(unsafe {
::typst::util::fat::vtable(&
Self(::typst::model::Content::new::<#ident>()) as &dyn #capability
)
});
}
}
});
quote! {
|id| {
#(#checks)*
None
}
}
}
/// Create the node's `Construct` implementation. /// Create the node's `Construct` implementation.
fn create_construct_impl(node: &Node) -> TokenStream { fn create_construct_impl(node: &Node) -> TokenStream {
let ident = &node.ident; let ident = &node.ident;
@ -353,8 +457,8 @@ fn create_set_impl(node: &Node) -> TokenStream {
.settable() .settable()
.filter(|field| !field.skip) .filter(|field| !field.skip)
.map(|field| { .map(|field| {
let ident = &field.ident;
let name = &field.name; let name = &field.name;
let set_ident = &field.set_ident;
let value = match &field.shorthand { let value = match &field.shorthand {
Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? }, Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? },
Some(Shorthand::Named(named)) => { Some(Shorthand::Named(named)) => {
@ -364,7 +468,7 @@ fn create_set_impl(node: &Node) -> TokenStream {
None => quote! { args.named(#name)? }, None => quote! { args.named(#name)? },
}; };
quote! { styles.set_opt(Self::#ident, #value); } quote! { styles.set_opt(#value.map(Self::#set_ident)); }
}) })
.collect(); .collect();
@ -417,123 +521,3 @@ fn create_set_impl(node: &Node) -> TokenStream {
} }
} }
} }
/// Create the module for a single field.
fn create_field_module(node: &Node, field: &Field) -> TokenStream {
let node_ident = &node.ident;
let ident = &field.ident;
let name = &field.name;
let ty = &field.ty;
let default = &field.default;
let mut output = quote! { #ty };
if field.resolve {
output = quote! { <#output as ::typst::model::Resolve>::Output };
}
if field.fold {
output = quote! { <#output as ::typst::model::Fold>::Output };
}
let value = if field.resolve && field.fold {
quote! {
values
.next()
.map(|value| {
::typst::model::Fold::fold(
::typst::model::Resolve::resolve(value, chain),
Self::get(chain, values),
)
})
.unwrap_or(#default)
}
} else if field.resolve {
quote! {
::typst::model::Resolve::resolve(
values.next().unwrap_or(#default),
chain
)
}
} else if field.fold {
quote! {
values
.next()
.map(|value| {
::typst::model::Fold::fold(
value,
Self::get(chain, values),
)
})
.unwrap_or(#default)
}
} else {
quote! {
values.next().unwrap_or(#default)
}
};
// Generate the contents of the module.
let scope = quote! {
use super::*;
pub struct Key<T>(pub ::std::marker::PhantomData<T>);
impl ::std::marker::Copy for Key<#ty> {}
impl ::std::clone::Clone for Key<#ty> {
fn clone(&self) -> Self { *self }
}
impl ::typst::model::Key for Key<#ty> {
type Value = #ty;
type Output = #output;
fn id() -> ::typst::model::KeyId {
static META: ::typst::model::KeyMeta = ::typst::model::KeyMeta {
name: #name,
};
::typst::model::KeyId::from_meta(&META)
}
fn node() -> ::typst::model::NodeId {
::typst::model::NodeId::of::<#node_ident>()
}
fn get(
chain: ::typst::model::StyleChain,
mut values: impl ::std::iter::Iterator<Item = Self::Value>,
) -> Self::Output {
#value
}
}
};
// Generate the module code.
quote! {
pub mod #ident { #scope }
}
}
/// Create the node's metadata vtable.
fn create_vtable(node: &Node) -> TokenStream {
let ident = &node.ident;
let checks =
node.capable
.iter()
.filter(|&ident| ident != "Construct")
.map(|capability| {
quote! {
if id == ::std::any::TypeId::of::<dyn #capability>() {
return Some(unsafe {
::typst::util::fat::vtable(&
Self(::typst::model::Content::new::<#ident>()) as &dyn #capability
)
});
}
}
});
quote! {
|id| {
#(#checks)*
None
}
}
}

View File

@ -274,7 +274,7 @@ impl Frame {
if self.is_empty() { if self.is_empty() {
return; return;
} }
for meta in styles.get(MetaNode::DATA) { for meta in MetaNode::data_in(styles) {
if matches!(meta, Meta::Hidden) { if matches!(meta, Meta::Hidden) {
self.clear(); self.clear();
break; break;

View File

@ -354,7 +354,7 @@ fn eval_markup(
} }
let tail = eval_markup(vm, exprs)?; let tail = eval_markup(vm, exprs)?;
seq.push(tail.styled_with_recipe(vm.world, recipe)?) seq.push(tail.apply_recipe(vm.world, recipe)?)
} }
expr => match expr.eval(vm)? { expr => match expr.eval(vm)? {
Value::Label(label) => { Value::Label(label) => {
@ -783,7 +783,7 @@ fn eval_code(
} }
let tail = eval_code(vm, exprs)?.display(); let tail = eval_code(vm, exprs)?.display();
Value::Content(tail.styled_with_recipe(vm.world, recipe)?) Value::Content(tail.apply_recipe(vm.world, recipe)?)
} }
_ => expr.eval(vm)?, _ => expr.eval(vm)?,
}; };

View File

@ -7,7 +7,7 @@ use std::ops::{Add, AddAssign};
use comemo::Tracked; use comemo::Tracked;
use ecow::{EcoString, EcoVec}; use ecow::{EcoString, EcoVec};
use super::{node, Guard, Key, Property, Recipe, Style, StyleMap}; use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm}; use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm};
use crate::syntax::Span; use crate::syntax::Span;
@ -66,14 +66,9 @@ impl Content {
self.with_field("label", label) self.with_field("label", label)
} }
/// Style this content with a single style property.
pub fn styled<K: Key>(self, key: K, value: K::Value) -> Self {
self.styled_with_entry(Style::Property(Property::new(key, value)))
}
/// Style this content with a style entry. /// Style this content with a style entry.
pub fn styled_with_entry(self, style: Style) -> Self { pub fn styled(self, style: impl Into<Style>) -> Self {
self.styled_with_map(style.into()) self.styled_with_map(style.into().into())
} }
/// Style this content with a full style map. /// Style this content with a full style map.
@ -90,7 +85,7 @@ impl Content {
} }
/// Style this content with a recipe, eagerly applying it if possible. /// Style this content with a recipe, eagerly applying it if possible.
pub fn styled_with_recipe( pub fn apply_recipe(
self, self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
recipe: Recipe, recipe: Recipe,
@ -98,7 +93,7 @@ impl Content {
if recipe.selector.is_none() { if recipe.selector.is_none() {
recipe.apply(world, self) recipe.apply(world, self)
} else { } else {
Ok(self.styled_with_entry(Style::Recipe(recipe))) Ok(self.styled(Style::Recipe(recipe)))
} }
} }
@ -135,6 +130,7 @@ impl Content {
} }
} }
/// Attach a field to the content.
pub fn with_field( pub fn with_field(
mut self, mut self,
name: impl Into<EcoString>, name: impl Into<EcoString>,
@ -154,6 +150,7 @@ impl Content {
} }
} }
/// Access a field on the content.
pub fn field(&self, name: &str) -> Option<&Value> { pub fn field(&self, name: &str) -> Option<&Value> {
static NONE: Value = Value::None; static NONE: Value = Value::None;
self.fields self.fields
@ -163,10 +160,6 @@ impl Content {
.or_else(|| (name == "label").then(|| &NONE)) .or_else(|| (name == "label").then(|| &NONE))
} }
pub fn fields(&self) -> &[(EcoString, Value)] {
&self.fields
}
#[track_caller] #[track_caller]
pub fn cast_field<T: Cast>(&self, name: &str) -> T { pub fn cast_field<T: Cast>(&self, name: &str) -> T {
match self.field(name) { match self.field(name) {
@ -175,6 +168,11 @@ impl Content {
} }
} }
/// List all fields on the content.
pub fn fields(&self) -> &[(EcoString, Value)] {
&self.fields
}
/// Whether the contained node is of type `T`. /// Whether the contained node is of type `T`.
pub fn is<T>(&self) -> bool pub fn is<T>(&self) -> bool
where where

View File

@ -1,8 +1,5 @@
use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::iter; use std::iter;
use std::marker::PhantomData;
use comemo::Tracked; use comemo::Tracked;
use ecow::EcoString; use ecow::EcoString;
@ -33,15 +30,13 @@ impl StyleMap {
/// If the property needs folding and the value is already contained in the /// If the property needs folding and the value is already contained in the
/// style map, `self` contributes the outer values and `value` is the inner /// style map, `self` contributes the outer values and `value` is the inner
/// one. /// one.
pub fn set<K: Key>(&mut self, key: K, value: K::Value) { pub fn set(&mut self, property: Property) {
self.0.push(Style::Property(Property::new(key, value))); self.0.push(Style::Property(property));
} }
/// Set an inner value for a style property if it is `Some(_)`. /// Set an inner value for a style property if it is `Some(_)`.
pub fn set_opt<K: Key>(&mut self, key: K, value: Option<K::Value>) { pub fn set_opt(&mut self, property: Option<Property>) {
if let Some(value) = value { self.0.extend(property.map(Style::Property));
self.set(key, value);
}
} }
/// Remove the style that was last set. /// Remove the style that was last set.
@ -49,14 +44,6 @@ impl StyleMap {
self.0.pop(); self.0.pop();
} }
/// Whether the map contains a style property for the given key.
pub fn contains<K: Key>(&self, _: K) -> bool {
self.0
.iter()
.filter_map(|entry| entry.property())
.any(|property| property.is::<K>())
}
/// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place.
pub fn apply(&mut self, outer: Self) { pub fn apply(&mut self, outer: Self) {
self.0.splice(0..0, outer.0.iter().cloned()); self.0.splice(0..0, outer.0.iter().cloned());
@ -75,7 +62,7 @@ impl StyleMap {
pub fn scoped(mut self) -> Self { pub fn scoped(mut self) -> Self {
for entry in &mut self.0 { for entry in &mut self.0 {
if let Style::Property(property) = entry { if let Style::Property(property) = entry {
property.make_scoped(); property.scoped = true;
} }
} }
self self
@ -163,37 +150,37 @@ impl Debug for Style {
} }
} }
impl From<Property> for Style {
fn from(property: Property) -> Self {
Self::Property(property)
}
}
/// A style property originating from a set rule or constructor. /// A style property originating from a set rule or constructor.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct Property { pub struct Property {
/// The id of the property's [key](Key).
key: KeyId,
/// The id of the node the property belongs to. /// The id of the node the property belongs to.
node: NodeId, node: NodeId,
/// The property's name.
name: EcoString,
/// The property's value.
value: Value,
/// Whether the property should only affect the first node down the /// Whether the property should only affect the first node down the
/// hierarchy. Used by constructors. /// hierarchy. Used by constructors.
scoped: bool, scoped: bool,
/// The property's value.
value: Value,
/// The span of the set rule the property stems from. /// The span of the set rule the property stems from.
origin: Option<Span>, origin: Option<Span>,
} }
impl Property { impl Property {
/// Create a new property from a key-value pair. /// Create a new property from a key-value pair.
pub fn new<K: Key>(_: K, value: K::Value) -> Self { pub fn new(node: NodeId, name: EcoString, value: Value) -> Self {
Self { Self { node, name, value, scoped: false, origin: None }
key: KeyId::of::<K>(),
node: K::node(),
value: value.into(),
scoped: false,
origin: None,
}
} }
/// Whether this property has the given key. /// Whether this property is the given one.
pub fn is<K: Key>(&self) -> bool { pub fn is(&self, node: NodeId, name: &str) -> bool {
self.key == KeyId::of::<K>() self.node == node && self.name == name
} }
/// Whether this property belongs to the node with the given id. /// Whether this property belongs to the node with the given id.
@ -201,37 +188,24 @@ impl Property {
self.node == node self.node == node
} }
/// Access the property's value if it is of the given key. /// Access the property's value as the given type.
#[track_caller] #[track_caller]
pub fn cast<K: Key>(&self) -> Option<K::Value> { pub fn cast<T: Cast>(&self) -> T {
if self.key == KeyId::of::<K>() { self.value.clone().cast().unwrap_or_else(|err| {
Some(self.value.clone().cast().unwrap_or_else(|err| { panic!(
panic!("{} (for {} with value {:?})", err, self.key.name(), self.value) "{} (for {}.{} with value {:?})",
})) err,
} else { self.node.name(),
None self.name,
} self.value
} )
})
/// The node this property is for.
pub fn node(&self) -> NodeId {
self.node
}
/// Whether the property is scoped.
pub fn scoped(&self) -> bool {
self.scoped
}
/// Make the property scoped.
pub fn make_scoped(&mut self) {
self.scoped = true;
} }
} }
impl Debug for Property { impl Debug for Property {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "#set {}({}: {:?})", self.node.name(), self.key.name(), self.value)?; write!(f, "#set {}({}: {:?})", self.node.name(), self.name, self.value)?;
if self.scoped { if self.scoped {
write!(f, " [scoped]")?; write!(f, " [scoped]")?;
} }
@ -239,92 +213,6 @@ impl Debug for Property {
} }
} }
impl PartialEq for Property {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
&& self.value.eq(&other.value)
&& self.scoped == other.scoped
}
}
trait Bounds: Debug + Sync + Send + 'static {
fn as_any(&self) -> &dyn Any;
}
impl<T> Bounds for T
where
T: Debug + Sync + Send + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
}
/// A style property key.
///
/// This trait is not intended to be implemented manually.
pub trait Key: Copy + 'static {
/// The unfolded type which this property is stored as in a style map.
type Value: Cast + Into<Value>;
/// The folded type of value that is returned when reading this property
/// from a style chain.
type Output;
/// The id of the property.
fn id() -> KeyId;
/// The id of the node the key belongs to.
fn node() -> NodeId;
/// Compute an output value from a sequence of values belonging to this key,
/// folding if necessary.
fn get(chain: StyleChain, values: impl Iterator<Item = Self::Value>) -> Self::Output;
}
/// A unique identifier for a style key.
#[derive(Copy, Clone)]
pub struct KeyId(&'static KeyMeta);
impl KeyId {
pub fn of<T: Key>() -> Self {
T::id()
}
pub fn from_meta(meta: &'static KeyMeta) -> Self {
Self(meta)
}
pub fn name(self) -> &'static str {
self.0.name
}
}
impl Debug for KeyId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.name())
}
}
impl Hash for KeyId {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.0 as *const _ as usize);
}
}
impl Eq for KeyId {}
impl PartialEq for KeyId {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.0, other.0)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct KeyMeta {
pub name: &'static str,
}
/// A show rule recipe. /// A show rule recipe.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct Recipe { pub struct Recipe {
@ -497,7 +385,7 @@ impl<'a> StyleChain<'a> {
if !self if !self
.entries() .entries()
.filter_map(Style::property) .filter_map(Style::property)
.any(|p| p.scoped() && *id == p.node()) .any(|p| p.scoped && *id == p.node)
{ {
return *self; return *self;
} }
@ -509,27 +397,39 @@ impl<'a> StyleChain<'a> {
} }
} }
/// Get the output value of a style property.
///
/// Returns the property's default value if no map in the chain contains an
/// entry for it. Also takes care of resolving and folding and returns
/// references where applicable.
pub fn get<K: Key>(self, key: K) -> K::Output {
K::get(self, self.values(key))
}
/// Iterate over all style recipes in the chain. /// Iterate over all style recipes in the chain.
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> { pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
self.entries().filter_map(Style::recipe) self.entries().filter_map(Style::recipe)
} }
/// Cast the first value for the given property in the chain.
pub fn property<T: Cast>(self, node: NodeId, name: &'a str) -> Option<T> {
self.properties(node, name).next()
}
/// Iterate over all values for the given property in the chain. /// Iterate over all values for the given property in the chain.
fn values<K: Key>(self, _: K) -> Values<'a, K> { pub fn properties<T: Cast>(
Values { self,
entries: self.entries(), node: NodeId,
key: PhantomData, name: &'a str,
barriers: 0, ) -> impl Iterator<Item = T> + '_ {
} let mut barriers = 0;
self.entries().filter_map(move |entry| {
match entry {
Style::Property(property) => {
if property.is(node, name) {
if !property.scoped || barriers <= 1 {
return Some(property.cast());
}
}
}
Style::Barrier(id) => {
barriers += (*id == node) as usize;
}
_ => {}
}
None
})
} }
/// Iterate over the entries of the chain. /// Iterate over the entries of the chain.
@ -610,38 +510,6 @@ impl<'a> Iterator for Links<'a> {
} }
} }
/// An iterator over the values in a style chain.
struct Values<'a, K> {
entries: Entries<'a>,
key: PhantomData<K>,
barriers: usize,
}
impl<'a, K: Key> Iterator for Values<'a, K> {
type Item = K::Value;
#[track_caller]
fn next(&mut self) -> Option<Self::Item> {
for entry in &mut self.entries {
match entry {
Style::Property(property) => {
if let Some(value) = property.cast::<K>() {
if !property.scoped() || self.barriers <= 1 {
return Some(value);
}
}
}
Style::Barrier(id) => {
self.barriers += (*id == K::node()) as usize;
}
_ => {}
}
}
None
}
}
/// A sequence of items with associated styles. /// A sequence of items with associated styles.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct StyleVec<T> { pub struct StyleVec<T> {

View File

@ -178,11 +178,13 @@ fn library() -> Library {
// Set page width to 120pt with 10pt margins, so that the inner page is // Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so // exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers. // that it multiplies to nice round numbers.
lib.styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into()));
lib.styles.set(PageNode::HEIGHT, Smart::Auto);
lib.styles lib.styles
.set(PageNode::MARGIN, Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into())))); .set(PageNode::set_width(Smart::Custom(Abs::pt(120.0).into())));
lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); lib.styles.set(PageNode::set_height(Smart::Auto));
lib.styles.set(PageNode::set_margin(Sides::splat(Some(Smart::Custom(
Abs::pt(10.0).into(),
)))));
lib.styles.set(TextNode::set_size(TextSize(Abs::pt(10.0).into())));
// Hook up helpers into the global scope. // Hook up helpers into the global scope.
lib.global.scope_mut().def_func::<TestFunc>("test"); lib.global.scope_mut().def_func::<TestFunc>("test");