mirror of
https://github.com/typst/typst
synced 2025-05-19 03:25:27 +08:00
Refactor text state 🆎
This commit is contained in:
parent
92c01da360
commit
51bf3268dd
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Dir>,
|
||||
/// 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<FallbackTree>,
|
||||
/// 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<Linear>) -> 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<FallbackTree>,
|
||||
/// 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",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub struct Text {
|
||||
pub text: String,
|
||||
pub size: Length,
|
||||
pub dir: Dir,
|
||||
pub fallback: Rc<FallbackTree>,
|
||||
pub families: Rc<FallbackTree>,
|
||||
pub variant: FontVariant,
|
||||
pub aligns: Gen<Align>,
|
||||
}
|
||||
@ -30,7 +30,7 @@ impl Layout for Text {
|
||||
self.size,
|
||||
self.dir,
|
||||
&mut loader,
|
||||
&self.fallback,
|
||||
&self.families,
|
||||
self.variant,
|
||||
)
|
||||
.await;
|
||||
|
@ -58,10 +58,10 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
|
||||
if let Some(linear) = args.find::<Linear>() {
|
||||
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::<StringLike>().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::<Spanned<ValueDict>>() {
|
||||
@ -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 {
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user