Refactor state

This commit is contained in:
Laurenz 2021-07-29 13:21:25 +02:00
parent 7d15dc634b
commit 2c6127dea6
12 changed files with 261 additions and 209 deletions

View File

@ -3,13 +3,13 @@ use std::rc::Rc;
use super::{Exec, ExecWithMap, State}; use super::{Exec, ExecWithMap, State};
use crate::diag::{Diag, DiagSet, Pass}; use crate::diag::{Diag, DiagSet, Pass};
use crate::util::EcoString;
use crate::eval::{ExprMap, Template}; use crate::eval::{ExprMap, Template};
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{ use crate::layout::{
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
}; };
use crate::syntax::{Span, SyntaxTree}; use crate::syntax::{Span, SyntaxTree};
use crate::util::EcoString;
use crate::Context; use crate::Context;
/// The context for execution. /// The context for execution.
@ -76,10 +76,10 @@ impl ExecContext {
/// Push text, but in monospace. /// Push text, but in monospace.
pub fn push_monospace_text(&mut self, text: impl Into<EcoString>) { pub fn push_monospace_text(&mut self, text: impl Into<EcoString>) {
let prev = Rc::clone(&self.state.text); let prev = Rc::clone(&self.state.font);
self.state.text_mut().monospace = true; self.state.font_mut().monospace = true;
self.push_text(text); self.push_text(text);
self.state.text = prev; self.state.font = prev;
} }
/// Push a word space into the active paragraph. /// Push a word space into the active paragraph.
@ -121,7 +121,7 @@ impl ExecContext {
/// Apply a forced paragraph break. /// Apply a forced paragraph break.
pub fn parbreak(&mut self) { pub fn parbreak(&mut self) {
let amount = self.state.text.par_spacing(); let amount = self.state.par_spacing();
self.stack.finish_par(&self.state); self.stack.finish_par(&self.state);
self.stack.push_soft(StackChild::Spacing(amount)); self.stack.push_soft(StackChild::Spacing(amount));
} }
@ -148,7 +148,7 @@ impl ExecContext {
ParChild::Text( ParChild::Text(
text.into(), text.into(),
self.state.aligns.cross, self.state.aligns.cross,
Rc::clone(&self.state.text), Rc::clone(&self.state.font),
) )
} }
} }
@ -187,7 +187,7 @@ struct StackBuilder {
impl StackBuilder { impl StackBuilder {
fn new(state: &State) -> Self { fn new(state: &State) -> Self {
Self { Self {
dirs: Gen::new(state.dir, Dir::TTB), dirs: state.dirs,
children: vec![], children: vec![],
last: Last::None, last: Last::None,
par: ParBuilder::new(state), par: ParBuilder::new(state),
@ -237,8 +237,8 @@ impl ParBuilder {
fn new(state: &State) -> Self { fn new(state: &State) -> Self {
Self { Self {
aligns: state.aligns, aligns: state.aligns,
dir: state.dir, dir: state.dirs.cross,
line_spacing: state.text.line_spacing(), line_spacing: state.line_spacing(),
children: vec![], children: vec![],
last: Last::None, last: Last::None,
} }

View File

@ -9,12 +9,12 @@ pub use state::*;
use std::fmt::Write; use std::fmt::Write;
use crate::diag::Pass; use crate::diag::Pass;
use crate::util::EcoString;
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
use crate::geom::{Dir, Gen}; use crate::geom::Gen;
use crate::layout::{LayoutTree, StackChild, StackNode}; use crate::layout::{LayoutTree, StackChild, StackNode};
use crate::pretty::pretty; use crate::pretty::pretty;
use crate::syntax::*; use crate::syntax::*;
use crate::util::EcoString;
use crate::Context; use crate::Context;
/// Execute a template to produce a layout tree. /// Execute a template to produce a layout tree.
@ -57,8 +57,8 @@ impl ExecWithMap for SyntaxNode {
Self::Space => ctx.push_word_space(), Self::Space => ctx.push_word_space(),
Self::Linebreak(_) => ctx.linebreak(), Self::Linebreak(_) => ctx.linebreak(),
Self::Parbreak(_) => ctx.parbreak(), Self::Parbreak(_) => ctx.parbreak(),
Self::Strong(_) => ctx.state.text_mut().strong ^= true, Self::Strong(_) => ctx.state.font_mut().strong ^= true,
Self::Emph(_) => ctx.state.text_mut().emph ^= true, Self::Emph(_) => ctx.state.font_mut().emph ^= true,
Self::Raw(n) => n.exec(ctx), Self::Raw(n) => n.exec(ctx),
Self::Heading(n) => n.exec_with_map(ctx, map), Self::Heading(n) => n.exec_with_map(ctx, map),
Self::List(n) => n.exec_with_map(ctx, map), Self::List(n) => n.exec_with_map(ctx, map),
@ -87,10 +87,10 @@ impl ExecWithMap for HeadingNode {
ctx.parbreak(); ctx.parbreak();
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let text = ctx.state.text_mut(); let font = ctx.state.font_mut();
let upscale = 1.6 - 0.1 * self.level as f64; let upscale = 1.6 - 0.1 * self.level as f64;
text.size *= upscale; font.size *= upscale;
text.strong = true; font.strong = true;
self.body.exec_with_map(ctx, map); self.body.exec_with_map(ctx, map);
ctx.state = snapshot; ctx.state = snapshot;
@ -118,11 +118,11 @@ fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &E
let label = ctx.exec_stack(|ctx| ctx.push_text(label)); let label = ctx.exec_stack(|ctx| ctx.push_text(label));
let body = ctx.exec_tree_stack(body, map); let body = ctx.exec_tree_stack(body, map);
let stack = StackNode { let stack = StackNode {
dirs: Gen::new(Dir::TTB, ctx.state.dir), dirs: Gen::new(ctx.state.dirs.main, ctx.state.dirs.cross),
aspect: None, aspect: None,
children: vec![ children: vec![
StackChild::Any(label.into(), Gen::default()), StackChild::Any(label.into(), Gen::default()),
StackChild::Spacing(ctx.state.text.size / 2.0), StackChild::Spacing(ctx.state.font.size / 2.0),
StackChild::Any(body.into(), Gen::default()), StackChild::Any(body.into(), Gen::default()),
], ],
}; };

View File

@ -12,29 +12,52 @@ use crate::paper::{PaperClass, PAPER_A4};
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct State { pub struct State {
/// The direction for text and other inline objects. /// The direction for text and other inline objects.
pub dir: Dir, pub dirs: Gen<Dir>,
/// The current alignments of layouts in their parents. /// The current alignments of layouts in their parents.
pub aligns: Gen<Align>, pub aligns: Gen<Align>,
/// The current page settings. /// The current page settings.
pub page: PageState, pub page: Rc<PageState>,
/// The current text settings. /// The current paragraph settings.
pub text: Rc<TextState>, pub par: Rc<ParState>,
/// The current font settings.
pub font: Rc<FontState>,
} }
impl State { impl State {
/// Access the `text` state mutably. /// Access the `page` state mutably.
pub fn text_mut(&mut self) -> &mut TextState { pub fn page_mut(&mut self) -> &mut PageState {
Rc::make_mut(&mut self.text) Rc::make_mut(&mut self.page)
}
/// Access the `par` state mutably.
pub fn par_mut(&mut self) -> &mut ParState {
Rc::make_mut(&mut self.par)
}
/// Access the `font` state mutably.
pub fn font_mut(&mut self) -> &mut FontState {
Rc::make_mut(&mut self.font)
}
/// The resolved line spacing.
pub fn line_spacing(&self) -> Length {
self.par.line_spacing.resolve(self.font.size)
}
/// The resolved paragraph spacing.
pub fn par_spacing(&self) -> Length {
self.par.par_spacing.resolve(self.font.size)
} }
} }
impl Default for State { impl Default for State {
fn default() -> Self { fn default() -> Self {
Self { Self {
dir: Dir::LTR, dirs: Gen::new(Dir::LTR, Dir::TTB),
aligns: Gen::splat(Align::Start), aligns: Gen::splat(Align::Start),
page: PageState::default(), page: Rc::new(PageState::default()),
text: Rc::new(TextState::default()), par: Rc::new(ParState::default()),
font: Rc::new(FontState::default()),
} }
} }
} }
@ -75,15 +98,27 @@ impl Default for PageState {
} }
} }
/// Defines text properties. /// Style paragraph properties.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct TextState { pub struct ParState {
/// A list of font families with generic class definitions (the final /// The spacing between paragraphs (dependent on scaled font size).
/// family list also depends on `monospace`). pub par_spacing: Linear,
pub families: Rc<FamilyList>, /// The spacing between lines (dependent on scaled font size).
/// The selected font variant (the final variant also depends on `strong` pub line_spacing: Linear,
/// and `emph`). }
pub variant: FontVariant,
impl Default for ParState {
fn default() -> Self {
Self {
par_spacing: Relative::new(1.0).into(),
line_spacing: Relative::new(0.5).into(),
}
}
}
/// Defines font properties.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FontState {
/// Whether the strong toggle is active or inactive. This determines /// Whether the strong toggle is active or inactive. This determines
/// whether the next `*` adds or removes font weight. /// whether the next `*` adds or removes font weight.
pub strong: bool, pub strong: bool,
@ -94,19 +129,18 @@ pub struct TextState {
pub monospace: bool, pub monospace: bool,
/// The font size. /// The font size.
pub size: Length, pub size: Length,
/// The spacing between words (dependent on scaled font size). /// The selected font variant (the final variant also depends on `strong`
// TODO: Don't ignore this. /// and `emph`).
pub word_spacing: Linear, pub variant: FontVariant,
/// The spacing between lines (dependent on scaled font size).
pub line_spacing: Linear,
/// The spacing between paragraphs (dependent on scaled font size).
pub par_spacing: Linear,
/// The top end of the text bounding box. /// The top end of the text bounding box.
pub top_edge: VerticalFontMetric, pub top_edge: VerticalFontMetric,
/// The bottom end of the text bounding box. /// The bottom end of the text bounding box.
pub bottom_edge: VerticalFontMetric, pub bottom_edge: VerticalFontMetric,
/// Glyph color. /// Glyph color.
pub fill: Paint, pub fill: Paint,
/// A list of font families with generic class definitions (the final
/// family list also depends on `monospace`).
pub families: Rc<FamilyState>,
/// The specifications for a strikethrough line, if any. /// The specifications for a strikethrough line, if any.
pub strikethrough: Option<Rc<LineState>>, pub strikethrough: Option<Rc<LineState>>,
/// The specifications for a underline, if any. /// The specifications for a underline, if any.
@ -115,22 +149,7 @@ pub struct TextState {
pub overline: Option<Rc<LineState>>, pub overline: Option<Rc<LineState>>,
} }
impl TextState { impl FontState {
/// Access the `families` list mutably.
pub fn families_mut(&mut self) -> &mut FamilyList {
Rc::make_mut(&mut self.families)
}
/// The resolved family iterator.
pub fn families(&self) -> impl Iterator<Item = &str> + Clone {
let head = if self.monospace {
self.families.monospace.as_slice()
} else {
&[]
};
head.iter().map(String::as_str).chain(self.families.iter())
}
/// The resolved variant with `strong` and `emph` factored in. /// The resolved variant with `strong` and `emph` factored in.
pub fn variant(&self) -> FontVariant { pub fn variant(&self) -> FontVariant {
let mut variant = self.variant; let mut variant = self.variant;
@ -150,26 +169,39 @@ impl TextState {
variant variant
} }
/// The resolved word spacing. /// The resolved family iterator.
pub fn word_spacing(&self) -> Length { pub fn families(&self) -> impl Iterator<Item = &str> + Clone {
self.word_spacing.resolve(self.size) let head = if self.monospace {
self.families.monospace.as_slice()
} else {
&[]
};
let core = self.families.list.iter().flat_map(move |family: &FontFamily| {
match family {
FontFamily::Named(name) => std::slice::from_ref(name),
FontFamily::Serif => &self.families.serif,
FontFamily::SansSerif => &self.families.sans_serif,
FontFamily::Monospace => &self.families.monospace,
}
});
head.iter()
.chain(core)
.chain(self.families.base.iter())
.map(String::as_str)
} }
/// The resolved line spacing. /// Access the `families` state mutably.
pub fn line_spacing(&self) -> Length { pub fn families_mut(&mut self) -> &mut FamilyState {
self.line_spacing.resolve(self.size) Rc::make_mut(&mut self.families)
}
/// The resolved paragraph spacing.
pub fn par_spacing(&self) -> Length {
self.par_spacing.resolve(self.size)
} }
} }
impl Default for TextState { impl Default for FontState {
fn default() -> Self { fn default() -> Self {
Self { Self {
families: Rc::new(FamilyList::default()), families: Rc::new(FamilyState::default()),
variant: FontVariant { variant: FontVariant {
style: FontStyle::Normal, style: FontStyle::Normal,
weight: FontWeight::REGULAR, weight: FontWeight::REGULAR,
@ -179,9 +211,6 @@ impl Default for TextState {
emph: false, emph: false,
monospace: false, monospace: false,
size: Length::pt(11.0), size: Length::pt(11.0),
word_spacing: Relative::new(0.25).into(),
line_spacing: Relative::new(0.5).into(),
par_spacing: Relative::new(1.0).into(),
top_edge: VerticalFontMetric::CapHeight, top_edge: VerticalFontMetric::CapHeight,
bottom_edge: VerticalFontMetric::Baseline, bottom_edge: VerticalFontMetric::Baseline,
fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)),
@ -194,63 +223,46 @@ impl Default for TextState {
/// Font family definitions. /// Font family definitions.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FamilyList { pub struct FamilyState {
/// The user-defined list of font families. /// The user-defined list of font families.
pub list: Vec<FontFamily>, pub list: Rc<Vec<FontFamily>>,
/// Definition of serif font families. /// Definition of serif font families.
pub serif: Vec<String>, pub serif: Rc<Vec<String>>,
/// Definition of sans-serif font families. /// Definition of sans-serif font families.
pub sans_serif: Vec<String>, pub sans_serif: Rc<Vec<String>>,
/// Definition of monospace font families used for raw text. /// Definition of monospace font families used for raw text.
pub monospace: Vec<String>, pub monospace: Rc<Vec<String>>,
/// Base fonts that are tried if the list has no match. /// Base fonts that are tried as last resort.
pub base: Vec<String>, pub base: Rc<Vec<String>>,
} }
impl FamilyList { impl Default for FamilyState {
/// Flat iterator over this map's family names.
pub fn iter(&self) -> impl Iterator<Item = &str> + Clone {
self.list
.iter()
.flat_map(move |family: &FontFamily| {
match family {
FontFamily::Named(name) => std::slice::from_ref(name),
FontFamily::Serif => &self.serif,
FontFamily::SansSerif => &self.sans_serif,
FontFamily::Monospace => &self.monospace,
}
})
.chain(&self.base)
.map(String::as_str)
}
}
impl Default for FamilyList {
fn default() -> Self { fn default() -> Self {
Self { Self {
list: vec![FontFamily::Serif], list: Rc::new(vec![FontFamily::Serif]),
serif: vec!["eb garamond".into()], serif: Rc::new(vec!["eb garamond".into()]),
sans_serif: vec!["pt sans".into()], sans_serif: Rc::new(vec!["pt sans".into()]),
monospace: vec!["inconsolata".into()], monospace: Rc::new(vec!["inconsolata".into()]),
base: vec!["twitter color emoji".into(), "latin modern math".into()], base: Rc::new(vec![
"twitter color emoji".into(),
"latin modern math".into(),
]),
} }
} }
} }
/// Describes a line that is positioned over, under or on top of text. /// Defines a line that is positioned over, under or on top of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LineState { pub struct LineState {
/// Stroke color of the line. /// Stroke color of the line, defaults to the text color if `None`.
///
/// Defaults to the text color if `None`.
pub stroke: Option<Paint>, pub stroke: Option<Paint>,
/// Thickness of the line's stroke. Calling functions should attempt to /// Thickness of the line's strokes (dependent on scaled font size), read
/// read this value from the appropriate font tables if this is `None`. /// from the font tables if `None`.
pub thickness: Option<Linear>, pub thickness: Option<Linear>,
/// Position of the line relative to the baseline. Calling functions should /// Position of the line relative to the baseline (dependent on scaled font
/// attempt to read this value from the appropriate font tables if this is /// size), read from the font tables if `None`.
/// `None`.
pub offset: Option<Linear>, pub offset: Option<Linear>,
/// Amount that the line will be longer or shorter than its associated text. /// Amount that the line will be longer or shorter than its associated text
/// (dependent on scaled font size).
pub extent: Linear, pub extent: Linear,
} }

View File

@ -60,6 +60,16 @@ impl Gen<Length> {
} }
} }
impl<T> Gen<Option<T>> {
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> {
Gen {
cross: self.cross.unwrap_or(other.cross),
main: self.main.unwrap_or(other.main),
}
}
}
impl<T> Get<GenAxis> for Gen<T> { impl<T> Get<GenAxis> for Gen<T> {
type Component = T; type Component = T;

View File

@ -75,6 +75,16 @@ impl Spec<Length> {
} }
} }
impl<T> Spec<Option<T>> {
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> {
Spec {
horizontal: self.horizontal.unwrap_or(other.horizontal),
vertical: self.vertical.unwrap_or(other.vertical),
}
}
}
impl<T> Get<SpecAxis> for Spec<T> { impl<T> Get<SpecAxis> for Spec<T> {
type Component = T; type Component = T;

View File

@ -5,7 +5,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator; use xi_unicode::LineBreakIterator;
use super::*; use super::*;
use crate::exec::TextState; use crate::exec::FontState;
use crate::util::{EcoString, RangeExt, SliceExt}; use crate::util::{EcoString, RangeExt, SliceExt};
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;
@ -29,7 +29,7 @@ pub enum ParChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),
/// A run of text and how to align it in its line. /// A run of text and how to align it in its line.
Text(EcoString, Align, Rc<TextState>), Text(EcoString, Align, Rc<FontState>),
/// Any child node and how to align it in its line. /// Any child node and how to align it in its line.
Any(LayoutNode, Align), Any(LayoutNode, Align),
} }

View File

@ -5,7 +5,7 @@ use std::ops::Range;
use rustybuzz::UnicodeBuffer; use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::{Element, Frame, Glyph, LayoutContext, Text};
use crate::exec::{LineState, TextState}; use crate::exec::{FontState, LineState};
use crate::font::{Face, FaceId, FontVariant, LineMetrics}; use crate::font::{Face, FaceId, FontVariant, LineMetrics};
use crate::geom::{Dir, Length, Point, Size}; use crate::geom::{Dir, Length, Point, Size};
use crate::layout::Geometry; use crate::layout::Geometry;
@ -23,7 +23,7 @@ pub struct ShapedText<'a> {
/// The text direction. /// The text direction.
pub dir: Dir, pub dir: Dir,
/// The properties used for font selection. /// The properties used for font selection.
pub state: &'a TextState, pub state: &'a FontState,
/// The font size. /// The font size.
pub size: Size, pub size: Size,
/// The baseline from the top of the frame. /// The baseline from the top of the frame.
@ -185,7 +185,7 @@ pub fn shape<'a>(
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
text: &'a str, text: &'a str,
dir: Dir, dir: Dir,
state: &'a TextState, state: &'a FontState,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let mut glyphs = vec![]; let mut glyphs = vec![];
if !text.is_empty() { if !text.is_empty() {
@ -346,7 +346,7 @@ fn shape_segment<'a>(
fn measure( fn measure(
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
glyphs: &[ShapedGlyph], glyphs: &[ShapedGlyph],
state: &TextState, state: &FontState,
) -> (Size, Length) { ) -> (Size, Length) {
let mut width = Length::zero(); let mut width = Length::zero();
let mut top = Length::zero(); let mut top = Length::zero();
@ -386,7 +386,7 @@ fn decorate(
pos: Point, pos: Point,
width: Length, width: Length,
face_id: FaceId, face_id: FaceId,
state: &TextState, state: &FontState,
) { ) {
let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| { let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| {
let metrics = metrics(ctx.fonts.get(face_id)); let metrics = metrics(ctx.fonts.get(face_id));

View File

@ -24,45 +24,45 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template(move |ctx| { Value::template(move |ctx| {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let state = ctx.state.page_mut();
if let Some(paper) = paper { if let Some(paper) = paper {
ctx.state.page.class = paper.class; state.class = paper.class;
ctx.state.page.size = paper.size(); state.size = paper.size();
} }
if let Some(width) = width { if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom; state.class = PaperClass::Custom;
ctx.state.page.size.width = width; state.size.width = width;
} }
if let Some(height) = height { if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom; state.class = PaperClass::Custom;
ctx.state.page.size.height = height; state.size.height = height;
} }
if let Some(margins) = margins { if let Some(margins) = margins {
ctx.state.page.margins = Sides::splat(Some(margins)); state.margins = Sides::splat(Some(margins));
} }
if let Some(left) = left { if let Some(left) = left {
ctx.state.page.margins.left = Some(left); state.margins.left = Some(left);
} }
if let Some(top) = top { if let Some(top) = top {
ctx.state.page.margins.top = Some(top); state.margins.top = Some(top);
} }
if let Some(right) = right { if let Some(right) = right {
ctx.state.page.margins.right = Some(right); state.margins.right = Some(right);
} }
if let Some(bottom) = bottom { if let Some(bottom) = bottom {
ctx.state.page.margins.bottom = Some(bottom); state.margins.bottom = Some(bottom);
} }
if flip.unwrap_or(false) { if flip.unwrap_or(false) {
let page = &mut ctx.state.page; std::mem::swap(&mut state.size.width, &mut state.size.height);
std::mem::swap(&mut page.size.width, &mut page.size.height);
} }
ctx.pagebreak(false, true, span); ctx.pagebreak(false, true, span);
@ -96,7 +96,7 @@ fn spacing_impl(ctx: &mut EvalContext, args: &mut FuncArgs, axis: GenAxis) -> Va
Value::template(move |ctx| { Value::template(move |ctx| {
if let Some(linear) = spacing { if let Some(linear) = spacing {
// TODO: Should this really always be font-size relative? // TODO: Should this really always be font-size relative?
let amount = linear.resolve(ctx.state.text.size); let amount = linear.resolve(ctx.state.font.size);
ctx.push_spacing(axis, amount); ctx.push_spacing(axis, amount);
} }
}) })
@ -180,7 +180,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `stack`: Stack children along an axis. /// `stack`: Stack children along an axis.
pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let dir = args.named(ctx, "dir").unwrap_or(Dir::TTB); let dir = args.named(ctx, "dir");
let children: Vec<_> = args.all().collect(); let children: Vec<_> = args.all().collect();
Value::template(move |ctx| { Value::template(move |ctx| {
@ -192,22 +192,26 @@ pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}) })
.collect(); .collect();
ctx.push_into_stack(StackNode { let mut dirs = Gen::new(None, dir).unwrap_or(ctx.state.dirs);
dirs: Gen::new(ctx.state.dir, dir),
aspect: None, // If the directions become aligned, fix up the cross direction since
children, // that's the one that is not user-defined.
}); if dirs.main.axis() == dirs.cross.axis() {
dirs.cross = ctx.state.dirs.main;
}
ctx.push_into_stack(StackNode { dirs, aspect: None, children });
}) })
} }
/// `grid`: Arrange children into a grid. /// `grid`: Arrange children into a grid.
pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let columns = args.named::<Tracks>(ctx, "columns").unwrap_or_default(); let columns = args.named(ctx, "columns").unwrap_or_default();
let rows = args.named::<Tracks>(ctx, "rows").unwrap_or_default(); let rows = args.named(ctx, "rows").unwrap_or_default();
let gutter_columns = args.named(ctx, "gutter-columns"); let gutter_columns = args.named(ctx, "gutter-columns");
let gutter_rows = args.named(ctx, "gutter-rows"); let gutter_rows = args.named(ctx, "gutter-rows");
let gutter = args let default = args
.named(ctx, "gutter") .named(ctx, "gutter")
.map(|v| vec![TrackSizing::Linear(v)]) .map(|v| vec![TrackSizing::Linear(v)])
.unwrap_or_default(); .unwrap_or_default();
@ -217,22 +221,40 @@ pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let children: Vec<_> = args.all().collect(); let children: Vec<_> = args.all().collect();
let tracks = Gen::new(columns, rows);
let gutter = Gen::new(
gutter_columns.unwrap_or_else(|| default.clone()),
gutter_rows.unwrap_or(default),
);
Value::template(move |ctx| { Value::template(move |ctx| {
let children = children let children = children
.iter() .iter()
.map(|child| ctx.exec_template_stack(child).into()) .map(|child| ctx.exec_template_stack(child).into())
.collect(); .collect();
let cross_dir = column_dir.unwrap_or(ctx.state.dir); let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(ctx.state.dirs);
let main_dir = row_dir.unwrap_or(cross_dir.axis().other().dir(true));
// If the directions become aligned, try to fix up the direction which
// is not user-defined.
if dirs.main.axis() == dirs.cross.axis() {
let target = if column_dir.is_some() {
&mut dirs.main
} else {
&mut dirs.cross
};
*target = if target.axis() == ctx.state.dirs.cross.axis() {
ctx.state.dirs.main
} else {
ctx.state.dirs.cross
};
}
ctx.push_into_stack(GridNode { ctx.push_into_stack(GridNode {
dirs: Gen::new(cross_dir, main_dir), dirs,
tracks: Gen::new(columns.clone(), rows.clone()), tracks: tracks.clone(),
gutter: Gen::new( gutter: gutter.clone(),
gutter_columns.as_ref().unwrap_or(&gutter).clone(),
gutter_rows.as_ref().unwrap_or(&gutter).clone(),
),
children, children,
}) })
}) })

View File

@ -1,17 +1,10 @@
use crate::exec::{LineState, TextState}; use crate::exec::{FontState, LineState};
use crate::layout::Paint; use crate::layout::Paint;
use super::*; use super::*;
/// `font`: Configure the font. /// `font`: Configure the font.
pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let families: Vec<_> = args.all().collect();
let list = if families.is_empty() {
args.named(ctx, "family")
} else {
Some(FontDef(families))
};
let size = args.eat::<Linear>().or_else(|| args.named(ctx, "size")); let size = args.eat::<Linear>().or_else(|| args.named(ctx, "size"));
let style = args.named(ctx, "style"); let style = args.named(ctx, "style");
let weight = args.named(ctx, "weight"); let weight = args.named(ctx, "weight");
@ -19,20 +12,25 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let top_edge = args.named(ctx, "top-edge"); let top_edge = args.named(ctx, "top-edge");
let bottom_edge = args.named(ctx, "bottom-edge"); let bottom_edge = args.named(ctx, "bottom-edge");
let fill = args.named(ctx, "fill"); let fill = args.named(ctx, "fill");
let families: Vec<_> = args.all().collect();
let list = if families.is_empty() {
args.named(ctx, "family")
} else {
Some(FontDef(Rc::new(families)))
};
let serif = args.named(ctx, "serif"); let serif = args.named(ctx, "serif");
let sans_serif = args.named(ctx, "sans-serif"); let sans_serif = args.named(ctx, "sans-serif");
let monospace = args.named(ctx, "monospace"); let monospace = args.named(ctx, "monospace");
let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| { Value::template(move |ctx| {
let state = ctx.state.text_mut(); let state = ctx.state.font_mut();
if let Some(linear) = size { if let Some(size) = size {
state.size = linear.resolve(state.size); state.size = size.resolve(state.size);
}
if let Some(FontDef(list)) = &list {
state.families_mut().list = list.clone();
} }
if let Some(style) = style { if let Some(style) = style {
@ -59,6 +57,10 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
state.fill = Paint::Color(fill); state.fill = Paint::Color(fill);
} }
if let Some(FontDef(list)) = &list {
state.families_mut().list = list.clone();
}
if let Some(FamilyDef(serif)) = &serif { if let Some(FamilyDef(serif)) = &serif {
state.families_mut().serif = serif.clone(); state.families_mut().serif = serif.clone();
} }
@ -75,41 +77,42 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}) })
} }
struct FontDef(Vec<FontFamily>); struct FontDef(Rc<Vec<FontFamily>>);
castable! { castable! {
FontDef: "font family or array of font families", FontDef: "font family or array of font families",
Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), Value::Str(string) => Self(Rc::new(vec![FontFamily::Named(string.to_lowercase())])),
Value::Array(values) => Self(values Value::Array(values) => Self(Rc::new(
.into_iter() values
.filter_map(|v| v.cast().ok()) .into_iter()
.collect() .filter_map(|v| v.cast().ok())
), .collect()
@family: FontFamily => Self(vec![family.clone()]), )),
@family: FontFamily => Self(Rc::new(vec![family.clone()])),
} }
struct FamilyDef(Vec<String>); struct FamilyDef(Rc<Vec<String>>);
castable! { castable! {
FamilyDef: "string or array of strings", FamilyDef: "string or array of strings",
Value::Str(string) => Self(vec![string.to_lowercase()]), Value::Str(string) => Self(Rc::new(vec![string.to_lowercase()])),
Value::Array(values) => Self(values Value::Array(values) => Self(Rc::new(
.into_iter() values
.filter_map(|v| v.cast().ok()) .into_iter()
.map(|string: EcoString| string.to_lowercase()) .filter_map(|v| v.cast().ok())
.collect() .map(|string: EcoString| string.to_lowercase())
), .collect()
)),
} }
/// `par`: Configure paragraphs. /// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let par_spacing = args.named(ctx, "spacing"); let par_spacing = args.named(ctx, "spacing");
let line_spacing = args.named(ctx, "leading"); let line_spacing = args.named(ctx, "leading");
let word_spacing = args.named(ctx, "word-spacing");
let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| { Value::template(move |ctx| {
let state = ctx.state.text_mut(); let state = ctx.state.par_mut();
if let Some(par_spacing) = par_spacing { if let Some(par_spacing) = par_spacing {
state.par_spacing = par_spacing; state.par_spacing = par_spacing;
@ -119,10 +122,6 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
state.line_spacing = line_spacing; state.line_spacing = line_spacing;
} }
if let Some(word_spacing) = word_spacing {
state.word_spacing = word_spacing;
}
ctx.parbreak(); ctx.parbreak();
body.exec(ctx); body.exec(ctx);
}) })
@ -130,20 +129,23 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `lang`: Configure the language. /// `lang`: Configure the language.
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let iso = args.eat::<EcoString>().map(|s| lang_dir(&s)); let iso = args.eat::<EcoString>();
let dir = match args.named::<Spanned<Dir>>(ctx, "dir") { let dir = if let Some(dir) = args.named::<Spanned<Dir>>(ctx, "dir") {
Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v), if dir.v.axis() == SpecAxis::Horizontal {
Some(dir) => { Some(dir.v)
} else {
ctx.diag(error!(dir.span, "must be horizontal")); ctx.diag(error!(dir.span, "must be horizontal"));
None None
} }
None => None, } else {
iso.as_deref().map(lang_dir)
}; };
let body = args.expect::<Template>(ctx, "body").unwrap_or_default(); let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| { Value::template(move |ctx| {
if let Some(dir) = dir.or(iso) { if let Some(dir) = dir {
ctx.state.dir = dir; ctx.state.dirs.cross = dir;
} }
ctx.parbreak(); ctx.parbreak();
@ -151,7 +153,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}) })
} }
/// The default direction for the language identified by `iso`. /// The default direction for the language identified by the given `iso` code.
fn lang_dir(iso: &str) -> Dir { fn lang_dir(iso: &str) -> Dir {
match iso.to_ascii_lowercase().as_str() { match iso.to_ascii_lowercase().as_str() {
"ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL, "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
@ -178,7 +180,7 @@ pub fn overline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
fn line_impl( fn line_impl(
ctx: &mut EvalContext, ctx: &mut EvalContext,
args: &mut FuncArgs, args: &mut FuncArgs,
substate: fn(&mut TextState) -> &mut Option<Rc<LineState>>, substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> Value { ) -> Value {
let stroke = args.eat().or_else(|| args.named(ctx, "stroke")); let stroke = args.eat().or_else(|| args.named(ctx, "stroke"));
let thickness = args.eat::<Linear>().or_else(|| args.named(ctx, "thickness")); let thickness = args.eat::<Linear>().or_else(|| args.named(ctx, "thickness"));
@ -197,7 +199,7 @@ fn line_impl(
}); });
Value::template(move |ctx| { Value::template(move |ctx| {
*substate(ctx.state.text_mut()) = state.clone(); *substate(ctx.state.font_mut()) = state.clone();
body.exec(ctx); body.exec(ctx);
}) })
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,12 +1,7 @@
// Test configuring paragraph properties. // Test configuring paragraph properties.
--- ---
// FIXME: Word spacing doesn't work due to new shaping process. #par!(spacing: 10pt, leading: 25%)
#par!(spacing: 10pt, leading: 25%, word-spacing: 1pt)
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.
---
// Test that it finishes an existing paragraph.
Hello #par!(word-spacing: 0pt) t h e r e !

View File

@ -63,8 +63,9 @@ fn main() {
// We want to have "unbounded" pages, so we allow them to be infinitely // We want to have "unbounded" pages, so we allow them to be infinitely
// large and fit them to match their content. // large and fit them to match their content.
let mut state = State::default(); let mut state = State::default();
state.page.size = Size::new(Length::pt(120.0), Length::inf()); let page = state.page_mut();
state.page.margins = Sides::splat(Some(Length::pt(10.0).into())); page.size = Size::new(Length::pt(120.0), Length::inf());
page.margins = Sides::splat(Some(Length::pt(10.0).into()));
// We hook up some extra test helpers into the global scope. // We hook up some extra test helpers into the global scope.
let mut std = typst::library::new(); let mut std = typst::library::new();