mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor state
This commit is contained in:
parent
7d15dc634b
commit
2c6127dea6
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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 |
@ -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 !
|
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user