Switch to const generics for nodes

This commit is contained in:
Laurenz 2022-02-17 11:55:28 +01:00
parent 5965515a1e
commit ab95627d87
11 changed files with 159 additions and 279 deletions

View File

@ -4,6 +4,7 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use syn::parse_quote; use syn::parse_quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Error, Ident, Result}; use syn::{Error, Ident, Result};
@ -118,7 +119,9 @@ struct Property {
} }
/// Parse the name and generic type arguments of the node type. /// Parse the name and generic type arguments of the node type.
fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> { fn parse_self(
self_ty: &syn::Type,
) -> Result<(String, Punctuated<syn::GenericArgument, syn::Token![,]>)> {
// Extract the node type for which we want to generate properties. // Extract the node type for which we want to generate properties.
let path = match self_ty { let path = match self_ty {
syn::Type::Path(path) => path, syn::Type::Path(path) => path,
@ -129,15 +132,8 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
let last = path.path.segments.last().unwrap(); let last = path.path.segments.last().unwrap();
let self_name = last.ident.to_string(); let self_name = last.ident.to_string();
let self_args = match &last.arguments { let self_args = match &last.arguments {
syn::PathArguments::AngleBracketed(args) => args syn::PathArguments::AngleBracketed(args) => args.args.clone(),
.args _ => Punctuated::new(),
.iter()
.filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.collect(),
_ => vec![],
}; };
Ok((self_name, self_args)) Ok((self_name, self_args))
@ -146,29 +142,35 @@ 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,
params: &syn::punctuated::Punctuated<syn::GenericParam, syn::Token![,]>, params: &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: &Punctuated<syn::GenericArgument, syn::Token![,]>,
) -> Result<(Property, syn::ItemMod)> { ) -> Result<(Property, syn::ItemMod)> {
// The module that will contain the `Key` type. // The display name, e.g. `TextNode::STRONG`.
let module_name = &item.ident; let name = format!("{}::{}", self_name, &item.ident);
// The type of the property's value is what the user of our macro wrote // The type of the property's value is what the user of our macro wrote
// as type of the const ... // as type of the 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 this..
let key_args = quote! { #value_ty #(, #self_args)* }; let key = quote! { Key<#value_ty, #self_args> };
let phantom_args = self_args.iter().filter(|arg| match arg {
// The display name, e.g. `TextNode::STRONG`. syn::GenericArgument::Type(syn::Type::Path(path)) => {
let name = format!("{}::{}", self_name, &item.ident); params.iter().all(|param| match param {
syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident),
_ => true,
})
}
_ => true,
});
// The default value of the property is what the user wrote as // The default value of the property is what the user wrote as
// initialization value of the const. // initialization value of the const.
let default = &item.expr; let default = &item.expr;
let mut folder = None; let mut fold = None;
let mut property = Property { let mut property = Property {
name: item.ident.clone(), name: item.ident.clone(),
shorthand: false, shorthand: false,
@ -177,25 +179,23 @@ fn process_const(
}; };
for attr in std::mem::take(&mut item.attrs) { for attr in std::mem::take(&mut item.attrs) {
if attr.path.is_ident("fold") { match attr.path.get_ident().map(ToString::to_string).as_deref() {
// Look for a folding function like `#[fold(u64::add)]`. Some("fold") => {
let func: syn::Expr = attr.parse_args()?; // Look for a folding function like `#[fold(u64::add)]`.
folder = Some(quote! { let func: syn::Expr = attr.parse_args()?;
const FOLDING: bool = true; fold = Some(quote! {
const FOLDING: bool = true;
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value { fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
let f: fn(Self::Value, Self::Value) -> Self::Value = #func; let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
f(inner, outer) f(inner, outer)
} }
}); });
} else if attr.path.is_ident("shorthand") { }
property.shorthand = true; Some("shorthand") => property.shorthand = true,
} else if attr.path.is_ident("variadic") { Some("variadic") => property.variadic = true,
property.variadic = true; Some("skip") => property.skip = true,
} else if attr.path.is_ident("skip") { _ => item.attrs.push(attr),
property.skip = true;
} else {
item.attrs.push(attr);
} }
} }
@ -206,28 +206,27 @@ fn process_const(
)); ));
} }
let nonfolding = folder.is_none().then(|| { let nonfolding = fold.is_none().then(|| {
quote! { quote! { impl<#params> Nonfolding for #key {} }
impl<#params> Nonfolding for Key<#key_args> {}
}
}); });
// Generate the module code. // Generate the module code.
let module_name = &item.ident;
let module = parse_quote! { let module = parse_quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
mod #module_name { mod #module_name {
use super::*; use super::*;
pub struct Key<VALUE, #params>(pub PhantomData<(VALUE, #key_args)>); pub struct Key<VALUE, #params>(pub PhantomData<(VALUE, #(#phantom_args,)*)>);
impl<#params> Copy for Key<#key_args> {} impl<#params> Copy for #key {}
impl<#params> Clone for Key<#key_args> { impl<#params> Clone for #key {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<#params> Property for Key<#key_args> { impl<#params> Property for #key {
type Value = #value_ty; type Value = #value_ty;
const NAME: &'static str = #name; const NAME: &'static str = #name;
@ -245,7 +244,7 @@ fn process_const(
&*LAZY &*LAZY
} }
#folder #fold
} }
#nonfolding #nonfolding
@ -253,8 +252,7 @@ fn process_const(
}; };
// Replace type and initializer expression with the `Key`. // Replace type and initializer expression with the `Key`.
item.attrs.retain(|attr| !attr.path.is_ident("fold")); item.ty = parse_quote! { #module_name::#key };
item.ty = parse_quote! { #module_name::Key<#key_args> };
item.expr = parse_quote! { #module_name::Key(PhantomData) }; item.expr = parse_quote! { #module_name::Key(PhantomData) };
Ok((property, module)) Ok((property, module))

View File

@ -7,7 +7,7 @@ use crate::syntax::{Span, Spanned};
/// Early-return with a vec-boxed [`Error`]. /// Early-return with a vec-boxed [`Error`].
macro_rules! bail { macro_rules! bail {
($span:expr, $message:expr $(,)?) => { ($span:expr, $message:expr $(,)?) => {
return Err($crate::diag::Error::boxed($span, $message,)) return Err($crate::diag::Error::boxed($span, $message))
}; };
($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { ($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {

View File

@ -41,7 +41,7 @@ use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative}; use crate::geom::{Angle, Fractional, Length, Relative};
use crate::image::ImageStore; use crate::image::ImageStore;
use crate::layout::Layout; use crate::layout::Layout;
use crate::library::{self}; use crate::library::{self, ORDERED, UNORDERED};
use crate::loading::Loader; use crate::loading::Loader;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
use crate::syntax::ast::*; use crate::syntax::ast::*;
@ -272,9 +272,9 @@ impl Eval for ListNode {
type Output = Template; type Output = Template;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
Ok(Template::show(library::ListNode { Ok(Template::show(library::ListNode::<UNORDERED> {
number: None,
child: self.body().eval(ctx)?.pack(), child: self.body().eval(ctx)?.pack(),
label: library::Unordered,
})) }))
} }
} }
@ -283,9 +283,9 @@ impl Eval for EnumNode {
type Output = Template; type Output = Template;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
Ok(Template::show(library::ListNode { Ok(Template::show(library::ListNode::<ORDERED> {
number: self.number(),
child: self.body().eval(ctx)?.pack(), child: self.body().eval(ctx)?.pack(),
label: library::Ordered(self.number()),
})) }))
} }
} }

View File

@ -14,7 +14,7 @@ use crate::layout::{Layout, LayoutNode};
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::library::{ use crate::library::{
DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind,
TextNode, Underline, TextNode, UNDERLINE,
}; };
use crate::util::EcoString; use crate::util::EcoString;
use crate::Context; use crate::Context;
@ -145,7 +145,7 @@ impl Template {
/// Underline this template. /// Underline this template.
pub fn underlined(self) -> Self { pub fn underlined(self) -> Self {
Self::show(DecoNode { kind: Underline, body: self }) Self::show(DecoNode::<UNDERLINE>(self))
} }
/// Create a new sequence template. /// Create a new sequence template.

View File

@ -18,9 +18,9 @@ use std::sync::Arc;
use crate::eval::StyleChain; use crate::eval::StyleChain;
use crate::font::FontStore; use crate::font::FontStore;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec}; use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec, Transform};
use crate::image::ImageStore; use crate::image::ImageStore;
use crate::library::{AlignNode, Move, PadNode, TransformNode}; use crate::library::{AlignNode, PadNode, TransformNode, MOVE};
use crate::util::Prehashed; use crate::util::Prehashed;
use crate::Context; use crate::Context;
@ -150,8 +150,8 @@ impl LayoutNode {
/// 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 {
if !offset.is_zero() { if !offset.is_zero() {
TransformNode { TransformNode::<MOVE> {
kind: Move(offset.x, offset.y), transform: Transform::translation(offset.x, offset.y),
child: self, child: self,
} }
.pack() .pack()

View File

@ -5,15 +5,10 @@ use super::TextNode;
/// Typeset underline, striken-through or overlined text. /// Typeset underline, striken-through or overlined text.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct DecoNode<L: LineKind> { pub struct DecoNode<const L: DecoLine>(pub Template);
/// The kind of line.
pub kind: L,
/// The decorated contents.
pub body: Template,
}
#[class] #[class]
impl<L: LineKind> DecoNode<L> { impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`. /// Stroke color of the line, defaults to the text color if `None`.
#[shorthand] #[shorthand]
pub const STROKE: Option<Paint> = None; pub const STROKE: Option<Paint> = None;
@ -32,17 +27,14 @@ impl<L: LineKind> DecoNode<L> {
pub const EVADE: bool = true; pub const EVADE: bool = true;
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
Ok(Template::show(Self { Ok(Template::show(Self(args.expect::<Template>("body")?)))
kind: L::default(),
body: args.expect::<Template>("body")?,
}))
} }
} }
impl<L: LineKind> Show for DecoNode<L> { impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, styles: StyleChain) -> Template { fn show(&self, styles: StyleChain) -> Template {
self.body.clone().styled(TextNode::LINES, vec![Decoration { self.0.clone().styled(TextNode::LINES, vec![Decoration {
line: L::LINE, line: L,
stroke: styles.get(Self::STROKE), stroke: styles.get(Self::STROKE),
thickness: styles.get(Self::THICKNESS), thickness: styles.get(Self::THICKNESS),
offset: styles.get(Self::OFFSET), offset: styles.get(Self::OFFSET),
@ -65,42 +57,14 @@ pub struct Decoration {
pub evade: bool, pub evade: bool,
} }
/// The kind of decorative line. /// A kind of decorative line.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub type DecoLine = usize;
pub enum DecoLine {
/// A line under text.
Underline,
/// A line through text.
Strikethrough,
/// A line over text.
Overline,
}
/// Different kinds of decorative lines for text.
pub trait LineKind: Debug + Default + Hash + Sync + Send + 'static {
const LINE: DecoLine;
}
/// A line under text. /// A line under text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const UNDERLINE: DecoLine = 0;
pub struct Underline;
impl LineKind for Underline {
const LINE: DecoLine = DecoLine::Underline;
}
/// A line through text. /// A line through text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const STRIKETHROUGH: DecoLine = 1;
pub struct Strikethrough;
impl LineKind for Strikethrough {
const LINE: DecoLine = DecoLine::Strikethrough;
}
/// A line over text. /// A line over text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const OVERLINE: DecoLine = 2;
pub struct Overline;
impl LineKind for Overline {
const LINE: DecoLine = DecoLine::Overline;
}

View File

@ -5,15 +5,15 @@ 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: ListLabel> { pub struct ListNode<const L: Labelling> {
/// The list label -- unordered or ordered with index. /// The number of the item.
pub label: L, pub number: Option<usize>,
/// The node that produces the item's body. /// The node that produces the item's body.
pub child: LayoutNode, pub child: LayoutNode,
} }
#[class] #[class]
impl<L: ListLabel> ListNode<L> { impl<const L: Labelling> 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.
@ -24,17 +24,22 @@ impl<L: ListLabel> ListNode<L> {
.all()? .all()?
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, child)| Template::show(Self { label: L::new(1 + i), child })) .map(|(i, child)| Template::show(Self { number: Some(1 + i), child }))
.sum()) .sum())
} }
} }
impl<L: ListLabel> Show for ListNode<L> { impl<const L: Labelling> Show for ListNode<L> {
fn show(&self, styles: StyleChain) -> Template { fn show(&self, styles: StyleChain) -> Template {
let em = styles.get(TextNode::SIZE).abs; let em = styles.get(TextNode::SIZE).abs;
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
let body_indent = styles.get(Self::BODY_INDENT).resolve(em); let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
let label = match L {
UNORDERED => '•'.into(),
ORDERED | _ => format_eco!("{}.", self.number.unwrap_or(1)),
};
Template::block(GridNode { Template::block(GridNode {
tracks: Spec::with_x(vec![ tracks: Spec::with_x(vec![
TrackSizing::Linear(label_indent.into()), TrackSizing::Linear(label_indent.into()),
@ -45,7 +50,7 @@ impl<L: ListLabel> Show for ListNode<L> {
gutter: Spec::default(), gutter: Spec::default(),
children: vec![ children: vec![
LayoutNode::default(), LayoutNode::default(),
Template::Text(self.label.label()).pack(), Template::Text(label).pack(),
LayoutNode::default(), LayoutNode::default(),
self.child.clone(), self.child.clone(),
], ],
@ -54,38 +59,10 @@ impl<L: ListLabel> Show for ListNode<L> {
} }
/// How to label a list. /// How to label a list.
pub trait ListLabel: Debug + Default + Hash + Sync + Send + 'static { pub type Labelling = usize;
/// Create a new list label.
fn new(number: usize) -> Self;
/// Return the item's label.
fn label(&self) -> EcoString;
}
/// Unordered list labelling style. /// Unordered list labelling style.
#[derive(Debug, Default, Hash)] pub const UNORDERED: Labelling = 0;
pub struct Unordered;
impl ListLabel for Unordered {
fn new(_: usize) -> Self {
Self
}
fn label(&self) -> EcoString {
'•'.into()
}
}
/// Ordered list labelling style. /// Ordered list labelling style.
#[derive(Debug, Default, Hash)] pub const ORDERED: Labelling = 1;
pub struct Ordered(pub Option<usize>);
impl ListLabel for Ordered {
fn new(number: usize) -> Self {
Self(Some(number))
}
fn label(&self) -> EcoString {
format_eco!("{}.", self.0.unwrap_or(1))
}
}

View File

@ -53,16 +53,8 @@ pub use text::*;
pub use transform::*; pub use transform::*;
pub use utility::*; pub use utility::*;
macro_rules! prelude { /// Helpful imports for creating library functionality.
($($reexport:item)*) => { pub mod prelude {
/// Helpful imports for creating library functionality.
pub mod prelude {
$(#[doc(no_inline)] $reexport)*
}
};
}
prelude! {
pub use std::fmt::{self, Debug, Formatter}; pub use std::fmt::{self, Debug, Formatter};
pub use std::hash::Hash; pub use std::hash::Hash;
pub use std::num::NonZeroUsize; pub use std::num::NonZeroUsize;
@ -101,19 +93,19 @@ pub fn new() -> Scope {
std.def_class::<EmphNode>("emph"); std.def_class::<EmphNode>("emph");
std.def_class::<RawNode>("raw"); std.def_class::<RawNode>("raw");
std.def_class::<MathNode>("math"); std.def_class::<MathNode>("math");
std.def_class::<DecoNode<Underline>>("underline"); std.def_class::<DecoNode<UNDERLINE>>("underline");
std.def_class::<DecoNode<Strikethrough>>("strike"); std.def_class::<DecoNode<STRIKETHROUGH>>("strike");
std.def_class::<DecoNode<Overline>>("overline"); std.def_class::<DecoNode<OVERLINE>>("overline");
std.def_class::<LinkNode>("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_class::<TableNode>("table"); std.def_class::<TableNode>("table");
std.def_class::<ImageNode>("image"); std.def_class::<ImageNode>("image");
std.def_class::<ShapeNode<Rect>>("rect"); std.def_class::<ShapeNode<RECT>>("rect");
std.def_class::<ShapeNode<Square>>("square"); std.def_class::<ShapeNode<SQUARE>>("square");
std.def_class::<ShapeNode<Ellipse>>("ellipse"); std.def_class::<ShapeNode<ELLIPSE>>("ellipse");
std.def_class::<ShapeNode<Circle>>("circle"); std.def_class::<ShapeNode<CIRCLE>>("circle");
// Layout. // Layout.
std.def_class::<HNode>("h"); std.def_class::<HNode>("h");
@ -123,9 +115,9 @@ pub fn new() -> Scope {
std.def_class::<AlignNode>("align"); std.def_class::<AlignNode>("align");
std.def_class::<PadNode>("pad"); std.def_class::<PadNode>("pad");
std.def_class::<PlaceNode>("place"); std.def_class::<PlaceNode>("place");
std.def_class::<TransformNode<Move>>("move"); std.def_class::<TransformNode<MOVE>>("move");
std.def_class::<TransformNode<Scale>>("scale"); std.def_class::<TransformNode<SCALE>>("scale");
std.def_class::<TransformNode<Rotate>>("rotate"); std.def_class::<TransformNode<ROTATE>>("rotate");
std.def_class::<HideNode>("hide"); std.def_class::<HideNode>("hide");
std.def_class::<StackNode>("stack"); std.def_class::<StackNode>("stack");
std.def_class::<GridNode>("grid"); std.def_class::<GridNode>("grid");

View File

@ -7,15 +7,10 @@ use super::TextNode;
/// Place a node into a sizable and fillable shape. /// Place a node into a sizable and fillable shape.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ShapeNode<S: ShapeKind> { pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
/// Which shape to place the child into.
pub kind: S,
/// The child node to place into the shape, if any.
pub child: Option<LayoutNode>,
}
#[class] #[class]
impl<S: ShapeKind> ShapeNode<S> { impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape. /// How to fill the shape.
pub const FILL: Option<Paint> = None; pub const FILL: Option<Paint> = None;
/// How the stroke the shape. /// How the stroke the shape.
@ -26,12 +21,10 @@ impl<S: ShapeKind> ShapeNode<S> {
pub const PADDING: Linear = Linear::zero(); pub const PADDING: Linear = Linear::zero();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
let size = if !S::ROUND && S::QUADRATIC { let size = match S {
args.named::<Length>("size")?.map(Linear::from) SQUARE => args.named::<Length>("size")?.map(Linear::from),
} else if S::ROUND && S::QUADRATIC { CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Linear::from(r)),
args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r)) _ => None,
} else {
None
}; };
let width = match size { let width = match size {
@ -45,14 +38,12 @@ impl<S: ShapeKind> ShapeNode<S> {
}; };
Ok(Template::inline( Ok(Template::inline(
ShapeNode { kind: S::default(), child: args.find()? } Self(args.find()?).pack().sized(Spec::new(width, height)),
.pack()
.sized(Spec::new(width, height)),
)) ))
} }
} }
impl<S: ShapeKind> Layout for ShapeNode<S> { impl<const S: ShapeKind> Layout for ShapeNode<S> {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -60,9 +51,9 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
styles: StyleChain, styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> { ) -> Vec<Constrained<Arc<Frame>>> {
let mut frames; let mut frames;
if let Some(child) = &self.child { if let Some(child) = &self.0 {
let mut padding = styles.get(Self::PADDING); let mut padding = styles.get(Self::PADDING);
if S::ROUND { if is_round(S) {
padding.rel += Relative::new(0.5 - SQRT_2 / 4.0); padding.rel += Relative::new(0.5 - SQRT_2 / 4.0);
} }
@ -74,7 +65,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
// 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 S::QUADRATIC { if is_quadratic(S) {
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)
@ -95,7 +86,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
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 S::QUADRATIC { if is_quadratic(S) {
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)
@ -121,7 +112,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
.map(|paint| Stroke { paint, thickness }); .map(|paint| Stroke { paint, thickness });
if fill.is_some() || stroke.is_some() { if fill.is_some() || stroke.is_some() {
let geometry = if S::ROUND { let geometry = if is_round(S) {
Geometry::Ellipse(frame.size) Geometry::Ellipse(frame.size)
} else { } else {
Geometry::Rect(frame.size) Geometry::Rect(frame.size)
@ -140,44 +131,27 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
} }
} }
/// Categorizes shapes. /// A category of shape.
pub trait ShapeKind: Debug + Default + Hash + Sync + Send + 'static { pub type ShapeKind = usize;
const ROUND: bool;
const QUADRATIC: bool;
}
/// A rectangle with equal side lengths. /// A rectangle with equal side lengths.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const SQUARE: ShapeKind = 0;
pub struct Square;
impl ShapeKind for Square {
const ROUND: bool = false;
const QUADRATIC: bool = true;
}
/// A quadrilateral with four right angles. /// A quadrilateral with four right angles.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const RECT: ShapeKind = 1;
pub struct Rect;
impl ShapeKind for Rect {
const ROUND: bool = false;
const QUADRATIC: bool = false;
}
/// An ellipse with coinciding foci. /// An ellipse with coinciding foci.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const CIRCLE: ShapeKind = 2;
pub struct Circle;
impl ShapeKind for Circle {
const ROUND: bool = true;
const QUADRATIC: bool = true;
}
/// A curve around two focal points. /// A curve around two focal points.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub const ELLIPSE: ShapeKind = 3;
pub struct Ellipse;
impl ShapeKind for Ellipse { /// Whether a shape kind is curvy.
const ROUND: bool = true; fn is_round(kind: ShapeKind) -> bool {
const QUADRATIC: bool = false; matches!(kind, CIRCLE | ELLIPSE)
}
/// Whether a shape kind has equal side length.
fn is_quadratic(kind: ShapeKind) -> bool {
matches!(kind, SQUARE | CIRCLE)
} }

View File

@ -9,7 +9,7 @@ use rustybuzz::{Feature, UnicodeBuffer};
use ttf_parser::{GlyphId, OutlineBuilder, Tag}; use ttf_parser::{GlyphId, OutlineBuilder, Tag};
use super::prelude::*; use super::prelude::*;
use super::{DecoLine, Decoration}; use super::Decoration;
use crate::font::{ use crate::font::{
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight, Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
VerticalFontMetric, VerticalFontMetric,
@ -826,12 +826,12 @@ impl<'a> ShapedText<'a> {
) { ) {
let face = fonts.get(text.face_id); let face = fonts.get(text.face_id);
let metrics = match deco.line { let metrics = match deco.line {
DecoLine::Underline => face.underline, super::STRIKETHROUGH => face.strikethrough,
DecoLine::Strikethrough => face.strikethrough, super::OVERLINE => face.overline,
DecoLine::Overline => face.overline, super::UNDERLINE | _ => face.underline,
}; };
let evade = deco.evade && deco.line != DecoLine::Strikethrough; let evade = deco.evade && deco.line != super::STRIKETHROUGH;
let extent = deco.extent.resolve(text.size); let extent = deco.extent.resolve(text.size);
let offset = deco let offset = deco
.offset .offset

View File

@ -5,27 +5,45 @@ use crate::geom::Transform;
/// Transform a node without affecting layout. /// Transform a node without affecting layout.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct TransformNode<T: TransformKind> { pub struct TransformNode<const T: TransformKind> {
/// Transformation to apply to the contents. /// Transformation to apply to the contents.
pub kind: T, pub transform: Transform,
/// The node whose contents should be transformed. /// The node whose contents should be transformed.
pub child: LayoutNode, pub child: LayoutNode,
} }
#[class] #[class]
impl<T: TransformKind> TransformNode<T> { impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation. /// The origin of the transformation.
pub const ORIGIN: Spec<Option<Align>> = Spec::default(); pub const ORIGIN: Spec<Option<Align>> = Spec::default();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
let transform = match T {
MOVE => {
let tx = args.named("x")?.unwrap_or_default();
let ty = args.named("y")?.unwrap_or_default();
Transform::translation(tx, ty)
}
ROTATE => {
let angle = args.named_or_find("angle")?.unwrap_or_default();
Transform::rotation(angle)
}
SCALE | _ => {
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());
Transform::scale(sx, sy)
}
};
Ok(Template::inline(Self { Ok(Template::inline(Self {
kind: T::construct(args)?, transform,
child: args.expect("body")?, child: args.expect("body")?,
})) }))
} }
} }
impl<T: TransformKind> Layout for TransformNode<T> { impl<const T: TransformKind> Layout for TransformNode<T> {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -33,14 +51,12 @@ impl<T: TransformKind> Layout for TransformNode<T> {
styles: StyleChain, styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> { ) -> Vec<Constrained<Arc<Frame>>> {
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); 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 } = 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(matrix) .pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y)); .pre_concat(Transform::translation(-x, -y));
Arc::make_mut(frame).transform(transform); Arc::make_mut(frame).transform(transform);
@ -51,54 +67,13 @@ impl<T: TransformKind> Layout for TransformNode<T> {
} }
/// Kinds of transformations. /// Kinds of transformations.
pub trait TransformKind: Debug + Hash + Sized + Sync + Send + 'static { pub type TransformKind = usize;
fn construct(args: &mut Args) -> TypResult<Self>;
fn matrix(&self) -> Transform;
}
/// A translation on the X and Y axes. /// A translation on the X and Y axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub const MOVE: TransformKind = 0;
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. /// A rotational transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub const ROTATE: TransformKind = 1;
pub struct Rotate(pub Angle);
impl TransformKind for Rotate {
fn construct(args: &mut Args) -> TypResult<Self> {
Ok(Self(args.named_or_find("angle")?.unwrap_or_default()))
}
fn matrix(&self) -> Transform {
Transform::rotation(self.0)
}
}
/// A scale transformation. /// A scale transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub const SCALE: TransformKind = 2;
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)
}
}