mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Set Rules Episode II: Attack of the properties
This commit is contained in:
parent
26bdc1f0f6
commit
40b87d4066
@ -19,17 +19,18 @@ debug = 0
|
||||
opt-level = 2
|
||||
|
||||
[dependencies]
|
||||
fxhash = "0.2.1"
|
||||
fxhash = "0.2"
|
||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||
itertools = "0.10"
|
||||
miniz_oxide = "0.4"
|
||||
once_cell = "1"
|
||||
pdf-writer = "0.4"
|
||||
rustybuzz = "0.4"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] }
|
||||
ttf-parser = "0.12"
|
||||
unicode-bidi = "0.3.5"
|
||||
unicode-segmentation = "1.8"
|
||||
unicode-segmentation = "1"
|
||||
unicode-xid = "0.2"
|
||||
usvg = { version = "0.19", default-features = false, features = ["text"] }
|
||||
xi-unicode = "0.3"
|
||||
|
@ -6,6 +6,8 @@ mod array;
|
||||
mod dict;
|
||||
#[macro_use]
|
||||
mod value;
|
||||
#[macro_use]
|
||||
mod styles;
|
||||
mod capture;
|
||||
mod function;
|
||||
mod node;
|
||||
@ -18,6 +20,7 @@ pub use dict::*;
|
||||
pub use function::*;
|
||||
pub use node::*;
|
||||
pub use scope::*;
|
||||
pub use styles::*;
|
||||
pub use value::*;
|
||||
|
||||
use std::cell::RefMut;
|
||||
@ -31,13 +34,12 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||
use crate::geom::{Angle, Fractional, Length, Relative, Spec};
|
||||
use crate::image::ImageStore;
|
||||
use crate::library::{GridNode, TrackSizing};
|
||||
use crate::library::{GridNode, TextNode, TrackSizing};
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::style::Style;
|
||||
use crate::syntax::ast::*;
|
||||
use crate::syntax::{Span, Spanned};
|
||||
use crate::util::{BoolExt, EcoString, RefMutExt};
|
||||
use crate::util::{EcoString, RefMutExt};
|
||||
use crate::Context;
|
||||
|
||||
/// Evaluate a parsed source file into a module.
|
||||
@ -70,8 +72,8 @@ pub struct EvalContext<'a> {
|
||||
pub modules: HashMap<SourceId, Module>,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// The active style.
|
||||
pub style: Style,
|
||||
/// The active styles.
|
||||
pub styles: Styles,
|
||||
}
|
||||
|
||||
impl<'a> EvalContext<'a> {
|
||||
@ -84,7 +86,7 @@ impl<'a> EvalContext<'a> {
|
||||
route: vec![source],
|
||||
modules: HashMap::new(),
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
style: ctx.style.clone(),
|
||||
styles: Styles::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,14 +160,10 @@ impl Eval for Markup {
|
||||
type Output = Node;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let snapshot = ctx.style.clone();
|
||||
|
||||
let mut result = Node::new();
|
||||
for piece in self.nodes() {
|
||||
result += piece.eval(ctx)?;
|
||||
}
|
||||
|
||||
ctx.style = snapshot;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@ -179,11 +177,11 @@ impl Eval for MarkupNode {
|
||||
Self::Linebreak => Node::Linebreak,
|
||||
Self::Parbreak => Node::Parbreak,
|
||||
Self::Strong => {
|
||||
ctx.style.text_mut().strong.flip();
|
||||
ctx.styles.set(TextNode::STRONG, !ctx.styles.get(TextNode::STRONG));
|
||||
Node::new()
|
||||
}
|
||||
Self::Emph => {
|
||||
ctx.style.text_mut().emph.flip();
|
||||
ctx.styles.set(TextNode::EMPH, !ctx.styles.get(TextNode::EMPH));
|
||||
Node::new()
|
||||
}
|
||||
Self::Text(text) => Node::Text(text.clone()),
|
||||
|
@ -9,6 +9,7 @@ use crate::geom::SpecAxis;
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::library::{
|
||||
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing,
|
||||
TextNode,
|
||||
};
|
||||
use crate::util::EcoString;
|
||||
|
||||
@ -158,10 +159,10 @@ impl NodePacker {
|
||||
fn walk(&mut self, node: Node) {
|
||||
match node {
|
||||
Node::Space => {
|
||||
self.push_inline(ParChild::Text(' '.into()));
|
||||
self.push_inline(ParChild::Text(TextNode(' '.into())));
|
||||
}
|
||||
Node::Linebreak => {
|
||||
self.push_inline(ParChild::Text('\n'.into()));
|
||||
self.push_inline(ParChild::Text(TextNode('\n'.into())));
|
||||
}
|
||||
Node::Parbreak => {
|
||||
self.parbreak();
|
||||
@ -170,7 +171,7 @@ impl NodePacker {
|
||||
self.pagebreak();
|
||||
}
|
||||
Node::Text(text) => {
|
||||
self.push_inline(ParChild::Text(text));
|
||||
self.push_inline(ParChild::Text(TextNode(text)));
|
||||
}
|
||||
Node::Spacing(axis, amount) => match axis {
|
||||
SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)),
|
||||
|
124
src/eval/styles.rs
Normal file
124
src/eval/styles.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::rc::Rc;
|
||||
|
||||
// Possible optimizations:
|
||||
// - Ref-count map for cheaper cloning and smaller footprint
|
||||
// - Store map in `Option` to make empty maps non-allocating
|
||||
// - Store small properties inline
|
||||
|
||||
/// A map of style properties.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Styles {
|
||||
map: HashMap<TypeId, Rc<dyn Any>>,
|
||||
}
|
||||
|
||||
impl Styles {
|
||||
/// Create a new, empty style map.
|
||||
pub fn new() -> Self {
|
||||
Self { map: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Set the value for a style property.
|
||||
pub fn set<P: Property>(&mut self, _: P, value: P::Value) {
|
||||
self.map.insert(TypeId::of::<P>(), Rc::new(value));
|
||||
}
|
||||
|
||||
/// Get the value of a copyable style property.
|
||||
///
|
||||
/// Returns the property's default value if the map does not contain an
|
||||
/// entry for it.
|
||||
pub fn get<P: Property>(&self, key: P) -> P::Value
|
||||
where
|
||||
P::Value: Copy,
|
||||
{
|
||||
self.get_inner(key).copied().unwrap_or_else(P::default)
|
||||
}
|
||||
|
||||
/// Get a reference to a style property.
|
||||
///
|
||||
/// Returns a reference to the property's default value if the map does not
|
||||
/// contain an entry for it.
|
||||
pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
|
||||
self.get_inner(key).unwrap_or_else(|| P::default_ref())
|
||||
}
|
||||
|
||||
/// Get a reference to a style directly in this map.
|
||||
fn get_inner<P: Property>(&self, _: P) -> Option<&P::Value> {
|
||||
self.map
|
||||
.get(&TypeId::of::<P>())
|
||||
.and_then(|boxed| boxed.downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Styles {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// TODO(set): Better debug printing possible?
|
||||
f.pad("Styles(..)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Stylistic property keys.
|
||||
pub trait Property: 'static {
|
||||
/// The type of this property, for example, this could be
|
||||
/// [`Length`](crate::geom::Length) for a `WIDTH` property.
|
||||
type Value;
|
||||
|
||||
/// The default value of the property.
|
||||
fn default() -> Self::Value;
|
||||
|
||||
/// A static reference to the default value of the property.
|
||||
///
|
||||
/// This is automatically implemented through lazy-initialization in the
|
||||
/// `properties!` macro. This way, expensive defaults don't need to be
|
||||
/// recreated all the time.
|
||||
fn default_ref() -> &'static Self::Value;
|
||||
}
|
||||
|
||||
macro_rules! set {
|
||||
($ctx:expr, $target:expr => $source:expr) => {
|
||||
if let Some(v) = $source {
|
||||
$ctx.styles.set($target, v);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! properties {
|
||||
($node:ty, $(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $type:ty = $default:expr
|
||||
),* $(,)?) => {
|
||||
// TODO(set): Fix possible name clash.
|
||||
mod properties {
|
||||
use std::marker::PhantomData;
|
||||
use super::*;
|
||||
|
||||
$(#[allow(non_snake_case)] mod $name {
|
||||
use $crate::eval::Property;
|
||||
use once_cell::sync::Lazy;
|
||||
use super::*;
|
||||
|
||||
pub struct Key<T>(pub PhantomData<T>);
|
||||
|
||||
impl Property for Key<$type> {
|
||||
type Value = $type;
|
||||
|
||||
fn default() -> Self::Value {
|
||||
$default
|
||||
}
|
||||
|
||||
fn default_ref() -> &'static Self::Value {
|
||||
static LAZY: Lazy<$type> = Lazy::new(|| $default);
|
||||
&*LAZY
|
||||
}
|
||||
}
|
||||
})*
|
||||
|
||||
impl $node {
|
||||
$($(#[$attr])* pub const $name: $name::Key<$type>
|
||||
= $name::Key(PhantomData);)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -171,14 +171,19 @@ pub struct Text {
|
||||
pub face_id: FaceId,
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
/// The width of the text run.
|
||||
pub width: Length,
|
||||
/// Glyph color.
|
||||
pub fill: Paint,
|
||||
/// The glyphs.
|
||||
pub glyphs: Vec<Glyph>,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
/// The width of the text run.
|
||||
pub fn width(&self) -> Length {
|
||||
self.glyphs.iter().map(|g| g.x_advance.to_length(self.size)).sum()
|
||||
}
|
||||
}
|
||||
|
||||
/// A glyph in a run of shaped text.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Glyph {
|
||||
|
26
src/lib.rs
26
src/lib.rs
@ -44,13 +44,12 @@ pub mod library;
|
||||
pub mod loading;
|
||||
pub mod parse;
|
||||
pub mod source;
|
||||
pub mod style;
|
||||
pub mod syntax;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::TypResult;
|
||||
use crate::eval::{Module, Scope};
|
||||
use crate::eval::{Module, Scope, Styles};
|
||||
use crate::font::FontStore;
|
||||
use crate::frame::Frame;
|
||||
use crate::image::ImageStore;
|
||||
@ -59,7 +58,6 @@ use crate::layout::{EvictionPolicy, LayoutCache};
|
||||
use crate::library::DocumentNode;
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::style::Style;
|
||||
|
||||
/// The core context which holds the loader, configuration and cached artifacts.
|
||||
pub struct Context {
|
||||
@ -76,8 +74,8 @@ pub struct Context {
|
||||
pub layouts: LayoutCache,
|
||||
/// The standard library scope.
|
||||
std: Scope,
|
||||
/// The default style.
|
||||
style: Style,
|
||||
/// The default styles.
|
||||
styles: Styles,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@ -96,9 +94,9 @@ impl Context {
|
||||
&self.std
|
||||
}
|
||||
|
||||
/// A read-only reference to the style.
|
||||
pub fn style(&self) -> &Style {
|
||||
&self.style
|
||||
/// A read-only reference to the styles.
|
||||
pub fn styles(&self) -> &Styles {
|
||||
&self.styles
|
||||
}
|
||||
|
||||
/// Evaluate a source file and return the resulting module.
|
||||
@ -136,7 +134,7 @@ impl Context {
|
||||
/// This struct is created by [`Context::builder`].
|
||||
pub struct ContextBuilder {
|
||||
std: Option<Scope>,
|
||||
style: Option<Style>,
|
||||
styles: Option<Styles>,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
policy: EvictionPolicy,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
@ -151,9 +149,9 @@ impl ContextBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// The initial properties for page size, font selection and so on.
|
||||
pub fn style(mut self, style: Style) -> Self {
|
||||
self.style = Some(style);
|
||||
/// The default properties for page size, font selection and so on.
|
||||
pub fn styles(mut self, styles: Styles) -> Self {
|
||||
self.styles = Some(styles);
|
||||
self
|
||||
}
|
||||
|
||||
@ -185,7 +183,7 @@ impl ContextBuilder {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
layouts: LayoutCache::new(self.policy, self.max_size),
|
||||
std: self.std.unwrap_or_else(library::new),
|
||||
style: self.style.unwrap_or_default(),
|
||||
styles: self.styles.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +192,7 @@ impl Default for ContextBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
std: None,
|
||||
style: None,
|
||||
styles: None,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
policy: EvictionPolicy::default(),
|
||||
#[cfg(feature = "layout-cache")]
|
||||
|
@ -2,18 +2,6 @@ use super::prelude::*;
|
||||
|
||||
/// `align`: Configure the alignment along the layouting axes.
|
||||
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
castable! {
|
||||
Spec<Option<Align>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<Align> => aligns.map(Some),
|
||||
|
||||
}
|
||||
|
||||
let aligns = args.expect::<Spec<_>>("alignment")?;
|
||||
let body = args.expect::<Node>("body")?;
|
||||
|
||||
@ -62,3 +50,19 @@ impl Layout for AlignNode {
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Align: "alignment",
|
||||
}
|
||||
|
||||
castable! {
|
||||
Spec<Option<Align>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: Align => {
|
||||
let mut aligns = Spec::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<Align> => aligns.map(Some),
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,11 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
||||
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let url = args.expect::<EcoString>("url")?;
|
||||
let body = args.find().unwrap_or_else(|| {
|
||||
Node::Text(url.trim_start_matches("mailto:").trim_start_matches("tel:").into())
|
||||
let mut text = url.as_str();
|
||||
for prefix in ["mailto:", "tel:"] {
|
||||
text = text.trim_start_matches(prefix);
|
||||
}
|
||||
Node::Text(text.into())
|
||||
});
|
||||
Ok(Value::Node(body.decorate(Decoration::Link(url))))
|
||||
}
|
||||
@ -120,7 +124,7 @@ impl LineDecoration {
|
||||
let extent = self.extent.resolve(text.size);
|
||||
|
||||
let subpos = Point::new(pos.x - extent, pos.y + offset);
|
||||
let target = Point::new(text.width + 2.0 * extent, Length::zero());
|
||||
let target = Point::new(text.width() + 2.0 * extent, Length::zero());
|
||||
let shape = Shape::stroked(Geometry::Line(target), stroke);
|
||||
frame.push(subpos, Element::Shape(shape));
|
||||
}
|
||||
|
@ -39,8 +39,11 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
row_gutter.unwrap_or(base_gutter),
|
||||
);
|
||||
|
||||
let children = args.all().map(Node::into_block).collect();
|
||||
Ok(Value::block(GridNode { tracks, gutter, children }))
|
||||
Ok(Value::block(GridNode {
|
||||
tracks,
|
||||
gutter,
|
||||
children: args.all().map(Node::into_block).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// A node that arranges its children in a grid.
|
||||
|
@ -26,7 +26,7 @@ mod prelude {
|
||||
pub use std::rc::Rc;
|
||||
|
||||
pub use crate::diag::{At, TypResult};
|
||||
pub use crate::eval::{Args, EvalContext, Node, Smart, Value};
|
||||
pub use crate::eval::{Args, EvalContext, Node, Property, Smart, Styles, Value};
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
pub use crate::layout::*;
|
||||
@ -54,7 +54,6 @@ pub use utility::*;
|
||||
|
||||
use crate::eval::{Scope, Value};
|
||||
use crate::geom::*;
|
||||
use crate::style::FontFamily;
|
||||
|
||||
/// Construct a scope containing all standard library definitions.
|
||||
pub fn new() -> Scope {
|
||||
@ -139,15 +138,6 @@ dynamic! {
|
||||
Dir: "direction",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Align: "alignment",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
FontFamily: "font family",
|
||||
Value::Str(string) => Self::Named(string.to_lowercase()),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Paint,
|
||||
Expected: "color",
|
||||
|
@ -1,70 +1,43 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::prelude::*;
|
||||
use super::PadNode;
|
||||
use crate::style::{Paper, PaperClass};
|
||||
|
||||
/// `page`: Configure pages.
|
||||
pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
castable! {
|
||||
Paper,
|
||||
Expected: "string",
|
||||
Value::Str(string) => Paper::from_name(&string).ok_or("unknown paper")?,
|
||||
Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
|
||||
}
|
||||
|
||||
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
||||
ctx.styles.set(PageNode::CLASS, paper.class());
|
||||
ctx.styles.set(PageNode::WIDTH, Smart::Custom(paper.width()));
|
||||
ctx.styles.set(PageNode::HEIGHT, Smart::Custom(paper.height()));
|
||||
}
|
||||
|
||||
if let Some(width) = args.named("width")? {
|
||||
ctx.styles.set(PageNode::CLASS, PaperClass::Custom);
|
||||
ctx.styles.set(PageNode::WIDTH, width);
|
||||
}
|
||||
|
||||
if let Some(height) = args.named("height")? {
|
||||
ctx.styles.set(PageNode::CLASS, PaperClass::Custom);
|
||||
ctx.styles.set(PageNode::HEIGHT, height);
|
||||
}
|
||||
|
||||
let paper = args.named::<Paper>("paper")?.or_else(|| args.find());
|
||||
let width = args.named::<Smart<_>>("width")?;
|
||||
let height = args.named::<Smart<_>>("height")?;
|
||||
let flip = args.named("flip")?;
|
||||
let margins = args.named("margins")?;
|
||||
let left = args.named("left")?;
|
||||
let top = args.named("top")?;
|
||||
let right = args.named("right")?;
|
||||
let bottom = args.named("bottom")?;
|
||||
let fill = args.named("fill")?;
|
||||
|
||||
let page = ctx.style.page_mut();
|
||||
|
||||
if let Some(paper) = paper {
|
||||
page.class = paper.class();
|
||||
page.size = paper.size();
|
||||
}
|
||||
|
||||
if let Some(width) = width {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.x = width.unwrap_or(Length::inf());
|
||||
}
|
||||
|
||||
if let Some(height) = height {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.y = height.unwrap_or(Length::inf());
|
||||
}
|
||||
|
||||
if flip.unwrap_or(false) {
|
||||
std::mem::swap(&mut page.size.x, &mut page.size.y);
|
||||
}
|
||||
|
||||
if let Some(margins) = margins {
|
||||
page.margins = Sides::splat(margins);
|
||||
}
|
||||
|
||||
if let Some(left) = left {
|
||||
page.margins.left = left;
|
||||
}
|
||||
|
||||
if let Some(top) = top {
|
||||
page.margins.top = top;
|
||||
}
|
||||
|
||||
if let Some(right) = right {
|
||||
page.margins.right = right;
|
||||
}
|
||||
|
||||
if let Some(bottom) = bottom {
|
||||
page.margins.bottom = bottom;
|
||||
}
|
||||
|
||||
if let Some(fill) = fill {
|
||||
page.fill = fill;
|
||||
}
|
||||
set!(ctx, PageNode::FLIPPED => args.named("flipped")?);
|
||||
set!(ctx, PageNode::LEFT => args.named("left")?.or(margins));
|
||||
set!(ctx, PageNode::TOP => args.named("top")?.or(margins));
|
||||
set!(ctx, PageNode::RIGHT => args.named("right")?.or(margins));
|
||||
set!(ctx, PageNode::BOTTOM => args.named("bottom")?.or(margins));
|
||||
set!(ctx, PageNode::FILL => args.named("fill")?);
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
@ -74,29 +47,69 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
||||
Ok(Value::Node(Node::Pagebreak))
|
||||
}
|
||||
|
||||
/// Layouts its children onto one or multiple pages.
|
||||
/// Layouts its child onto one or multiple pages.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PageNode(pub PackedNode);
|
||||
|
||||
properties! {
|
||||
PageNode,
|
||||
|
||||
/// The unflipped width of the page.
|
||||
WIDTH: Smart<Length> = Smart::Custom(Paper::default().width()),
|
||||
/// The unflipped height of the page.
|
||||
HEIGHT: Smart<Length> = Smart::Custom(Paper::default().height()),
|
||||
/// The class of paper. Defines the default margins.
|
||||
CLASS: PaperClass = Paper::default().class(),
|
||||
/// Whether the page is flipped into landscape orientation.
|
||||
FLIPPED: bool = false,
|
||||
/// The left margin.
|
||||
LEFT: Smart<Linear> = Smart::Auto,
|
||||
/// The right margin.
|
||||
RIGHT: Smart<Linear> = Smart::Auto,
|
||||
/// The top margin.
|
||||
TOP: Smart<Linear> = Smart::Auto,
|
||||
/// The bottom margin.
|
||||
BOTTOM: Smart<Linear> = Smart::Auto,
|
||||
/// The page's background color.
|
||||
FILL: Option<Paint> = None,
|
||||
}
|
||||
|
||||
impl PageNode {
|
||||
/// Layout the page run into a sequence of frames, one per page.
|
||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||
// TODO(set): Get style from styles.
|
||||
let style = crate::style::PageStyle::default();
|
||||
// TODO(set): Take styles as parameter.
|
||||
let styles = Styles::new();
|
||||
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let expand = style.size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(style.size, style.size, expand);
|
||||
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||
let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
||||
let mut size = Size::new(width, height);
|
||||
if styles.get(Self::FLIPPED) {
|
||||
std::mem::swap(&mut size.x, &mut size.y);
|
||||
}
|
||||
|
||||
// Determine the margins.
|
||||
let class = styles.get(Self::CLASS);
|
||||
let default = class.default_margins();
|
||||
let padding = Sides {
|
||||
left: styles.get(Self::LEFT).unwrap_or(default.left),
|
||||
right: styles.get(Self::RIGHT).unwrap_or(default.right),
|
||||
top: styles.get(Self::TOP).unwrap_or(default.top),
|
||||
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
||||
};
|
||||
|
||||
// Pad the child.
|
||||
let padded = PadNode { child: self.0.clone(), padding }.pack();
|
||||
|
||||
// Layout the child.
|
||||
let padding = style.margins();
|
||||
let padded = PadNode { child: self.0.clone(), padding }.pack();
|
||||
let expand = size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(size, size, expand);
|
||||
let mut frames: Vec<_> =
|
||||
padded.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
||||
|
||||
// Add background fill if requested.
|
||||
if let Some(fill) = style.fill {
|
||||
if let Some(fill) = styles.get(Self::FILL) {
|
||||
for frame in &mut frames {
|
||||
let shape = Shape::filled(Geometry::Rect(frame.size), fill);
|
||||
Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||
@ -106,3 +119,256 @@ impl PageNode {
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
||||
/// Specification of a paper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Paper {
|
||||
/// The broad class this paper belongs to.
|
||||
class: PaperClass,
|
||||
/// The width of the paper in millimeters.
|
||||
width: f64,
|
||||
/// The height of the paper in millimeters.
|
||||
height: f64,
|
||||
}
|
||||
|
||||
impl Paper {
|
||||
/// The class of the paper.
|
||||
pub fn class(self) -> PaperClass {
|
||||
self.class
|
||||
}
|
||||
|
||||
/// The width of the paper.
|
||||
pub fn width(self) -> Length {
|
||||
Length::mm(self.width)
|
||||
}
|
||||
|
||||
/// The height of the paper.
|
||||
pub fn height(self) -> Length {
|
||||
Length::mm(self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Paper {
|
||||
fn default() -> Self {
|
||||
Paper::A4
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines default margins for a class of related papers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PaperClass {
|
||||
Custom,
|
||||
Base,
|
||||
US,
|
||||
Newspaper,
|
||||
Book,
|
||||
}
|
||||
|
||||
impl PaperClass {
|
||||
/// The default margins for this page class.
|
||||
fn default_margins(self) -> Sides<Linear> {
|
||||
let f = |r| Relative::new(r).into();
|
||||
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
|
||||
match self {
|
||||
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
|
||||
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
|
||||
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines paper constants and a paper parsing implementation.
|
||||
macro_rules! papers {
|
||||
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
|
||||
/// Predefined papers.
|
||||
///
|
||||
/// Each paper is parsable from its name in kebab-case.
|
||||
impl Paper {
|
||||
$(pub const $var: Self = Self {
|
||||
class: PaperClass::$class,
|
||||
width: $width,
|
||||
height: $height,
|
||||
};)*
|
||||
}
|
||||
|
||||
impl FromStr for Paper {
|
||||
type Err = ParsePaperError;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
match name.to_lowercase().as_str() {
|
||||
$($($pats)* => Ok(Self::$var),)*
|
||||
_ => Err(ParsePaperError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error when parsing a [`Paper`] from a string fails.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct ParsePaperError;
|
||||
|
||||
impl Display for ParsePaperError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("invalid paper name")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParsePaperError {}
|
||||
};
|
||||
}
|
||||
|
||||
// All paper sizes in mm.
|
||||
//
|
||||
// Resources:
|
||||
// - https://papersizes.io/
|
||||
// - https://en.wikipedia.org/wiki/Paper_size
|
||||
// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm
|
||||
// - https://vintagepaper.co/blogs/news/traditional-paper-sizes
|
||||
papers! {
|
||||
// ---------------------------------------------------------------------- //
|
||||
// ISO 216 A Series
|
||||
(A0: Base, 841.0, 1189.0, "a0")
|
||||
(A1: Base, 594.0, 841.0, "a1")
|
||||
(A2: Base, 420.0, 594.0, "a2")
|
||||
(A3: Base, 297.0, 420.0, "a3")
|
||||
(A4: Base, 210.0, 297.0, "a4")
|
||||
(A5: Base, 148.0, 210.0, "a5")
|
||||
(A6: Book, 105.0, 148.0, "a6")
|
||||
(A7: Base, 74.0, 105.0, "a7")
|
||||
(A8: Base, 52.0, 74.0, "a8")
|
||||
(A9: Base, 37.0, 52.0, "a9")
|
||||
(A10: Base, 26.0, 37.0, "a10")
|
||||
(A11: Base, 18.0, 26.0, "a11")
|
||||
|
||||
// ISO 216 B Series
|
||||
(ISO_B1: Base, 707.0, 1000.0, "iso-b1")
|
||||
(ISO_B2: Base, 500.0, 707.0, "iso-b2")
|
||||
(ISO_B3: Base, 353.0, 500.0, "iso-b3")
|
||||
(ISO_B4: Base, 250.0, 353.0, "iso-b4")
|
||||
(ISO_B5: Book, 176.0, 250.0, "iso-b5")
|
||||
(ISO_B6: Book, 125.0, 176.0, "iso-b6")
|
||||
(ISO_B7: Base, 88.0, 125.0, "iso-b7")
|
||||
(ISO_B8: Base, 62.0, 88.0, "iso-b8")
|
||||
|
||||
// ISO 216 C Series
|
||||
(ISO_C3: Base, 324.0, 458.0, "iso-c3")
|
||||
(ISO_C4: Base, 229.0, 324.0, "iso-c4")
|
||||
(ISO_C5: Base, 162.0, 229.0, "iso-c5")
|
||||
(ISO_C6: Base, 114.0, 162.0, "iso-c6")
|
||||
(ISO_C7: Base, 81.0, 114.0, "iso-c7")
|
||||
(ISO_C8: Base, 57.0, 81.0, "iso-c8")
|
||||
|
||||
// DIN D Series (extension to ISO)
|
||||
(DIN_D3: Base, 272.0, 385.0, "din-d3")
|
||||
(DIN_D4: Base, 192.0, 272.0, "din-d4")
|
||||
(DIN_D5: Base, 136.0, 192.0, "din-d5")
|
||||
(DIN_D6: Base, 96.0, 136.0, "din-d6")
|
||||
(DIN_D7: Base, 68.0, 96.0, "din-d7")
|
||||
(DIN_D8: Base, 48.0, 68.0, "din-d8")
|
||||
|
||||
// SIS (used in academia)
|
||||
(SIS_G5: Base, 169.0, 239.0, "sis-g5")
|
||||
(SIS_E5: Base, 115.0, 220.0, "sis-e5")
|
||||
|
||||
// ANSI Extensions
|
||||
(ANSI_A: Base, 216.0, 279.0, "ansi-a")
|
||||
(ANSI_B: Base, 279.0, 432.0, "ansi-b")
|
||||
(ANSI_C: Base, 432.0, 559.0, "ansi-c")
|
||||
(ANSI_D: Base, 559.0, 864.0, "ansi-d")
|
||||
(ANSI_E: Base, 864.0, 1118.0, "ansi-e")
|
||||
|
||||
// ANSI Architectural Paper
|
||||
(ARCH_A: Base, 229.0, 305.0, "arch-a")
|
||||
(ARCH_B: Base, 305.0, 457.0, "arch-b")
|
||||
(ARCH_C: Base, 457.0, 610.0, "arch-c")
|
||||
(ARCH_D: Base, 610.0, 914.0, "arch-d")
|
||||
(ARCH_E1: Base, 762.0, 1067.0, "arch-e1")
|
||||
(ARCH_E: Base, 914.0, 1219.0, "arch-e")
|
||||
|
||||
// JIS B Series
|
||||
(JIS_B0: Base, 1030.0, 1456.0, "jis-b0")
|
||||
(JIS_B1: Base, 728.0, 1030.0, "jis-b1")
|
||||
(JIS_B2: Base, 515.0, 728.0, "jis-b2")
|
||||
(JIS_B3: Base, 364.0, 515.0, "jis-b3")
|
||||
(JIS_B4: Base, 257.0, 364.0, "jis-b4")
|
||||
(JIS_B5: Base, 182.0, 257.0, "jis-b5")
|
||||
(JIS_B6: Base, 128.0, 182.0, "jis-b6")
|
||||
(JIS_B7: Base, 91.0, 128.0, "jis-b7")
|
||||
(JIS_B8: Base, 64.0, 91.0, "jis-b8")
|
||||
(JIS_B9: Base, 45.0, 64.0, "jis-b9")
|
||||
(JIS_B10: Base, 32.0, 45.0, "jis-b10")
|
||||
(JIS_B11: Base, 22.0, 32.0, "jis-b11")
|
||||
|
||||
// SAC D Series
|
||||
(SAC_D0: Base, 764.0, 1064.0, "sac-d0")
|
||||
(SAC_D1: Base, 532.0, 760.0, "sac-d1")
|
||||
(SAC_D2: Base, 380.0, 528.0, "sac-d2")
|
||||
(SAC_D3: Base, 264.0, 376.0, "sac-d3")
|
||||
(SAC_D4: Base, 188.0, 260.0, "sac-d4")
|
||||
(SAC_D5: Base, 130.0, 184.0, "sac-d5")
|
||||
(SAC_D6: Base, 92.0, 126.0, "sac-d6")
|
||||
|
||||
// ISO 7810 ID
|
||||
(ISO_ID_1: Base, 85.6, 53.98, "iso-id-1")
|
||||
(ISO_ID_2: Base, 74.0, 105.0, "iso-id-2")
|
||||
(ISO_ID_3: Base, 88.0, 125.0, "iso-id-3")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Asia
|
||||
(ASIA_F4: Base, 210.0, 330.0, "asia-f4")
|
||||
|
||||
// Japan
|
||||
(JP_SHIROKU_BAN_4: Base, 264.0, 379.0, "jp-shiroku-ban-4")
|
||||
(JP_SHIROKU_BAN_5: Base, 189.0, 262.0, "jp-shiroku-ban-5")
|
||||
(JP_SHIROKU_BAN_6: Base, 127.0, 188.0, "jp-shiroku-ban-6")
|
||||
(JP_KIKU_4: Base, 227.0, 306.0, "jp-kiku-4")
|
||||
(JP_KIKU_5: Base, 151.0, 227.0, "jp-kiku-5")
|
||||
(JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card")
|
||||
|
||||
// China
|
||||
(CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card")
|
||||
|
||||
// Europe
|
||||
(EU_BUSINESS_CARD: Base, 85.0, 55.0, "eu-business-card")
|
||||
|
||||
// French Traditional (AFNOR)
|
||||
(FR_TELLIERE: Base, 340.0, 440.0, "fr-tellière")
|
||||
(FR_COURONNE_ECRITURE: Base, 360.0, 460.0, "fr-couronne-écriture")
|
||||
(FR_COURONNE_EDITION: Base, 370.0, 470.0, "fr-couronne-édition")
|
||||
(FR_RAISIN: Base, 500.0, 650.0, "fr-raisin")
|
||||
(FR_CARRE: Base, 450.0, 560.0, "fr-carré")
|
||||
(FR_JESUS: Base, 560.0, 760.0, "fr-jésus")
|
||||
|
||||
// United Kingdom Imperial
|
||||
(UK_BRIEF: Base, 406.4, 342.9, "uk-brief")
|
||||
(UK_DRAFT: Base, 254.0, 406.4, "uk-draft")
|
||||
(UK_FOOLSCAP: Base, 203.2, 330.2, "uk-foolscap")
|
||||
(UK_QUARTO: Base, 203.2, 254.0, "uk-quarto")
|
||||
(UK_CROWN: Base, 508.0, 381.0, "uk-crown")
|
||||
(UK_BOOK_A: Book, 111.0, 178.0, "uk-book-a")
|
||||
(UK_BOOK_B: Book, 129.0, 198.0, "uk-book-b")
|
||||
|
||||
// Unites States
|
||||
(US_LETTER: US, 215.9, 279.4, "us-letter")
|
||||
(US_LEGAL: US, 215.9, 355.6, "us-legal")
|
||||
(US_TABLOID: US, 279.4, 431.8, "us-tabloid")
|
||||
(US_EXECUTIVE: US, 184.15, 266.7, "us-executive")
|
||||
(US_FOOLSCAP_FOLIO: US, 215.9, 342.9, "us-foolscap-folio")
|
||||
(US_STATEMENT: US, 139.7, 215.9, "us-statement")
|
||||
(US_LEDGER: US, 431.8, 279.4, "us-ledger")
|
||||
(US_OFICIO: US, 215.9, 340.36, "us-oficio")
|
||||
(US_GOV_LETTER: US, 203.2, 266.7, "us-gov-letter")
|
||||
(US_GOV_LEGAL: US, 215.9, 330.2, "us-gov-legal")
|
||||
(US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card")
|
||||
(US_DIGEST: Book, 139.7, 215.9, "us-digest")
|
||||
(US_TRADE: Book, 152.4, 228.6, "us-trade")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Other
|
||||
(NEWSPAPER_COMPACT: Newspaper, 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: Newspaper, 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: Newspaper, 381.0, 578.0, "newspaper-broadsheet")
|
||||
(PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9")
|
||||
(PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3")
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ use unicode_bidi::{BidiInfo, Level};
|
||||
use xi_unicode::LineBreakIterator;
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{shape, Decoration, ShapedText, Spacing};
|
||||
use crate::style::TextStyle;
|
||||
use super::{shape, Decoration, ShapedText, Spacing, TextNode};
|
||||
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
||||
|
||||
/// `par`: Configure paragraphs.
|
||||
@ -38,24 +37,14 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
align = Some(v);
|
||||
}
|
||||
|
||||
let par = ctx.style.par_mut();
|
||||
|
||||
if let Some(dir) = dir {
|
||||
par.dir = dir;
|
||||
par.align = if dir == Dir::LTR { Align::Left } else { Align::Right };
|
||||
if let (Some(dir), None) = (dir, align) {
|
||||
align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
|
||||
}
|
||||
|
||||
if let Some(align) = align {
|
||||
par.align = align;
|
||||
}
|
||||
|
||||
if let Some(leading) = leading {
|
||||
par.leading = leading;
|
||||
}
|
||||
|
||||
if let Some(spacing) = spacing {
|
||||
par.spacing = spacing;
|
||||
}
|
||||
set!(ctx, ParNode::DIR => dir);
|
||||
set!(ctx, ParNode::ALIGN => align);
|
||||
set!(ctx, ParNode::LEADING => leading);
|
||||
set!(ctx, ParNode::SPACING => spacing);
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
@ -64,24 +53,39 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ParNode(pub Vec<ParChild>);
|
||||
|
||||
properties! {
|
||||
ParNode,
|
||||
|
||||
/// The direction for text and inline objects.
|
||||
DIR: Dir = Dir::LTR,
|
||||
/// How to align text and inline objects in their line.
|
||||
ALIGN: Align = Align::Left,
|
||||
// TODO(set): Make relative to font size.
|
||||
/// The spacing between lines (dependent on scaled font size).
|
||||
LEADING: Length = Length::pt(6.5),
|
||||
/// The spacing between paragraphs (dependent on scaled font size).
|
||||
SPACING: Length = Length::pt(12.0),
|
||||
}
|
||||
|
||||
impl Layout for ParNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
// TODO(set): Take styles as parameter.
|
||||
let styles = Styles::new();
|
||||
|
||||
// Collect all text into one string used for BiDi analysis.
|
||||
let text = self.collect_text();
|
||||
|
||||
// Find out the BiDi embedding levels.
|
||||
// TODO(set): Get dir from styles.
|
||||
let bidi = BidiInfo::new(&text, Level::from_dir(Dir::LTR));
|
||||
let default_level = Level::from_dir(styles.get(Self::DIR));
|
||||
let bidi = BidiInfo::new(&text, default_level);
|
||||
|
||||
// Prepare paragraph layout by building a representation on which we can
|
||||
// do line breaking without layouting each and every line from scratch.
|
||||
// TODO(set): Get text style from styles.
|
||||
let style = crate::style::TextStyle::default();
|
||||
let layouter = ParLayouter::new(self, ctx, regions, bidi, &style);
|
||||
let layouter = ParLayouter::new(self, ctx, regions, bidi, &styles);
|
||||
|
||||
// Find suitable linebreaks.
|
||||
layouter.layout(ctx, regions.clone())
|
||||
@ -115,7 +119,7 @@ impl ParNode {
|
||||
fn strings(&self) -> impl Iterator<Item = &str> {
|
||||
self.0.iter().map(|child| match child {
|
||||
ParChild::Spacing(_) => " ",
|
||||
ParChild::Text(ref piece, ..) => piece,
|
||||
ParChild::Text(ref piece, ..) => &piece.0,
|
||||
ParChild::Node(..) => "\u{FFFC}",
|
||||
ParChild::Decorate(_) | ParChild::Undecorate => "",
|
||||
})
|
||||
@ -128,7 +132,8 @@ pub enum ParChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Spacing),
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(EcoString),
|
||||
// TODO(set): A single text run may also have its own style.
|
||||
Text(TextNode),
|
||||
/// Any child node and how to align it in its line.
|
||||
Node(PackedNode),
|
||||
/// A decoration that applies until a matching `Undecorate`.
|
||||
@ -188,7 +193,7 @@ impl<'a> ParLayouter<'a> {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
bidi: BidiInfo<'a>,
|
||||
style: &'a TextStyle,
|
||||
styles: &'a Styles,
|
||||
) -> Self {
|
||||
let mut items = vec![];
|
||||
let mut ranges = vec![];
|
||||
@ -215,7 +220,7 @@ impl<'a> ParLayouter<'a> {
|
||||
cursor += group.len();
|
||||
let subrange = start .. cursor;
|
||||
let text = &bidi.text[subrange.clone()];
|
||||
let shaped = shape(ctx, text, style, level.dir());
|
||||
let shaped = shape(ctx, text, styles, level.dir());
|
||||
items.push(ParItem::Text(shaped));
|
||||
ranges.push(subrange);
|
||||
}
|
||||
@ -243,9 +248,8 @@ impl<'a> ParLayouter<'a> {
|
||||
}
|
||||
|
||||
Self {
|
||||
// TODO(set): Get alignment and leading from styles.
|
||||
align: Align::Left,
|
||||
leading: Length::pt(6.0),
|
||||
align: styles.get(ParNode::ALIGN),
|
||||
leading: styles.get(ParNode::LEADING),
|
||||
bidi,
|
||||
items,
|
||||
ranges,
|
||||
@ -540,7 +544,7 @@ impl<'a> LineLayout<'a> {
|
||||
// Compute the reordered ranges in visual order (left to right).
|
||||
self.par.bidi.visual_runs(para, self.line.clone())
|
||||
} else {
|
||||
<_>::default()
|
||||
(vec![], vec![])
|
||||
};
|
||||
|
||||
runs.into_iter()
|
||||
|
@ -7,24 +7,21 @@ pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let tx = args.named("dx")?.unwrap_or_default();
|
||||
let ty = args.named("dy")?.unwrap_or_default();
|
||||
let body: Node = args.expect("body")?;
|
||||
Ok(Value::block(PlacedNode {
|
||||
child: body.into_block().moved(Point::new(tx, ty)).aligned(aligns),
|
||||
}))
|
||||
Ok(Value::block(PlacedNode(
|
||||
body.into_block().moved(Point::new(tx, ty)).aligned(aligns),
|
||||
)))
|
||||
}
|
||||
|
||||
/// A node that places its child absolutely.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PlacedNode {
|
||||
/// The node to be placed.
|
||||
pub child: PackedNode,
|
||||
}
|
||||
pub struct PlacedNode(pub PackedNode);
|
||||
|
||||
impl PlacedNode {
|
||||
/// Whether this node wants to be placed relative to its its parent's base
|
||||
/// origin. instead of relative to the parent's current flow/cursor
|
||||
/// position.
|
||||
pub fn out_of_flow(&self) -> bool {
|
||||
self.child
|
||||
self.0
|
||||
.downcast::<AlignNode>()
|
||||
.map_or(false, |node| node.aligns.y.is_some())
|
||||
}
|
||||
@ -46,7 +43,7 @@ impl Layout for PlacedNode {
|
||||
Regions::one(regions.base, regions.base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.child.layout(ctx, &pod);
|
||||
let mut frames = self.0.layout(ctx, &pod);
|
||||
let Constrained { item: frame, cts } = &mut frames[0];
|
||||
|
||||
// If expansion is off, zero all sizes so that we don't take up any
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,7 @@ fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
|
||||
.named("origin")?
|
||||
.unwrap_or(Spec::splat(None))
|
||||
.unwrap_or(Align::CENTER_HORIZON);
|
||||
|
||||
Ok(Value::inline(
|
||||
body.into_block().transformed(transform, origin),
|
||||
))
|
||||
|
419
src/style/mod.rs
419
src/style/mod.rs
@ -1,419 +0,0 @@
|
||||
//! Style properties.
|
||||
|
||||
mod paper;
|
||||
|
||||
pub use paper::*;
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ttf_parser::Tag;
|
||||
|
||||
use crate::eval::Smart;
|
||||
use crate::font::*;
|
||||
use crate::geom::*;
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// Defines a set of properties a template can be instantiated with.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Style {
|
||||
/// The page settings.
|
||||
pub page: Rc<PageStyle>,
|
||||
/// The paragraph settings.
|
||||
pub par: Rc<ParStyle>,
|
||||
/// The current text settings.
|
||||
pub text: Rc<TextStyle>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Access the `page` style mutably.
|
||||
pub fn page_mut(&mut self) -> &mut PageStyle {
|
||||
Rc::make_mut(&mut self.page)
|
||||
}
|
||||
|
||||
/// Access the `par` style mutably.
|
||||
pub fn par_mut(&mut self) -> &mut ParStyle {
|
||||
Rc::make_mut(&mut self.par)
|
||||
}
|
||||
|
||||
/// Access the `text` style mutably.
|
||||
pub fn text_mut(&mut self) -> &mut TextStyle {
|
||||
Rc::make_mut(&mut self.text)
|
||||
}
|
||||
|
||||
/// The resolved line spacing.
|
||||
pub fn leading(&self) -> Length {
|
||||
self.par.leading.resolve(self.text.size)
|
||||
}
|
||||
|
||||
/// The resolved paragraph spacing.
|
||||
pub fn par_spacing(&self) -> Length {
|
||||
self.par.spacing.resolve(self.text.size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
page: Rc::new(PageStyle::default()),
|
||||
par: Rc::new(ParStyle::default()),
|
||||
text: Rc::new(TextStyle::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines style properties of pages.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct PageStyle {
|
||||
/// The class of this page.
|
||||
pub class: PaperClass,
|
||||
/// The width and height of the page.
|
||||
pub size: Size,
|
||||
/// The amount of white space on each side of the page. If a side is set to
|
||||
/// `None`, the default for the paper class is used.
|
||||
pub margins: Sides<Smart<Linear>>,
|
||||
/// The background fill of the page.
|
||||
pub fill: Option<Paint>,
|
||||
}
|
||||
|
||||
impl PageStyle {
|
||||
/// The resolved margins.
|
||||
pub fn margins(&self) -> Sides<Linear> {
|
||||
let default = self.class.default_margins();
|
||||
Sides {
|
||||
left: self.margins.left.unwrap_or(default.left),
|
||||
top: self.margins.top.unwrap_or(default.top),
|
||||
right: self.margins.right.unwrap_or(default.right),
|
||||
bottom: self.margins.bottom.unwrap_or(default.bottom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PageStyle {
|
||||
fn default() -> Self {
|
||||
let paper = Paper::A4;
|
||||
Self {
|
||||
class: paper.class(),
|
||||
size: paper.size(),
|
||||
margins: Sides::splat(Smart::Auto),
|
||||
fill: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines style properties of paragraphs.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ParStyle {
|
||||
/// The direction for text and inline objects.
|
||||
pub dir: Dir,
|
||||
/// How to align text and inline objects in their line.
|
||||
pub align: Align,
|
||||
/// The spacing between lines (dependent on scaled font size).
|
||||
pub leading: Linear,
|
||||
/// The spacing between paragraphs (dependent on scaled font size).
|
||||
pub spacing: Linear,
|
||||
}
|
||||
|
||||
impl Default for ParStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dir: Dir::LTR,
|
||||
align: Align::Left,
|
||||
leading: Relative::new(0.65).into(),
|
||||
spacing: Relative::new(1.2).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines style properties of text.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct TextStyle {
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
/// 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<FamilyStyle>,
|
||||
/// OpenType features.
|
||||
pub features: Rc<FontFeatures>,
|
||||
/// The amount of space that should be added between character.
|
||||
pub tracking: Em,
|
||||
/// Whether 300 extra font weight should be added to what is defined by the
|
||||
/// `variant`.
|
||||
pub strong: bool,
|
||||
/// Whether the the font style defined by the `variant` should be inverted.
|
||||
pub emph: bool,
|
||||
/// Whether a monospace font should be preferred.
|
||||
pub monospace: bool,
|
||||
/// Whether font fallback to a base list should occur.
|
||||
pub fallback: bool,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
/// The resolved variant with `strong` and `emph` factored in.
|
||||
pub fn variant(&self) -> FontVariant {
|
||||
let mut variant = self.variant;
|
||||
|
||||
if self.strong {
|
||||
variant.weight = variant.weight.thicken(300);
|
||||
}
|
||||
|
||||
if self.emph {
|
||||
variant.style = match variant.style {
|
||||
FontStyle::Normal => FontStyle::Italic,
|
||||
FontStyle::Italic => FontStyle::Normal,
|
||||
FontStyle::Oblique => FontStyle::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
variant
|
||||
}
|
||||
|
||||
/// The resolved family iterator.
|
||||
pub fn families(&self) -> impl Iterator<Item = &str> + Clone {
|
||||
let head = if self.monospace {
|
||||
self.families.monospace.as_slice()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
let core = self.families.list.iter().flat_map(move |family| {
|
||||
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,
|
||||
}
|
||||
});
|
||||
|
||||
let tail = if self.fallback {
|
||||
self.families.base.as_slice()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
head.iter().chain(core).chain(tail).map(EcoString::as_str)
|
||||
}
|
||||
|
||||
/// Access the `families` style mutably.
|
||||
pub fn families_mut(&mut self) -> &mut FamilyStyle {
|
||||
Rc::make_mut(&mut self.families)
|
||||
}
|
||||
|
||||
/// Access the font `features` mutably.
|
||||
pub fn features_mut(&mut self) -> &mut FontFeatures {
|
||||
Rc::make_mut(&mut self.features)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: Length::pt(11.0),
|
||||
variant: FontVariant {
|
||||
style: FontStyle::Normal,
|
||||
weight: FontWeight::REGULAR,
|
||||
stretch: FontStretch::NORMAL,
|
||||
},
|
||||
top_edge: VerticalFontMetric::CapHeight,
|
||||
bottom_edge: VerticalFontMetric::Baseline,
|
||||
fill: RgbaColor::BLACK.into(),
|
||||
families: Rc::new(FamilyStyle::default()),
|
||||
features: Rc::new(FontFeatures::default()),
|
||||
tracking: Em::zero(),
|
||||
strong: false,
|
||||
emph: false,
|
||||
monospace: false,
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Font list with family definitions.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FamilyStyle {
|
||||
/// The user-defined list of font families.
|
||||
pub list: Vec<FontFamily>,
|
||||
/// Definition of serif font families.
|
||||
pub serif: Vec<EcoString>,
|
||||
/// Definition of sans-serif font families.
|
||||
pub sans_serif: Vec<EcoString>,
|
||||
/// Definition of monospace font families used for raw text.
|
||||
pub monospace: Vec<EcoString>,
|
||||
/// Base fonts that are tried as last resort.
|
||||
pub base: Vec<EcoString>,
|
||||
}
|
||||
|
||||
impl Default for FamilyStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
list: vec![FontFamily::SansSerif],
|
||||
serif: vec!["ibm plex serif".into()],
|
||||
sans_serif: vec!["ibm plex sans".into()],
|
||||
monospace: vec!["ibm plex mono".into()],
|
||||
base: vec![
|
||||
"ibm plex sans".into(),
|
||||
"latin modern math".into(),
|
||||
"twitter color emoji".into(),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic or named font family.
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub enum FontFamily {
|
||||
/// A family that has "serifs", small strokes attached to letters.
|
||||
Serif,
|
||||
/// A family in which glyphs do not have "serifs", small attached strokes.
|
||||
SansSerif,
|
||||
/// A family in which (almost) all glyphs are of equal width.
|
||||
Monospace,
|
||||
/// A specific family with a name.
|
||||
Named(EcoString),
|
||||
}
|
||||
|
||||
impl Debug for FontFamily {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Serif => "serif",
|
||||
Self::SansSerif => "sans-serif",
|
||||
Self::Monospace => "monospace",
|
||||
Self::Named(s) => s,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether various kinds of ligatures should appear.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FontFeatures {
|
||||
/// Whether to apply kerning ("kern").
|
||||
pub kerning: bool,
|
||||
/// Whether the text should use small caps. ("smcp")
|
||||
pub smallcaps: bool,
|
||||
/// Whether to apply stylistic alternates. ("salt")
|
||||
pub alternates: bool,
|
||||
/// Which stylistic set to apply. ("ss01" - "ss20")
|
||||
pub stylistic_set: Option<StylisticSet>,
|
||||
/// Configuration of ligature features.
|
||||
pub ligatures: LigatureFeatures,
|
||||
/// Configuration of numbers features.
|
||||
pub numbers: NumberFeatures,
|
||||
/// Raw OpenType features to apply.
|
||||
pub raw: Vec<(Tag, u32)>,
|
||||
}
|
||||
|
||||
impl Default for FontFeatures {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kerning: true,
|
||||
smallcaps: false,
|
||||
alternates: false,
|
||||
stylistic_set: None,
|
||||
ligatures: LigatureFeatures::default(),
|
||||
numbers: NumberFeatures::default(),
|
||||
raw: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A stylistic set in a font face.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct StylisticSet(u8);
|
||||
|
||||
impl StylisticSet {
|
||||
/// Creates a new set, clamping to 1-20.
|
||||
pub fn new(index: u8) -> Self {
|
||||
Self(index.clamp(1, 20))
|
||||
}
|
||||
|
||||
/// Get the value, guaranteed to be 1-20.
|
||||
pub fn get(self) -> u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether various kinds of ligatures should appear.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LigatureFeatures {
|
||||
/// Standard ligatures. ("liga", "clig")
|
||||
pub standard: bool,
|
||||
/// Ligatures that should be used sparringly. ("dlig")
|
||||
pub discretionary: bool,
|
||||
/// Historical ligatures. ("hlig")
|
||||
pub historical: bool,
|
||||
}
|
||||
|
||||
impl Default for LigatureFeatures {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
standard: true,
|
||||
discretionary: false,
|
||||
historical: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the style of numbers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct NumberFeatures {
|
||||
/// Whether to use lining or old-style numbers.
|
||||
pub type_: Smart<NumberType>,
|
||||
/// Whether to use proportional or tabular numbers.
|
||||
pub width: Smart<NumberWidth>,
|
||||
/// How to position numbers vertically.
|
||||
pub position: NumberPosition,
|
||||
/// Whether to have a slash through the zero glyph. ("zero")
|
||||
pub slashed_zero: bool,
|
||||
/// Whether to convert fractions. ("frac")
|
||||
pub fractions: bool,
|
||||
}
|
||||
|
||||
impl Default for NumberFeatures {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
type_: Smart::Auto,
|
||||
width: Smart::Auto,
|
||||
position: NumberPosition::Normal,
|
||||
slashed_zero: false,
|
||||
fractions: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which kind of numbers / figures to select.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum NumberType {
|
||||
/// Numbers that fit well with capital text. ("lnum")
|
||||
Lining,
|
||||
/// Numbers that fit well into flow of upper- and lowercase text. ("onum")
|
||||
OldStyle,
|
||||
}
|
||||
|
||||
/// The width of numbers / figures.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum NumberWidth {
|
||||
/// Number widths are glyph specific. ("pnum")
|
||||
Proportional,
|
||||
/// All numbers are of equal width / monospaced. ("tnum")
|
||||
Tabular,
|
||||
}
|
||||
|
||||
/// How to position numbers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum NumberPosition {
|
||||
/// Numbers are positioned on the same baseline as text.
|
||||
Normal,
|
||||
/// Numbers are smaller and placed at the bottom. ("subs")
|
||||
Subscript,
|
||||
/// Numbers are smaller and placed at the top. ("sups")
|
||||
Superscript,
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
use crate::geom::{Length, Linear, Relative, Sides, Size};
|
||||
|
||||
/// Specification of a paper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Paper {
|
||||
/// The broad class this paper belongs to.
|
||||
class: PaperClass,
|
||||
/// The width of the paper in millimeters.
|
||||
width: f64,
|
||||
/// The height of the paper in millimeters.
|
||||
height: f64,
|
||||
}
|
||||
|
||||
/// Defines default margins for a class of related papers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PaperClass {
|
||||
Custom,
|
||||
Base,
|
||||
US,
|
||||
Newspaper,
|
||||
Book,
|
||||
}
|
||||
|
||||
impl PaperClass {
|
||||
/// The default margins for this page class.
|
||||
pub fn default_margins(self) -> Sides<Linear> {
|
||||
let f = |r| Relative::new(r).into();
|
||||
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
|
||||
match self {
|
||||
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
|
||||
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
|
||||
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! papers {
|
||||
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
|
||||
impl Paper {
|
||||
/// Parse a paper from its name.
|
||||
///
|
||||
/// Both lower and upper case are fine.
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name.to_lowercase().as_str() {
|
||||
$($($pats)* => Some(Self::$var),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The class of the paper.
|
||||
pub fn class(self) -> PaperClass {
|
||||
self.class
|
||||
}
|
||||
|
||||
/// The size of the paper.
|
||||
pub fn size(self) -> Size {
|
||||
Size::new(Length::mm(self.width), Length::mm(self.height))
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined papers.
|
||||
///
|
||||
/// Each paper is parsable from its name in kebab-case.
|
||||
impl Paper {
|
||||
$(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)*
|
||||
}
|
||||
};
|
||||
|
||||
(@$var:ident, $names:expr, $class:ident, $width:expr, $height:expr) => {
|
||||
pub const $var: Self = Self {
|
||||
class: PaperClass::$class,
|
||||
width: $width,
|
||||
height: $height,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// All paper sizes in mm.
|
||||
//
|
||||
// Resources:
|
||||
// - https://papersizes.io/
|
||||
// - https://en.wikipedia.org/wiki/Paper_size
|
||||
// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm
|
||||
// - https://vintagepaper.co/blogs/news/traditional-paper-sizes
|
||||
papers! {
|
||||
// ---------------------------------------------------------------------- //
|
||||
// ISO 216 A Series
|
||||
(A0: Base, 841.0, 1189.0, "a0")
|
||||
(A1: Base, 594.0, 841.0, "a1")
|
||||
(A2: Base, 420.0, 594.0, "a2")
|
||||
(A3: Base, 297.0, 420.0, "a3")
|
||||
(A4: Base, 210.0, 297.0, "a4")
|
||||
(A5: Base, 148.0, 210.0, "a5")
|
||||
(A6: Book, 105.0, 148.0, "a6")
|
||||
(A7: Base, 74.0, 105.0, "a7")
|
||||
(A8: Base, 52.0, 74.0, "a8")
|
||||
(A9: Base, 37.0, 52.0, "a9")
|
||||
(A10: Base, 26.0, 37.0, "a10")
|
||||
(A11: Base, 18.0, 26.0, "a11")
|
||||
|
||||
// ISO 216 B Series
|
||||
(ISO_B1: Base, 707.0, 1000.0, "iso-b1")
|
||||
(ISO_B2: Base, 500.0, 707.0, "iso-b2")
|
||||
(ISO_B3: Base, 353.0, 500.0, "iso-b3")
|
||||
(ISO_B4: Base, 250.0, 353.0, "iso-b4")
|
||||
(ISO_B5: Book, 176.0, 250.0, "iso-b5")
|
||||
(ISO_B6: Book, 125.0, 176.0, "iso-b6")
|
||||
(ISO_B7: Base, 88.0, 125.0, "iso-b7")
|
||||
(ISO_B8: Base, 62.0, 88.0, "iso-b8")
|
||||
|
||||
// ISO 216 C Series
|
||||
(ISO_C3: Base, 324.0, 458.0, "iso-c3")
|
||||
(ISO_C4: Base, 229.0, 324.0, "iso-c4")
|
||||
(ISO_C5: Base, 162.0, 229.0, "iso-c5")
|
||||
(ISO_C6: Base, 114.0, 162.0, "iso-c6")
|
||||
(ISO_C7: Base, 81.0, 114.0, "iso-c7")
|
||||
(ISO_C8: Base, 57.0, 81.0, "iso-c8")
|
||||
|
||||
// DIN D Series (extension to ISO)
|
||||
(DIN_D3: Base, 272.0, 385.0, "din-d3")
|
||||
(DIN_D4: Base, 192.0, 272.0, "din-d4")
|
||||
(DIN_D5: Base, 136.0, 192.0, "din-d5")
|
||||
(DIN_D6: Base, 96.0, 136.0, "din-d6")
|
||||
(DIN_D7: Base, 68.0, 96.0, "din-d7")
|
||||
(DIN_D8: Base, 48.0, 68.0, "din-d8")
|
||||
|
||||
// SIS (used in academia)
|
||||
(SIS_G5: Base, 169.0, 239.0, "sis-g5")
|
||||
(SIS_E5: Base, 115.0, 220.0, "sis-e5")
|
||||
|
||||
// ANSI Extensions
|
||||
(ANSI_A: Base, 216.0, 279.0, "ansi-a")
|
||||
(ANSI_B: Base, 279.0, 432.0, "ansi-b")
|
||||
(ANSI_C: Base, 432.0, 559.0, "ansi-c")
|
||||
(ANSI_D: Base, 559.0, 864.0, "ansi-d")
|
||||
(ANSI_E: Base, 864.0, 1118.0, "ansi-e")
|
||||
|
||||
// ANSI Architectural Paper
|
||||
(ARCH_A: Base, 229.0, 305.0, "arch-a")
|
||||
(ARCH_B: Base, 305.0, 457.0, "arch-b")
|
||||
(ARCH_C: Base, 457.0, 610.0, "arch-c")
|
||||
(ARCH_D: Base, 610.0, 914.0, "arch-d")
|
||||
(ARCH_E1: Base, 762.0, 1067.0, "arch-e1")
|
||||
(ARCH_E: Base, 914.0, 1219.0, "arch-e")
|
||||
|
||||
// JIS B Series
|
||||
(JIS_B0: Base, 1030.0, 1456.0, "jis-b0")
|
||||
(JIS_B1: Base, 728.0, 1030.0, "jis-b1")
|
||||
(JIS_B2: Base, 515.0, 728.0, "jis-b2")
|
||||
(JIS_B3: Base, 364.0, 515.0, "jis-b3")
|
||||
(JIS_B4: Base, 257.0, 364.0, "jis-b4")
|
||||
(JIS_B5: Base, 182.0, 257.0, "jis-b5")
|
||||
(JIS_B6: Base, 128.0, 182.0, "jis-b6")
|
||||
(JIS_B7: Base, 91.0, 128.0, "jis-b7")
|
||||
(JIS_B8: Base, 64.0, 91.0, "jis-b8")
|
||||
(JIS_B9: Base, 45.0, 64.0, "jis-b9")
|
||||
(JIS_B10: Base, 32.0, 45.0, "jis-b10")
|
||||
(JIS_B11: Base, 22.0, 32.0, "jis-b11")
|
||||
|
||||
// SAC D Series
|
||||
(SAC_D0: Base, 764.0, 1064.0, "sac-d0")
|
||||
(SAC_D1: Base, 532.0, 760.0, "sac-d1")
|
||||
(SAC_D2: Base, 380.0, 528.0, "sac-d2")
|
||||
(SAC_D3: Base, 264.0, 376.0, "sac-d3")
|
||||
(SAC_D4: Base, 188.0, 260.0, "sac-d4")
|
||||
(SAC_D5: Base, 130.0, 184.0, "sac-d5")
|
||||
(SAC_D6: Base, 92.0, 126.0, "sac-d6")
|
||||
|
||||
// ISO 7810 ID
|
||||
(ISO_ID_1: Base, 85.6, 53.98, "iso-id-1")
|
||||
(ISO_ID_2: Base, 74.0, 105.0, "iso-id-2")
|
||||
(ISO_ID_3: Base, 88.0, 125.0, "iso-id-3")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Asia
|
||||
(ASIA_F4: Base, 210.0, 330.0, "asia-f4")
|
||||
|
||||
// Japan
|
||||
(JP_SHIROKU_BAN_4: Base, 264.0, 379.0, "jp-shiroku-ban-4")
|
||||
(JP_SHIROKU_BAN_5: Base, 189.0, 262.0, "jp-shiroku-ban-5")
|
||||
(JP_SHIROKU_BAN_6: Base, 127.0, 188.0, "jp-shiroku-ban-6")
|
||||
(JP_KIKU_4: Base, 227.0, 306.0, "jp-kiku-4")
|
||||
(JP_KIKU_5: Base, 151.0, 227.0, "jp-kiku-5")
|
||||
(JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card")
|
||||
|
||||
// China
|
||||
(CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card")
|
||||
|
||||
// Europe
|
||||
(EU_BUSINESS_CARD: Base, 85.0, 55.0, "eu-business-card")
|
||||
|
||||
// French Traditional (AFNOR)
|
||||
(FR_TELLIERE: Base, 340.0, 440.0, "fr-tellière")
|
||||
(FR_COURONNE_ECRITURE: Base, 360.0, 460.0, "fr-couronne-écriture")
|
||||
(FR_COURONNE_EDITION: Base, 370.0, 470.0, "fr-couronne-édition")
|
||||
(FR_RAISIN: Base, 500.0, 650.0, "fr-raisin")
|
||||
(FR_CARRE: Base, 450.0, 560.0, "fr-carré")
|
||||
(FR_JESUS: Base, 560.0, 760.0, "fr-jésus")
|
||||
|
||||
// United Kingdom Imperial
|
||||
(UK_BRIEF: Base, 406.4, 342.9, "uk-brief")
|
||||
(UK_DRAFT: Base, 254.0, 406.4, "uk-draft")
|
||||
(UK_FOOLSCAP: Base, 203.2, 330.2, "uk-foolscap")
|
||||
(UK_QUARTO: Base, 203.2, 254.0, "uk-quarto")
|
||||
(UK_CROWN: Base, 508.0, 381.0, "uk-crown")
|
||||
(UK_BOOK_A: Book, 111.0, 178.0, "uk-book-a")
|
||||
(UK_BOOK_B: Book, 129.0, 198.0, "uk-book-b")
|
||||
|
||||
// Unites States
|
||||
(US_LETTER: US, 215.9, 279.4, "us-letter")
|
||||
(US_LEGAL: US, 215.9, 355.6, "us-legal")
|
||||
(US_TABLOID: US, 279.4, 431.8, "us-tabloid")
|
||||
(US_EXECUTIVE: US, 184.15, 266.7, "us-executive")
|
||||
(US_FOOLSCAP_FOLIO: US, 215.9, 342.9, "us-foolscap-folio")
|
||||
(US_STATEMENT: US, 139.7, 215.9, "us-statement")
|
||||
(US_LEDGER: US, 431.8, 279.4, "us-ledger")
|
||||
(US_OFICIO: US, 215.9, 340.36, "us-oficio")
|
||||
(US_GOV_LETTER: US, 203.2, 266.7, "us-gov-letter")
|
||||
(US_GOV_LEGAL: US, 215.9, 330.2, "us-gov-legal")
|
||||
(US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card")
|
||||
(US_DIGEST: Book, 139.7, 215.9, "us-digest")
|
||||
(US_TRADE: Book, 152.4, 228.6, "us-trade")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Other
|
||||
(NEWSPAPER_COMPACT: Newspaper, 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: Newspaper, 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: Newspaper, 381.0, 578.0, "newspaper-broadsheet")
|
||||
(PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9")
|
||||
(PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3")
|
||||
}
|
@ -13,18 +13,6 @@ use std::ops::Range;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Additional methods for booleans.
|
||||
pub trait BoolExt {
|
||||
/// Toggle the value of the bool in place.
|
||||
fn flip(&mut self);
|
||||
}
|
||||
|
||||
impl BoolExt for bool {
|
||||
fn flip(&mut self) {
|
||||
*self = !*self;
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for options.
|
||||
pub trait OptionExt<T> {
|
||||
/// Sets `other` as the value if `self` is `None` or if it contains a value
|
||||
|
@ -12,20 +12,17 @@ use usvg::FitTo;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use typst::diag::Error;
|
||||
use typst::eval::{Smart, Value};
|
||||
use typst::eval::{Smart, Styles, Value};
|
||||
use typst::font::Face;
|
||||
use typst::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
||||
use typst::geom::{
|
||||
self, Color, Length, Paint, PathElement, RgbaColor, Sides, Size, Transform,
|
||||
};
|
||||
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Size, Transform};
|
||||
use typst::image::{Image, RasterImage, Svg};
|
||||
use typst::layout::layout;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use typst::library::DocumentNode;
|
||||
use typst::library::{DocumentNode, PageNode, TextNode};
|
||||
use typst::loading::FsLoader;
|
||||
use typst::parse::Scanner;
|
||||
use typst::source::SourceFile;
|
||||
use typst::style::Style;
|
||||
use typst::syntax::Span;
|
||||
use typst::Context;
|
||||
|
||||
@ -64,12 +61,17 @@ fn main() {
|
||||
println!("Running {} tests", len);
|
||||
}
|
||||
|
||||
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||
// large and fit them to match their content.
|
||||
let mut style = Style::default();
|
||||
style.page_mut().size = Size::new(Length::pt(120.0), Length::inf());
|
||||
style.page_mut().margins = Sides::splat(Smart::Custom(Length::pt(10.0).into()));
|
||||
style.text_mut().size = Length::pt(10.0);
|
||||
// Set page width to 120pt with 10pt margins, so that the inner page is
|
||||
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
||||
// that it multiplies to nice round numbers.
|
||||
let mut styles = Styles::new();
|
||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0)));
|
||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||
styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));
|
||||
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
|
||||
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
|
||||
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
|
||||
styles.set(TextNode::SIZE, Length::pt(10.0));
|
||||
|
||||
// Hook up an assert function into the global scope.
|
||||
let mut std = typst::library::new();
|
||||
@ -87,7 +89,7 @@ fn main() {
|
||||
|
||||
// Create loader and context.
|
||||
let loader = FsLoader::new().with_path(FONT_DIR).wrap();
|
||||
let mut ctx = Context::builder().std(std).style(style).build(loader);
|
||||
let mut ctx = Context::builder().std(std).styles(styles).build(loader);
|
||||
|
||||
// Run all the tests.
|
||||
let mut ok = true;
|
||||
|
Loading…
x
Reference in New Issue
Block a user