mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Make all nodes into classes
This commit is contained in:
parent
0b62439090
commit
e74ae6ce70
@ -7,48 +7,82 @@ use syn::parse_quote;
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Error, Result};
|
use syn::{Error, Result};
|
||||||
|
|
||||||
/// Generate node properties.
|
/// Turn a node into a class.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn class(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let impl_block = syn::parse_macro_input!(item as syn::ItemImpl);
|
let impl_block = syn::parse_macro_input!(item as syn::ItemImpl);
|
||||||
expand(impl_block).unwrap_or_else(|err| err.to_compile_error()).into()
|
expand(impl_block).unwrap_or_else(|err| err.to_compile_error()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand a property impl block for a node.
|
/// Expand an impl block for a node.
|
||||||
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
||||||
// Split the node type into name and generic type arguments.
|
// Split the node type into name and generic type arguments.
|
||||||
|
let params = &impl_block.generics.params;
|
||||||
let self_ty = &*impl_block.self_ty;
|
let self_ty = &*impl_block.self_ty;
|
||||||
let (self_name, self_args) = parse_self(self_ty)?;
|
let (self_name, self_args) = parse_self(self_ty)?;
|
||||||
|
|
||||||
// Rewrite the const items from values to keys.
|
let module = quote::format_ident!("{}_types", self_name);
|
||||||
let mut modules = vec![];
|
|
||||||
for item in &mut impl_block.items {
|
let mut key_modules = vec![];
|
||||||
if let syn::ImplItem::Const(item) = item {
|
let mut construct = None;
|
||||||
let module = process_const(
|
let mut set = None;
|
||||||
item,
|
|
||||||
&impl_block.generics,
|
for item in std::mem::take(&mut impl_block.items) {
|
||||||
self_ty,
|
match item {
|
||||||
&self_name,
|
syn::ImplItem::Const(mut item) => {
|
||||||
&self_args,
|
key_modules.push(process_const(
|
||||||
)?;
|
&mut item, params, self_ty, &self_name, &self_args,
|
||||||
modules.push(module);
|
)?);
|
||||||
|
impl_block.items.push(syn::ImplItem::Const(item));
|
||||||
|
}
|
||||||
|
syn::ImplItem::Method(method) => {
|
||||||
|
match method.sig.ident.to_string().as_str() {
|
||||||
|
"construct" => construct = Some(method),
|
||||||
|
"set" => set = Some(method),
|
||||||
|
_ => return Err(Error::new(method.span(), "unexpected method")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::new(item.span(), "unexpected item")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let construct =
|
||||||
|
construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?;
|
||||||
|
|
||||||
|
let set = if impl_block.items.is_empty() {
|
||||||
|
set.unwrap_or_else(|| {
|
||||||
|
parse_quote! {
|
||||||
|
fn set(_: &mut Args, _: &mut StyleMap) -> TypResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
set.ok_or_else(|| Error::new(impl_block.span(), "missing set method"))?
|
||||||
|
};
|
||||||
|
|
||||||
// Put everything into a module with a hopefully unique type to isolate
|
// Put everything into a module with a hopefully unique type to isolate
|
||||||
// it from the outside.
|
// it from the outside.
|
||||||
let module = quote::format_ident!("{}_types", self_name);
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod #module {
|
mod #module {
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use crate::eval::{Nonfolding, Property};
|
use crate::eval::{Construct, Nonfolding, Property, Set};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#impl_block
|
#impl_block
|
||||||
#(#modules)*
|
|
||||||
|
impl<#params> Construct for #self_ty {
|
||||||
|
#construct
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<#params> Set for #self_ty {
|
||||||
|
#set
|
||||||
|
}
|
||||||
|
|
||||||
|
#(#key_modules)*
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -82,7 +116,7 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
|
|||||||
/// Process a single const item.
|
/// Process a single const item.
|
||||||
fn process_const(
|
fn process_const(
|
||||||
item: &mut syn::ImplItemConst,
|
item: &mut syn::ImplItemConst,
|
||||||
impl_generics: &syn::Generics,
|
params: &syn::punctuated::Punctuated<syn::GenericParam, syn::Token![,]>,
|
||||||
self_ty: &syn::Type,
|
self_ty: &syn::Type,
|
||||||
self_name: &str,
|
self_name: &str,
|
||||||
self_args: &[&syn::Type],
|
self_args: &[&syn::Type],
|
||||||
@ -95,7 +129,6 @@ fn process_const(
|
|||||||
let value_ty = &item.ty;
|
let value_ty = &item.ty;
|
||||||
|
|
||||||
// ... but the real type of the const becomes Key<#key_args>.
|
// ... but the real type of the const becomes Key<#key_args>.
|
||||||
let key_params = &impl_generics.params;
|
|
||||||
let key_args = quote! { #value_ty #(, #self_args)* };
|
let key_args = quote! { #value_ty #(, #self_args)* };
|
||||||
|
|
||||||
// The display name, e.g. `TextNode::STRONG`.
|
// The display name, e.g. `TextNode::STRONG`.
|
||||||
@ -107,7 +140,7 @@ fn process_const(
|
|||||||
|
|
||||||
let mut folder = None;
|
let mut folder = None;
|
||||||
let mut nonfolding = Some(quote! {
|
let mut nonfolding = Some(quote! {
|
||||||
impl<#key_params> Nonfolding for Key<#key_args> {}
|
impl<#params> Nonfolding for Key<#key_args> {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Look for a folding function like `#[fold(u64::add)]`.
|
// Look for a folding function like `#[fold(u64::add)]`.
|
||||||
@ -132,16 +165,16 @@ fn process_const(
|
|||||||
mod #module_name {
|
mod #module_name {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub struct Key<T, #key_params>(pub PhantomData<(T, #key_args)>);
|
pub struct Key<VALUE, #params>(pub PhantomData<(VALUE, #key_args)>);
|
||||||
|
|
||||||
impl<#key_params> Copy for Key<#key_args> {}
|
impl<#params> Copy for Key<#key_args> {}
|
||||||
impl<#key_params> Clone for Key<#key_args> {
|
impl<#params> Clone for Key<#key_args> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<#key_params> Property for Key<#key_args> {
|
impl<#params> Property for Key<#key_args> {
|
||||||
type Value = #value_ty;
|
type Value = #value_ty;
|
||||||
|
|
||||||
const NAME: &'static str = #name;
|
const NAME: &'static str = #name;
|
||||||
|
@ -248,7 +248,7 @@ impl Eval for ListNode {
|
|||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
Ok(Node::block(library::ListNode {
|
Ok(Node::block(library::ListNode {
|
||||||
child: self.body().eval(ctx)?.into_block(),
|
child: self.body().eval(ctx)?.into_block(),
|
||||||
labelling: library::Unordered,
|
kind: library::Unordered,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +259,7 @@ impl Eval for EnumNode {
|
|||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
Ok(Node::block(library::ListNode {
|
Ok(Node::block(library::ListNode {
|
||||||
child: self.body().eval(ctx)?.into_block(),
|
child: self.body().eval(ctx)?.into_block(),
|
||||||
labelling: library::Ordered(self.number()),
|
kind: library::Ordered(self.number()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,6 +450,7 @@ impl Eval for CallExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
|
let span = self.callee().span();
|
||||||
let callee = self.callee().eval(ctx)?;
|
let callee = self.callee().eval(ctx)?;
|
||||||
let mut args = self.args().eval(ctx)?;
|
let mut args = self.args().eval(ctx)?;
|
||||||
|
|
||||||
@ -470,13 +471,14 @@ impl Eval for CallExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Value::Class(class) => {
|
Value::Class(class) => {
|
||||||
let node = class.construct(ctx, &mut args)?;
|
let point = || Tracepoint::Call(Some(class.name().to_string()));
|
||||||
|
let node = class.construct(ctx, &mut args).trace(point, self.span())?;
|
||||||
args.finish()?;
|
args.finish()?;
|
||||||
Ok(Value::Node(node))
|
Ok(Value::Node(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
v => bail!(
|
v => bail!(
|
||||||
self.callee().span(),
|
span,
|
||||||
"expected callable or collection, found {}",
|
"expected callable or collection, found {}",
|
||||||
v.type_name(),
|
v.type_name(),
|
||||||
),
|
),
|
||||||
|
@ -10,7 +10,7 @@ use crate::diag::StrResult;
|
|||||||
use crate::geom::SpecAxis;
|
use crate::geom::SpecAxis;
|
||||||
use crate::layout::{Layout, PackedNode, RootNode};
|
use crate::layout::{Layout, PackedNode, RootNode};
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, TextNode,
|
FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, TextNode,
|
||||||
};
|
};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -98,6 +98,10 @@ impl Node {
|
|||||||
|
|
||||||
/// Style this node with a full style map.
|
/// Style this node with a full style map.
|
||||||
pub fn styled_with_map(mut self, styles: StyleMap) -> Self {
|
pub fn styled_with_map(mut self, styles: StyleMap) -> Self {
|
||||||
|
if styles.is_empty() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
if let Self::Sequence(vec) = &mut self {
|
if let Self::Sequence(vec) = &mut self {
|
||||||
if let [styled] = vec.as_mut_slice() {
|
if let [styled] = vec.as_mut_slice() {
|
||||||
styled.map.apply(&styles);
|
styled.map.apply(&styles);
|
||||||
@ -193,7 +197,7 @@ impl Packer {
|
|||||||
|
|
||||||
/// Finish up and return the resulting flow.
|
/// Finish up and return the resulting flow.
|
||||||
fn into_block(mut self) -> PackedNode {
|
fn into_block(mut self) -> PackedNode {
|
||||||
self.parbreak(None);
|
self.parbreak(None, false);
|
||||||
FlowNode(self.flow.children).pack()
|
FlowNode(self.flow.children).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +213,7 @@ impl Packer {
|
|||||||
Node::Space => {
|
Node::Space => {
|
||||||
// A text space is "soft", meaning that it can be eaten up by
|
// A text space is "soft", meaning that it can be eaten up by
|
||||||
// adjacent line breaks or explicit spacings.
|
// adjacent line breaks or explicit spacings.
|
||||||
self.par.last.soft(Styled::new(ParChild::text(' '), styles));
|
self.par.last.soft(Styled::new(ParChild::text(' '), styles), false);
|
||||||
}
|
}
|
||||||
Node::Linebreak => {
|
Node::Linebreak => {
|
||||||
// A line break eats up surrounding text spaces.
|
// A line break eats up surrounding text spaces.
|
||||||
@ -222,12 +226,12 @@ impl Packer {
|
|||||||
// styles (`Some(_)`) whereas paragraph breaks forced by
|
// styles (`Some(_)`) whereas paragraph breaks forced by
|
||||||
// incompatibility take their styles from the preceding
|
// incompatibility take their styles from the preceding
|
||||||
// paragraph.
|
// paragraph.
|
||||||
self.parbreak(Some(styles));
|
self.parbreak(Some(styles), true);
|
||||||
}
|
}
|
||||||
Node::Colbreak => {
|
Node::Colbreak => {
|
||||||
// Explicit column breaks end the current paragraph and then
|
// Explicit column breaks end the current paragraph and then
|
||||||
// discards the paragraph break.
|
// discards the paragraph break.
|
||||||
self.parbreak(None);
|
self.parbreak(None, false);
|
||||||
self.make_flow_compatible(&styles);
|
self.make_flow_compatible(&styles);
|
||||||
self.flow.children.push(Styled::new(FlowChild::Skip, styles));
|
self.flow.children.push(Styled::new(FlowChild::Skip, styles));
|
||||||
self.flow.last.hard();
|
self.flow.last.hard();
|
||||||
@ -252,7 +256,7 @@ impl Packer {
|
|||||||
Node::Spacing(SpecAxis::Vertical, kind) => {
|
Node::Spacing(SpecAxis::Vertical, kind) => {
|
||||||
// Explicit vertical spacing ends the current paragraph and then
|
// Explicit vertical spacing ends the current paragraph and then
|
||||||
// discards the paragraph break.
|
// discards the paragraph break.
|
||||||
self.parbreak(None);
|
self.parbreak(None, false);
|
||||||
self.make_flow_compatible(&styles);
|
self.make_flow_compatible(&styles);
|
||||||
self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles));
|
self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles));
|
||||||
self.flow.last.hard();
|
self.flow.last.hard();
|
||||||
@ -284,14 +288,15 @@ impl Packer {
|
|||||||
|
|
||||||
/// Insert an inline-level element into the current paragraph.
|
/// Insert an inline-level element into the current paragraph.
|
||||||
fn push_inline(&mut self, child: Styled<ParChild>) {
|
fn push_inline(&mut self, child: Styled<ParChild>) {
|
||||||
if let Some(styled) = self.par.last.any() {
|
|
||||||
self.push_coalescing(styled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The node must be both compatible with the current page and the
|
// The node must be both compatible with the current page and the
|
||||||
// current paragraph.
|
// current paragraph.
|
||||||
self.make_flow_compatible(&child.map);
|
self.make_flow_compatible(&child.map);
|
||||||
self.make_par_compatible(&child.map);
|
self.make_par_compatible(&child.map);
|
||||||
|
|
||||||
|
if let Some(styled) = self.par.last.any() {
|
||||||
|
self.push_coalescing(styled);
|
||||||
|
}
|
||||||
|
|
||||||
self.push_coalescing(child);
|
self.push_coalescing(child);
|
||||||
self.par.last.any();
|
self.par.last.any();
|
||||||
}
|
}
|
||||||
@ -314,13 +319,13 @@ impl Packer {
|
|||||||
|
|
||||||
/// Insert a block-level element into the current flow.
|
/// Insert a block-level element into the current flow.
|
||||||
fn push_block(&mut self, node: Styled<PackedNode>) {
|
fn push_block(&mut self, node: Styled<PackedNode>) {
|
||||||
let placed = node.item.is::<PlacedNode>();
|
let placed = node.item.is::<PlaceNode>();
|
||||||
|
|
||||||
self.parbreak(None);
|
self.parbreak(Some(node.map.clone()), false);
|
||||||
self.make_flow_compatible(&node.map);
|
self.make_flow_compatible(&node.map);
|
||||||
self.flow.children.extend(self.flow.last.any());
|
self.flow.children.extend(self.flow.last.any());
|
||||||
self.flow.children.push(node.map(FlowChild::Node));
|
self.flow.children.push(node.map(FlowChild::Node));
|
||||||
self.parbreak(None);
|
self.parbreak(None, false);
|
||||||
|
|
||||||
// Prevent paragraph spacing between the placed node and the paragraph
|
// Prevent paragraph spacing between the placed node and the paragraph
|
||||||
// below it.
|
// below it.
|
||||||
@ -330,18 +335,13 @@ impl Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Advance to the next paragraph.
|
/// Advance to the next paragraph.
|
||||||
fn parbreak(&mut self, break_styles: Option<StyleMap>) {
|
fn parbreak(&mut self, break_styles: Option<StyleMap>, important: bool) {
|
||||||
// Erase any styles that will be inherited anyway.
|
// Erase any styles that will be inherited anyway.
|
||||||
let Builder { mut children, styles, .. } = mem::take(&mut self.par);
|
let Builder { mut children, styles, .. } = mem::take(&mut self.par);
|
||||||
for Styled { map, .. } in &mut children {
|
for Styled { map, .. } in &mut children {
|
||||||
map.erase(&styles);
|
map.erase(&styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For explicit paragraph breaks, `break_styles` is already `Some(_)`.
|
|
||||||
// For page breaks due to incompatibility, we fall back to the styles
|
|
||||||
// of the preceding paragraph.
|
|
||||||
let break_styles = break_styles.unwrap_or_else(|| styles.clone());
|
|
||||||
|
|
||||||
// We don't want empty paragraphs.
|
// We don't want empty paragraphs.
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
// The paragraph's children are all compatible with the page, so the
|
// The paragraph's children are all compatible with the page, so the
|
||||||
@ -352,14 +352,30 @@ impl Packer {
|
|||||||
self.flow.children.push(Styled::new(FlowChild::Node(par), styles));
|
self.flow.children.push(Styled::new(FlowChild::Node(par), styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actually styled breaks have precedence over whatever was before.
|
||||||
|
if break_styles.is_some() {
|
||||||
|
if let Last::Soft(_, false) = self.flow.last {
|
||||||
|
self.flow.last = Last::Any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For explicit paragraph breaks, `break_styles` is already `Some(_)`.
|
||||||
|
// For page breaks due to incompatibility, we fall back to the styles
|
||||||
|
// of the preceding thing.
|
||||||
|
let break_styles = break_styles
|
||||||
|
.or_else(|| self.flow.children.last().map(|styled| styled.map.clone()))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Insert paragraph spacing.
|
// Insert paragraph spacing.
|
||||||
self.flow.last.soft(Styled::new(FlowChild::Break, break_styles));
|
self.flow
|
||||||
|
.last
|
||||||
|
.soft(Styled::new(FlowChild::Break, break_styles), important);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advance to the next page.
|
/// Advance to the next page.
|
||||||
fn pagebreak(&mut self) {
|
fn pagebreak(&mut self) {
|
||||||
if self.top {
|
if self.top {
|
||||||
self.parbreak(None);
|
self.parbreak(None, false);
|
||||||
|
|
||||||
// Take the flow and erase any styles that will be inherited anyway.
|
// Take the flow and erase any styles that will be inherited anyway.
|
||||||
let Builder { mut children, styles, .. } = mem::take(&mut self.flow);
|
let Builder { mut children, styles, .. } = mem::take(&mut self.flow);
|
||||||
@ -381,7 +397,7 @@ impl Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.par.styles.compatible::<ParNode>(styles) {
|
if !self.par.styles.compatible::<ParNode>(styles) {
|
||||||
self.parbreak(None);
|
self.parbreak(Some(styles.clone()), false);
|
||||||
self.par.styles = styles.clone();
|
self.par.styles = styles.clone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -441,8 +457,10 @@ enum Last<N> {
|
|||||||
/// Hard nodes: Linebreaks and explicit spacing.
|
/// Hard nodes: Linebreaks and explicit spacing.
|
||||||
Hard,
|
Hard,
|
||||||
/// Soft nodes: Word spaces and paragraph breaks. These are saved here
|
/// Soft nodes: Word spaces and paragraph breaks. These are saved here
|
||||||
/// temporarily and then applied once an `Any` node appears.
|
/// temporarily and then applied once an `Any` node appears. The boolean
|
||||||
Soft(N),
|
/// says whether this soft node is "important" and preferrable to other soft
|
||||||
|
/// nodes (that is the case for explicit paragraph breaks).
|
||||||
|
Soft(N, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N> Last<N> {
|
impl<N> Last<N> {
|
||||||
@ -450,16 +468,19 @@ impl<N> Last<N> {
|
|||||||
/// now if currently in `Soft` state.
|
/// now if currently in `Soft` state.
|
||||||
fn any(&mut self) -> Option<N> {
|
fn any(&mut self) -> Option<N> {
|
||||||
match mem::replace(self, Self::Any) {
|
match mem::replace(self, Self::Any) {
|
||||||
Self::Soft(soft) => Some(soft),
|
Self::Soft(soft, _) => Some(soft),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transition into the `Soft` state, but only if in `Any`. Otherwise, the
|
/// Transition into the `Soft` state, but only if in `Any`. Otherwise, the
|
||||||
/// soft node is discarded.
|
/// soft node is discarded.
|
||||||
fn soft(&mut self, soft: N) {
|
fn soft(&mut self, soft: N, important: bool) {
|
||||||
if let Self::Any = self {
|
if matches!(
|
||||||
*self = Self::Soft(soft);
|
(&self, important),
|
||||||
|
(Self::Any, _) | (Self::Soft(_, false), true)
|
||||||
|
) {
|
||||||
|
*self = Self::Soft(soft, important);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +397,13 @@ primitive! { EcoString: "string", Str }
|
|||||||
primitive! { Array: "array", Array }
|
primitive! { Array: "array", Array }
|
||||||
primitive! { Dict: "dictionary", Dict }
|
primitive! { Dict: "dictionary", Dict }
|
||||||
primitive! { Node: "template", Node }
|
primitive! { Node: "template", Node }
|
||||||
primitive! { Function: "function", Func }
|
primitive! { Function: "function",
|
||||||
|
Func,
|
||||||
|
Class(v) => Function::new(
|
||||||
|
Some(v.name().clone()),
|
||||||
|
move |ctx, args| v.construct(ctx, args).map(Value::Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
primitive! { Class: "class", Class }
|
primitive! { Class: "class", Class }
|
||||||
|
|
||||||
impl Cast<Value> for Value {
|
impl Cast<Value> for Value {
|
||||||
|
@ -30,7 +30,7 @@ impl Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A scaling transform.
|
/// A scaling transform.
|
||||||
pub const fn scaling(sx: Relative, sy: Relative) -> Self {
|
pub const fn scale(sx: Relative, sy: Relative) -> Self {
|
||||||
Self { sx, sy, ..Self::identity() }
|
Self { sx, sy, ..Self::identity() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ use std::rc::Rc;
|
|||||||
use crate::eval::{StyleChain, Styled};
|
use crate::eval::{StyleChain, Styled};
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform};
|
use crate::geom::{Align, Linear, Point, Sides, Size, Spec};
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::library::{AlignNode, PadNode, PageNode, SizedNode, TransformNode};
|
use crate::library::{AlignNode, Move, PadNode, PageNode, SizedNode, TransformNode};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// The root layout node, a document consisting of top-level page runs.
|
/// The root layout node, a document consisting of top-level page runs.
|
||||||
@ -177,13 +177,12 @@ impl PackedNode {
|
|||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
/// Transform this node's contents without affecting layout.
|
||||||
pub fn moved(self, offset: Point) -> Self {
|
pub fn moved(self, offset: Point) -> Self {
|
||||||
self.transformed(Transform::translation(offset.x, offset.y), Align::LEFT_TOP)
|
if !offset.is_zero() {
|
||||||
}
|
TransformNode {
|
||||||
|
kind: Move(offset.x, offset.y),
|
||||||
/// Transform this node's contents without affecting layout.
|
child: self,
|
||||||
pub fn transformed(self, transform: Transform, origin: Spec<Align>) -> Self {
|
}
|
||||||
if !transform.is_identity() {
|
.pack()
|
||||||
TransformNode { transform, origin, child: self }.pack()
|
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::ParNode;
|
use super::ParNode;
|
||||||
|
|
||||||
/// `align`: Configure the alignment along the layouting axes.
|
/// Align a node along the layouting axes.
|
||||||
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let aligns: Spec<_> = args.find().unwrap_or_default();
|
|
||||||
let body: PackedNode = args.expect("body")?;
|
|
||||||
Ok(Value::block(body.aligned(aligns)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that aligns its child.
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct AlignNode {
|
pub struct AlignNode {
|
||||||
/// How to align the node horizontally and vertically.
|
/// How to align the node horizontally and vertically.
|
||||||
@ -19,6 +12,15 @@ pub struct AlignNode {
|
|||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl AlignNode {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let aligns: Spec<_> = args.find().unwrap_or_default();
|
||||||
|
let body: PackedNode = args.expect("body")?;
|
||||||
|
Ok(Node::block(body.aligned(aligns)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for AlignNode {
|
impl Layout for AlignNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
@ -3,32 +3,34 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::ParNode;
|
use super::ParNode;
|
||||||
|
|
||||||
/// `columns`: Set content into multiple columns.
|
|
||||||
pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
Ok(Value::block(ColumnsNode {
|
|
||||||
columns: args.expect("column count")?,
|
|
||||||
gutter: args.named("gutter")?.unwrap_or(Relative::new(0.04).into()),
|
|
||||||
child: args.expect("body")?,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `colbreak`: Start a new column.
|
|
||||||
pub fn colbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
|
||||||
Ok(Value::Node(Node::Colbreak))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that separates a region into multiple equally sized columns.
|
/// A node that separates a region into multiple equally sized columns.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ColumnsNode {
|
pub struct ColumnsNode {
|
||||||
/// How many columns there should be.
|
/// How many columns there should be.
|
||||||
pub columns: NonZeroUsize,
|
pub columns: NonZeroUsize,
|
||||||
/// The size of the gutter space between each column.
|
|
||||||
pub gutter: Linear,
|
|
||||||
/// The child to be layouted into the columns. Most likely, this should be a
|
/// The child to be layouted into the columns. Most likely, this should be a
|
||||||
/// flow or stack node.
|
/// flow or stack node.
|
||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl ColumnsNode {
|
||||||
|
/// The size of the gutter space between each column.
|
||||||
|
pub const GUTTER: Linear = Relative::new(0.04).into();
|
||||||
|
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::block(Self {
|
||||||
|
columns: args.expect("column count")?,
|
||||||
|
child: args.expect("body")?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
|
styles.set_opt(Self::GUTTER, args.named("gutter")?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -57,7 +59,7 @@ impl Layout for ColumnsNode {
|
|||||||
.iter()
|
.iter()
|
||||||
.take(1 + regions.backlog.len() + regions.last.iter().len())
|
.take(1 + regions.backlog.len() + regions.last.iter().len())
|
||||||
{
|
{
|
||||||
let gutter = self.gutter.resolve(base.x);
|
let gutter = styles.get(Self::GUTTER).resolve(base.x);
|
||||||
let width = (current.x - gutter * (columns - 1) as f64) / columns as f64;
|
let width = (current.x - gutter * (columns - 1) as f64) / columns as f64;
|
||||||
let size = Size::new(width, current.y);
|
let size = Size::new(width, current.y);
|
||||||
gutters.push(gutter);
|
gutters.push(gutter);
|
||||||
@ -131,3 +133,13 @@ impl Layout for ColumnsNode {
|
|||||||
finished
|
finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A column break.
|
||||||
|
pub struct ColbreakNode;
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl ColbreakNode {
|
||||||
|
fn construct(_: &mut EvalContext, _: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Colbreak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
79
src/library/deco.rs
Normal file
79
src/library/deco.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
//! Text decorations.
|
||||||
|
|
||||||
|
use super::prelude::*;
|
||||||
|
use super::TextNode;
|
||||||
|
|
||||||
|
/// Typeset underline, striken-through or overlined text.
|
||||||
|
pub struct DecoNode<L: LineKind>(pub L);
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl<L: LineKind> DecoNode<L> {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let deco = Decoration {
|
||||||
|
line: L::LINE,
|
||||||
|
stroke: args.named("stroke")?.or_else(|| args.find()),
|
||||||
|
thickness: args.named::<Linear>("thickness")?.or_else(|| args.find()),
|
||||||
|
offset: args.named("offset")?,
|
||||||
|
extent: args.named("extent")?.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
Ok(args.expect::<Node>("body")?.styled(TextNode::LINES, vec![deco]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a line that is positioned over, under or on top of text.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Decoration {
|
||||||
|
/// Which line to draw.
|
||||||
|
pub line: DecoLine,
|
||||||
|
/// Stroke color of the line, defaults to the text color if `None`.
|
||||||
|
pub stroke: Option<Paint>,
|
||||||
|
/// Thickness of the line's strokes (dependent on scaled font size), read
|
||||||
|
/// from the font tables if `None`.
|
||||||
|
pub thickness: Option<Linear>,
|
||||||
|
/// Position of the line relative to the baseline (dependent on scaled font
|
||||||
|
/// size), read from the font tables if `None`.
|
||||||
|
pub offset: Option<Linear>,
|
||||||
|
/// Amount that the line will be longer or shorter than its associated text
|
||||||
|
/// (dependent on scaled font size).
|
||||||
|
pub extent: Linear,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of decorative line.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum DecoLine {
|
||||||
|
/// A line under text.
|
||||||
|
Underline,
|
||||||
|
/// A line through text.
|
||||||
|
Strikethrough,
|
||||||
|
/// A line over text.
|
||||||
|
Overline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Differents kinds of decorative lines for text.
|
||||||
|
pub trait LineKind {
|
||||||
|
const LINE: DecoLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A line under text.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Underline;
|
||||||
|
|
||||||
|
impl LineKind for Underline {
|
||||||
|
const LINE: DecoLine = DecoLine::Underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A line through text.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Strikethrough;
|
||||||
|
|
||||||
|
impl LineKind for Strikethrough {
|
||||||
|
const LINE: DecoLine = DecoLine::Strikethrough;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A line over text.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Overline;
|
||||||
|
|
||||||
|
impl LineKind for Overline {
|
||||||
|
const LINE: DecoLine = DecoLine::Overline;
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, ParNode, PlacedNode, SpacingKind, TextNode};
|
use super::{AlignNode, ParNode, PlaceNode, SpacingKind, TextNode};
|
||||||
|
|
||||||
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
||||||
///
|
///
|
||||||
@ -172,7 +172,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
) {
|
) {
|
||||||
// Placed nodes that are out of flow produce placed items which aren't
|
// Placed nodes that are out of flow produce placed items which aren't
|
||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = node.downcast::<PlacedNode>() {
|
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
let frame = node.layout(ctx, &self.regions, styles).remove(0);
|
let frame = node.layout(ctx, &self.regions, styles).remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame.item));
|
self.items.push(FlowItem::Placed(frame.item));
|
||||||
|
@ -2,23 +2,6 @@
|
|||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// `grid`: Arrange children into a grid.
|
|
||||||
pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
|
||||||
let rows = args.named("rows")?.unwrap_or_default();
|
|
||||||
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
|
||||||
let column_gutter = args.named("column-gutter")?;
|
|
||||||
let row_gutter = args.named("row-gutter")?;
|
|
||||||
Ok(Value::block(GridNode {
|
|
||||||
tracks: Spec::new(columns, rows),
|
|
||||||
gutter: Spec::new(
|
|
||||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
|
||||||
row_gutter.unwrap_or(base_gutter),
|
|
||||||
),
|
|
||||||
children: args.all().collect(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that arranges its children in a grid.
|
/// A node that arranges its children in a grid.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct GridNode {
|
pub struct GridNode {
|
||||||
@ -30,6 +13,25 @@ pub struct GridNode {
|
|||||||
pub children: Vec<PackedNode>,
|
pub children: Vec<PackedNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl GridNode {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
|
let rows = args.named("rows")?.unwrap_or_default();
|
||||||
|
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
||||||
|
let column_gutter = args.named("column-gutter")?;
|
||||||
|
let row_gutter = args.named("row-gutter")?;
|
||||||
|
Ok(Node::block(Self {
|
||||||
|
tracks: Spec::new(columns, rows),
|
||||||
|
gutter: Spec::new(
|
||||||
|
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||||
|
row_gutter.unwrap_or(base_gutter),
|
||||||
|
),
|
||||||
|
children: args.all().collect(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
@ -13,25 +13,21 @@ pub struct HeadingNode {
|
|||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
/// The heading's font family.
|
/// The heading's font family.
|
||||||
pub const FAMILY: Smart<FontFamily> = Smart::Auto;
|
pub const FAMILY: Smart<FontFamily> = Smart::Auto;
|
||||||
/// The fill color of text in the heading. Just the surrounding text color
|
/// The fill color of text in the heading. Just the surrounding text color
|
||||||
/// if `auto`.
|
/// if `auto`.
|
||||||
pub const FILL: Smart<Paint> = Smart::Auto;
|
pub const FILL: Smart<Paint> = Smart::Auto;
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for HeadingNode {
|
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
Ok(Node::block(Self {
|
Ok(Node::block(Self {
|
||||||
child: args.expect("body")?,
|
child: args.expect("body")?,
|
||||||
level: args.named("level")?.unwrap_or(1),
|
level: args.named("level")?.unwrap_or(1),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Set for HeadingNode {
|
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
styles.set_opt(Self::FAMILY, args.named("family")?);
|
styles.set_opt(Self::FAMILY, args.named("family")?);
|
||||||
styles.set_opt(Self::FILL, args.named("fill")?);
|
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||||
|
@ -1,45 +1,41 @@
|
|||||||
//! Raster and vector graphics.
|
//! Raster and vector graphics.
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use super::TextNode;
|
||||||
use crate::diag::Error;
|
use crate::diag::Error;
|
||||||
use crate::image::ImageId;
|
use crate::image::ImageId;
|
||||||
|
|
||||||
/// `image`: An image.
|
|
||||||
pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
// Load the image.
|
|
||||||
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
|
||||||
let full = ctx.make_path(&path.v);
|
|
||||||
let id = ctx.images.load(&full).map_err(|err| {
|
|
||||||
Error::boxed(path.span, match err.kind() {
|
|
||||||
io::ErrorKind::NotFound => "file not found".into(),
|
|
||||||
_ => format!("failed to load image ({})", err),
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let width = args.named("width")?;
|
|
||||||
let height = args.named("height")?;
|
|
||||||
let fit = args.named("fit")?.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(Value::inline(
|
|
||||||
ImageNode { id, fit }.pack().sized(Spec::new(width, height)),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An image node.
|
/// An image node.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ImageNode {
|
pub struct ImageNode(pub ImageId);
|
||||||
/// The id of the image file.
|
|
||||||
pub id: ImageId,
|
|
||||||
/// How the image should adjust itself to a given area.
|
|
||||||
pub fit: ImageFit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl ImageNode {
|
impl ImageNode {
|
||||||
/// An URL the image should link to.
|
/// How the image should adjust itself to a given area.
|
||||||
pub const LINK: Option<String> = None;
|
pub const FIT: ImageFit = ImageFit::Cover;
|
||||||
|
|
||||||
|
fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
|
let full = ctx.make_path(&path.v);
|
||||||
|
let id = ctx.images.load(&full).map_err(|err| {
|
||||||
|
Error::boxed(path.span, match err.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => "file not found".into(),
|
||||||
|
_ => format!("failed to load image ({})", err),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let width = args.named("width")?;
|
||||||
|
let height = args.named("height")?;
|
||||||
|
|
||||||
|
Ok(Node::inline(
|
||||||
|
ImageNode(id).pack().sized(Spec::new(width, height)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
|
styles.set_opt(Self::FIT, args.named("fit")?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
@ -49,7 +45,7 @@ impl Layout for ImageNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let img = ctx.images.get(self.id);
|
let img = ctx.images.get(self.0);
|
||||||
let pxw = img.width() as f64;
|
let pxw = img.width() as f64;
|
||||||
let pxh = img.height() as f64;
|
let pxh = img.height() as f64;
|
||||||
let px_ratio = pxw / pxh;
|
let px_ratio = pxw / pxh;
|
||||||
@ -70,10 +66,11 @@ impl Layout for ImageNode {
|
|||||||
Size::new(Length::pt(pxw), Length::pt(pxh))
|
Size::new(Length::pt(pxw), Length::pt(pxh))
|
||||||
};
|
};
|
||||||
|
|
||||||
// The actual size of the fitted image.
|
// Compute the actual size of the fitted image.
|
||||||
let fitted = match self.fit {
|
let fit = styles.get(Self::FIT);
|
||||||
|
let fitted = match fit {
|
||||||
ImageFit::Cover | ImageFit::Contain => {
|
ImageFit::Cover | ImageFit::Contain => {
|
||||||
if wide == (self.fit == ImageFit::Contain) {
|
if wide == (fit == ImageFit::Contain) {
|
||||||
Size::new(target.x, target.x / px_ratio)
|
Size::new(target.x, target.x / px_ratio)
|
||||||
} else {
|
} else {
|
||||||
Size::new(target.y * px_ratio, target.y)
|
Size::new(target.y * px_ratio, target.y)
|
||||||
@ -86,16 +83,16 @@ impl Layout for ImageNode {
|
|||||||
// the frame to the target size, center aligning the image in the
|
// the frame to the target size, center aligning the image in the
|
||||||
// process.
|
// process.
|
||||||
let mut frame = Frame::new(fitted);
|
let mut frame = Frame::new(fitted);
|
||||||
frame.push(Point::zero(), Element::Image(self.id, fitted));
|
frame.push(Point::zero(), Element::Image(self.0, fitted));
|
||||||
frame.resize(target, Align::CENTER_HORIZON);
|
frame.resize(target, Align::CENTER_HORIZON);
|
||||||
|
|
||||||
// Create a clipping group if only part of the image should be visible.
|
// Create a clipping group if only part of the image should be visible.
|
||||||
if self.fit == ImageFit::Cover && !target.fits(fitted) {
|
if fit == ImageFit::Cover && !target.fits(fitted) {
|
||||||
frame.clip();
|
frame.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get_ref(Self::LINK) {
|
if let Some(url) = styles.get_ref(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,12 +111,6 @@ pub enum ImageFit {
|
|||||||
Stretch,
|
Stretch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ImageFit {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Cover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
ImageFit,
|
ImageFit,
|
||||||
Expected: "string",
|
Expected: "string",
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
//! Hyperlinking.
|
//! Hyperlinking.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{ImageNode, ShapeNode, TextNode};
|
use super::TextNode;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// `link`: Link text and other elements to an URL.
|
/// Link text and other elements to an URL.
|
||||||
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub struct LinkNode;
|
||||||
let url: String = args.expect::<EcoString>("url")?.into();
|
|
||||||
let body = args.find().unwrap_or_else(|| {
|
|
||||||
let mut text = url.as_str();
|
|
||||||
for prefix in ["mailto:", "tel:"] {
|
|
||||||
text = text.trim_start_matches(prefix);
|
|
||||||
}
|
|
||||||
Node::Text(text.into())
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
#[class]
|
||||||
map.set(TextNode::LINK, Some(url.clone()));
|
impl LinkNode {
|
||||||
map.set(ImageNode::LINK, Some(url.clone()));
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
map.set(ShapeNode::LINK, Some(url));
|
let url: String = args.expect::<EcoString>("url")?.into();
|
||||||
Ok(Value::Node(body.styled_with_map(map)))
|
let body = args.find().unwrap_or_else(|| {
|
||||||
|
let mut text = url.as_str();
|
||||||
|
for prefix in ["mailto:", "tel:"] {
|
||||||
|
text = text.trim_start_matches(prefix);
|
||||||
|
}
|
||||||
|
Node::Text(text.into())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(body.styled(TextNode::LINK, Some(url)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,31 @@
|
|||||||
//! Unordered (bulleted) and ordered (numbered) lists.
|
//! Unordered (bulleted) and ordered (numbered) lists.
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{GridNode, TextNode, TrackSizing};
|
use super::{GridNode, TextNode, TrackSizing};
|
||||||
|
|
||||||
/// An unordered or ordered list.
|
/// An unordered or ordered list.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ListNode<L> {
|
pub struct ListNode<L: ListKind> {
|
||||||
|
/// The list labelling style -- unordered or ordered.
|
||||||
|
pub kind: L,
|
||||||
/// The node that produces the item's body.
|
/// The node that produces the item's body.
|
||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
/// The list labelling style -- unordered or ordered.
|
|
||||||
pub labelling: L,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl<L: Labelling> ListNode<L> {
|
impl<L: ListKind> ListNode<L> {
|
||||||
/// The indentation of each item's label.
|
/// The indentation of each item's label.
|
||||||
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
|
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
|
||||||
/// The space between the label and the body of each item.
|
/// The space between the label and the body of each item.
|
||||||
pub const BODY_INDENT: Linear = Relative::new(0.5).into();
|
pub const BODY_INDENT: Linear = Relative::new(0.5).into();
|
||||||
}
|
|
||||||
|
|
||||||
impl<L: Labelling> Construct for ListNode<L> {
|
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
Ok(args
|
Ok(args
|
||||||
.all()
|
.all()
|
||||||
.map(|child: PackedNode| Node::block(Self { child, labelling: L::default() }))
|
.map(|child: PackedNode| Node::block(Self { kind: L::default(), child }))
|
||||||
.sum())
|
.sum())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<L: Labelling> Set for ListNode<L> {
|
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?);
|
styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?);
|
||||||
styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?);
|
styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?);
|
||||||
@ -39,7 +33,7 @@ impl<L: Labelling> Set for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<L: Labelling> Layout for ListNode<L> {
|
impl<L: ListKind> Layout for ListNode<L> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -60,7 +54,7 @@ impl<L: Labelling> Layout for ListNode<L> {
|
|||||||
gutter: Spec::default(),
|
gutter: Spec::default(),
|
||||||
children: vec![
|
children: vec![
|
||||||
PackedNode::default(),
|
PackedNode::default(),
|
||||||
Node::Text(self.labelling.label()).into_block(),
|
Node::Text(self.kind.label()).into_block(),
|
||||||
PackedNode::default(),
|
PackedNode::default(),
|
||||||
self.child.clone(),
|
self.child.clone(),
|
||||||
],
|
],
|
||||||
@ -71,7 +65,7 @@ impl<L: Labelling> Layout for ListNode<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// How to label a list.
|
/// How to label a list.
|
||||||
pub trait Labelling: Debug + Default + Hash + 'static {
|
pub trait ListKind: Debug + Default + Hash + 'static {
|
||||||
/// Return the item's label.
|
/// Return the item's label.
|
||||||
fn label(&self) -> EcoString;
|
fn label(&self) -> EcoString;
|
||||||
}
|
}
|
||||||
@ -80,7 +74,7 @@ pub trait Labelling: Debug + Default + Hash + 'static {
|
|||||||
#[derive(Debug, Default, Hash)]
|
#[derive(Debug, Default, Hash)]
|
||||||
pub struct Unordered;
|
pub struct Unordered;
|
||||||
|
|
||||||
impl Labelling for Unordered {
|
impl ListKind for Unordered {
|
||||||
fn label(&self) -> EcoString {
|
fn label(&self) -> EcoString {
|
||||||
'•'.into()
|
'•'.into()
|
||||||
}
|
}
|
||||||
@ -90,7 +84,7 @@ impl Labelling for Unordered {
|
|||||||
#[derive(Debug, Default, Hash)]
|
#[derive(Debug, Default, Hash)]
|
||||||
pub struct Ordered(pub Option<usize>);
|
pub struct Ordered(pub Option<usize>);
|
||||||
|
|
||||||
impl Labelling for Ordered {
|
impl ListKind for Ordered {
|
||||||
fn label(&self) -> EcoString {
|
fn label(&self) -> EcoString {
|
||||||
format_eco!("{}.", self.0.unwrap_or(1))
|
format_eco!("{}.", self.0.unwrap_or(1))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
pub mod align;
|
pub mod align;
|
||||||
pub mod columns;
|
pub mod columns;
|
||||||
|
pub mod deco;
|
||||||
pub mod flow;
|
pub mod flow;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
pub mod heading;
|
pub mod heading;
|
||||||
@ -26,6 +27,7 @@ pub mod utility;
|
|||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
pub use align::*;
|
pub use align::*;
|
||||||
pub use columns::*;
|
pub use columns::*;
|
||||||
|
pub use deco::*;
|
||||||
pub use flow::*;
|
pub use flow::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
pub use heading::*;
|
pub use heading::*;
|
||||||
@ -56,8 +58,9 @@ prelude! {
|
|||||||
pub use std::fmt::{self, Debug, Formatter};
|
pub use std::fmt::{self, Debug, Formatter};
|
||||||
pub use std::num::NonZeroUsize;
|
pub use std::num::NonZeroUsize;
|
||||||
pub use std::rc::Rc;
|
pub use std::rc::Rc;
|
||||||
|
pub use std::hash::Hash;
|
||||||
|
|
||||||
pub use typst_macros::properties;
|
pub use typst_macros::class;
|
||||||
|
|
||||||
pub use crate::diag::{At, TypResult};
|
pub use crate::diag::{At, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
@ -81,41 +84,39 @@ pub fn new() -> Scope {
|
|||||||
|
|
||||||
// Structure and semantics.
|
// Structure and semantics.
|
||||||
std.def_class::<PageNode>("page");
|
std.def_class::<PageNode>("page");
|
||||||
|
std.def_class::<PagebreakNode>("pagebreak");
|
||||||
std.def_class::<ParNode>("par");
|
std.def_class::<ParNode>("par");
|
||||||
|
std.def_class::<ParbreakNode>("parbreak");
|
||||||
|
std.def_class::<LinebreakNode>("linebreak");
|
||||||
std.def_class::<TextNode>("text");
|
std.def_class::<TextNode>("text");
|
||||||
std.def_func("underline", underline);
|
std.def_class::<DecoNode<Underline>>("underline");
|
||||||
std.def_func("strike", strike);
|
std.def_class::<DecoNode<Strikethrough>>("strike");
|
||||||
std.def_func("overline", overline);
|
std.def_class::<DecoNode<Overline>>("overline");
|
||||||
std.def_func("link", link);
|
std.def_class::<LinkNode>("link");
|
||||||
std.def_class::<HeadingNode>("heading");
|
std.def_class::<HeadingNode>("heading");
|
||||||
std.def_class::<ListNode<Unordered>>("list");
|
std.def_class::<ListNode<Unordered>>("list");
|
||||||
std.def_class::<ListNode<Ordered>>("enum");
|
std.def_class::<ListNode<Ordered>>("enum");
|
||||||
std.def_func("image", image);
|
std.def_class::<ImageNode>("image");
|
||||||
std.def_func("rect", rect);
|
std.def_class::<ShapeNode<Rect>>("rect");
|
||||||
std.def_func("square", square);
|
std.def_class::<ShapeNode<Square>>("square");
|
||||||
std.def_func("ellipse", ellipse);
|
std.def_class::<ShapeNode<Ellipse>>("ellipse");
|
||||||
std.def_func("circle", circle);
|
std.def_class::<ShapeNode<Circle>>("circle");
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
std.def_func("h", h);
|
std.def_class::<HNode>("h");
|
||||||
std.def_func("v", v);
|
std.def_class::<VNode>("v");
|
||||||
std.def_func("box", box_);
|
std.def_class::<BoxNode>("box");
|
||||||
std.def_func("block", block);
|
std.def_class::<BlockNode>("block");
|
||||||
std.def_func("align", align);
|
std.def_class::<AlignNode>("align");
|
||||||
std.def_func("pad", pad);
|
std.def_class::<PadNode>("pad");
|
||||||
std.def_func("place", place);
|
std.def_class::<PlaceNode>("place");
|
||||||
std.def_func("move", move_);
|
std.def_class::<TransformNode<Move>>("move");
|
||||||
std.def_func("scale", scale);
|
std.def_class::<TransformNode<Scale>>("scale");
|
||||||
std.def_func("rotate", rotate);
|
std.def_class::<TransformNode<Rotate>>("rotate");
|
||||||
std.def_func("stack", stack);
|
std.def_class::<StackNode>("stack");
|
||||||
std.def_func("grid", grid);
|
std.def_class::<GridNode>("grid");
|
||||||
std.def_func("columns", columns);
|
std.def_class::<ColumnsNode>("columns");
|
||||||
|
std.def_class::<ColbreakNode>("colbreak");
|
||||||
// Breaks.
|
|
||||||
std.def_func("pagebreak", pagebreak);
|
|
||||||
std.def_func("colbreak", colbreak);
|
|
||||||
std.def_func("parbreak", parbreak);
|
|
||||||
std.def_func("linebreak", linebreak);
|
|
||||||
|
|
||||||
// Utility functions.
|
// Utility functions.
|
||||||
std.def_func("assert", assert);
|
std.def_func("assert", assert);
|
||||||
|
@ -2,25 +2,7 @@
|
|||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// `pad`: Pad content at the sides.
|
/// Pad content at the sides.
|
||||||
pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let all = args.find();
|
|
||||||
let left = args.named("left")?;
|
|
||||||
let top = args.named("top")?;
|
|
||||||
let right = args.named("right")?;
|
|
||||||
let bottom = args.named("bottom")?;
|
|
||||||
let body: PackedNode = args.expect("body")?;
|
|
||||||
let padding = Sides::new(
|
|
||||||
left.or(all).unwrap_or_default(),
|
|
||||||
top.or(all).unwrap_or_default(),
|
|
||||||
right.or(all).unwrap_or_default(),
|
|
||||||
bottom.or(all).unwrap_or_default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Value::block(body.padded(padding)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that adds padding to its child.
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PadNode {
|
pub struct PadNode {
|
||||||
/// The amount of padding.
|
/// The amount of padding.
|
||||||
@ -29,6 +11,26 @@ pub struct PadNode {
|
|||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl PadNode {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let all = args.find();
|
||||||
|
let left = args.named("left")?;
|
||||||
|
let top = args.named("top")?;
|
||||||
|
let right = args.named("right")?;
|
||||||
|
let bottom = args.named("bottom")?;
|
||||||
|
let body: PackedNode = args.expect("body")?;
|
||||||
|
let padding = Sides::new(
|
||||||
|
left.or(all).unwrap_or_default(),
|
||||||
|
top.or(all).unwrap_or_default(),
|
||||||
|
right.or(all).unwrap_or_default(),
|
||||||
|
bottom.or(all).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Node::block(body.padded(padding)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for PadNode {
|
impl Layout for PadNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
@ -10,7 +10,7 @@ use super::ColumnsNode;
|
|||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct PageNode(pub PackedNode);
|
pub struct PageNode(pub PackedNode);
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
/// The unflipped width of the page.
|
/// The unflipped width of the page.
|
||||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::default().width());
|
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::default().width());
|
||||||
@ -32,17 +32,11 @@ impl PageNode {
|
|||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
||||||
/// How much space is between the page's columns.
|
|
||||||
pub const COLUMN_GUTTER: Linear = Relative::new(0.04).into();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for PageNode {
|
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
Ok(Node::Page(Self(args.expect("body")?)))
|
Ok(Node::Page(Self(args.expect("body")?)))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Set for PageNode {
|
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
||||||
styles.set(Self::CLASS, paper.class());
|
styles.set(Self::CLASS, paper.class());
|
||||||
@ -69,7 +63,6 @@ impl Set for PageNode {
|
|||||||
styles.set_opt(Self::FLIPPED, args.named("flipped")?);
|
styles.set_opt(Self::FLIPPED, args.named("flipped")?);
|
||||||
styles.set_opt(Self::FILL, args.named("fill")?);
|
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||||
styles.set_opt(Self::COLUMNS, args.named("columns")?);
|
styles.set_opt(Self::COLUMNS, args.named("columns")?);
|
||||||
styles.set_opt(Self::COLUMN_GUTTER, args.named("column-gutter")?);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -102,12 +95,7 @@ impl PageNode {
|
|||||||
// Realize columns with columns node.
|
// Realize columns with columns node.
|
||||||
let columns = styles.get(Self::COLUMNS);
|
let columns = styles.get(Self::COLUMNS);
|
||||||
if columns.get() > 1 {
|
if columns.get() > 1 {
|
||||||
child = ColumnsNode {
|
child = ColumnsNode { columns, child: self.0.clone() }.pack();
|
||||||
columns,
|
|
||||||
gutter: styles.get(Self::COLUMN_GUTTER),
|
|
||||||
child: self.0.clone(),
|
|
||||||
}
|
|
||||||
.pack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realize margins with padding node.
|
// Realize margins with padding node.
|
||||||
@ -142,9 +130,14 @@ impl Debug for PageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `pagebreak`: Start a new page.
|
/// A page break.
|
||||||
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
pub struct PagebreakNode;
|
||||||
Ok(Value::Node(Node::Pagebreak))
|
|
||||||
|
#[class]
|
||||||
|
impl PagebreakNode {
|
||||||
|
fn construct(_: &mut EvalContext, _: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Pagebreak)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specification of a paper.
|
/// Specification of a paper.
|
||||||
|
@ -15,7 +15,7 @@ use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct ParNode(pub Vec<Styled<ParChild>>);
|
pub struct ParNode(pub Vec<Styled<ParChild>>);
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
/// The direction for text and inline objects.
|
/// The direction for text and inline objects.
|
||||||
pub const DIR: Dir = Dir::LTR;
|
pub const DIR: Dir = Dir::LTR;
|
||||||
@ -25,9 +25,7 @@ impl ParNode {
|
|||||||
pub const LEADING: Linear = Relative::new(0.65).into();
|
pub const LEADING: Linear = Relative::new(0.65).into();
|
||||||
/// The spacing between paragraphs (dependent on scaled font size).
|
/// The spacing between paragraphs (dependent on scaled font size).
|
||||||
pub const SPACING: Linear = Relative::new(1.2).into();
|
pub const SPACING: Linear = Relative::new(1.2).into();
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for ParNode {
|
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
// since that happens automatically through markup. Instead, it just
|
// since that happens automatically through markup. Instead, it just
|
||||||
@ -35,13 +33,8 @@ impl Construct for ParNode {
|
|||||||
// adjacent stuff and it styles the contained paragraphs.
|
// adjacent stuff and it styles the contained paragraphs.
|
||||||
Ok(Node::Block(args.expect("body")?))
|
Ok(Node::Block(args.expect("body")?))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Set for ParNode {
|
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
let spacing = args.named("spacing")?;
|
|
||||||
let leading = args.named("leading")?;
|
|
||||||
|
|
||||||
let mut dir =
|
let mut dir =
|
||||||
args.named("lang")?
|
args.named("lang")?
|
||||||
.map(|iso: EcoString| match iso.to_lowercase().as_str() {
|
.map(|iso: EcoString| match iso.to_lowercase().as_str() {
|
||||||
@ -69,8 +62,8 @@ impl Set for ParNode {
|
|||||||
|
|
||||||
styles.set_opt(Self::DIR, dir);
|
styles.set_opt(Self::DIR, dir);
|
||||||
styles.set_opt(Self::ALIGN, align);
|
styles.set_opt(Self::ALIGN, align);
|
||||||
styles.set_opt(Self::LEADING, leading);
|
styles.set_opt(Self::LEADING, args.named("leading")?);
|
||||||
styles.set_opt(Self::SPACING, spacing);
|
styles.set_opt(Self::SPACING, args.named("spacing")?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -166,14 +159,24 @@ impl Debug for ParChild {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `parbreak`: Start a new paragraph.
|
/// A paragraph break.
|
||||||
pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
pub struct ParbreakNode;
|
||||||
Ok(Value::Node(Node::Parbreak))
|
|
||||||
|
#[class]
|
||||||
|
impl ParbreakNode {
|
||||||
|
fn construct(_: &mut EvalContext, _: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Parbreak)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `linebreak`: Start a new line.
|
/// A line break.
|
||||||
pub fn linebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
pub struct LinebreakNode;
|
||||||
Ok(Value::Node(Node::Linebreak))
|
|
||||||
|
#[class]
|
||||||
|
impl LinebreakNode {
|
||||||
|
fn construct(_: &mut EvalContext, _: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Linebreak)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A paragraph representation in which children are already layouted and text
|
/// A paragraph representation in which children are already layouted and text
|
||||||
|
@ -3,33 +3,24 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::AlignNode;
|
use super::AlignNode;
|
||||||
|
|
||||||
/// `place`: Place content at an absolute position.
|
/// Place content at an absolute position.
|
||||||
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let aligns = args.find().unwrap_or(Spec::with_x(Some(Align::Left)));
|
|
||||||
let tx = args.named("dx")?.unwrap_or_default();
|
|
||||||
let ty = args.named("dy")?.unwrap_or_default();
|
|
||||||
let body: PackedNode = args.expect("body")?;
|
|
||||||
Ok(Value::block(PlacedNode(
|
|
||||||
body.moved(Point::new(tx, ty)).aligned(aligns),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that places its child absolutely.
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PlacedNode(pub PackedNode);
|
pub struct PlaceNode(pub PackedNode);
|
||||||
|
|
||||||
impl PlacedNode {
|
#[class]
|
||||||
/// Whether this node wants to be placed relative to its its parent's base
|
impl PlaceNode {
|
||||||
/// origin. instead of relative to the parent's current flow/cursor
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
/// position.
|
let aligns = args.find().unwrap_or(Spec::with_x(Some(Align::Left)));
|
||||||
pub fn out_of_flow(&self) -> bool {
|
let tx = args.named("dx")?.unwrap_or_default();
|
||||||
self.0
|
let ty = args.named("dy")?.unwrap_or_default();
|
||||||
.downcast::<AlignNode>()
|
let body: PackedNode = args.expect("body")?;
|
||||||
.map_or(false, |node| node.aligns.y.is_some())
|
Ok(Node::block(Self(
|
||||||
|
body.moved(Point::new(tx, ty)).aligned(aligns),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PlacedNode {
|
impl Layout for PlaceNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -63,3 +54,14 @@ impl Layout for PlacedNode {
|
|||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlaceNode {
|
||||||
|
/// 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.0
|
||||||
|
.downcast::<AlignNode>()
|
||||||
|
.map_or(false, |node| node.aligns.y.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,110 +3,64 @@
|
|||||||
use std::f64::consts::SQRT_2;
|
use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use super::TextNode;
|
||||||
/// `rect`: A rectangle with optional content.
|
|
||||||
pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let width = args.named("width")?;
|
|
||||||
let height = args.named("height")?;
|
|
||||||
shape_impl(args, ShapeKind::Rect, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `square`: A square with optional content.
|
|
||||||
pub fn square(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let size = args.named::<Length>("size")?.map(Linear::from);
|
|
||||||
let width = match size {
|
|
||||||
None => args.named("width")?,
|
|
||||||
size => size,
|
|
||||||
};
|
|
||||||
let height = match size {
|
|
||||||
None => args.named("height")?,
|
|
||||||
size => size,
|
|
||||||
};
|
|
||||||
shape_impl(args, ShapeKind::Square, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ellipse`: An ellipse with optional content.
|
|
||||||
pub fn ellipse(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let width = args.named("width")?;
|
|
||||||
let height = args.named("height")?;
|
|
||||||
shape_impl(args, ShapeKind::Ellipse, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `circle`: A circle with optional content.
|
|
||||||
pub fn circle(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r));
|
|
||||||
let width = match diameter {
|
|
||||||
None => args.named("width")?,
|
|
||||||
diameter => diameter,
|
|
||||||
};
|
|
||||||
let height = match diameter {
|
|
||||||
None => args.named("height")?,
|
|
||||||
diameter => diameter,
|
|
||||||
};
|
|
||||||
shape_impl(args, ShapeKind::Circle, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shape_impl(
|
|
||||||
args: &mut Args,
|
|
||||||
kind: ShapeKind,
|
|
||||||
width: Option<Linear>,
|
|
||||||
height: Option<Linear>,
|
|
||||||
) -> TypResult<Value> {
|
|
||||||
// The default appearance of a shape.
|
|
||||||
let default = Stroke {
|
|
||||||
paint: RgbaColor::BLACK.into(),
|
|
||||||
thickness: Length::pt(1.0),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse fill & stroke.
|
|
||||||
let fill = args.named("fill")?.unwrap_or(None);
|
|
||||||
let stroke = match (args.named("stroke")?, args.named("thickness")?) {
|
|
||||||
(None, None) => fill.is_none().then(|| default),
|
|
||||||
(color, thickness) => color.unwrap_or(Some(default.paint)).map(|paint| Stroke {
|
|
||||||
paint,
|
|
||||||
thickness: thickness.unwrap_or(default.thickness),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shorthand for padding.
|
|
||||||
let mut padding = args.named::<Linear>("padding")?.unwrap_or_default();
|
|
||||||
|
|
||||||
// Padding with this ratio ensures that a rectangular child fits
|
|
||||||
// perfectly into a circle / an ellipse.
|
|
||||||
if kind.is_round() {
|
|
||||||
padding.rel += Relative::new(0.5 - SQRT_2 / 4.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The shape's contents.
|
|
||||||
let child = args.find().map(|body: PackedNode| body.padded(Sides::splat(padding)));
|
|
||||||
|
|
||||||
Ok(Value::inline(
|
|
||||||
ShapeNode { kind, fill, stroke, child }
|
|
||||||
.pack()
|
|
||||||
.sized(Spec::new(width, height)),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Places its child into a sizable and fillable shape.
|
/// Places its child into a sizable and fillable shape.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ShapeNode {
|
pub struct ShapeNode<S: ShapeKind> {
|
||||||
/// Which shape to place the child into.
|
/// Which shape to place the child into.
|
||||||
pub kind: ShapeKind,
|
pub kind: S,
|
||||||
/// How to fill the shape.
|
|
||||||
pub fill: Option<Paint>,
|
|
||||||
/// How the stroke the shape.
|
|
||||||
pub stroke: Option<Stroke>,
|
|
||||||
/// The child node to place into the shape, if any.
|
/// The child node to place into the shape, if any.
|
||||||
pub child: Option<PackedNode>,
|
pub child: Option<PackedNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl ShapeNode {
|
impl<S: ShapeKind> ShapeNode<S> {
|
||||||
/// An URL the shape should link to.
|
/// How to fill the shape.
|
||||||
pub const LINK: Option<String> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
|
/// How the stroke the shape.
|
||||||
|
pub const STROKE: Smart<Option<Paint>> = Smart::Auto;
|
||||||
|
/// The stroke's thickness.
|
||||||
|
pub const THICKNESS: Length = Length::pt(1.0);
|
||||||
|
/// The How much to pad the shape's content.
|
||||||
|
pub const PADDING: Linear = Linear::zero();
|
||||||
|
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let size = if !S::ROUND && S::QUADRATIC {
|
||||||
|
args.named::<Length>("size")?.map(Linear::from)
|
||||||
|
} else if S::ROUND && S::QUADRATIC {
|
||||||
|
args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = match size {
|
||||||
|
None => args.named("width")?,
|
||||||
|
size => size,
|
||||||
|
};
|
||||||
|
|
||||||
|
let height = match size {
|
||||||
|
None => args.named("height")?,
|
||||||
|
size => size,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Node::inline(
|
||||||
|
ShapeNode { kind: S::default(), child: args.find() }
|
||||||
|
.pack()
|
||||||
|
.sized(Spec::new(width, height)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
|
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||||
|
styles.set_opt(Self::STROKE, args.named("stroke")?);
|
||||||
|
styles.set_opt(Self::THICKNESS, args.named("thickness")?);
|
||||||
|
styles.set_opt(Self::PADDING, args.named("padding")?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ShapeNode {
|
impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -115,12 +69,20 @@ impl Layout for ShapeNode {
|
|||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames;
|
let mut frames;
|
||||||
if let Some(child) = &self.child {
|
if let Some(child) = &self.child {
|
||||||
|
let mut padding = styles.get(Self::PADDING);
|
||||||
|
if S::ROUND {
|
||||||
|
padding.rel += Relative::new(0.5 - SQRT_2 / 4.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the child.
|
||||||
|
let child = child.clone().padded(Sides::splat(padding));
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.current, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.current, regions.base, regions.expand);
|
||||||
frames = child.layout(ctx, &pod, styles);
|
frames = child.layout(ctx, &pod, styles);
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
// the result is really a square or circle.
|
// the result is really a square or circle.
|
||||||
if self.kind.is_quadratic() {
|
if S::QUADRATIC {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
let length = if regions.expand.x || regions.expand.y {
|
||||||
let target = regions.expand.select(regions.current, Size::zero());
|
let target = regions.expand.select(regions.current, Size::zero());
|
||||||
target.x.max(target.y)
|
target.x.max(target.y)
|
||||||
@ -141,7 +103,7 @@ impl Layout for ShapeNode {
|
|||||||
let mut size =
|
let mut size =
|
||||||
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.current);
|
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.current);
|
||||||
|
|
||||||
if self.kind.is_quadratic() {
|
if S::QUADRATIC {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
let length = if regions.expand.x || regions.expand.y {
|
||||||
let target = regions.expand.select(regions.current, Size::zero());
|
let target = regions.expand.select(regions.current, Size::zero());
|
||||||
target.x.max(target.y)
|
target.x.max(target.y)
|
||||||
@ -159,23 +121,26 @@ impl Layout for ShapeNode {
|
|||||||
let frame = Rc::make_mut(&mut frames[0].item);
|
let frame = Rc::make_mut(&mut frames[0].item);
|
||||||
|
|
||||||
// Add fill and/or stroke.
|
// Add fill and/or stroke.
|
||||||
if self.fill.is_some() || self.stroke.is_some() {
|
let fill = styles.get(Self::FILL);
|
||||||
let geometry = match self.kind {
|
let thickness = styles.get(Self::THICKNESS);
|
||||||
ShapeKind::Square | ShapeKind::Rect => Geometry::Rect(frame.size),
|
let stroke = styles
|
||||||
ShapeKind::Circle | ShapeKind::Ellipse => Geometry::Ellipse(frame.size),
|
.get(Self::STROKE)
|
||||||
};
|
.unwrap_or(fill.is_none().then(|| RgbaColor::BLACK.into()))
|
||||||
|
.map(|paint| Stroke { paint, thickness });
|
||||||
let shape = Shape {
|
|
||||||
geometry,
|
if fill.is_some() || stroke.is_some() {
|
||||||
fill: self.fill,
|
let geometry = if S::ROUND {
|
||||||
stroke: self.stroke,
|
Geometry::Ellipse(frame.size)
|
||||||
|
} else {
|
||||||
|
Geometry::Rect(frame.size)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let shape = Shape { geometry, fill, stroke };
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get_ref(Self::LINK) {
|
if let Some(url) = styles.get_ref(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,27 +148,44 @@ impl Layout for ShapeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a shape.
|
/// Categorizes shapes.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
pub trait ShapeKind: Debug + Default + Hash + 'static {
|
||||||
pub enum ShapeKind {
|
const ROUND: bool;
|
||||||
/// A rectangle with equal side lengths.
|
const QUADRATIC: bool;
|
||||||
Square,
|
|
||||||
/// A quadrilateral with four right angles.
|
|
||||||
Rect,
|
|
||||||
/// An ellipse with coinciding foci.
|
|
||||||
Circle,
|
|
||||||
/// A curve around two focal points.
|
|
||||||
Ellipse,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeKind {
|
/// A rectangle with equal side lengths.
|
||||||
/// Whether the shape is curved.
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub fn is_round(self) -> bool {
|
pub struct Square;
|
||||||
matches!(self, Self::Circle | Self::Ellipse)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the shape has a fixed 1-1 aspect ratio.
|
impl ShapeKind for Square {
|
||||||
pub fn is_quadratic(self) -> bool {
|
const ROUND: bool = false;
|
||||||
matches!(self, Self::Square | Self::Circle)
|
const QUADRATIC: bool = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A quadrilateral with four right angles.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Rect;
|
||||||
|
|
||||||
|
impl ShapeKind for Rect {
|
||||||
|
const ROUND: bool = false;
|
||||||
|
const QUADRATIC: bool = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ellipse with coinciding foci.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Circle;
|
||||||
|
|
||||||
|
impl ShapeKind for Circle {
|
||||||
|
const ROUND: bool = true;
|
||||||
|
const QUADRATIC: bool = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A curve around two focal points.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Ellipse;
|
||||||
|
|
||||||
|
impl ShapeKind for Ellipse {
|
||||||
|
const ROUND: bool = true;
|
||||||
|
const QUADRATIC: bool = false;
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,27 @@
|
|||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// `box`: Size content and place it into a paragraph.
|
/// Size content and place it into a paragraph.
|
||||||
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub struct BoxNode;
|
||||||
let width = args.named("width")?;
|
|
||||||
let height = args.named("height")?;
|
#[class]
|
||||||
let body: PackedNode = args.find().unwrap_or_default();
|
impl BoxNode {
|
||||||
Ok(Value::inline(body.sized(Spec::new(width, height))))
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
let width = args.named("width")?;
|
||||||
|
let height = args.named("height")?;
|
||||||
|
let body: PackedNode = args.find().unwrap_or_default();
|
||||||
|
Ok(Node::inline(body.sized(Spec::new(width, height))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `block`: Place content into the flow.
|
/// Place content into a separate flow.
|
||||||
pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub struct BlockNode;
|
||||||
let body: PackedNode = args.find().unwrap_or_default();
|
|
||||||
Ok(Value::block(body))
|
#[class]
|
||||||
|
impl BlockNode {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Block(args.find().unwrap_or_default()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that sizes its child.
|
/// A node that sizes its child.
|
||||||
|
@ -2,20 +2,24 @@
|
|||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// `h`: Horizontal spacing.
|
/// Horizontal spacing.
|
||||||
pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub struct HNode;
|
||||||
Ok(Value::Node(Node::Spacing(
|
|
||||||
SpecAxis::Horizontal,
|
#[class]
|
||||||
args.expect("spacing")?,
|
impl HNode {
|
||||||
)))
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Spacing(SpecAxis::Horizontal, args.expect("spacing")?))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `v`: Vertical spacing.
|
/// Vertical spacing.
|
||||||
pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub struct VNode;
|
||||||
Ok(Value::Node(Node::Spacing(
|
|
||||||
SpecAxis::Vertical,
|
#[class]
|
||||||
args.expect("spacing")?,
|
impl VNode {
|
||||||
)))
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::Spacing(SpecAxis::Vertical, args.expect("spacing")?))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kinds of spacing.
|
/// Kinds of spacing.
|
||||||
|
@ -3,16 +3,7 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, SpacingKind};
|
use super::{AlignNode, SpacingKind};
|
||||||
|
|
||||||
/// `stack`: Stack children along an axis.
|
/// Stack children along an axis.
|
||||||
pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
Ok(Value::block(StackNode {
|
|
||||||
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
|
||||||
spacing: args.named("spacing")?,
|
|
||||||
children: args.all().collect(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that stacks its children.
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct StackNode {
|
pub struct StackNode {
|
||||||
/// The stacking direction.
|
/// The stacking direction.
|
||||||
@ -23,6 +14,17 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[class]
|
||||||
|
impl StackNode {
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::block(Self {
|
||||||
|
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
||||||
|
spacing: args.named("spacing")?,
|
||||||
|
children: args.all().collect(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Layout for StackNode {
|
impl Layout for StackNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
@ -9,6 +9,7 @@ use rustybuzz::{Feature, UnicodeBuffer};
|
|||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use super::{DecoLine, Decoration};
|
||||||
use crate::font::{
|
use crate::font::{
|
||||||
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
|
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
|
||||||
VerticalFontMetric,
|
VerticalFontMetric,
|
||||||
@ -20,7 +21,7 @@ use crate::util::{EcoString, SliceExt};
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct TextNode(pub EcoString);
|
pub struct TextNode(pub EcoString);
|
||||||
|
|
||||||
#[properties]
|
#[class]
|
||||||
impl TextNode {
|
impl TextNode {
|
||||||
/// A prioritized sequence of font families.
|
/// A prioritized sequence of font families.
|
||||||
pub const FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif];
|
pub const FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif];
|
||||||
@ -52,7 +53,7 @@ impl TextNode {
|
|||||||
pub const FILL: Paint = RgbaColor::BLACK.into();
|
pub const FILL: Paint = RgbaColor::BLACK.into();
|
||||||
/// Decorative lines.
|
/// Decorative lines.
|
||||||
#[fold(|a, b| a.into_iter().chain(b).collect())]
|
#[fold(|a, b| a.into_iter().chain(b).collect())]
|
||||||
pub const LINES: Vec<LineDecoration> = vec![];
|
pub const LINES: Vec<Decoration> = vec![];
|
||||||
/// An URL the text should link to.
|
/// An URL the text should link to.
|
||||||
pub const LINK: Option<String> = None;
|
pub const LINK: Option<String> = None;
|
||||||
|
|
||||||
@ -92,18 +93,14 @@ impl TextNode {
|
|||||||
pub const FRACTIONS: bool = false;
|
pub const FRACTIONS: bool = false;
|
||||||
/// Raw OpenType features to apply.
|
/// Raw OpenType features to apply.
|
||||||
pub const FEATURES: Vec<(Tag, u32)> = vec![];
|
pub const FEATURES: Vec<(Tag, u32)> = vec![];
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for TextNode {
|
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
// The text constructor is special: It doesn't create a text node.
|
// The text constructor is special: It doesn't create a text node.
|
||||||
// Instead, it leaves the passed argument structurally unchanged, but
|
// Instead, it leaves the passed argument structurally unchanged, but
|
||||||
// styles all text in it.
|
// styles all text in it.
|
||||||
args.expect("body")
|
args.expect("body")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Set for TextNode {
|
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
let list = args.named("family")?.or_else(|| {
|
let list = args.named("family")?.or_else(|| {
|
||||||
let families: Vec<_> = args.all().collect();
|
let families: Vec<_> = args.all().collect();
|
||||||
@ -382,60 +379,6 @@ castable! {
|
|||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `strike`: Typeset striken-through text.
|
|
||||||
pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
line_impl(args, LineKind::Strikethrough)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `underline`: Typeset underlined text.
|
|
||||||
pub fn underline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
line_impl(args, LineKind::Underline)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `overline`: Typeset text with an overline.
|
|
||||||
pub fn overline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
line_impl(args, LineKind::Overline)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
|
||||||
let stroke = args.named("stroke")?.or_else(|| args.find());
|
|
||||||
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find());
|
|
||||||
let offset = args.named("offset")?;
|
|
||||||
let extent = args.named("extent")?.unwrap_or_default();
|
|
||||||
let body: Node = args.expect("body")?;
|
|
||||||
let deco = LineDecoration { kind, stroke, thickness, offset, extent };
|
|
||||||
Ok(Value::Node(body.styled(TextNode::LINES, vec![deco])))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines a line that is positioned over, under or on top of text.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct LineDecoration {
|
|
||||||
/// The kind of line.
|
|
||||||
pub kind: LineKind,
|
|
||||||
/// Stroke color of the line, defaults to the text color if `None`.
|
|
||||||
pub stroke: Option<Paint>,
|
|
||||||
/// Thickness of the line's strokes (dependent on scaled font size), read
|
|
||||||
/// from the font tables if `None`.
|
|
||||||
pub thickness: Option<Linear>,
|
|
||||||
/// Position of the line relative to the baseline (dependent on scaled font
|
|
||||||
/// size), read from the font tables if `None`.
|
|
||||||
pub offset: Option<Linear>,
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text
|
|
||||||
/// (dependent on scaled font size).
|
|
||||||
pub extent: Linear,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kind of line decoration.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum LineKind {
|
|
||||||
/// A line under text.
|
|
||||||
Underline,
|
|
||||||
/// A line through text.
|
|
||||||
Strikethrough,
|
|
||||||
/// A line over text.
|
|
||||||
Overline,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shape text into [`ShapedText`].
|
/// Shape text into [`ShapedText`].
|
||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
fonts: &mut FontStore,
|
fonts: &mut FontStore,
|
||||||
@ -848,23 +791,23 @@ impl<'a> ShapedText<'a> {
|
|||||||
frame.push(pos, Element::Text(text));
|
frame.push(pos, Element::Text(text));
|
||||||
|
|
||||||
// Apply line decorations.
|
// Apply line decorations.
|
||||||
for line in self.styles.get_cloned(TextNode::LINES) {
|
for deco in self.styles.get_cloned(TextNode::LINES) {
|
||||||
let face = fonts.get(face_id);
|
let face = fonts.get(face_id);
|
||||||
let metrics = match line.kind {
|
let metrics = match deco.line {
|
||||||
LineKind::Underline => face.underline,
|
DecoLine::Underline => face.underline,
|
||||||
LineKind::Strikethrough => face.strikethrough,
|
DecoLine::Strikethrough => face.strikethrough,
|
||||||
LineKind::Overline => face.overline,
|
DecoLine::Overline => face.overline,
|
||||||
};
|
};
|
||||||
|
|
||||||
let extent = line.extent.resolve(size);
|
let extent = deco.extent.resolve(size);
|
||||||
let offset = line
|
let offset = deco
|
||||||
.offset
|
.offset
|
||||||
.map(|s| s.resolve(size))
|
.map(|s| s.resolve(size))
|
||||||
.unwrap_or(-metrics.position.resolve(size));
|
.unwrap_or(-metrics.position.resolve(size));
|
||||||
|
|
||||||
let stroke = Stroke {
|
let stroke = Stroke {
|
||||||
paint: line.stroke.unwrap_or(fill),
|
paint: deco.stroke.unwrap_or(fill),
|
||||||
thickness: line
|
thickness: deco
|
||||||
.thickness
|
.thickness
|
||||||
.map(|s| s.resolve(size))
|
.map(|s| s.resolve(size))
|
||||||
.unwrap_or(metrics.thickness.resolve(size)),
|
.unwrap_or(metrics.thickness.resolve(size)),
|
||||||
|
@ -3,64 +3,49 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::geom::Transform;
|
use crate::geom::Transform;
|
||||||
|
|
||||||
/// `move`: Move content without affecting layout.
|
|
||||||
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let tx = args.named("x")?.unwrap_or_default();
|
|
||||||
let ty = args.named("y")?.unwrap_or_default();
|
|
||||||
let transform = Transform::translation(tx, ty);
|
|
||||||
transform_impl(args, transform)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `scale`: Scale content without affecting layout.
|
|
||||||
pub fn scale(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let all = args.find();
|
|
||||||
let sx = args.named("x")?.or(all).unwrap_or(Relative::one());
|
|
||||||
let sy = args.named("y")?.or(all).unwrap_or(Relative::one());
|
|
||||||
let transform = Transform::scaling(sx, sy);
|
|
||||||
transform_impl(args, transform)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `rotate`: Rotate content without affecting layout.
|
|
||||||
pub fn rotate(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let angle = args.named("angle")?.or_else(|| args.find()).unwrap_or_default();
|
|
||||||
let transform = Transform::rotation(angle);
|
|
||||||
transform_impl(args, transform)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
|
|
||||||
let body: PackedNode = args.expect("body")?;
|
|
||||||
let origin = args
|
|
||||||
.named("origin")?
|
|
||||||
.unwrap_or(Spec::splat(None))
|
|
||||||
.unwrap_or(Align::CENTER_HORIZON);
|
|
||||||
|
|
||||||
Ok(Value::inline(body.transformed(transform, origin)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that transforms its child without affecting layout.
|
/// A node that transforms its child without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct TransformNode {
|
pub struct TransformNode<T: TransformKind> {
|
||||||
/// Transformation to apply to the contents.
|
/// Transformation to apply to the contents.
|
||||||
pub transform: Transform,
|
pub kind: T,
|
||||||
/// The origin of the transformation.
|
|
||||||
pub origin: Spec<Align>,
|
|
||||||
/// The node whose contents should be transformed.
|
/// The node whose contents should be transformed.
|
||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for TransformNode {
|
#[class]
|
||||||
|
impl<T: TransformKind> TransformNode<T> {
|
||||||
|
/// The origin of the transformation.
|
||||||
|
pub const ORIGIN: Spec<Option<Align>> = Spec::default();
|
||||||
|
|
||||||
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
|
Ok(Node::inline(Self {
|
||||||
|
kind: T::construct(args)?,
|
||||||
|
child: args.expect("body")?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
|
styles.set_opt(Self::ORIGIN, args.named("origin")?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransformKind> Layout for TransformNode<T> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||||
|
let matrix = self.kind.matrix();
|
||||||
|
|
||||||
let mut frames = self.child.layout(ctx, regions, styles);
|
let mut frames = self.child.layout(ctx, regions, styles);
|
||||||
|
|
||||||
for Constrained { item: frame, .. } in &mut frames {
|
for Constrained { item: frame, .. } in &mut frames {
|
||||||
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||||
let transform = Transform::translation(x, y)
|
let transform = Transform::translation(x, y)
|
||||||
.pre_concat(self.transform)
|
.pre_concat(matrix)
|
||||||
.pre_concat(Transform::translation(-x, -y));
|
.pre_concat(Transform::translation(-x, -y));
|
||||||
|
|
||||||
Rc::make_mut(frame).transform(transform);
|
Rc::make_mut(frame).transform(transform);
|
||||||
@ -69,3 +54,58 @@ impl Layout for TransformNode {
|
|||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Kinds of transformations.
|
||||||
|
pub trait TransformKind: Debug + Hash + Sized + 'static {
|
||||||
|
fn construct(args: &mut Args) -> TypResult<Self>;
|
||||||
|
fn matrix(&self) -> Transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A translation on the X and Y axes.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Move(pub Length, pub Length);
|
||||||
|
|
||||||
|
impl TransformKind for Move {
|
||||||
|
fn construct(args: &mut Args) -> TypResult<Self> {
|
||||||
|
let tx = args.named("x")?.unwrap_or_default();
|
||||||
|
let ty = args.named("y")?.unwrap_or_default();
|
||||||
|
Ok(Self(tx, ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matrix(&self) -> Transform {
|
||||||
|
Transform::translation(self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rotational transformation.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Rotate(pub Angle);
|
||||||
|
|
||||||
|
impl TransformKind for Rotate {
|
||||||
|
fn construct(args: &mut Args) -> TypResult<Self> {
|
||||||
|
Ok(Self(
|
||||||
|
args.named("angle")?.or_else(|| args.find()).unwrap_or_default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matrix(&self) -> Transform {
|
||||||
|
Transform::rotation(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scale transformation.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Scale(pub Relative, pub Relative);
|
||||||
|
|
||||||
|
impl TransformKind for Scale {
|
||||||
|
fn construct(args: &mut Args) -> TypResult<Self> {
|
||||||
|
let all = args.find();
|
||||||
|
let sx = args.named("x")?.or(all).unwrap_or(Relative::one());
|
||||||
|
let sy = args.named("y")?.or(all).unwrap_or(Relative::one());
|
||||||
|
Ok(Self(sx, sy))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matrix(&self) -> Transform {
|
||||||
|
Transform::scale(self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use std::str::FromStr;
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use crate::eval::Array;
|
use crate::eval::Array;
|
||||||
|
|
||||||
/// `assert`: Ensure that a condition is fulfilled.
|
/// Ensure that a condition is fulfilled.
|
||||||
pub fn assert(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn assert(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
|
||||||
if !v {
|
if !v {
|
||||||
@ -15,18 +15,17 @@ pub fn assert(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `type`: The name of a value's type.
|
/// The name of a value's type.
|
||||||
pub fn type_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn type_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.type_name().into())
|
Ok(args.expect::<Value>("value")?.type_name().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `repr`: The string representation of a value.
|
/// The string representation of a value.
|
||||||
pub fn repr(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn repr(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Value>("value")?.repr().into())
|
Ok(args.expect::<Value>("value")?.repr().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `join`: Join a sequence of values, optionally interspersing it with another
|
/// Join a sequence of values, optionally interspersing it with another value.
|
||||||
/// value.
|
|
||||||
pub fn join(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn join(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let span = args.span;
|
let span = args.span;
|
||||||
let sep = args.named::<Value>("sep")?.unwrap_or(Value::None);
|
let sep = args.named::<Value>("sep")?.unwrap_or(Value::None);
|
||||||
@ -46,7 +45,7 @@ pub fn join(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `int`: Convert a value to a integer.
|
/// Convert a value to a integer.
|
||||||
pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Int(match v {
|
Ok(Value::Int(match v {
|
||||||
@ -61,7 +60,7 @@ pub fn int(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `float`: Convert a value to a float.
|
/// Convert a value to a float.
|
||||||
pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Float(match v {
|
Ok(Value::Float(match v {
|
||||||
@ -75,7 +74,7 @@ pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `str`: Try to convert a value to a string.
|
/// Try to convert a value to a string.
|
||||||
pub fn str(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn str(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("value")?;
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Str(match v {
|
Ok(Value::Str(match v {
|
||||||
@ -86,7 +85,7 @@ pub fn str(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `rgb`: Create an RGB(A) color.
|
/// Create an RGB(A) color.
|
||||||
pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(Value::from(
|
Ok(Value::from(
|
||||||
if let Some(string) = args.find::<Spanned<EcoString>>() {
|
if let Some(string) = args.find::<Spanned<EcoString>>() {
|
||||||
@ -111,7 +110,7 @@ pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `abs`: The absolute value of a numeric value.
|
/// The absolute value of a numeric value.
|
||||||
pub fn abs(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn abs(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("numeric value")?;
|
let Spanned { v, span } = args.expect("numeric value")?;
|
||||||
Ok(match v {
|
Ok(match v {
|
||||||
@ -126,12 +125,12 @@ pub fn abs(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `min`: The minimum of a sequence of values.
|
/// The minimum of a sequence of values.
|
||||||
pub fn min(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn min(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Less)
|
minmax(args, Ordering::Less)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `max`: The maximum of a sequence of values.
|
/// The maximum of a sequence of values.
|
||||||
pub fn max(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn max(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Greater)
|
minmax(args, Ordering::Greater)
|
||||||
}
|
}
|
||||||
@ -157,7 +156,7 @@ fn minmax(args: &mut Args, goal: Ordering) -> TypResult<Value> {
|
|||||||
Ok(extremum)
|
Ok(extremum)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `range`: Create a sequence of numbers.
|
/// Create a sequence of numbers.
|
||||||
pub fn range(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn range(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let first = args.expect::<i64>("end")?;
|
let first = args.expect::<i64>("end")?;
|
||||||
let (start, end) = match args.eat::<i64>()? {
|
let (start, end) = match args.eat::<i64>()? {
|
||||||
@ -182,17 +181,17 @@ pub fn range(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
Ok(Value::Array(Array::from_vec(seq)))
|
Ok(Value::Array(Array::from_vec(seq)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `lower`: Convert a string to lowercase.
|
/// Convert a string to lowercase.
|
||||||
pub fn lower(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn lower(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<EcoString>("string")?.to_lowercase().into())
|
Ok(args.expect::<EcoString>("string")?.to_lowercase().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `upper`: Convert a string to uppercase.
|
/// Convert a string to uppercase.
|
||||||
pub fn upper(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn upper(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(args.expect::<EcoString>("string")?.to_uppercase().into())
|
Ok(args.expect::<EcoString>("string")?.to_uppercase().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `len`: The length of a string, an array or a dictionary.
|
/// The length of a string, an array or a dictionary.
|
||||||
pub fn len(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn len(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("collection")?;
|
let Spanned { v, span } = args.expect("collection")?;
|
||||||
Ok(Value::Int(match v {
|
Ok(Value::Int(match v {
|
||||||
@ -207,7 +206,7 @@ pub fn len(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `sorted`: The sorted version of an array.
|
/// The sorted version of an array.
|
||||||
pub fn sorted(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn sorted(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?;
|
let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?;
|
||||||
Ok(Value::Array(v.sorted().at(span)?))
|
Ok(Value::Array(v.sorted().at(span)?))
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 9.1 KiB |
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test normal operation and RTL directions.
|
// Test normal operation and RTL directions.
|
||||||
#set page(height: 3.25cm, width: 7.05cm, columns: 2, column-gutter: 30pt)
|
#set page(height: 3.25cm, width: 7.05cm, columns: 2)
|
||||||
|
#set columns(gutter: 30pt)
|
||||||
#set text("Noto Sans Arabic", serif)
|
#set text("Noto Sans Arabic", serif)
|
||||||
#set par(lang: "ar")
|
#set par(lang: "ar")
|
||||||
|
|
||||||
@ -10,7 +11,7 @@
|
|||||||
العديد من التفاعلات الكيميائية. (DNA) من أهم الأحماض النووية التي تُشكِّل
|
العديد من التفاعلات الكيميائية. (DNA) من أهم الأحماض النووية التي تُشكِّل
|
||||||
إلى جانب كل من البروتينات والليبيدات والسكريات المتعددة
|
إلى جانب كل من البروتينات والليبيدات والسكريات المتعددة
|
||||||
#rect(fill: eastern, height: 8pt, width: 6pt)
|
#rect(fill: eastern, height: 8pt, width: 6pt)
|
||||||
الجزيئات الضخمة الأربعة الضرورية للحياة.
|
الجزيئات الضخمة الأربعة الضرورية للحياة.
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `columns` function.
|
// Test the `columns` function.
|
||||||
@ -28,7 +29,7 @@
|
|||||||
#set page(height: 5cm, width: 7.05cm, columns: 2)
|
#set page(height: 5cm, width: 7.05cm, columns: 2)
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet is a common blind text
|
Lorem ipsum dolor sit amet is a common blind text
|
||||||
and I again am in need of filling up this page
|
and I again am in need of filling up this page
|
||||||
#align(bottom, rect(fill: eastern, width: 100%, height: 12pt))
|
#align(bottom, rect(fill: eastern, width: 100%, height: 12pt))
|
||||||
#colbreak()
|
#colbreak()
|
||||||
|
|
||||||
@ -49,7 +50,8 @@ a page for a test but it does get the job done.
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test setting a column gutter and more than two columns.
|
// Test setting a column gutter and more than two columns.
|
||||||
#set page(height: 3.25cm, width: 7.05cm, columns: 3, column-gutter: 30pt)
|
#set page(height: 3.25cm, width: 7.05cm, columns: 3)
|
||||||
|
#set columns(gutter: 30pt)
|
||||||
|
|
||||||
#rect(width: 100%, height: 2.5cm, fill: conifer)
|
#rect(width: 100%, height: 2.5cm, fill: conifer)
|
||||||
#rect(width: 100%, height: 2cm, fill: eastern)
|
#rect(width: 100%, height: 2cm, fill: eastern)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
// Test auto sizing.
|
// Test auto sizing.
|
||||||
|
|
||||||
Auto-sized circle. \
|
Auto-sized circle. \
|
||||||
#circle(fill: rgb("eb5278"), thickness: 2pt,
|
#circle(fill: rgb("eb5278"), stroke: black, thickness: 2pt,
|
||||||
align(center + horizon)[But, soft!]
|
align(center + horizon)[But, soft!]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
rect(fill: eastern, stroke: none),
|
rect(fill: eastern, stroke: none),
|
||||||
rect(fill: forest, stroke: none, thickness: 2pt),
|
rect(fill: forest, stroke: none, thickness: 2pt),
|
||||||
rect(fill: forest, stroke: conifer),
|
rect(fill: forest, stroke: conifer),
|
||||||
rect(fill: forest, thickness: 2pt),
|
rect(fill: forest, stroke: black, thickness: 2pt),
|
||||||
rect(fill: forest, stroke: conifer, thickness: 2pt),
|
rect(fill: forest, stroke: conifer, thickness: 2pt),
|
||||||
) {
|
) {
|
||||||
(align(horizon)[{i + 1}.], rect, [])
|
(align(horizon)[{i + 1}.], rect, [])
|
||||||
|
@ -25,11 +25,26 @@ World
|
|||||||
|
|
||||||
You
|
You
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test that paragraphs break due to incompatibility has correct spacing.
|
||||||
|
A #set par(spacing: 0pt); B #parbreak() C
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test that paragraph breaks due to block nodes have the correct spacing.
|
||||||
|
- A
|
||||||
|
|
||||||
|
#set par(spacing: 0pt)
|
||||||
|
- B
|
||||||
|
- C
|
||||||
|
#set par(spacing: 5pt)
|
||||||
|
- D
|
||||||
|
- E
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test that paragraph break due to incompatibility respects
|
// Test that paragraph break due to incompatibility respects
|
||||||
// spacing defined by the two adjacent paragraphs.
|
// spacing defined by the two adjacent paragraphs.
|
||||||
#let a = [#set par(spacing: 40pt);Hello]
|
#let a = [#set par(spacing: 40pt);Hello]
|
||||||
#let b = [#set par(spacing: 60pt);World]
|
#let b = [#set par(spacing: 10pt);World]
|
||||||
{a}{b}
|
{a}{b}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
Loading…
x
Reference in New Issue
Block a user