mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Unify font and page functions 💕
- Removes font weight and width warnings for now, will be added again later - Adds a bit hacky get_first function for tuples, will be refactored soon anyway
This commit is contained in:
parent
659248d52f
commit
efb78831a7
@ -3,24 +3,33 @@ use crate::length::ScaleLength;
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
function! {
|
||||||
/// `font.family`: Set the font family.
|
/// `font`: Configure the font.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FontFamilyFunc {
|
pub struct FontFunc {
|
||||||
body: Option<SyntaxModel>,
|
body: Option<SyntaxModel>,
|
||||||
|
size: Option<ScaleLength>,
|
||||||
|
style: Option<FontStyle>,
|
||||||
|
weight: Option<FontWeight>,
|
||||||
|
width: Option<FontWidth>,
|
||||||
list: Vec<String>,
|
list: Vec<String>,
|
||||||
classes: Vec<(String, Vec<String>)>,
|
classes: Vec<(String, Vec<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
parse(header, body, ctx, f) {
|
||||||
|
let size = header.args.pos.get_first::<ScaleLength>(&mut f.diagnostics);
|
||||||
|
|
||||||
|
let style = header.args.key.get::<FontStyle>(&mut f.diagnostics, "style");
|
||||||
|
let weight = header.args.key.get::<FontWeight>(&mut f.diagnostics, "weight");
|
||||||
|
let width = header.args.key.get::<FontWidth>(&mut f.diagnostics, "width");
|
||||||
|
|
||||||
let list = header.args.pos.get_all::<StringLike>(&mut f.diagnostics)
|
let list = header.args.pos.get_all::<StringLike>(&mut f.diagnostics)
|
||||||
.map(|s| s.0.to_lowercase())
|
.map(|s| s.0.to_lowercase())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let tuples: Vec<_> = header.args.key
|
let classes = header.args.key
|
||||||
.get_all::<String, Tuple>(&mut f.diagnostics)
|
.get_all::<String, Tuple>(&mut f.diagnostics)
|
||||||
.collect();
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
let classes = tuples.into_iter()
|
|
||||||
.map(|(class, mut tuple)| {
|
.map(|(class, mut tuple)| {
|
||||||
let fallback = tuple.get_all::<StringLike>(&mut f.diagnostics)
|
let fallback = tuple.get_all::<StringLike>(&mut f.diagnostics)
|
||||||
.map(|s| s.0.to_lowercase())
|
.map(|s| s.0.to_lowercase())
|
||||||
@ -29,140 +38,41 @@ function! {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
FontFamilyFunc {
|
FontFunc {
|
||||||
body: body!(opt: body, ctx, f),
|
body: body!(opt: body, ctx, f),
|
||||||
|
size,
|
||||||
list,
|
list,
|
||||||
classes,
|
classes,
|
||||||
|
style,
|
||||||
|
weight,
|
||||||
|
width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
styled(&self.body, ctx, Some(()),
|
styled(&self.body, ctx, Some(()),
|
||||||
|s, _| {
|
|t, _| {
|
||||||
|
self.size.with(|s| match s {
|
||||||
|
ScaleLength::Absolute(length) => {
|
||||||
|
t.base_font_size = length.as_raw();
|
||||||
|
t.font_scale = 1.0;
|
||||||
|
}
|
||||||
|
ScaleLength::Scaled(scale) => t.font_scale = scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.style.with(|s| t.variant.style = s);
|
||||||
|
self.weight.with(|w| t.variant.weight = w);
|
||||||
|
self.width.with(|w| t.variant.width = w);
|
||||||
|
|
||||||
if !self.list.is_empty() {
|
if !self.list.is_empty() {
|
||||||
*s.fallback.list_mut() = self.list.clone();
|
*t.fallback.list_mut() = self.list.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (class, fallback) in &self.classes {
|
for (class, fallback) in &self.classes {
|
||||||
s.fallback.set_class_list(class.clone(), fallback.clone());
|
t.fallback.set_class_list(class.clone(), fallback.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fallback.flatten();
|
t.fallback.flatten();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `font.style`: Set the font style (normal / italic).
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct FontStyleFunc {
|
|
||||||
body: Option<SyntaxModel>,
|
|
||||||
style: Option<FontStyle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
|
||||||
FontStyleFunc {
|
|
||||||
body: body!(opt: body, ctx, f),
|
|
||||||
style: header.args.pos.get::<FontStyle>(&mut f.diagnostics)
|
|
||||||
.or_missing(&mut f.diagnostics, header.name.span, "style"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
styled(&self.body, ctx, self.style, |t, s| t.variant.style = s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `font.weight`: Set text with a given weight.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct FontWeightFunc {
|
|
||||||
body: Option<SyntaxModel>,
|
|
||||||
weight: Option<FontWeight>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
|
||||||
let body = body!(opt: body, ctx, f);
|
|
||||||
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.diagnostics)
|
|
||||||
.map(|Spanned { v: (weight, is_clamped), span }| {
|
|
||||||
if is_clamped {
|
|
||||||
warning!(
|
|
||||||
@f, span,
|
|
||||||
"weight should be between 100 and 900, clamped to {}",
|
|
||||||
weight.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
weight
|
|
||||||
})
|
|
||||||
.or_missing(&mut f.diagnostics, header.name.span, "weight");
|
|
||||||
|
|
||||||
FontWeightFunc { body, weight }
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `font.width`: Set text with a given width.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct FontWidthFunc {
|
|
||||||
body: Option<SyntaxModel>,
|
|
||||||
width: Option<FontWidth>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
|
||||||
let body = body!(opt: body, ctx, f);
|
|
||||||
let width = header.args.pos.get::<Spanned<(FontWidth, bool)>>(&mut f.diagnostics)
|
|
||||||
.map(|Spanned { v: (width, is_clamped), span }| {
|
|
||||||
if is_clamped {
|
|
||||||
warning!(
|
|
||||||
@f, span,
|
|
||||||
"width should be between 1 and 9, clamped to {}",
|
|
||||||
width.to_number(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
width
|
|
||||||
})
|
|
||||||
.or_missing(&mut f.diagnostics, header.name.span, "width");
|
|
||||||
|
|
||||||
FontWidthFunc { body, width }
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
styled(&self.body, ctx, self.width, |t, w| t.variant.width = w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `font.size`: Sets the font size.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct FontSizeFunc {
|
|
||||||
body: Option<SyntaxModel>,
|
|
||||||
size: Option<ScaleLength>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
|
||||||
FontSizeFunc {
|
|
||||||
body: body!(opt: body, ctx, f),
|
|
||||||
size: header.args.pos.get::<ScaleLength>(&mut f.diagnostics)
|
|
||||||
.or_missing(&mut f.diagnostics, header.name.span, "size")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
styled(&self.body, ctx, self.size, |t, s| {
|
|
||||||
match s {
|
|
||||||
ScaleLength::Absolute(length) => {
|
|
||||||
t.base_font_size = length.as_raw();
|
|
||||||
t.font_scale = 1.0;
|
|
||||||
}
|
|
||||||
ScaleLength::Scaled(scale) => t.font_scale = scale,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -16,11 +16,7 @@ pub fn std() -> Scope {
|
|||||||
std.add::<ValFunc>("val");
|
std.add::<ValFunc>("val");
|
||||||
|
|
||||||
// Font setup
|
// Font setup
|
||||||
std.add::<FontFamilyFunc>("font.family");
|
std.add::<FontFunc>("font");
|
||||||
std.add::<FontStyleFunc>("font.style");
|
|
||||||
std.add::<FontWeightFunc>("font.weight");
|
|
||||||
std.add::<FontWidthFunc>("font.width");
|
|
||||||
std.add::<FontSizeFunc>("font.size");
|
|
||||||
std.add_with_meta::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
|
std.add_with_meta::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
@ -40,8 +36,7 @@ pub fn std() -> Scope {
|
|||||||
std.add_with_meta::<SpacingFunc>("v", Some(Vertical));
|
std.add_with_meta::<SpacingFunc>("v", Some(Vertical));
|
||||||
|
|
||||||
// Page setup
|
// Page setup
|
||||||
std.add::<PageSizeFunc>("page.size");
|
std.add::<PageFunc>("page");
|
||||||
std.add::<PageMarginsFunc>("page.margins");
|
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,21 @@ use crate::paper::{Paper, PaperClass};
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
function! {
|
||||||
/// `page.size`: Set the size of pages.
|
/// `page`: Configure pages.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PageSizeFunc {
|
pub struct PageFunc {
|
||||||
paper: Option<Paper>,
|
paper: Option<Paper>,
|
||||||
extents: AxisMap<Length>,
|
extents: AxisMap<Length>,
|
||||||
|
padding: PaddingMap,
|
||||||
flip: bool,
|
flip: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
body!(nope: body, f);
|
body!(nope: body, f);
|
||||||
PageSizeFunc {
|
PageFunc {
|
||||||
paper: header.args.pos.get::<Paper>(&mut f.diagnostics),
|
paper: header.args.pos.get::<Paper>(&mut f.diagnostics),
|
||||||
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
|
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
|
||||||
|
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
|
||||||
flip: header.args.key.get::<bool>(&mut f.diagnostics, "flip").unwrap_or(false),
|
flip: header.args.key.get::<bool>(&mut f.diagnostics, "flip").unwrap_or(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,27 +40,8 @@ function! {
|
|||||||
style.dimensions.swap();
|
style.dimensions.swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![SetPageStyle(style)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `page.margins`: Sets the page margins.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct PageMarginsFunc {
|
|
||||||
padding: PaddingMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
|
||||||
body!(nope: body, f);
|
|
||||||
PageMarginsFunc {
|
|
||||||
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
let mut style = ctx.style.page;
|
|
||||||
self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins);
|
self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins);
|
||||||
|
|
||||||
vec![SetPageStyle(style)]
|
vec![SetPageStyle(style)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,6 +268,25 @@ impl Tuple {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract (and remove) the first matching value without removing and
|
||||||
|
/// generating diagnostics for all previous items that did not match.
|
||||||
|
pub fn get_first<V: Value>(&mut self, _: &mut Diagnostics) -> Option<V> {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < self.items.len() {
|
||||||
|
let expr = self.items[i].clone();
|
||||||
|
match V::parse(expr) {
|
||||||
|
Ok(output) => {
|
||||||
|
self.items.remove(i);
|
||||||
|
return Some(output)
|
||||||
|
}
|
||||||
|
Err(_) => {},
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract and return an iterator over all values that match and generate
|
/// Extract and return an iterator over all values that match and generate
|
||||||
/// diagnostics for all items that do not match.
|
/// diagnostics for all items that do not match.
|
||||||
pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics)
|
pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics)
|
||||||
|
@ -178,7 +178,7 @@ impl PaddingMap {
|
|||||||
pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap {
|
pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap {
|
||||||
let mut map = DedupMap::new();
|
let mut map = DedupMap::new();
|
||||||
|
|
||||||
let all = args.pos.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics);
|
let all = args.key.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics, "margins");
|
||||||
if let Some(Spanned { v, span }) = all {
|
if let Some(Spanned { v, span }) = all {
|
||||||
map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span });
|
map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span });
|
||||||
}
|
}
|
||||||
|
@ -82,13 +82,22 @@ pub enum FuncArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extra methods on [`Options`](Option) used for argument parsing.
|
/// Extra methods on [`Options`](Option) used for argument parsing.
|
||||||
pub trait OptionExt: Sized {
|
pub trait OptionExt<T>: Sized {
|
||||||
|
/// Calls `f` with `val` if this is `Some(val)`.
|
||||||
|
fn with(self, f: impl FnOnce(T));
|
||||||
|
|
||||||
/// Add an error about a missing argument `arg` with the given span if the
|
/// Add an error about a missing argument `arg` with the given span if the
|
||||||
/// option is `None`.
|
/// option is `None`.
|
||||||
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self;
|
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> OptionExt for Option<T> {
|
impl<T> OptionExt<T> for Option<T> {
|
||||||
|
fn with(self, f: impl FnOnce(T)) {
|
||||||
|
if let Some(val) = self {
|
||||||
|
f(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self {
|
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self {
|
||||||
if self.is_none() {
|
if self.is_none() {
|
||||||
diagnostics.push(error!(span, "missing argument: {}", arg));
|
diagnostics.push(error!(span, "missing argument: {}", arg));
|
||||||
|
@ -143,22 +143,21 @@ impl Value for FontStyle {
|
|||||||
|
|
||||||
/// The additional boolean specifies whether a number was clamped into the range
|
/// The additional boolean specifies whether a number was clamped into the range
|
||||||
/// 100 - 900 to make it a valid font weight.
|
/// 100 - 900 to make it a valid font weight.
|
||||||
impl Value for (FontWeight, bool) {
|
impl Value for FontWeight {
|
||||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||||
match expr.v {
|
match expr.v {
|
||||||
Expr::Number(weight) => {
|
Expr::Number(weight) => {
|
||||||
let weight = weight.round();
|
let weight = weight.round();
|
||||||
if weight >= 100.0 && weight <= 900.0 {
|
if weight >= 100.0 && weight <= 900.0 {
|
||||||
Ok((FontWeight(weight as u16), false))
|
Ok(FontWeight(weight as u16))
|
||||||
} else {
|
} else {
|
||||||
let clamped = weight.min(900.0).max(100.0);
|
let clamped = weight.min(900.0).max(100.0);
|
||||||
Ok((FontWeight(clamped as u16), true))
|
Ok(FontWeight(clamped as u16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Ident(id) => {
|
Expr::Ident(id) => {
|
||||||
FontWeight::from_name(id.as_str())
|
FontWeight::from_name(id.as_str())
|
||||||
.ok_or_else(|| error!("invalid font weight"))
|
.ok_or_else(|| error!("invalid font weight"))
|
||||||
.map(|weight| (weight, false))
|
|
||||||
}
|
}
|
||||||
other => Err(
|
other => Err(
|
||||||
error!("expected identifier or number, found {}", other.name())
|
error!("expected identifier or number, found {}", other.name())
|
||||||
@ -169,22 +168,21 @@ impl Value for (FontWeight, bool) {
|
|||||||
|
|
||||||
/// The additional boolean specifies whether a number was clamped into the range
|
/// The additional boolean specifies whether a number was clamped into the range
|
||||||
/// 1 - 9 to make it a valid font width.
|
/// 1 - 9 to make it a valid font width.
|
||||||
impl Value for (FontWidth, bool) {
|
impl Value for FontWidth {
|
||||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||||
match expr.v {
|
match expr.v {
|
||||||
Expr::Number(width) => {
|
Expr::Number(width) => {
|
||||||
let width = width.round();
|
let width = width.round();
|
||||||
if width >= 1.0 && width <= 9.0 {
|
if width >= 1.0 && width <= 9.0 {
|
||||||
Ok((FontWidth::new(width as u16).unwrap(), false))
|
Ok(FontWidth::new(width as u16).unwrap())
|
||||||
} else {
|
} else {
|
||||||
let clamped = width.min(9.0).max(1.0);
|
let clamped = width.min(9.0).max(1.0);
|
||||||
Ok((FontWidth::new(clamped as u16).unwrap(), true))
|
Ok(FontWidth::new(clamped as u16).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Ident(id) => {
|
Expr::Ident(id) => {
|
||||||
FontWidth::from_name(id.as_str())
|
FontWidth::from_name(id.as_str())
|
||||||
.ok_or_else(|| error!("invalid font width"))
|
.ok_or_else(|| error!("invalid font width"))
|
||||||
.map(|width| (width, false))
|
|
||||||
}
|
}
|
||||||
other => Err(
|
other => Err(
|
||||||
error!("expected identifier or number, found {}", other.name())
|
error!("expected identifier or number, found {}", other.name())
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
[page.size: width=450pt, height=300pt]
|
[page: width=450pt, height=300pt, margins=1cm]
|
||||||
[page.margins: 1cm]
|
|
||||||
|
|
||||||
[box][
|
[box][
|
||||||
*Technische Universität Berlin* [n]
|
*Technische Universität Berlin* [n]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
[page.size: w=5cm, h=5cm]
|
[page: w=5cm, h=5cm, margins=0cm]
|
||||||
[page.margins: 0cm]
|
|
||||||
|
|
||||||
// Test 1
|
// Test 1
|
||||||
[box: w=1, h=1, debug=false][
|
[box: w=1, h=1, debug=false][
|
||||||
|
Loading…
x
Reference in New Issue
Block a user