mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Rework style chain access
This commit is contained in:
parent
e5eab73374
commit
d7a65fa26d
@ -39,10 +39,12 @@ static FONTS: Lazy<(Prehashed<FontBook>, Vec<Font>)> = Lazy::new(|| {
|
||||
|
||||
static LIBRARY: Lazy<Prehashed<Library>> = Lazy::new(|| {
|
||||
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
|
||||
.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());
|
||||
Prehashed::new(lib)
|
||||
});
|
||||
|
@ -16,8 +16,7 @@ use crate::prelude::*;
|
||||
/// Category: layout
|
||||
#[node(Show)]
|
||||
#[set({
|
||||
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
|
||||
styles.set(Self::ALIGNMENT, aligns);
|
||||
styles.set(Self::set_alignment(args.find()?.unwrap_or_default()));
|
||||
})]
|
||||
pub struct AlignNode {
|
||||
/// The alignment along both axes.
|
||||
|
@ -68,7 +68,7 @@ impl Layout for ColumnsNode {
|
||||
|
||||
// Determine the width of the gutter and each column.
|
||||
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 backlog: Vec<_> = std::iter::once(®ions.size.y)
|
||||
@ -90,7 +90,7 @@ impl Layout for ColumnsNode {
|
||||
let mut frames = body.layout(vt, styles, pod)?.into_iter();
|
||||
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;
|
||||
|
||||
// Stitch together the columns for each region.
|
||||
|
@ -134,7 +134,7 @@ impl Layout for BoxNode {
|
||||
|
||||
// Apply inset.
|
||||
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()) {
|
||||
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();
|
||||
|
||||
// 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() {
|
||||
frame.set_baseline(frame.baseline() - shift);
|
||||
}
|
||||
|
||||
// Prepare fill and stroke.
|
||||
let fill = styles.get(Self::FILL);
|
||||
let stroke = styles
|
||||
.get(Self::STROKE)
|
||||
.map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||
let fill = Self::fill_in(styles);
|
||||
let stroke =
|
||||
Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||
|
||||
// Add fill and/or stroke.
|
||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||
let outset = styles.get(Self::OUTSET);
|
||||
let radius = styles.get(Self::RADIUS);
|
||||
let outset = Self::outset_in(styles);
|
||||
let radius = Self::radius_in(styles);
|
||||
frame.fill_and_stroke(fill, stroke, outset, radius);
|
||||
}
|
||||
|
||||
@ -220,16 +219,16 @@ impl Layout for BoxNode {
|
||||
#[set({
|
||||
let spacing = args.named("spacing")?;
|
||||
styles.set_opt(
|
||||
Self::ABOVE,
|
||||
args.named("above")?
|
||||
.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(
|
||||
Self::BELOW,
|
||||
args.named("below")?
|
||||
.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 {
|
||||
@ -361,7 +360,7 @@ impl Layout for BlockNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
// Apply inset.
|
||||
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()) {
|
||||
body = body.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||
}
|
||||
@ -376,7 +375,7 @@ impl Layout for BlockNode {
|
||||
.unwrap_or(regions.base());
|
||||
|
||||
// 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.
|
||||
if sizing.x == Smart::Auto {
|
||||
let pod = Regions::one(size, Axes::splat(false));
|
||||
@ -414,10 +413,9 @@ impl Layout for BlockNode {
|
||||
};
|
||||
|
||||
// Prepare fill and stroke.
|
||||
let fill = styles.get(Self::FILL);
|
||||
let stroke = styles
|
||||
.get(Self::STROKE)
|
||||
.map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||
let fill = Self::fill_in(styles);
|
||||
let stroke =
|
||||
Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||
|
||||
// Add fill and/or stroke.
|
||||
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());
|
||||
}
|
||||
|
||||
let outset = styles.get(Self::OUTSET);
|
||||
let radius = styles.get(Self::RADIUS);
|
||||
let outset = Self::outset_in(styles);
|
||||
let radius = Self::radius_in(styles);
|
||||
for frame in frames.iter_mut().skip(skip as usize) {
|
||||
frame.fill_and_stroke(fill, stroke, outset, radius);
|
||||
}
|
||||
|
@ -187,21 +187,20 @@ impl Layout for EnumNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let numbering = styles.get(Self::NUMBERING);
|
||||
let indent = styles.get(Self::INDENT);
|
||||
let body_indent = styles.get(Self::BODY_INDENT);
|
||||
let numbering = Self::numbering_in(styles);
|
||||
let indent = Self::indent_in(styles);
|
||||
let body_indent = Self::body_indent_in(styles);
|
||||
let gutter = if self.tight() {
|
||||
styles.get(ParNode::LEADING).into()
|
||||
ParNode::leading_in(styles).into()
|
||||
} else {
|
||||
styles
|
||||
.get(Self::SPACING)
|
||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||
Self::spacing_in(styles)
|
||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let mut cells = vec![];
|
||||
let mut number = NonZeroUsize::new(1).unwrap();
|
||||
let mut parents = styles.get(Self::PARENTS);
|
||||
let full = styles.get(Self::FULL);
|
||||
let mut parents = Self::parents_in(styles);
|
||||
let full = Self::full_in(styles);
|
||||
|
||||
for item in self.children() {
|
||||
number = item.number().unwrap_or(number);
|
||||
@ -223,7 +222,7 @@ impl Layout for EnumNode {
|
||||
cells.push(Content::empty());
|
||||
cells.push(resolved);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -134,8 +134,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
par: &ParNode,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<()> {
|
||||
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles);
|
||||
let leading = styles.get(ParNode::LEADING);
|
||||
let aligns = AlignNode::alignment_in(styles).resolve(styles);
|
||||
let leading = ParNode::leading_in(styles);
|
||||
let consecutive = self.last_was_par;
|
||||
let frames = par
|
||||
.layout(vt, styles, consecutive, self.regions.base(), self.regions.expand.x)?
|
||||
@ -180,8 +180,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
content: &Content,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<()> {
|
||||
let aligns = styles.get(AlignNode::ALIGNMENT).resolve(styles);
|
||||
let sticky = styles.get(BlockNode::STICKY);
|
||||
let aligns = AlignNode::alignment_in(styles).resolve(styles);
|
||||
let sticky = BlockNode::sticky_in(styles);
|
||||
let pod = Regions::one(self.regions.base(), Axes::splat(false));
|
||||
let layoutable = content.with::<dyn Layout>().unwrap();
|
||||
let frame = layoutable.layout(vt, styles, pod)?.into_frame();
|
||||
@ -208,10 +208,10 @@ impl<'a> FlowLayouter<'a> {
|
||||
}
|
||||
|
||||
// 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.
|
||||
let sticky = styles.get(BlockNode::STICKY);
|
||||
let sticky = BlockNode::sticky_in(styles);
|
||||
let fragment = block.layout(vt, styles, self.regions)?;
|
||||
for (i, frame) in fragment.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
|
@ -262,7 +262,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> {
|
||||
}
|
||||
|
||||
// Reverse for RTL.
|
||||
let is_rtl = styles.get(TextNode::DIR) == Dir::RTL;
|
||||
let is_rtl = TextNode::dir_in(styles) == Dir::RTL;
|
||||
if is_rtl {
|
||||
cols.reverse();
|
||||
}
|
||||
|
@ -25,6 +25,6 @@ pub struct HideNode {
|
||||
|
||||
impl Show for HideNode {
|
||||
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])))
|
||||
}
|
||||
}
|
||||
|
@ -127,25 +127,24 @@ impl Layout for ListNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let indent = styles.get(Self::INDENT);
|
||||
let body_indent = styles.get(Self::BODY_INDENT);
|
||||
let indent = Self::indent_in(styles);
|
||||
let body_indent = Self::body_indent_in(styles);
|
||||
let gutter = if self.tight() {
|
||||
styles.get(ParNode::LEADING).into()
|
||||
ParNode::leading_in(styles).into()
|
||||
} else {
|
||||
styles
|
||||
.get(Self::SPACING)
|
||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||
Self::spacing_in(styles)
|
||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let depth = styles.get(Self::DEPTH);
|
||||
let marker = styles.get(Self::MARKER).resolve(vt.world(), depth)?;
|
||||
let depth = Self::depth_in(styles);
|
||||
let marker = Self::marker_in(styles).resolve(vt.world(), depth)?;
|
||||
|
||||
let mut cells = vec![];
|
||||
for item in self.children() {
|
||||
cells.push(Content::empty());
|
||||
cells.push(marker.clone());
|
||||
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(
|
||||
|
@ -450,13 +450,13 @@ impl<'a> FlowBuilder<'a> {
|
||||
};
|
||||
|
||||
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());
|
||||
self.0.push(spacing.pack(), styles);
|
||||
}
|
||||
|
||||
let above = styles.get(BlockNode::ABOVE);
|
||||
let below = styles.get(BlockNode::BELOW);
|
||||
let above = BlockNode::above_in(styles);
|
||||
let below = BlockNode::below_in(styles);
|
||||
self.0.push(above.clone().pack(), styles);
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(below.clone().pack(), styles);
|
||||
|
@ -30,8 +30,8 @@ use crate::prelude::*;
|
||||
#[node]
|
||||
#[set({
|
||||
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
||||
styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
|
||||
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
|
||||
styles.set(Self::set_width(Smart::Custom(paper.width().into())));
|
||||
styles.set(Self::set_height(Smart::Custom(paper.height().into())));
|
||||
}
|
||||
})]
|
||||
pub struct PageNode {
|
||||
@ -260,10 +260,10 @@ impl PageNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
|
||||
let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf());
|
||||
let width = Self::width_in(styles).unwrap_or(Abs::inf());
|
||||
let height = Self::height_in(styles).unwrap_or(Abs::inf());
|
||||
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);
|
||||
}
|
||||
|
||||
@ -274,12 +274,12 @@ impl PageNode {
|
||||
|
||||
// Determine the margins.
|
||||
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();
|
||||
|
||||
// Realize columns.
|
||||
let columns = styles.get(Self::COLUMNS);
|
||||
let columns = Self::columns_in(styles);
|
||||
if columns.get() > 1 {
|
||||
child = ColumnsNode::new(columns, child).pack();
|
||||
}
|
||||
@ -291,11 +291,11 @@ impl PageNode {
|
||||
let regions = Regions::repeat(size, size.map(Abs::is_finite));
|
||||
let mut fragment = child.layout(vt, styles, regions)?;
|
||||
|
||||
let fill = styles.get(Self::FILL);
|
||||
let header = styles.get(Self::HEADER);
|
||||
let footer = styles.get(Self::FOOTER);
|
||||
let foreground = styles.get(Self::FOREGROUND);
|
||||
let background = styles.get(Self::BACKGROUND);
|
||||
let fill = Self::fill_in(styles);
|
||||
let header = Self::header_in(styles);
|
||||
let footer = Self::footer_in(styles);
|
||||
let foreground = Self::foreground_in(styles);
|
||||
let background = Self::background_in(styles);
|
||||
|
||||
// Realize overlays.
|
||||
for frame in &mut fragment {
|
||||
|
@ -2,7 +2,7 @@ use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||
use unicode_script::{Script, UnicodeScript};
|
||||
use xi_unicode::LineBreakIterator;
|
||||
|
||||
use typst::model::{Key, StyledNode};
|
||||
use typst::model::StyledNode;
|
||||
|
||||
use super::{BoxNode, HNode, Sizing, Spacing};
|
||||
use crate::layout::AlignNode;
|
||||
@ -495,7 +495,7 @@ fn collect<'a>(
|
||||
let mut iter = children.iter().peekable();
|
||||
|
||||
if consecutive {
|
||||
let indent = styles.get(ParNode::INDENT);
|
||||
let indent = ParNode::indent_in(*styles);
|
||||
if !indent.is_zero()
|
||||
&& children
|
||||
.iter()
|
||||
@ -530,7 +530,7 @@ fn collect<'a>(
|
||||
Segment::Text(1)
|
||||
} else if let Some(node) = child.to::<TextNode>() {
|
||||
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()));
|
||||
} else {
|
||||
full.push_str(&node.text());
|
||||
@ -545,9 +545,9 @@ fn collect<'a>(
|
||||
Segment::Text(c.len_utf8())
|
||||
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
||||
let prev = full.len();
|
||||
if styles.get(SmartQuoteNode::ENABLED) {
|
||||
let lang = styles.get(TextNode::LANG);
|
||||
let region = styles.get(TextNode::REGION);
|
||||
if SmartQuoteNode::enabled_in(styles) {
|
||||
let lang = TextNode::lang_in(styles);
|
||||
let region = TextNode::region_in(styles);
|
||||
let quotes = Quotes::from_lang(lang, region);
|
||||
let peeked = iter.peek().and_then(|child| {
|
||||
if let Some(node) = child.to::<TextNode>() {
|
||||
@ -613,7 +613,7 @@ fn prepare<'a>(
|
||||
) -> SourceResult<Preparation<'a>> {
|
||||
let bidi = BidiInfo::new(
|
||||
text,
|
||||
match styles.get(TextNode::DIR) {
|
||||
match TextNode::dir_in(styles) {
|
||||
Dir::LTR => Some(BidiLevel::ltr()),
|
||||
Dir::RTL => Some(BidiLevel::rtl()),
|
||||
_ => None,
|
||||
@ -642,7 +642,7 @@ fn prepare<'a>(
|
||||
Segment::Formula(formula) => {
|
||||
let pod = Regions::one(region, Axes::splat(false));
|
||||
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));
|
||||
}
|
||||
Segment::Box(node) => {
|
||||
@ -651,7 +651,7 @@ fn prepare<'a>(
|
||||
} else {
|
||||
let pod = Regions::one(region, Axes::splat(false));
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -664,10 +664,10 @@ fn prepare<'a>(
|
||||
bidi,
|
||||
items,
|
||||
styles,
|
||||
hyphenate: shared_get(styles, children, TextNode::HYPHENATE),
|
||||
lang: shared_get(styles, children, TextNode::LANG),
|
||||
align: styles.get(AlignNode::ALIGNMENT).x.resolve(styles),
|
||||
justify: styles.get(ParNode::JUSTIFY),
|
||||
hyphenate: shared_get(styles, children, TextNode::hyphenate_in),
|
||||
lang: shared_get(styles, children, TextNode::lang_in),
|
||||
align: AlignNode::alignment_in(styles).x.resolve(styles),
|
||||
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
|
||||
/// paragraph.
|
||||
fn shared_get<'a, K: Key>(
|
||||
fn shared_get<'a, T: PartialEq>(
|
||||
styles: StyleChain<'a>,
|
||||
children: &[Content],
|
||||
key: K,
|
||||
) -> Option<K::Output> {
|
||||
getter: fn(StyleChain) -> T,
|
||||
) -> Option<T> {
|
||||
let value = getter(styles);
|
||||
children
|
||||
.iter()
|
||||
.filter_map(|child| child.to::<StyledNode>())
|
||||
.all(|node| !node.map().contains(key))
|
||||
.then(|| styles.get(key))
|
||||
.all(|node| getter(styles.chain(&node.map())) == value)
|
||||
.then(|| value)
|
||||
}
|
||||
|
||||
/// Find suitable linebreaks.
|
||||
fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| {
|
||||
if p.styles.get(ParNode::JUSTIFY) {
|
||||
let linebreaks = ParNode::linebreaks_in(p.styles).unwrap_or_else(|| {
|
||||
if ParNode::justify_in(p.styles) {
|
||||
Linebreaks::Optimized
|
||||
} else {
|
||||
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),
|
||||
}];
|
||||
|
||||
let em = p.styles.get(TextNode::SIZE);
|
||||
let em = TextNode::size_in(p.styles);
|
||||
|
||||
for (end, mandatory, hyphen) in breakpoints(p) {
|
||||
let k = table.len();
|
||||
@ -1005,7 +1006,7 @@ impl Breakpoints<'_> {
|
||||
.hyphenate
|
||||
.or_else(|| {
|
||||
let shaped = self.p.find(offset)?.text()?;
|
||||
Some(shaped.styles.get(TextNode::HYPHENATE))
|
||||
Some(TextNode::hyphenate_in(shaped.styles))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@ -1014,7 +1015,7 @@ impl Breakpoints<'_> {
|
||||
fn lang(&self, offset: usize) -> Option<hypher::Lang> {
|
||||
let lang = self.p.lang.or_else(|| {
|
||||
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()?;
|
||||
@ -1155,7 +1156,7 @@ fn finalize(
|
||||
.collect::<SourceResult<_>>()?;
|
||||
|
||||
// Prevent orphans.
|
||||
let leading = p.styles.get(ParNode::LEADING);
|
||||
let leading = ParNode::leading_in(p.styles);
|
||||
if frames.len() >= 2 && !frames[1].is_empty() {
|
||||
let second = frames.remove(1);
|
||||
let first = &mut frames[0];
|
||||
@ -1199,7 +1200,7 @@ fn commit(
|
||||
if let Some(Item::Text(text)) = reordered.first() {
|
||||
if let Some(glyph) = text.glyphs.first() {
|
||||
if !text.dir.is_positive()
|
||||
&& text.styles.get(TextNode::OVERHANG)
|
||||
&& TextNode::overhang_in(text.styles)
|
||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||
{
|
||||
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(glyph) = text.glyphs.last() {
|
||||
if text.dir.is_positive()
|
||||
&& text.styles.get(TextNode::OVERHANG)
|
||||
&& TextNode::overhang_in(text.styles)
|
||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||
{
|
||||
let amount = overhang(glyph.c) * glyph.x_advance.at(text.size);
|
||||
@ -1257,7 +1258,7 @@ fn commit(
|
||||
let region = Size::new(amount, full);
|
||||
let pod = Regions::one(region, Axes::new(true, false));
|
||||
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);
|
||||
} else {
|
||||
offset += amount;
|
||||
|
@ -40,7 +40,7 @@ impl Layout for RepeatNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
let pod = Regions::one(regions.size, Axes::new(false, false));
|
||||
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 width = piece.width();
|
||||
|
@ -201,10 +201,9 @@ impl<'a> StackLayouter<'a> {
|
||||
|
||||
// Block-axis alignment of the `AlignNode` is respected
|
||||
// by the stack node.
|
||||
let aligns = if let Some(styled) = block.to::<StyledNode>() {
|
||||
styles.chain(&styled.map()).get(AlignNode::ALIGNMENT)
|
||||
} else {
|
||||
styles.get(AlignNode::ALIGNMENT)
|
||||
let aligns = match block.to::<StyledNode>() {
|
||||
Some(styled) => AlignNode::alignment_in(styles.chain(&styled.map())),
|
||||
None => AlignNode::alignment_in(styles),
|
||||
};
|
||||
|
||||
let aligns = aligns.resolve(styles);
|
||||
|
@ -128,8 +128,8 @@ impl Layout for TableNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let inset = styles.get(Self::INSET);
|
||||
let align = styles.get(Self::ALIGN);
|
||||
let inset = Self::inset_in(styles);
|
||||
let align = Self::align_in(styles);
|
||||
|
||||
let tracks = Axes::new(self.columns().0, self.rows().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 y = i / cols;
|
||||
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)
|
||||
})
|
||||
.collect::<SourceResult<_>>()?;
|
||||
|
||||
let fill = styles.get(Self::FILL);
|
||||
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
|
||||
let fill = Self::fill_in(styles);
|
||||
let stroke = Self::stroke_in(styles).map(PartialStroke::unwrap_or_default);
|
||||
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let layouter = GridLayouter::new(
|
||||
|
@ -90,14 +90,13 @@ impl Layout for TermsNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let indent = styles.get(Self::INDENT);
|
||||
let body_indent = styles.get(Self::HANGING_INDENT);
|
||||
let indent = Self::indent_in(styles);
|
||||
let body_indent = Self::hanging_indent_in(styles);
|
||||
let gutter = if self.tight() {
|
||||
styles.get(ParNode::LEADING).into()
|
||||
ParNode::leading_in(styles).into()
|
||||
} else {
|
||||
styles
|
||||
.get(Self::SPACING)
|
||||
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount())
|
||||
Self::spacing_in(styles)
|
||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let mut cells = vec![];
|
||||
|
@ -122,7 +122,7 @@ impl Layout for RotateNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||
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 ts = Transform::translate(x, y)
|
||||
.pre_concat(Transform::rotate(self.angle()))
|
||||
@ -199,7 +199,7 @@ impl Layout for ScaleNode {
|
||||
) -> SourceResult<Fragment> {
|
||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||
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 transform = Transform::translate(x, y)
|
||||
.pre_concat(Transform::scale(self.x(), self.y()))
|
||||
|
@ -164,8 +164,8 @@ fn styles() -> StyleMap {
|
||||
fn items() -> LangItems {
|
||||
LangItems {
|
||||
layout: |world, content, styles| content.layout_root(world, styles),
|
||||
em: |styles| styles.get(text::TextNode::SIZE),
|
||||
dir: |styles| styles.get(text::TextNode::DIR),
|
||||
em: text::TextNode::size_in,
|
||||
dir: text::TextNode::dir_in,
|
||||
space: || text::SpaceNode::new().pack(),
|
||||
linebreak: || text::LinebreakNode::new().pack(),
|
||||
text: |text| text::TextNode::new(text).pack(),
|
||||
@ -178,7 +178,7 @@ fn items() -> LangItems {
|
||||
raw: |text, lang, block| {
|
||||
let content = text::RawNode::new(text).with_block(block).pack();
|
||||
match lang {
|
||||
Some(_) => content.styled(text::RawNode::LANG, lang),
|
||||
Some(_) => content.styled(text::RawNode::set_lang(lang)),
|
||||
None => content,
|
||||
}
|
||||
},
|
||||
|
@ -49,7 +49,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
) -> Self {
|
||||
let table = font.ttf().tables().math.unwrap();
|
||||
let constants = table.constants.unwrap();
|
||||
let size = styles.get(TextNode::SIZE);
|
||||
let size = TextNode::size_in(styles);
|
||||
let ttf = font.ttf();
|
||||
let space_width = ttf
|
||||
.glyph_index(' ')
|
||||
@ -175,21 +175,20 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
|
||||
pub fn style(&mut self, style: MathStyle) {
|
||||
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.map.set(TextNode::SIZE, TextSize(self.size.into()));
|
||||
self.map.set(
|
||||
TextNode::STYLE,
|
||||
if style.italic == Smart::Custom(true) {
|
||||
self.map.set(TextNode::set_size(TextSize(self.size.into())));
|
||||
self.map
|
||||
.set(TextNode::set_style(if style.italic == Smart::Custom(true) {
|
||||
FontStyle::Italic
|
||||
} else {
|
||||
FontStyle::Normal
|
||||
},
|
||||
);
|
||||
self.map.set(
|
||||
TextNode::WEIGHT,
|
||||
if style.bold { FontWeight::BOLD } else { FontWeight::REGULAR },
|
||||
);
|
||||
}));
|
||||
self.map.set(TextNode::set_weight(if style.bold {
|
||||
FontWeight::BOLD
|
||||
} else {
|
||||
FontWeight::REGULAR
|
||||
}));
|
||||
self.style = style;
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ fn layout(
|
||||
line_pos,
|
||||
Element::Shape(
|
||||
Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
|
||||
paint: ctx.styles().get(TextNode::FILL),
|
||||
paint: TextNode::fill_in(ctx.styles()),
|
||||
thickness,
|
||||
}),
|
||||
),
|
||||
|
@ -180,8 +180,8 @@ impl GlyphFragment {
|
||||
id,
|
||||
c,
|
||||
font: ctx.font.clone(),
|
||||
lang: ctx.styles().get(TextNode::LANG),
|
||||
fill: ctx.styles().get(TextNode::FILL),
|
||||
lang: TextNode::lang_in(ctx.styles()),
|
||||
fill: TextNode::fill_in(ctx.styles()),
|
||||
style: ctx.style,
|
||||
font_size: ctx.size,
|
||||
width,
|
||||
|
@ -35,7 +35,7 @@ pub struct VecNode {
|
||||
|
||||
impl LayoutMath for VecNode {
|
||||
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)?;
|
||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||
}
|
||||
@ -115,7 +115,7 @@ impl Construct for MatNode {
|
||||
|
||||
impl LayoutMath for MatNode {
|
||||
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())?;
|
||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||
}
|
||||
@ -156,7 +156,7 @@ pub struct CasesNode {
|
||||
|
||||
impl LayoutMath for CasesNode {
|
||||
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)?;
|
||||
layout_delimiters(ctx, frame, Some(delim.open()), None)
|
||||
}
|
||||
|
@ -158,11 +158,10 @@ impl Show for FormulaNode {
|
||||
impl Finalize for FormulaNode {
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
realized
|
||||
.styled(TextNode::WEIGHT, FontWeight::from_number(450))
|
||||
.styled(
|
||||
TextNode::FONT,
|
||||
FontList(vec![FontFamily::new("New Computer Modern Math")]),
|
||||
)
|
||||
.styled(TextNode::set_weight(FontWeight::from_number(450)))
|
||||
.styled(TextNode::set_font(FontList(vec![FontFamily::new(
|
||||
"New Computer Modern Math",
|
||||
)])))
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,10 +195,10 @@ impl Layout for FormulaNode {
|
||||
let mut frame = ctx.layout_frame(self)?;
|
||||
|
||||
if !block {
|
||||
let slack = styles.get(ParNode::LEADING) * 0.7;
|
||||
let top_edge = styles.get(TextNode::TOP_EDGE).resolve(styles, font.metrics());
|
||||
let slack = ParNode::leading_in(styles) * 0.7;
|
||||
let top_edge = TextNode::top_edge_in(styles).resolve(styles, font.metrics());
|
||||
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 descent = bottom_edge.max(frame.descent() - slack);
|
||||
@ -232,7 +231,9 @@ impl LayoutMath for Content {
|
||||
|
||||
if let Some(styled) = self.to::<StyledNode>() {
|
||||
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)?;
|
||||
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
|
||||
return Ok(());
|
||||
@ -241,7 +242,7 @@ impl LayoutMath for Content {
|
||||
let prev_map = std::mem::replace(&mut ctx.map, map);
|
||||
let prev_size = ctx.size;
|
||||
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)?;
|
||||
ctx.size = prev_size;
|
||||
ctx.map = prev_map;
|
||||
|
@ -128,7 +128,7 @@ fn layout(
|
||||
line_pos,
|
||||
Element::Shape(
|
||||
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 }),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -103,7 +103,7 @@ impl MathRow {
|
||||
|
||||
pub fn to_frame(self, ctx: &MathContext) -> Frame {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ impl MathRow {
|
||||
if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
|
||||
let fragments: Vec<_> = std::mem::take(&mut self.0);
|
||||
let leading = if ctx.style.size >= MathSize::Text {
|
||||
ctx.styles().get(ParNode::LEADING)
|
||||
ParNode::leading_in(ctx.styles())
|
||||
} else {
|
||||
TIGHT_LEADING.scaled(ctx)
|
||||
};
|
||||
|
@ -58,8 +58,8 @@ impl LayoutRoot for DocumentNode {
|
||||
|
||||
Ok(Document {
|
||||
pages,
|
||||
title: styles.get(Self::TITLE).clone(),
|
||||
author: styles.get(Self::AUTHOR).0.clone(),
|
||||
title: Self::title_in(styles),
|
||||
author: Self::author_in(styles).0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -106,15 +106,15 @@ impl Prepare for HeadingNode {
|
||||
}
|
||||
|
||||
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))?;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 mut map = StyleMap::new();
|
||||
map.set(TextNode::SIZE, TextSize(size.into()));
|
||||
map.set(TextNode::WEIGHT, FontWeight::BOLD);
|
||||
map.set(BlockNode::ABOVE, VNode::block_around(above.into()));
|
||||
map.set(BlockNode::BELOW, VNode::block_around(below.into()));
|
||||
map.set(BlockNode::STICKY, true);
|
||||
map.set(TextNode::set_size(TextSize(size.into())));
|
||||
map.set(TextNode::set_weight(FontWeight::BOLD));
|
||||
map.set(BlockNode::set_above(VNode::block_around(above.into())));
|
||||
map.set(BlockNode::set_below(VNode::block_around(below.into())));
|
||||
map.set(BlockNode::set_sticky(true));
|
||||
realized.styled_with_map(map)
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +91,8 @@ impl Show for LinkNode {
|
||||
impl Finalize for LinkNode {
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
realized
|
||||
.styled(MetaNode::DATA, vec![Meta::Link(self.dest())])
|
||||
.styled(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)))
|
||||
.styled(MetaNode::set_data(vec![Meta::Link(self.dest())]))
|
||||
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,9 +102,9 @@ impl Show for OutlineNode {
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Content> {
|
||||
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(|| {
|
||||
TextNode::packed(match styles.get(TextNode::LANG) {
|
||||
TextNode::packed(match TextNode::lang_in(styles) {
|
||||
Lang::GERMAN => "Inhaltsverzeichnis",
|
||||
Lang::ENGLISH | _ => "Contents",
|
||||
})
|
||||
@ -113,13 +113,13 @@ impl Show for OutlineNode {
|
||||
seq.push(
|
||||
HeadingNode::new(title)
|
||||
.pack()
|
||||
.styled(HeadingNode::NUMBERING, None)
|
||||
.styled(HeadingNode::OUTLINED, false),
|
||||
.styled(HeadingNode::set_numbering(None))
|
||||
.styled(HeadingNode::set_outlined(false)),
|
||||
);
|
||||
}
|
||||
|
||||
let indent = styles.get(Self::INDENT);
|
||||
let depth = styles.get(Self::DEPTH);
|
||||
let indent = Self::indent_in(styles);
|
||||
let depth = Self::depth_in(styles);
|
||||
|
||||
let mut ancestors: Vec<&Content> = vec![];
|
||||
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
||||
@ -171,7 +171,7 @@ impl Show for OutlineNode {
|
||||
seq.push(start.linked(Destination::Internal(loc)));
|
||||
|
||||
// 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(
|
||||
BoxNode::new()
|
||||
|
@ -1,6 +1,8 @@
|
||||
//! Extension traits.
|
||||
|
||||
use crate::layout::{AlignNode, MoveNode, PadNode};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{EmphNode, FontFamily, FontList, StrongNode, TextNode, UnderlineNode};
|
||||
|
||||
/// Additional methods on content.
|
||||
pub trait ContentExt {
|
||||
@ -28,27 +30,27 @@ pub trait ContentExt {
|
||||
|
||||
impl ContentExt for Content {
|
||||
fn strong(self) -> Self {
|
||||
crate::text::StrongNode::new(self).pack()
|
||||
StrongNode::new(self).pack()
|
||||
}
|
||||
|
||||
fn emph(self) -> Self {
|
||||
crate::text::EmphNode::new(self).pack()
|
||||
EmphNode::new(self).pack()
|
||||
}
|
||||
|
||||
fn underlined(self) -> Self {
|
||||
crate::text::UnderlineNode::new(self).pack()
|
||||
UnderlineNode::new(self).pack()
|
||||
}
|
||||
|
||||
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 {
|
||||
self.styled(crate::layout::AlignNode::ALIGNMENT, aligns)
|
||||
self.styled(AlignNode::set_alignment(aligns))
|
||||
}
|
||||
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||
crate::layout::PadNode::new(self)
|
||||
PadNode::new(self)
|
||||
.with_left(padding.left)
|
||||
.with_top(padding.top)
|
||||
.with_right(padding.right)
|
||||
@ -57,10 +59,7 @@ impl ContentExt for Content {
|
||||
}
|
||||
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||
crate::layout::MoveNode::new(self)
|
||||
.with_dx(delta.x)
|
||||
.with_dy(delta.y)
|
||||
.pack()
|
||||
MoveNode::new(self).with_dx(delta.x).with_dy(delta.y).pack()
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,18 +67,15 @@ impl ContentExt for Content {
|
||||
pub trait StyleMapExt {
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
/// 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 {
|
||||
fn set_family(&mut self, preferred: crate::text::FontFamily, existing: StyleChain) {
|
||||
self.set(
|
||||
crate::text::TextNode::FONT,
|
||||
crate::text::FontList(
|
||||
std::iter::once(preferred)
|
||||
.chain(existing.get(crate::text::TextNode::FONT).0.iter().cloned())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
||||
self.set(TextNode::set_font(FontList(
|
||||
std::iter::once(preferred)
|
||||
.chain(TextNode::font_in(existing))
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -75,16 +75,13 @@ pub struct UnderlineNode {
|
||||
|
||||
impl Show for UnderlineNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(
|
||||
TextNode::DECO,
|
||||
Decoration {
|
||||
line: DecoLine::Underline,
|
||||
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: styles.get(Self::EVADE),
|
||||
},
|
||||
))
|
||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||
line: DecoLine::Underline,
|
||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
||||
offset: Self::offset_in(styles),
|
||||
extent: Self::extent_in(styles),
|
||||
evade: Self::evade_in(styles),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,16 +162,13 @@ pub struct OverlineNode {
|
||||
|
||||
impl Show for OverlineNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(
|
||||
TextNode::DECO,
|
||||
Decoration {
|
||||
line: DecoLine::Overline,
|
||||
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: styles.get(Self::EVADE),
|
||||
},
|
||||
))
|
||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||
line: DecoLine::Overline,
|
||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
||||
offset: Self::offset_in(styles),
|
||||
extent: Self::extent_in(styles),
|
||||
evade: Self::evade_in(styles),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,16 +233,13 @@ pub struct StrikeNode {
|
||||
|
||||
impl Show for StrikeNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(
|
||||
TextNode::DECO,
|
||||
Decoration {
|
||||
line: DecoLine::Strikethrough,
|
||||
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: false,
|
||||
},
|
||||
))
|
||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||
line: DecoLine::Strikethrough,
|
||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
||||
offset: Self::offset_in(styles),
|
||||
extent: Self::extent_in(styles),
|
||||
evade: false,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ pub struct StrongNode {
|
||||
|
||||
impl Show for StrongNode {
|
||||
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 {
|
||||
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> {
|
||||
Ok(match args.expect("string or content")? {
|
||||
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]
|
||||
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
|
||||
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.
|
||||
|
@ -578,6 +578,15 @@ cast_to_value! {
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
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! {
|
||||
FontList,
|
||||
family: FontFamily => Self(vec![family]),
|
||||
@ -664,7 +673,7 @@ impl Resolve for HorizontalDir {
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
match self.0 {
|
||||
Smart::Auto => styles.get(TextNode::LANG).dir(),
|
||||
Smart::Auto => TextNode::lang_in(styles).dir(),
|
||||
Smart::Custom(dir) => dir,
|
||||
}
|
||||
}
|
||||
@ -688,7 +697,7 @@ impl Resolve for Hyphenate {
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
match self.0 {
|
||||
Smart::Auto => styles.get(ParNode::JUSTIFY),
|
||||
Smart::Auto => ParNode::justify_in(styles),
|
||||
Smart::Custom(v) => v,
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ impl Prepare for RawNode {
|
||||
mut this: Content,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Content> {
|
||||
this.push_field("lang", styles.get(Self::LANG).clone());
|
||||
this.push_field("lang", Self::lang_in(styles).clone());
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ impl Prepare for RawNode {
|
||||
impl Show for RawNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
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
|
||||
.settings
|
||||
.foreground
|
||||
@ -181,11 +181,11 @@ impl Show for RawNode {
|
||||
impl Finalize for RawNode {
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::OVERHANG, false);
|
||||
map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
|
||||
map.set(TextNode::SIZE, TextSize(Em::new(0.8).into()));
|
||||
map.set(TextNode::FONT, FontList(vec![FontFamily::new("DejaVu Sans Mono")]));
|
||||
map.set(SmartQuoteNode::ENABLED, false);
|
||||
map.set(TextNode::set_overhang(false));
|
||||
map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))));
|
||||
map.set(TextNode::set_size(TextSize(Em::new(0.8).into())));
|
||||
map.set(TextNode::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")])));
|
||||
map.set(SmartQuoteNode::set_enabled(false));
|
||||
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();
|
||||
if paint != foreground {
|
||||
body = body.styled(TextNode::FILL, paint);
|
||||
body = body.styled(TextNode::set_fill(paint));
|
||||
}
|
||||
|
||||
if style.font_style.contains(synt::FontStyle::BOLD) {
|
||||
|
@ -88,10 +88,10 @@ impl<'a> ShapedText<'a> {
|
||||
let mut frame = Frame::new(size);
|
||||
frame.set_baseline(top);
|
||||
|
||||
let shift = self.styles.get(TextNode::BASELINE);
|
||||
let lang = self.styles.get(TextNode::LANG);
|
||||
let decos = self.styles.get(TextNode::DECO);
|
||||
let fill = self.styles.get(TextNode::FILL);
|
||||
let shift = TextNode::baseline_in(self.styles);
|
||||
let lang = TextNode::lang_in(self.styles);
|
||||
let decos = TextNode::deco_in(self.styles);
|
||||
let fill = TextNode::fill_in(self.styles);
|
||||
|
||||
for ((font, y_offset), group) in
|
||||
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 bottom = Abs::zero();
|
||||
|
||||
let top_edge = self.styles.get(TextNode::TOP_EDGE);
|
||||
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
|
||||
let top_edge = TextNode::top_edge_in(self.styles);
|
||||
let bottom_edge = TextNode::bottom_edge_in(self.styles);
|
||||
|
||||
// Expand top and bottom by reading the font's vertical metrics.
|
||||
let mut expand = |font: &Font| {
|
||||
@ -315,8 +315,7 @@ pub fn shape<'a>(
|
||||
styles: StyleChain<'a>,
|
||||
dir: Dir,
|
||||
) -> ShapedText<'a> {
|
||||
let size = styles.get(TextNode::SIZE);
|
||||
|
||||
let size = TextNode::size_in(styles);
|
||||
let mut ctx = ShapingContext {
|
||||
vt,
|
||||
size,
|
||||
@ -325,7 +324,7 @@ pub fn shape<'a>(
|
||||
styles,
|
||||
variant: variant(styles),
|
||||
tags: tags(styles),
|
||||
fallback: styles.get(TextNode::FALLBACK),
|
||||
fallback: TextNode::fallback_in(styles),
|
||||
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.
|
||||
fn track_and_space(ctx: &mut ShapingContext) {
|
||||
let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), ctx.size);
|
||||
let spacing = ctx
|
||||
.styles
|
||||
.get(TextNode::SPACING)
|
||||
.map(|abs| Em::from_length(abs, ctx.size));
|
||||
let tracking = Em::from_length(TextNode::tracking_in(ctx.styles), ctx.size);
|
||||
let spacing =
|
||||
TextNode::spacing_in(ctx.styles).map(|abs| Em::from_length(abs, ctx.size));
|
||||
|
||||
let mut glyphs = ctx.glyphs.iter_mut().peekable();
|
||||
while let Some(glyph) = glyphs.next() {
|
||||
@ -527,17 +524,17 @@ fn nbsp_delta(font: &Font) -> Option<Em> {
|
||||
/// Resolve the font variant.
|
||||
pub fn variant(styles: StyleChain) -> FontVariant {
|
||||
let mut variant = FontVariant::new(
|
||||
styles.get(TextNode::STYLE),
|
||||
styles.get(TextNode::WEIGHT),
|
||||
styles.get(TextNode::STRETCH),
|
||||
TextNode::style_in(styles),
|
||||
TextNode::weight_in(styles),
|
||||
TextNode::stretch_in(styles),
|
||||
);
|
||||
|
||||
let delta = styles.get(TextNode::DELTA);
|
||||
let delta = TextNode::delta_in(styles);
|
||||
variant.weight = variant
|
||||
.weight
|
||||
.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 {
|
||||
FontStyle::Normal => FontStyle::Italic,
|
||||
FontStyle::Italic => FontStyle::Normal,
|
||||
@ -558,10 +555,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
|
||||
"segoe ui emoji",
|
||||
];
|
||||
|
||||
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
|
||||
styles
|
||||
.get(TextNode::FONT)
|
||||
.0
|
||||
let tail = if TextNode::fallback_in(styles) { FALLBACKS } else { &[] };
|
||||
TextNode::font_in(styles)
|
||||
.into_iter()
|
||||
.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.
|
||||
if !styles.get(TextNode::KERNING) {
|
||||
if !TextNode::kerning_in(styles) {
|
||||
feat(b"kern", 0);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if styles.get(TextNode::ALTERNATES) {
|
||||
if TextNode::alternates_in(styles) {
|
||||
feat(b"salt", 1);
|
||||
}
|
||||
|
||||
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];
|
||||
feat(&storage, 1);
|
||||
}
|
||||
|
||||
if !styles.get(TextNode::LIGATURES) {
|
||||
if !TextNode::ligatures_in(styles) {
|
||||
feat(b"liga", 0);
|
||||
feat(b"clig", 0);
|
||||
}
|
||||
|
||||
if styles.get(TextNode::DISCRETIONARY_LIGATURES) {
|
||||
if TextNode::discretionary_ligatures_in(styles) {
|
||||
feat(b"dlig", 1);
|
||||
}
|
||||
|
||||
if styles.get(TextNode::HISTORICAL_LIGATURES) {
|
||||
if TextNode::historical_ligatures_in(styles) {
|
||||
feat(b"hilg", 1);
|
||||
}
|
||||
|
||||
match styles.get(TextNode::NUMBER_TYPE) {
|
||||
match TextNode::number_type_in(styles) {
|
||||
Smart::Auto => {}
|
||||
Smart::Custom(NumberType::Lining) => feat(b"lnum", 1),
|
||||
Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1),
|
||||
}
|
||||
|
||||
match styles.get(TextNode::NUMBER_WIDTH) {
|
||||
match TextNode::number_width_in(styles) {
|
||||
Smart::Auto => {}
|
||||
Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 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);
|
||||
}
|
||||
|
||||
if styles.get(TextNode::FRACTIONS) {
|
||||
if TextNode::fractions_in(styles) {
|
||||
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, ..))
|
||||
}
|
||||
|
||||
@ -636,8 +631,8 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
|
||||
/// Process the language and and region of a style chain into a
|
||||
/// rustybuzz-compatible BCP 47 language.
|
||||
fn language(styles: StyleChain) -> rustybuzz::Language {
|
||||
let mut bcp: EcoString = styles.get(TextNode::LANG).as_str().into();
|
||||
if let Some(region) = styles.get(TextNode::REGION) {
|
||||
let mut bcp: EcoString = TextNode::lang_in(styles).as_str().into();
|
||||
if let Some(region) = TextNode::region_in(styles) {
|
||||
bcp.push('-');
|
||||
bcp.push_str(region.as_str());
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ impl Show for SubNode {
|
||||
) -> SourceResult<Content> {
|
||||
let body = self.body();
|
||||
let mut transformed = None;
|
||||
if styles.get(Self::TYPOGRAPHIC) {
|
||||
if Self::typographic_in(styles) {
|
||||
if let Some(text) = search_text(&body, true) {
|
||||
if is_shapable(vt, &text, styles) {
|
||||
transformed = Some(TextNode::packed(text));
|
||||
@ -68,10 +68,8 @@ impl Show for SubNode {
|
||||
};
|
||||
|
||||
Ok(transformed.unwrap_or_else(|| {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
||||
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
||||
body.styled_with_map(map)
|
||||
body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
|
||||
.styled(TextNode::set_size(Self::size_in(styles)))
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -132,7 +130,7 @@ impl Show for SuperNode {
|
||||
) -> SourceResult<Content> {
|
||||
let body = self.body();
|
||||
let mut transformed = None;
|
||||
if styles.get(Self::TYPOGRAPHIC) {
|
||||
if Self::typographic_in(styles) {
|
||||
if let Some(text) = search_text(&body, false) {
|
||||
if is_shapable(vt, &text, styles) {
|
||||
transformed = Some(TextNode::packed(text));
|
||||
@ -141,10 +139,8 @@ impl Show for SuperNode {
|
||||
};
|
||||
|
||||
Ok(transformed.unwrap_or_else(|| {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
||||
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
||||
body.styled_with_map(map)
|
||||
body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
|
||||
.styled(TextNode::set_size(Self::size_in(styles)))
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -174,7 +170,7 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
|
||||
/// given string.
|
||||
fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool {
|
||||
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
|
||||
.book()
|
||||
.select(family.as_str(), variant(styles))
|
||||
|
@ -90,7 +90,7 @@ impl Layout for ImageNode {
|
||||
};
|
||||
|
||||
// Compute the actual size of the fitted image.
|
||||
let fit = styles.get(Self::FIT);
|
||||
let fit = Self::fit_in(styles);
|
||||
let fitted = match fit {
|
||||
ImageFit::Cover | ImageFit::Contain => {
|
||||
if wide == (fit == ImageFit::Contain) {
|
||||
|
@ -84,7 +84,7 @@ impl Layout for LineNode {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
||||
let stroke = Self::stroke_in(styles).unwrap_or_default();
|
||||
|
||||
let origin = self
|
||||
.start()
|
||||
|
@ -167,11 +167,11 @@ impl Layout for RectNode {
|
||||
ShapeKind::Rect,
|
||||
&self.body(),
|
||||
Axes::new(self.width(), self.height()),
|
||||
styles.get(Self::FILL),
|
||||
styles.get(Self::STROKE),
|
||||
styles.get(Self::INSET),
|
||||
styles.get(Self::OUTSET),
|
||||
styles.get(Self::RADIUS),
|
||||
Self::fill_in(styles),
|
||||
Self::stroke_in(styles),
|
||||
Self::inset_in(styles),
|
||||
Self::outset_in(styles),
|
||||
Self::radius_in(styles),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -301,11 +301,11 @@ impl Layout for SquareNode {
|
||||
ShapeKind::Square,
|
||||
&self.body(),
|
||||
Axes::new(self.width(), self.height()),
|
||||
styles.get(Self::FILL),
|
||||
styles.get(Self::STROKE),
|
||||
styles.get(Self::INSET),
|
||||
styles.get(Self::OUTSET),
|
||||
styles.get(Self::RADIUS),
|
||||
Self::fill_in(styles),
|
||||
Self::stroke_in(styles),
|
||||
Self::inset_in(styles),
|
||||
Self::outset_in(styles),
|
||||
Self::radius_in(styles),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -394,10 +394,10 @@ impl Layout for EllipseNode {
|
||||
ShapeKind::Ellipse,
|
||||
&self.body(),
|
||||
Axes::new(self.width(), self.height()),
|
||||
styles.get(Self::FILL),
|
||||
styles.get(Self::STROKE).map(Sides::splat),
|
||||
styles.get(Self::INSET),
|
||||
styles.get(Self::OUTSET),
|
||||
Self::fill_in(styles),
|
||||
Self::stroke_in(styles).map(Sides::splat),
|
||||
Self::inset_in(styles),
|
||||
Self::outset_in(styles),
|
||||
Corners::splat(Rel::zero()),
|
||||
)
|
||||
}
|
||||
@ -522,10 +522,10 @@ impl Layout for CircleNode {
|
||||
ShapeKind::Circle,
|
||||
&self.body(),
|
||||
Axes::new(self.width(), self.height()),
|
||||
styles.get(Self::FILL),
|
||||
styles.get(Self::STROKE).map(Sides::splat),
|
||||
styles.get(Self::INSET),
|
||||
styles.get(Self::OUTSET),
|
||||
Self::fill_in(styles),
|
||||
Self::stroke_in(styles).map(Sides::splat),
|
||||
Self::inset_in(styles),
|
||||
Self::outset_in(styles),
|
||||
Corners::splat(Rel::zero()),
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ struct Field {
|
||||
attrs: Vec<syn::Attribute>,
|
||||
vis: syn::Visibility,
|
||||
ident: Ident,
|
||||
ident_in: Ident,
|
||||
with_ident: Ident,
|
||||
set_ident: Ident,
|
||||
name: String,
|
||||
|
||||
positional: bool,
|
||||
@ -36,6 +38,7 @@ struct Field {
|
||||
skip: bool,
|
||||
|
||||
ty: syn::Type,
|
||||
output: syn::Type,
|
||||
default: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
@ -62,20 +65,17 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
||||
|
||||
let mut fields = vec![];
|
||||
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");
|
||||
};
|
||||
|
||||
let mut attrs = field.attrs.clone();
|
||||
let settable = has_attr(&mut attrs, "settable");
|
||||
if settable {
|
||||
ident = Ident::new(&ident.to_string().to_uppercase(), ident.span());
|
||||
}
|
||||
|
||||
let field = Field {
|
||||
let mut field = Field {
|
||||
vis: field.vis.clone(),
|
||||
ident: ident.clone(),
|
||||
ident_in: Ident::new(&format!("{}_in", 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),
|
||||
|
||||
positional: has_attr(&mut attrs, "positional"),
|
||||
@ -88,12 +88,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
||||
Some(ident) => Shorthand::Named(ident),
|
||||
}),
|
||||
|
||||
settable,
|
||||
settable: has_attr(&mut attrs, "settable"),
|
||||
fold: has_attr(&mut attrs, "fold"),
|
||||
resolve: has_attr(&mut attrs, "resolve"),
|
||||
skip: has_attr(&mut attrs, "skip"),
|
||||
|
||||
ty: field.ty.clone(),
|
||||
output: field.ty.clone(),
|
||||
default: parse_attr(&mut attrs, "default")?.map(|opt| {
|
||||
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 {
|
||||
bail!(ident, "expected positional, named, variadic, or settable");
|
||||
}
|
||||
@ -140,34 +150,22 @@ fn create(node: &Node) -> TokenStream {
|
||||
let attrs = &node.attrs;
|
||||
let vis = &node.vis;
|
||||
let ident = &node.ident;
|
||||
let name = &node.name;
|
||||
|
||||
// Inherent methods and functions.
|
||||
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
|
||||
.capable
|
||||
.iter()
|
||||
.all(|capability| capability != "Construct")
|
||||
.then(|| create_construct_impl(node));
|
||||
let set = create_set_impl(node);
|
||||
let builders = node.inherent().map(create_builder_method);
|
||||
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);
|
||||
});
|
||||
}
|
||||
let node = create_node_impl(node);
|
||||
|
||||
quote! {
|
||||
#(#attrs)*
|
||||
@ -178,7 +176,10 @@ fn create(node: &Node) -> TokenStream {
|
||||
|
||||
impl #ident {
|
||||
#new
|
||||
#(#builders)*
|
||||
#(#field_methods)*
|
||||
#(#with_fields_methods)*
|
||||
#(#field_in_methods)*
|
||||
#(#field_style_methods)*
|
||||
|
||||
/// The node's span.
|
||||
pub fn span(&self) -> Option<::typst::syntax::Span> {
|
||||
@ -186,25 +187,7 @@ fn create(node: &Node) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl #ident {
|
||||
#(#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
|
||||
}
|
||||
}
|
||||
|
||||
#node
|
||||
#construct
|
||||
#set
|
||||
|
||||
@ -213,12 +196,6 @@ fn create(node: &Node) -> TokenStream {
|
||||
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.
|
||||
fn create_accessor_method(field: &Field) -> TokenStream {
|
||||
fn create_field_method(field: &Field) -> TokenStream {
|
||||
let Field { attrs, vis, ident, name, ty, .. } = field;
|
||||
quote! {
|
||||
#(#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.
|
||||
fn create_construct_impl(node: &Node) -> TokenStream {
|
||||
let ident = &node.ident;
|
||||
@ -353,8 +457,8 @@ fn create_set_impl(node: &Node) -> TokenStream {
|
||||
.settable()
|
||||
.filter(|field| !field.skip)
|
||||
.map(|field| {
|
||||
let ident = &field.ident;
|
||||
let name = &field.name;
|
||||
let set_ident = &field.set_ident;
|
||||
let value = match &field.shorthand {
|
||||
Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? },
|
||||
Some(Shorthand::Named(named)) => {
|
||||
@ -364,7 +468,7 @@ fn create_set_impl(node: &Node) -> TokenStream {
|
||||
None => quote! { args.named(#name)? },
|
||||
};
|
||||
|
||||
quote! { styles.set_opt(Self::#ident, #value); }
|
||||
quote! { styles.set_opt(#value.map(Self::#set_ident)); }
|
||||
})
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ impl Frame {
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
for meta in styles.get(MetaNode::DATA) {
|
||||
for meta in MetaNode::data_in(styles) {
|
||||
if matches!(meta, Meta::Hidden) {
|
||||
self.clear();
|
||||
break;
|
||||
|
@ -354,7 +354,7 @@ fn eval_markup(
|
||||
}
|
||||
|
||||
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)? {
|
||||
Value::Label(label) => {
|
||||
@ -783,7 +783,7 @@ fn eval_code(
|
||||
}
|
||||
|
||||
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)?,
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ use std::ops::{Add, AddAssign};
|
||||
use comemo::Tracked;
|
||||
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::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm};
|
||||
use crate::syntax::Span;
|
||||
@ -66,14 +66,9 @@ impl Content {
|
||||
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.
|
||||
pub fn styled_with_entry(self, style: Style) -> Self {
|
||||
self.styled_with_map(style.into())
|
||||
pub fn styled(self, style: impl Into<Style>) -> Self {
|
||||
self.styled_with_map(style.into().into())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn styled_with_recipe(
|
||||
pub fn apply_recipe(
|
||||
self,
|
||||
world: Tracked<dyn World>,
|
||||
recipe: Recipe,
|
||||
@ -98,7 +93,7 @@ impl Content {
|
||||
if recipe.selector.is_none() {
|
||||
recipe.apply(world, self)
|
||||
} 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(
|
||||
mut self,
|
||||
name: impl Into<EcoString>,
|
||||
@ -154,6 +150,7 @@ impl Content {
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a field on the content.
|
||||
pub fn field(&self, name: &str) -> Option<&Value> {
|
||||
static NONE: Value = Value::None;
|
||||
self.fields
|
||||
@ -163,10 +160,6 @@ impl Content {
|
||||
.or_else(|| (name == "label").then(|| &NONE))
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> &[(EcoString, Value)] {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn cast_field<T: Cast>(&self, name: &str) -> T {
|
||||
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`.
|
||||
pub fn is<T>(&self) -> bool
|
||||
where
|
||||
|
@ -1,8 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use comemo::Tracked;
|
||||
use ecow::EcoString;
|
||||
@ -33,15 +30,13 @@ impl StyleMap {
|
||||
/// 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
|
||||
/// one.
|
||||
pub fn set<K: Key>(&mut self, key: K, value: K::Value) {
|
||||
self.0.push(Style::Property(Property::new(key, value)));
|
||||
pub fn set(&mut self, property: Property) {
|
||||
self.0.push(Style::Property(property));
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
if let Some(value) = value {
|
||||
self.set(key, value);
|
||||
}
|
||||
pub fn set_opt(&mut self, property: Option<Property>) {
|
||||
self.0.extend(property.map(Style::Property));
|
||||
}
|
||||
|
||||
/// Remove the style that was last set.
|
||||
@ -49,14 +44,6 @@ impl StyleMap {
|
||||
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.
|
||||
pub fn apply(&mut self, outer: Self) {
|
||||
self.0.splice(0..0, outer.0.iter().cloned());
|
||||
@ -75,7 +62,7 @@ impl StyleMap {
|
||||
pub fn scoped(mut self) -> Self {
|
||||
for entry in &mut self.0 {
|
||||
if let Style::Property(property) = entry {
|
||||
property.make_scoped();
|
||||
property.scoped = true;
|
||||
}
|
||||
}
|
||||
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.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Property {
|
||||
/// The id of the property's [key](Key).
|
||||
key: KeyId,
|
||||
/// The id of the node the property belongs to.
|
||||
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
|
||||
/// hierarchy. Used by constructors.
|
||||
scoped: bool,
|
||||
/// The property's value.
|
||||
value: Value,
|
||||
/// The span of the set rule the property stems from.
|
||||
origin: Option<Span>,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
/// Create a new property from a key-value pair.
|
||||
pub fn new<K: Key>(_: K, value: K::Value) -> Self {
|
||||
Self {
|
||||
key: KeyId::of::<K>(),
|
||||
node: K::node(),
|
||||
value: value.into(),
|
||||
scoped: false,
|
||||
origin: None,
|
||||
}
|
||||
pub fn new(node: NodeId, name: EcoString, value: Value) -> Self {
|
||||
Self { node, name, value, scoped: false, origin: None }
|
||||
}
|
||||
|
||||
/// Whether this property has the given key.
|
||||
pub fn is<K: Key>(&self) -> bool {
|
||||
self.key == KeyId::of::<K>()
|
||||
/// Whether this property is the given one.
|
||||
pub fn is(&self, node: NodeId, name: &str) -> bool {
|
||||
self.node == node && self.name == name
|
||||
}
|
||||
|
||||
/// Whether this property belongs to the node with the given id.
|
||||
@ -201,37 +188,24 @@ impl Property {
|
||||
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]
|
||||
pub fn cast<K: Key>(&self) -> Option<K::Value> {
|
||||
if self.key == KeyId::of::<K>() {
|
||||
Some(self.value.clone().cast().unwrap_or_else(|err| {
|
||||
panic!("{} (for {} with value {:?})", err, self.key.name(), self.value)
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
pub fn cast<T: Cast>(&self) -> T {
|
||||
self.value.clone().cast().unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"{} (for {}.{} with value {:?})",
|
||||
err,
|
||||
self.node.name(),
|
||||
self.name,
|
||||
self.value
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Property {
|
||||
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 {
|
||||
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.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Recipe {
|
||||
@ -497,7 +385,7 @@ impl<'a> StyleChain<'a> {
|
||||
if !self
|
||||
.entries()
|
||||
.filter_map(Style::property)
|
||||
.any(|p| p.scoped() && *id == p.node())
|
||||
.any(|p| p.scoped && *id == p.node)
|
||||
{
|
||||
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.
|
||||
pub fn recipes(self) -> impl Iterator<Item = &'a 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.
|
||||
fn values<K: Key>(self, _: K) -> Values<'a, K> {
|
||||
Values {
|
||||
entries: self.entries(),
|
||||
key: PhantomData,
|
||||
barriers: 0,
|
||||
}
|
||||
pub fn properties<T: Cast>(
|
||||
self,
|
||||
node: NodeId,
|
||||
name: &'a str,
|
||||
) -> 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.
|
||||
@ -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.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct StyleVec<T> {
|
||||
|
@ -178,11 +178,13 @@ fn library() -> Library {
|
||||
// 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
|
||||
// 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
|
||||
.set(PageNode::MARGIN, Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into()))));
|
||||
lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
|
||||
.set(PageNode::set_width(Smart::Custom(Abs::pt(120.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.
|
||||
lib.global.scope_mut().def_func::<TestFunc>("test");
|
||||
|
Loading…
x
Reference in New Issue
Block a user