summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-07-29 13:21:25 +0200
committerLaurenz <laurmaedje@gmail.com>2021-07-29 13:28:19 +0200
commit2c6127dea611944abb09a0d38375ad7cf9baced0 (patch)
tree6572d169d4ce26edc38a880860ebae2f49639fb8 /src
parent7d15dc634b3be1b6e284bb6b2450e3736d3e6e8d (diff)
Refactor state
Diffstat (limited to 'src')
-rw-r--r--src/exec/context.rs18
-rw-r--r--src/exec/mod.rs18
-rw-r--r--src/exec/state.rs206
-rw-r--r--src/geom/gen.rs10
-rw-r--r--src/geom/spec.rs10
-rw-r--r--src/layout/par.rs4
-rw-r--r--src/layout/shaping.rs10
-rw-r--r--src/library/layout.rs84
-rw-r--r--src/library/text.rs96
9 files changed, 256 insertions, 200 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 3a3eb702..4d351692 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -3,13 +3,13 @@ use std::rc::Rc;
use super::{Exec, ExecWithMap, State};
use crate::diag::{Diag, DiagSet, Pass};
-use crate::util::EcoString;
use crate::eval::{ExprMap, Template};
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
};
use crate::syntax::{Span, SyntaxTree};
+use crate::util::EcoString;
use crate::Context;
/// The context for execution.
@@ -76,10 +76,10 @@ impl ExecContext {
/// Push text, but in monospace.
pub fn push_monospace_text(&mut self, text: impl Into<EcoString>) {
- let prev = Rc::clone(&self.state.text);
- self.state.text_mut().monospace = true;
+ let prev = Rc::clone(&self.state.font);
+ self.state.font_mut().monospace = true;
self.push_text(text);
- self.state.text = prev;
+ self.state.font = prev;
}
/// Push a word space into the active paragraph.
@@ -121,7 +121,7 @@ impl ExecContext {
/// Apply a forced paragraph break.
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.push_soft(StackChild::Spacing(amount));
}
@@ -148,7 +148,7 @@ impl ExecContext {
ParChild::Text(
text.into(),
self.state.aligns.cross,
- Rc::clone(&self.state.text),
+ Rc::clone(&self.state.font),
)
}
}
@@ -187,7 +187,7 @@ struct StackBuilder {
impl StackBuilder {
fn new(state: &State) -> Self {
Self {
- dirs: Gen::new(state.dir, Dir::TTB),
+ dirs: state.dirs,
children: vec![],
last: Last::None,
par: ParBuilder::new(state),
@@ -237,8 +237,8 @@ impl ParBuilder {
fn new(state: &State) -> Self {
Self {
aligns: state.aligns,
- dir: state.dir,
- line_spacing: state.text.line_spacing(),
+ dir: state.dirs.cross,
+ line_spacing: state.line_spacing(),
children: vec![],
last: Last::None,
}
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index ff4faa22..8bac76e8 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -9,12 +9,12 @@ pub use state::*;
use std::fmt::Write;
use crate::diag::Pass;
-use crate::util::EcoString;
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::pretty::pretty;
use crate::syntax::*;
+use crate::util::EcoString;
use crate::Context;
/// Execute a template to produce a layout tree.
@@ -57,8 +57,8 @@ impl ExecWithMap for SyntaxNode {
Self::Space => ctx.push_word_space(),
Self::Linebreak(_) => ctx.linebreak(),
Self::Parbreak(_) => ctx.parbreak(),
- Self::Strong(_) => ctx.state.text_mut().strong ^= true,
- Self::Emph(_) => ctx.state.text_mut().emph ^= true,
+ Self::Strong(_) => ctx.state.font_mut().strong ^= true,
+ Self::Emph(_) => ctx.state.font_mut().emph ^= true,
Self::Raw(n) => n.exec(ctx),
Self::Heading(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();
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;
- text.size *= upscale;
- text.strong = true;
+ font.size *= upscale;
+ font.strong = true;
self.body.exec_with_map(ctx, map);
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 body = ctx.exec_tree_stack(body, map);
let stack = StackNode {
- dirs: Gen::new(Dir::TTB, ctx.state.dir),
+ dirs: Gen::new(ctx.state.dirs.main, ctx.state.dirs.cross),
aspect: None,
children: vec![
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()),
],
};
diff --git a/src/exec/state.rs b/src/exec/state.rs
index 51bbe395..ce30e042 100644
--- a/src/exec/state.rs
+++ b/src/exec/state.rs
@@ -12,29 +12,52 @@ use crate::paper::{PaperClass, PAPER_A4};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct State {
/// The direction for text and other inline objects.
- pub dir: Dir,
+ pub dirs: Gen<Dir>,
/// The current alignments of layouts in their parents.
pub aligns: Gen<Align>,
/// The current page settings.
- pub page: PageState,
- /// The current text settings.
- pub text: Rc<TextState>,
+ pub page: Rc<PageState>,
+ /// The current paragraph settings.
+ pub par: Rc<ParState>,
+ /// The current font settings.
+ pub font: Rc<FontState>,
}
impl State {
- /// Access the `text` state mutably.
- pub fn text_mut(&mut self) -> &mut TextState {
- Rc::make_mut(&mut self.text)
+ /// Access the `page` state mutably.
+ pub fn page_mut(&mut self) -> &mut PageState {
+ 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 {
fn default() -> Self {
Self {
- dir: Dir::LTR,
+ dirs: Gen::new(Dir::LTR, Dir::TTB),
aligns: Gen::splat(Align::Start),
- page: PageState::default(),
- text: Rc::new(TextState::default()),
+ page: Rc::new(PageState::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)]
-pub struct TextState {
- /// A list of font families with generic class definitions (the final
- /// family list also depends on `monospace`).
- pub families: Rc<FamilyList>,
- /// The selected font variant (the final variant also depends on `strong`
- /// and `emph`).
- pub variant: FontVariant,
+pub struct ParState {
+ /// The spacing between paragraphs (dependent on scaled font size).
+ pub par_spacing: Linear,
+ /// The spacing between lines (dependent on scaled font size).
+ pub line_spacing: Linear,
+}
+
+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 next `*` adds or removes font weight.
pub strong: bool,
@@ -94,19 +129,18 @@ pub struct TextState {
pub monospace: bool,
/// The font size.
pub size: Length,
- /// The spacing between words (dependent on scaled font size).
- // TODO: Don't ignore this.
- pub word_spacing: Linear,
- /// 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 selected font variant (the final variant also depends on `strong`
+ /// and `emph`).
+ pub variant: FontVariant,
/// The top end of the text bounding box.
pub top_edge: VerticalFontMetric,
/// The bottom end of the text bounding box.
pub bottom_edge: VerticalFontMetric,
/// Glyph color.
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.
pub strikethrough: Option<Rc<LineState>>,
/// The specifications for a underline, if any.
@@ -115,22 +149,7 @@ pub struct TextState {
pub overline: Option<Rc<LineState>>,
}
-impl TextState {
- /// 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())
- }
-
+impl FontState {
/// The resolved variant with `strong` and `emph` factored in.
pub fn variant(&self) -> FontVariant {
let mut variant = self.variant;
@@ -150,26 +169,39 @@ impl TextState {
variant
}
- /// The resolved word spacing.
- pub fn word_spacing(&self) -> Length {
- self.word_spacing.resolve(self.size)
- }
+ /// The resolved family iterator.
+ pub fn families(&self) -> impl Iterator<Item = &str> + Clone {
+ let head = if self.monospace {
+ self.families.monospace.as_slice()
+ } else {
+ &[]
+ };
- /// The resolved line spacing.
- pub fn line_spacing(&self) -> Length {
- self.line_spacing.resolve(self.size)
+ 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 paragraph spacing.
- pub fn par_spacing(&self) -> Length {
- self.par_spacing.resolve(self.size)
+ /// Access the `families` state mutably.
+ pub fn families_mut(&mut self) -> &mut FamilyState {
+ Rc::make_mut(&mut self.families)
}
}
-impl Default for TextState {
+impl Default for FontState {
fn default() -> Self {
Self {
- families: Rc::new(FamilyList::default()),
+ families: Rc::new(FamilyState::default()),
variant: FontVariant {
style: FontStyle::Normal,
weight: FontWeight::REGULAR,
@@ -179,9 +211,6 @@ impl Default for TextState {
emph: false,
monospace: false,
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,
bottom_edge: VerticalFontMetric::Baseline,
fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)),
@@ -194,63 +223,46 @@ impl Default for TextState {
/// Font family definitions.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct FamilyList {
+pub struct FamilyState {
/// The user-defined list of font families.
- pub list: Vec<FontFamily>,
+ pub list: Rc<Vec<FontFamily>>,
/// Definition of serif font families.
- pub serif: Vec<String>,
+ pub serif: Rc<Vec<String>>,
/// 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.
- pub monospace: Vec<String>,
- /// Base fonts that are tried if the list has no match.
- pub base: Vec<String>,
-}
-
-impl FamilyList {
- /// 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)
- }
+ pub monospace: Rc<Vec<String>>,
+ /// Base fonts that are tried as last resort.
+ pub base: Rc<Vec<String>>,
}
-impl Default for FamilyList {
+impl Default for FamilyState {
fn default() -> Self {
Self {
- list: vec![FontFamily::Serif],
- serif: vec!["eb garamond".into()],
- sans_serif: vec!["pt sans".into()],
- monospace: vec!["inconsolata".into()],
- base: vec!["twitter color emoji".into(), "latin modern math".into()],
+ list: Rc::new(vec![FontFamily::Serif]),
+ serif: Rc::new(vec!["eb garamond".into()]),
+ sans_serif: Rc::new(vec!["pt sans".into()]),
+ monospace: Rc::new(vec!["inconsolata".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)]
pub struct LineState {
- /// Stroke color of the line.
- ///
- /// Defaults to the text color if `None`.
+ /// Stroke color of the line, defaults to the text color if `None`.
pub stroke: Option<Paint>,
- /// Thickness of the line's stroke. Calling functions should attempt to
- /// read this value from the appropriate font tables if this is `None`.
+ /// Thickness of the line's strokes (dependent on scaled font size), read
+ /// from the font tables if `None`.
pub thickness: Option<Linear>,
- /// Position of the line relative to the baseline. Calling functions should
- /// attempt to read this value from the appropriate font tables if this is
- /// `None`.
+ /// Position of the line relative to the baseline (dependent on scaled font
+ /// size), read from the font tables if `None`.
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,
}
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
index 57dc277d..075b8620 100644
--- a/src/geom/gen.rs
+++ b/src/geom/gen.rs
@@ -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> {
type Component = T;
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index f8f62f9f..ead67f11 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -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> {
type Component = T;
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 03d7efd5..a88a0f0b 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -5,7 +5,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator;
use super::*;
-use crate::exec::TextState;
+use crate::exec::FontState;
use crate::util::{EcoString, RangeExt, SliceExt};
type Range = std::ops::Range<usize>;
@@ -29,7 +29,7 @@ pub enum ParChild {
/// Spacing between other nodes.
Spacing(Length),
/// 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(LayoutNode, Align),
}
diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs
index 0cfb01a8..3ede5122 100644
--- a/src/layout/shaping.rs
+++ b/src/layout/shaping.rs
@@ -5,7 +5,7 @@ use std::ops::Range;
use rustybuzz::UnicodeBuffer;
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::geom::{Dir, Length, Point, Size};
use crate::layout::Geometry;
@@ -23,7 +23,7 @@ pub struct ShapedText<'a> {
/// The text direction.
pub dir: Dir,
/// The properties used for font selection.
- pub state: &'a TextState,
+ pub state: &'a FontState,
/// The font size.
pub size: Size,
/// The baseline from the top of the frame.
@@ -185,7 +185,7 @@ pub fn shape<'a>(
ctx: &mut LayoutContext,
text: &'a str,
dir: Dir,
- state: &'a TextState,
+ state: &'a FontState,
) -> ShapedText<'a> {
let mut glyphs = vec![];
if !text.is_empty() {
@@ -346,7 +346,7 @@ fn shape_segment<'a>(
fn measure(
ctx: &mut LayoutContext,
glyphs: &[ShapedGlyph],
- state: &TextState,
+ state: &FontState,
) -> (Size, Length) {
let mut width = Length::zero();
let mut top = Length::zero();
@@ -386,7 +386,7 @@ fn decorate(
pos: Point,
width: Length,
face_id: FaceId,
- state: &TextState,
+ state: &FontState,
) {
let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| {
let metrics = metrics(ctx.fonts.get(face_id));
diff --git a/src/library/layout.rs b/src/library/layout.rs
index eea4afb5..f3f3f81e 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -24,45 +24,45 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template(move |ctx| {
let snapshot = ctx.state.clone();
+ let state = ctx.state.page_mut();
if let Some(paper) = paper {
- ctx.state.page.class = paper.class;
- ctx.state.page.size = paper.size();
+ state.class = paper.class;
+ state.size = paper.size();
}
if let Some(width) = width {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.width = width;
+ state.class = PaperClass::Custom;
+ state.size.width = width;
}
if let Some(height) = height {
- ctx.state.page.class = PaperClass::Custom;
- ctx.state.page.size.height = height;
+ state.class = PaperClass::Custom;
+ state.size.height = height;
}
if let Some(margins) = margins {
- ctx.state.page.margins = Sides::splat(Some(margins));
+ state.margins = Sides::splat(Some(margins));
}
if let Some(left) = left {
- ctx.state.page.margins.left = Some(left);
+ state.margins.left = Some(left);
}
if let Some(top) = top {
- ctx.state.page.margins.top = Some(top);
+ state.margins.top = Some(top);
}
if let Some(right) = right {
- ctx.state.page.margins.right = Some(right);
+ state.margins.right = Some(right);
}
if let Some(bottom) = bottom {
- ctx.state.page.margins.bottom = Some(bottom);
+ state.margins.bottom = Some(bottom);
}
if flip.unwrap_or(false) {
- let page = &mut ctx.state.page;
- std::mem::swap(&mut page.size.width, &mut page.size.height);
+ std::mem::swap(&mut state.size.width, &mut state.size.height);
}
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| {
if let Some(linear) = spacing {
// 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);
}
})
@@ -180,7 +180,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `stack`: Stack children along an axis.
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();
Value::template(move |ctx| {
@@ -192,22 +192,26 @@ pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
})
.collect();
- ctx.push_into_stack(StackNode {
- dirs: Gen::new(ctx.state.dir, dir),
- aspect: None,
- children,
- });
+ let mut dirs = Gen::new(None, dir).unwrap_or(ctx.state.dirs);
+
+ // If the directions become aligned, fix up the cross direction since
+ // 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.
pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let columns = args.named::<Tracks>(ctx, "columns").unwrap_or_default();
- let rows = args.named::<Tracks>(ctx, "rows").unwrap_or_default();
+ let columns = args.named(ctx, "columns").unwrap_or_default();
+ let rows = args.named(ctx, "rows").unwrap_or_default();
let gutter_columns = args.named(ctx, "gutter-columns");
let gutter_rows = args.named(ctx, "gutter-rows");
- let gutter = args
+ let default = args
.named(ctx, "gutter")
.map(|v| vec![TrackSizing::Linear(v)])
.unwrap_or_default();
@@ -217,22 +221,40 @@ pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
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| {
let children = children
.iter()
.map(|child| ctx.exec_template_stack(child).into())
.collect();
- let cross_dir = column_dir.unwrap_or(ctx.state.dir);
- let main_dir = row_dir.unwrap_or(cross_dir.axis().other().dir(true));
+ let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(ctx.state.dirs);
+
+ // 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 {
- dirs: Gen::new(cross_dir, main_dir),
- tracks: Gen::new(columns.clone(), rows.clone()),
- gutter: Gen::new(
- gutter_columns.as_ref().unwrap_or(&gutter).clone(),
- gutter_rows.as_ref().unwrap_or(&gutter).clone(),
- ),
+ dirs,
+ tracks: tracks.clone(),
+ gutter: gutter.clone(),
children,
})
})
diff --git a/src/library/text.rs b/src/library/text.rs
index b56cbcd3..54e9794a 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -1,17 +1,10 @@
-use crate::exec::{LineState, TextState};
+use crate::exec::{FontState, LineState};
use crate::layout::Paint;
use super::*;
/// `font`: Configure the font.
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 style = args.named(ctx, "style");
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 bottom_edge = args.named(ctx, "bottom-edge");
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 sans_serif = args.named(ctx, "sans-serif");
let monospace = args.named(ctx, "monospace");
+
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| {
- let state = ctx.state.text_mut();
+ let state = ctx.state.font_mut();
- if let Some(linear) = size {
- state.size = linear.resolve(state.size);
- }
-
- if let Some(FontDef(list)) = &list {
- state.families_mut().list = list.clone();
+ if let Some(size) = size {
+ state.size = size.resolve(state.size);
}
if let Some(style) = style {
@@ -59,6 +57,10 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
state.fill = Paint::Color(fill);
}
+ if let Some(FontDef(list)) = &list {
+ state.families_mut().list = list.clone();
+ }
+
if let Some(FamilyDef(serif)) = &serif {
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! {
FontDef: "font family or array of font families",
- Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]),
- Value::Array(values) => Self(values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .collect()
- ),
- @family: FontFamily => Self(vec![family.clone()]),
+ Value::Str(string) => Self(Rc::new(vec![FontFamily::Named(string.to_lowercase())])),
+ Value::Array(values) => Self(Rc::new(
+ values
+ .into_iter()
+ .filter_map(|v| v.cast().ok())
+ .collect()
+ )),
+ @family: FontFamily => Self(Rc::new(vec![family.clone()])),
}
-struct FamilyDef(Vec<String>);
+struct FamilyDef(Rc<Vec<String>>);
castable! {
FamilyDef: "string or array of strings",
- Value::Str(string) => Self(vec![string.to_lowercase()]),
- Value::Array(values) => Self(values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .map(|string: EcoString| string.to_lowercase())
- .collect()
- ),
+ Value::Str(string) => Self(Rc::new(vec![string.to_lowercase()])),
+ Value::Array(values) => Self(Rc::new(
+ values
+ .into_iter()
+ .filter_map(|v| v.cast().ok())
+ .map(|string: EcoString| string.to_lowercase())
+ .collect()
+ )),
}
/// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let par_spacing = args.named(ctx, "spacing");
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();
Value::template(move |ctx| {
- let state = ctx.state.text_mut();
+ let state = ctx.state.par_mut();
if let Some(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;
}
- if let Some(word_spacing) = word_spacing {
- state.word_spacing = word_spacing;
- }
-
ctx.parbreak();
body.exec(ctx);
})
@@ -130,20 +129,23 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// `lang`: Configure the language.
pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let iso = args.eat::<EcoString>().map(|s| lang_dir(&s));
- let dir = match args.named::<Spanned<Dir>>(ctx, "dir") {
- Some(dir) if dir.v.axis() == SpecAxis::Horizontal => Some(dir.v),
- Some(dir) => {
+ let iso = args.eat::<EcoString>();
+ let dir = if let Some(dir) = args.named::<Spanned<Dir>>(ctx, "dir") {
+ if dir.v.axis() == SpecAxis::Horizontal {
+ Some(dir.v)
+ } else {
ctx.diag(error!(dir.span, "must be horizontal"));
None
}
- None => None,
+ } else {
+ iso.as_deref().map(lang_dir)
};
+
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
Value::template(move |ctx| {
- if let Some(dir) = dir.or(iso) {
- ctx.state.dir = dir;
+ if let Some(dir) = dir {
+ ctx.state.dirs.cross = dir;
}
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 {
match iso.to_ascii_lowercase().as_str() {
"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(
ctx: &mut EvalContext,
args: &mut FuncArgs,
- substate: fn(&mut TextState) -> &mut Option<Rc<LineState>>,
+ substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> Value {
let stroke = args.eat().or_else(|| args.named(ctx, "stroke"));
let thickness = args.eat::<Linear>().or_else(|| args.named(ctx, "thickness"));
@@ -197,7 +199,7 @@ fn line_impl(
});
Value::template(move |ctx| {
- *substate(ctx.state.text_mut()) = state.clone();
+ *substate(ctx.state.font_mut()) = state.clone();
body.exec(ctx);
})
}