diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index fc8bbbd6b..313dddc49 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -179,7 +179,8 @@ impl EvalContext {
/// Start a paragraph group based on the active text state.
pub fn start_par_group(&mut self) {
let dirs = self.state.dirs;
- let line_spacing = self.state.text.line_spacing();
+ let em = self.state.font.font_size();
+ let line_spacing = self.state.par.line_spacing.eval(em);
let aligns = self.state.aligns;
self.start_group((dirs, line_spacing, aligns));
}
@@ -204,13 +205,13 @@ impl EvalContext {
/// Construct a text node from the given string based on the active text
/// state.
pub fn make_text_node(&self, text: String) -> Text {
- let mut variant = self.state.text.variant;
+ let mut variant = self.state.font.variant;
- if self.state.text.strong {
+ if self.state.font.strong {
variant.weight = variant.weight.thicken(300);
}
- if self.state.text.emph {
+ if self.state.font.emph {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
@@ -221,8 +222,8 @@ impl EvalContext {
Text {
text,
dir: self.state.dirs.cross,
- size: self.state.text.font_size(),
- fallback: Rc::clone(&self.state.text.fallback),
+ size: self.state.font.font_size(),
+ families: Rc::clone(&self.state.font.families),
variant,
aligns: self.state.aligns,
}
@@ -256,8 +257,9 @@ impl Eval for SynNode {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
match self {
SynNode::Space => {
+ let em = ctx.state.font.font_size();
ctx.push(Spacing {
- amount: ctx.state.text.word_spacing(),
+ amount: ctx.state.par.word_spacing.eval(em),
softness: Softness::Soft,
});
}
@@ -274,19 +276,20 @@ impl Eval for SynNode {
SynNode::Parbreak => {
ctx.end_par_group();
+ let em = ctx.state.font.font_size();
ctx.push(Spacing {
- amount: ctx.state.text.par_spacing(),
+ amount: ctx.state.par.par_spacing.eval(em),
softness: Softness::Soft,
});
ctx.start_par_group();
}
SynNode::Emph => {
- ctx.state.text.emph ^= true;
+ ctx.state.font.emph ^= true;
}
SynNode::Strong => {
- ctx.state.text.strong ^= true;
+ ctx.state.font.strong ^= true;
}
SynNode::Heading(heading) => {
@@ -311,8 +314,8 @@ impl Eval for NodeHeading {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
let prev = ctx.state.clone();
let upscale = 1.5 - 0.1 * self.level.v as f64;
- ctx.state.text.font_size.scale *= upscale;
- ctx.state.text.strong = true;
+ ctx.state.font.scale *= upscale;
+ ctx.state.font.strong = true;
self.contents.eval(ctx);
@@ -324,10 +327,10 @@ impl Eval for NodeRaw {
type Output = ();
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
- let prev = Rc::clone(&ctx.state.text.fallback);
- let fallback = Rc::make_mut(&mut ctx.state.text.fallback);
- fallback.list.insert(0, "monospace".to_string());
- fallback.flatten();
+ let prev = Rc::clone(&ctx.state.font.families);
+ let families = Rc::make_mut(&mut ctx.state.font.families);
+ families.list.insert(0, "monospace".to_string());
+ families.flatten();
let mut children = vec![];
for line in &self.lines {
@@ -341,7 +344,7 @@ impl Eval for NodeRaw {
expand: Spec::new(false, false),
});
- ctx.state.text.fallback = prev;
+ ctx.state.font.families = prev;
}
}
diff --git a/src/eval/state.rs b/src/eval/state.rs
index 3ae6b4142..9c5476d91 100644
--- a/src/eval/state.rs
+++ b/src/eval/state.rs
@@ -13,10 +13,12 @@ use crate::paper::{Paper, PaperClass, PAPER_A4};
pub struct State {
/// The scope that contains function definitions.
pub scope: Scope,
- /// The text state.
- pub text: TextState,
/// The page state.
pub page: PageState,
+ /// The paragraph state.
+ pub par: ParState,
+ /// The font state.
+ pub font: FontState,
/// The active layouting directions.
pub dirs: Gen
,
/// The active alignments.
@@ -27,118 +29,16 @@ impl Default for State {
fn default() -> Self {
Self {
scope: crate::library::_std(),
- text: TextState::default(),
page: PageState::default(),
+ par: ParState::default(),
+ font: FontState::default(),
dirs: Gen::new(Dir::TTB, Dir::LTR),
aligns: Gen::new(Align::Start, Align::Start),
}
}
}
-/// Defines which fonts to use and how to space text.
-#[derive(Debug, Clone, PartialEq)]
-pub struct TextState {
- /// A tree of font family names and generic class names.
- pub fallback: Rc,
- /// The selected font variant.
- pub variant: FontVariant,
- /// Whether the strong toggle is active or inactive. This determines
- /// whether the next `*` adds or removes font weight.
- pub strong: bool,
- /// Whether the emphasis toggle is active or inactive. This determines
- /// whether the next `_` makes italic or non-italic.
- pub emph: bool,
- /// The font size.
- pub font_size: FontSize,
- /// The word spacing (relative to the the font size).
- pub word_spacing: Linear,
- /// The line spacing (relative to the the font size).
- pub line_spacing: Linear,
- /// The paragraphs spacing (relative to the the font size).
- pub par_spacing: Linear,
-}
-
-impl TextState {
- /// The absolute font size.
- pub fn font_size(&self) -> Length {
- self.font_size.eval()
- }
-
- /// The absolute word spacing.
- pub fn word_spacing(&self) -> Length {
- self.word_spacing.eval(self.font_size())
- }
-
- /// The absolute line spacing.
- pub fn line_spacing(&self) -> Length {
- self.line_spacing.eval(self.font_size())
- }
-
- /// The absolute paragraph spacing.
- pub fn par_spacing(&self) -> Length {
- self.par_spacing.eval(self.font_size())
- }
-}
-
-impl Default for TextState {
- fn default() -> Self {
- Self {
- fallback: Rc::new(fallback! {
- list: ["sans-serif"],
- classes: {
- "serif" => ["source serif pro", "noto serif"],
- "sans-serif" => ["source sans pro", "noto sans"],
- "monospace" => ["source code pro", "noto sans mono"],
- "math" => ["latin modern math", "serif"],
- },
- base: [
- "source sans pro", "noto sans", "segoe ui emoji",
- "noto emoji", "latin modern math",
- ],
- }),
- variant: FontVariant {
- style: FontStyle::Normal,
- weight: FontWeight::REGULAR,
- stretch: FontStretch::Normal,
- },
- strong: false,
- emph: false,
- font_size: FontSize::abs(Length::pt(11.0)),
- word_spacing: Relative::new(0.25).into(),
- line_spacing: Relative::new(0.2).into(),
- par_spacing: Relative::new(0.5).into(),
- }
- }
-}
-
-/// The font size, defined by base and scale.
-#[derive(Debug, Clone, PartialEq)]
-pub struct FontSize {
- /// The base font size, updated whenever the font size is set absolutely.
- pub base: Length,
- /// The scale to apply on the base font size, updated when the font size
- /// is set relatively.
- pub scale: Linear,
-}
-
-impl FontSize {
- /// Create a new font size.
- pub fn new(base: Length, scale: impl Into) -> Self {
- Self { base, scale: scale.into() }
- }
-
- /// Create a new font size with the given `base` and a scale of `1.0`.
- pub fn abs(base: Length) -> Self {
- Self::new(base, Relative::ONE)
- }
-
- /// Compute the absolute font size.
- pub fn eval(&self) -> Length {
- self.scale.eval(self.base)
- }
-}
-
-/// Defines the size and margins of a page.
+/// Defines page properties.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PageState {
/// The class of this page.
@@ -177,3 +77,87 @@ impl Default for PageState {
Self::new(PAPER_A4)
}
}
+
+/// Defines paragraph properties.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct ParState {
+ /// The spacing between words (dependent on scaled font size).
+ 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,
+}
+
+impl Default for ParState {
+ fn default() -> Self {
+ Self {
+ word_spacing: Relative::new(0.25).into(),
+ line_spacing: Relative::new(0.2).into(),
+ par_spacing: Relative::new(0.5).into(),
+ }
+ }
+}
+
+/// Defines font properties.
+#[derive(Debug, Clone, PartialEq)]
+pub struct FontState {
+ /// A tree of font family names and generic class names.
+ pub families: Rc,
+ /// The selected font variant.
+ pub variant: FontVariant,
+ /// The font size.
+ pub size: Length,
+ /// The linear to apply on the base font size.
+ pub scale: Linear,
+ /// Whether the strong toggle is active or inactive. This determines
+ /// whether the next `*` adds or removes font weight.
+ pub strong: bool,
+ /// Whether the emphasis toggle is active or inactive. This determines
+ /// whether the next `_` makes italic or non-italic.
+ pub emph: bool,
+}
+
+impl FontState {
+ /// The absolute font size.
+ pub fn font_size(&self) -> Length {
+ self.scale.eval(self.size)
+ }
+}
+
+impl Default for FontState {
+ fn default() -> Self {
+ Self {
+ families: Rc::new(default_font_families()),
+ variant: FontVariant {
+ style: FontStyle::Normal,
+ weight: FontWeight::REGULAR,
+ stretch: FontStretch::Normal,
+ },
+ size: Length::pt(11.0),
+ scale: Linear::ONE,
+ strong: false,
+ emph: false,
+ }
+ }
+}
+
+/// The default tree of font fallbacks.
+fn default_font_families() -> FallbackTree {
+ fallback! {
+ list: ["sans-serif"],
+ classes: {
+ "serif" => ["source serif pro", "noto serif"],
+ "sans-serif" => ["source sans pro", "noto sans"],
+ "monospace" => ["source code pro", "noto sans mono"],
+ "math" => ["latin modern math", "serif"],
+ },
+ base: [
+ "source sans pro",
+ "noto sans",
+ "segoe ui emoji",
+ "noto emoji",
+ "latin modern math",
+ ],
+ }
+}
diff --git a/src/geom/linear.rs b/src/geom/linear.rs
index 2567d264c..d9860d43b 100644
--- a/src/geom/linear.rs
+++ b/src/geom/linear.rs
@@ -11,7 +11,10 @@ pub struct Linear {
impl Linear {
/// The zero linear.
- pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO };
+ pub const ZERO: Self = Self { rel: Relative::ZERO, abs: Length::ZERO };
+
+ /// The linear with a relative part of `100%` and no absolute part.
+ pub const ONE: Self = Self { rel: Relative::ONE, abs: Length::ZERO };
/// Create a new linear.
pub fn new(rel: Relative, abs: Length) -> Self {
@@ -24,7 +27,7 @@ impl Linear {
self.rel.eval(one) + self.abs
}
- /// Whether this linear's relative component is zero.
+ /// Whether this linear's relative part is zero.
pub fn is_absolute(self) -> bool {
self.rel == Relative::ZERO
}
diff --git a/src/layout/text.rs b/src/layout/text.rs
index fafc1b14e..7ba4be210 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -12,7 +12,7 @@ pub struct Text {
pub text: String,
pub size: Length,
pub dir: Dir,
- pub fallback: Rc,
+ pub families: Rc,
pub variant: FontVariant,
pub aligns: Gen,
}
@@ -30,7 +30,7 @@ impl Layout for Text {
self.size,
self.dir,
&mut loader,
- &self.fallback,
+ &self.families,
self.variant,
)
.await;
diff --git a/src/library/font.rs b/src/library/font.rs
index e6d9cd127..7460b21b6 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -58,10 +58,10 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
if let Some(linear) = args.find::() {
if linear.is_absolute() {
- ctx.state.text.font_size.base = linear.abs;
- ctx.state.text.font_size.scale = Relative::ONE.into();
+ ctx.state.font.size = linear.abs;
+ ctx.state.font.scale = Relative::ONE.into();
} else {
- ctx.state.text.font_size.scale = linear;
+ ctx.state.font.scale = linear;
}
}
@@ -69,20 +69,20 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
let list: Vec<_> = args.find_all::().map(|s| s.to_lowercase()).collect();
if !list.is_empty() {
- Rc::make_mut(&mut ctx.state.text.fallback).list = list;
+ Rc::make_mut(&mut ctx.state.font.families).list = list;
needs_flattening = true;
}
if let Some(style) = args.get::<_, FontStyle>(ctx, "style") {
- ctx.state.text.variant.style = style;
+ ctx.state.font.variant.style = style;
}
if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") {
- ctx.state.text.variant.weight = weight;
+ ctx.state.font.variant.weight = weight;
}
if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") {
- ctx.state.text.variant.stretch = stretch;
+ ctx.state.font.variant.stretch = stretch;
}
for (class, dict) in args.find_all_str::>() {
@@ -91,14 +91,14 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
.map(|s| s.to_lowercase())
.collect();
- Rc::make_mut(&mut ctx.state.text.fallback).update_class_list(class, fallback);
+ Rc::make_mut(&mut ctx.state.font.families).update_class_list(class, fallback);
needs_flattening = true;
}
args.done(ctx);
if needs_flattening {
- Rc::make_mut(&mut ctx.state.text.fallback).flatten();
+ Rc::make_mut(&mut ctx.state.font.families).flatten();
}
if let Some(body) = body {
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index a2b21e93c..765153810 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
args.done(ctx);
if let Some(linear) = spacing {
- let amount = linear.eval(ctx.state.text.font_size());
+ let amount = linear.eval(ctx.state.font.font_size());
let spacing = Spacing { amount, softness: Softness::Hard };
if ctx.state.dirs.main.axis() == axis {
ctx.end_par_group();