Switch to const generics for nodes
This commit is contained in:
parent
5965515a1e
commit
ab95627d87
@ -4,6 +4,7 @@ use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Error, Ident, Result};
|
||||
|
||||
@ -118,7 +119,9 @@ struct Property {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
let path = match self_ty {
|
||||
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 self_name = last.ident.to_string();
|
||||
let self_args = match &last.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => args
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
syn::GenericArgument::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
syn::PathArguments::AngleBracketed(args) => args.args.clone(),
|
||||
_ => Punctuated::new(),
|
||||
};
|
||||
|
||||
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.
|
||||
fn process_const(
|
||||
item: &mut syn::ImplItemConst,
|
||||
params: &syn::punctuated::Punctuated<syn::GenericParam, syn::Token![,]>,
|
||||
params: &Punctuated<syn::GenericParam, syn::Token![,]>,
|
||||
self_ty: &syn::Type,
|
||||
self_name: &str,
|
||||
self_args: &[&syn::Type],
|
||||
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
|
||||
) -> Result<(Property, syn::ItemMod)> {
|
||||
// The module that will contain the `Key` type.
|
||||
let module_name = &item.ident;
|
||||
// The display name, e.g. `TextNode::STRONG`.
|
||||
let name = format!("{}::{}", self_name, &item.ident);
|
||||
|
||||
// The type of the property's value is what the user of our macro wrote
|
||||
// as type of the const ...
|
||||
let value_ty = &item.ty;
|
||||
|
||||
// ... but the real type of the const becomes Key<#key_args>.
|
||||
let key_args = quote! { #value_ty #(, #self_args)* };
|
||||
|
||||
// The display name, e.g. `TextNode::STRONG`.
|
||||
let name = format!("{}::{}", self_name, &item.ident);
|
||||
// ... but the real type of the const becomes this..
|
||||
let key = quote! { Key<#value_ty, #self_args> };
|
||||
let phantom_args = self_args.iter().filter(|arg| match arg {
|
||||
syn::GenericArgument::Type(syn::Type::Path(path)) => {
|
||||
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
|
||||
// initialization value of the const.
|
||||
let default = &item.expr;
|
||||
|
||||
let mut folder = None;
|
||||
let mut fold = None;
|
||||
let mut property = Property {
|
||||
name: item.ident.clone(),
|
||||
shorthand: false,
|
||||
@ -177,25 +179,23 @@ fn process_const(
|
||||
};
|
||||
|
||||
for attr in std::mem::take(&mut item.attrs) {
|
||||
if attr.path.is_ident("fold") {
|
||||
// Look for a folding function like `#[fold(u64::add)]`.
|
||||
let func: syn::Expr = attr.parse_args()?;
|
||||
folder = Some(quote! {
|
||||
const FOLDING: bool = true;
|
||||
match attr.path.get_ident().map(ToString::to_string).as_deref() {
|
||||
Some("fold") => {
|
||||
// Look for a folding function like `#[fold(u64::add)]`.
|
||||
let func: syn::Expr = attr.parse_args()?;
|
||||
fold = Some(quote! {
|
||||
const FOLDING: bool = true;
|
||||
|
||||
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
||||
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
|
||||
f(inner, outer)
|
||||
}
|
||||
});
|
||||
} else if attr.path.is_ident("shorthand") {
|
||||
property.shorthand = true;
|
||||
} else if attr.path.is_ident("variadic") {
|
||||
property.variadic = true;
|
||||
} else if attr.path.is_ident("skip") {
|
||||
property.skip = true;
|
||||
} else {
|
||||
item.attrs.push(attr);
|
||||
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
||||
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
|
||||
f(inner, outer)
|
||||
}
|
||||
});
|
||||
}
|
||||
Some("shorthand") => property.shorthand = true,
|
||||
Some("variadic") => property.variadic = true,
|
||||
Some("skip") => property.skip = true,
|
||||
_ => item.attrs.push(attr),
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,28 +206,27 @@ fn process_const(
|
||||
));
|
||||
}
|
||||
|
||||
let nonfolding = folder.is_none().then(|| {
|
||||
quote! {
|
||||
impl<#params> Nonfolding for Key<#key_args> {}
|
||||
}
|
||||
let nonfolding = fold.is_none().then(|| {
|
||||
quote! { impl<#params> Nonfolding for #key {} }
|
||||
});
|
||||
|
||||
// Generate the module code.
|
||||
let module_name = &item.ident;
|
||||
let module = parse_quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod #module_name {
|
||||
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> Clone for Key<#key_args> {
|
||||
impl<#params> Copy for #key {}
|
||||
impl<#params> Clone for #key {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<#params> Property for Key<#key_args> {
|
||||
impl<#params> Property for #key {
|
||||
type Value = #value_ty;
|
||||
|
||||
const NAME: &'static str = #name;
|
||||
@ -245,7 +244,7 @@ fn process_const(
|
||||
&*LAZY
|
||||
}
|
||||
|
||||
#folder
|
||||
#fold
|
||||
}
|
||||
|
||||
#nonfolding
|
||||
@ -253,8 +252,7 @@ fn process_const(
|
||||
};
|
||||
|
||||
// Replace type and initializer expression with the `Key`.
|
||||
item.attrs.retain(|attr| !attr.path.is_ident("fold"));
|
||||
item.ty = parse_quote! { #module_name::Key<#key_args> };
|
||||
item.ty = parse_quote! { #module_name::#key };
|
||||
item.expr = parse_quote! { #module_name::Key(PhantomData) };
|
||||
|
||||
Ok((property, module))
|
||||
|
@ -7,7 +7,7 @@ use crate::syntax::{Span, Spanned};
|
||||
/// Early-return with a vec-boxed [`Error`].
|
||||
macro_rules! bail {
|
||||
($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),+ $(,)?) => {
|
||||
|
@ -41,7 +41,7 @@ use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||
use crate::image::ImageStore;
|
||||
use crate::layout::Layout;
|
||||
use crate::library::{self};
|
||||
use crate::library::{self, ORDERED, UNORDERED};
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::syntax::ast::*;
|
||||
@ -272,9 +272,9 @@ impl Eval for ListNode {
|
||||
type Output = Template;
|
||||
|
||||
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(),
|
||||
label: library::Unordered,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -283,9 +283,9 @@ impl Eval for EnumNode {
|
||||
type Output = Template;
|
||||
|
||||
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(),
|
||||
label: library::Ordered(self.number()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use crate::layout::{Layout, LayoutNode};
|
||||
use crate::library::prelude::*;
|
||||
use crate::library::{
|
||||
DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind,
|
||||
TextNode, Underline,
|
||||
TextNode, UNDERLINE,
|
||||
};
|
||||
use crate::util::EcoString;
|
||||
use crate::Context;
|
||||
@ -145,7 +145,7 @@ impl Template {
|
||||
|
||||
/// Underline this template.
|
||||
pub fn underlined(self) -> Self {
|
||||
Self::show(DecoNode { kind: Underline, body: self })
|
||||
Self::show(DecoNode::<UNDERLINE>(self))
|
||||
}
|
||||
|
||||
/// Create a new sequence template.
|
||||
|
@ -18,9 +18,9 @@ use std::sync::Arc;
|
||||
use crate::eval::StyleChain;
|
||||
use crate::font::FontStore;
|
||||
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::library::{AlignNode, Move, PadNode, TransformNode};
|
||||
use crate::library::{AlignNode, PadNode, TransformNode, MOVE};
|
||||
use crate::util::Prehashed;
|
||||
use crate::Context;
|
||||
|
||||
@ -150,8 +150,8 @@ impl LayoutNode {
|
||||
/// Transform this node's contents without affecting layout.
|
||||
pub fn moved(self, offset: Point) -> Self {
|
||||
if !offset.is_zero() {
|
||||
TransformNode {
|
||||
kind: Move(offset.x, offset.y),
|
||||
TransformNode::<MOVE> {
|
||||
transform: Transform::translation(offset.x, offset.y),
|
||||
child: self,
|
||||
}
|
||||
.pack()
|
||||
|
@ -5,15 +5,10 @@ use super::TextNode;
|
||||
|
||||
/// Typeset underline, striken-through or overlined text.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct DecoNode<L: LineKind> {
|
||||
/// The kind of line.
|
||||
pub kind: L,
|
||||
/// The decorated contents.
|
||||
pub body: Template,
|
||||
}
|
||||
pub struct DecoNode<const L: DecoLine>(pub Template);
|
||||
|
||||
#[class]
|
||||
impl<L: LineKind> DecoNode<L> {
|
||||
impl<const L: DecoLine> DecoNode<L> {
|
||||
/// Stroke color of the line, defaults to the text color if `None`.
|
||||
#[shorthand]
|
||||
pub const STROKE: Option<Paint> = None;
|
||||
@ -32,17 +27,14 @@ impl<L: LineKind> DecoNode<L> {
|
||||
pub const EVADE: bool = true;
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(Template::show(Self {
|
||||
kind: L::default(),
|
||||
body: args.expect::<Template>("body")?,
|
||||
}))
|
||||
Ok(Template::show(Self(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 {
|
||||
self.body.clone().styled(TextNode::LINES, vec![Decoration {
|
||||
line: L::LINE,
|
||||
self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
||||
line: L,
|
||||
stroke: styles.get(Self::STROKE),
|
||||
thickness: styles.get(Self::THICKNESS),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
@ -65,42 +57,14 @@ pub struct Decoration {
|
||||
pub evade: bool,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Different kinds of decorative lines for text.
|
||||
pub trait LineKind: Debug + Default + Hash + Sync + Send + 'static {
|
||||
const LINE: DecoLine;
|
||||
}
|
||||
/// A kind of decorative line.
|
||||
pub type DecoLine = usize;
|
||||
|
||||
/// A line under text.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Underline;
|
||||
|
||||
impl LineKind for Underline {
|
||||
const LINE: DecoLine = DecoLine::Underline;
|
||||
}
|
||||
pub const UNDERLINE: DecoLine = 0;
|
||||
|
||||
/// A line through text.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Strikethrough;
|
||||
|
||||
impl LineKind for Strikethrough {
|
||||
const LINE: DecoLine = DecoLine::Strikethrough;
|
||||
}
|
||||
pub const STRIKETHROUGH: DecoLine = 1;
|
||||
|
||||
/// A line over text.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Overline;
|
||||
|
||||
impl LineKind for Overline {
|
||||
const LINE: DecoLine = DecoLine::Overline;
|
||||
}
|
||||
pub const OVERLINE: DecoLine = 2;
|
||||
|
@ -5,15 +5,15 @@ use super::{GridNode, TextNode, TrackSizing};
|
||||
|
||||
/// An unordered or ordered list.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ListNode<L: ListLabel> {
|
||||
/// The list label -- unordered or ordered with index.
|
||||
pub label: L,
|
||||
pub struct ListNode<const L: Labelling> {
|
||||
/// The number of the item.
|
||||
pub number: Option<usize>,
|
||||
/// The node that produces the item's body.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl<L: ListLabel> ListNode<L> {
|
||||
impl<const L: Labelling> ListNode<L> {
|
||||
/// The indentation of each item's label.
|
||||
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
|
||||
/// The space between the label and the body of each item.
|
||||
@ -24,17 +24,22 @@ impl<L: ListLabel> ListNode<L> {
|
||||
.all()?
|
||||
.into_iter()
|
||||
.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())
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: ListLabel> Show for ListNode<L> {
|
||||
impl<const L: Labelling> Show for ListNode<L> {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let label_indent = styles.get(Self::LABEL_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 {
|
||||
tracks: Spec::with_x(vec![
|
||||
TrackSizing::Linear(label_indent.into()),
|
||||
@ -45,7 +50,7 @@ impl<L: ListLabel> Show for ListNode<L> {
|
||||
gutter: Spec::default(),
|
||||
children: vec![
|
||||
LayoutNode::default(),
|
||||
Template::Text(self.label.label()).pack(),
|
||||
Template::Text(label).pack(),
|
||||
LayoutNode::default(),
|
||||
self.child.clone(),
|
||||
],
|
||||
@ -54,38 +59,10 @@ impl<L: ListLabel> Show for ListNode<L> {
|
||||
}
|
||||
|
||||
/// How to label a list.
|
||||
pub trait ListLabel: Debug + Default + Hash + Sync + Send + 'static {
|
||||
/// Create a new list label.
|
||||
fn new(number: usize) -> Self;
|
||||
|
||||
/// Return the item's label.
|
||||
fn label(&self) -> EcoString;
|
||||
}
|
||||
pub type Labelling = usize;
|
||||
|
||||
/// Unordered list labelling style.
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Unordered;
|
||||
|
||||
impl ListLabel for Unordered {
|
||||
fn new(_: usize) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn label(&self) -> EcoString {
|
||||
'•'.into()
|
||||
}
|
||||
}
|
||||
pub const UNORDERED: Labelling = 0;
|
||||
|
||||
/// Ordered list labelling style.
|
||||
#[derive(Debug, Default, Hash)]
|
||||
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))
|
||||
}
|
||||
}
|
||||
pub const ORDERED: Labelling = 1;
|
||||
|
@ -53,16 +53,8 @@ pub use text::*;
|
||||
pub use transform::*;
|
||||
pub use utility::*;
|
||||
|
||||
macro_rules! prelude {
|
||||
($($reexport:item)*) => {
|
||||
/// Helpful imports for creating library functionality.
|
||||
pub mod prelude {
|
||||
$(#[doc(no_inline)] $reexport)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
prelude! {
|
||||
/// Helpful imports for creating library functionality.
|
||||
pub mod prelude {
|
||||
pub use std::fmt::{self, Debug, Formatter};
|
||||
pub use std::hash::Hash;
|
||||
pub use std::num::NonZeroUsize;
|
||||
@ -101,19 +93,19 @@ pub fn new() -> Scope {
|
||||
std.def_class::<EmphNode>("emph");
|
||||
std.def_class::<RawNode>("raw");
|
||||
std.def_class::<MathNode>("math");
|
||||
std.def_class::<DecoNode<Underline>>("underline");
|
||||
std.def_class::<DecoNode<Strikethrough>>("strike");
|
||||
std.def_class::<DecoNode<Overline>>("overline");
|
||||
std.def_class::<DecoNode<UNDERLINE>>("underline");
|
||||
std.def_class::<DecoNode<STRIKETHROUGH>>("strike");
|
||||
std.def_class::<DecoNode<OVERLINE>>("overline");
|
||||
std.def_class::<LinkNode>("link");
|
||||
std.def_class::<HeadingNode>("heading");
|
||||
std.def_class::<ListNode<Unordered>>("list");
|
||||
std.def_class::<ListNode<Ordered>>("enum");
|
||||
std.def_class::<ListNode<UNORDERED>>("list");
|
||||
std.def_class::<ListNode<ORDERED>>("enum");
|
||||
std.def_class::<TableNode>("table");
|
||||
std.def_class::<ImageNode>("image");
|
||||
std.def_class::<ShapeNode<Rect>>("rect");
|
||||
std.def_class::<ShapeNode<Square>>("square");
|
||||
std.def_class::<ShapeNode<Ellipse>>("ellipse");
|
||||
std.def_class::<ShapeNode<Circle>>("circle");
|
||||
std.def_class::<ShapeNode<RECT>>("rect");
|
||||
std.def_class::<ShapeNode<SQUARE>>("square");
|
||||
std.def_class::<ShapeNode<ELLIPSE>>("ellipse");
|
||||
std.def_class::<ShapeNode<CIRCLE>>("circle");
|
||||
|
||||
// Layout.
|
||||
std.def_class::<HNode>("h");
|
||||
@ -123,9 +115,9 @@ pub fn new() -> Scope {
|
||||
std.def_class::<AlignNode>("align");
|
||||
std.def_class::<PadNode>("pad");
|
||||
std.def_class::<PlaceNode>("place");
|
||||
std.def_class::<TransformNode<Move>>("move");
|
||||
std.def_class::<TransformNode<Scale>>("scale");
|
||||
std.def_class::<TransformNode<Rotate>>("rotate");
|
||||
std.def_class::<TransformNode<MOVE>>("move");
|
||||
std.def_class::<TransformNode<SCALE>>("scale");
|
||||
std.def_class::<TransformNode<ROTATE>>("rotate");
|
||||
std.def_class::<HideNode>("hide");
|
||||
std.def_class::<StackNode>("stack");
|
||||
std.def_class::<GridNode>("grid");
|
||||
|
@ -7,15 +7,10 @@ use super::TextNode;
|
||||
|
||||
/// Place a node into a sizable and fillable shape.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ShapeNode<S: ShapeKind> {
|
||||
/// Which shape to place the child into.
|
||||
pub kind: S,
|
||||
/// The child node to place into the shape, if any.
|
||||
pub child: Option<LayoutNode>,
|
||||
}
|
||||
pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
||||
|
||||
#[class]
|
||||
impl<S: ShapeKind> ShapeNode<S> {
|
||||
impl<const S: ShapeKind> ShapeNode<S> {
|
||||
/// How to fill the shape.
|
||||
pub const FILL: Option<Paint> = None;
|
||||
/// How the stroke the shape.
|
||||
@ -26,12 +21,10 @@ impl<S: ShapeKind> ShapeNode<S> {
|
||||
pub const PADDING: Linear = Linear::zero();
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
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 size = match S {
|
||||
SQUARE => args.named::<Length>("size")?.map(Linear::from),
|
||||
CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Linear::from(r)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let width = match size {
|
||||
@ -45,14 +38,12 @@ impl<S: ShapeKind> ShapeNode<S> {
|
||||
};
|
||||
|
||||
Ok(Template::inline(
|
||||
ShapeNode { kind: S::default(), child: args.find()? }
|
||||
.pack()
|
||||
.sized(Spec::new(width, height)),
|
||||
Self(args.find()?).pack().sized(Spec::new(width, height)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -60,9 +51,9 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let mut frames;
|
||||
if let Some(child) = &self.child {
|
||||
if let Some(child) = &self.0 {
|
||||
let mut padding = styles.get(Self::PADDING);
|
||||
if S::ROUND {
|
||||
if is_round(S) {
|
||||
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
|
||||
// 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 target = regions.expand.select(regions.current, Size::zero());
|
||||
target.x.max(target.y)
|
||||
@ -95,7 +86,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
let mut size =
|
||||
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 target = regions.expand.select(regions.current, Size::zero());
|
||||
target.x.max(target.y)
|
||||
@ -121,7 +112,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
.map(|paint| Stroke { paint, thickness });
|
||||
|
||||
if fill.is_some() || stroke.is_some() {
|
||||
let geometry = if S::ROUND {
|
||||
let geometry = if is_round(S) {
|
||||
Geometry::Ellipse(frame.size)
|
||||
} else {
|
||||
Geometry::Rect(frame.size)
|
||||
@ -140,44 +131,27 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Categorizes shapes.
|
||||
pub trait ShapeKind: Debug + Default + Hash + Sync + Send + 'static {
|
||||
const ROUND: bool;
|
||||
const QUADRATIC: bool;
|
||||
}
|
||||
/// A category of shape.
|
||||
pub type ShapeKind = usize;
|
||||
|
||||
/// A rectangle with equal side lengths.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Square;
|
||||
|
||||
impl ShapeKind for Square {
|
||||
const ROUND: bool = false;
|
||||
const QUADRATIC: bool = true;
|
||||
}
|
||||
pub const SQUARE: ShapeKind = 0;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
pub const RECT: ShapeKind = 1;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
pub const CIRCLE: ShapeKind = 2;
|
||||
|
||||
/// A curve around two focal points.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Ellipse;
|
||||
pub const ELLIPSE: ShapeKind = 3;
|
||||
|
||||
impl ShapeKind for Ellipse {
|
||||
const ROUND: bool = true;
|
||||
const QUADRATIC: bool = false;
|
||||
/// Whether a shape kind is curvy.
|
||||
fn is_round(kind: ShapeKind) -> bool {
|
||||
matches!(kind, CIRCLE | ELLIPSE)
|
||||
}
|
||||
|
||||
/// Whether a shape kind has equal side length.
|
||||
fn is_quadratic(kind: ShapeKind) -> bool {
|
||||
matches!(kind, SQUARE | CIRCLE)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use rustybuzz::{Feature, UnicodeBuffer};
|
||||
use ttf_parser::{GlyphId, OutlineBuilder, Tag};
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{DecoLine, Decoration};
|
||||
use super::Decoration;
|
||||
use crate::font::{
|
||||
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
|
||||
VerticalFontMetric,
|
||||
@ -826,12 +826,12 @@ impl<'a> ShapedText<'a> {
|
||||
) {
|
||||
let face = fonts.get(text.face_id);
|
||||
let metrics = match deco.line {
|
||||
DecoLine::Underline => face.underline,
|
||||
DecoLine::Strikethrough => face.strikethrough,
|
||||
DecoLine::Overline => face.overline,
|
||||
super::STRIKETHROUGH => face.strikethrough,
|
||||
super::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 offset = deco
|
||||
.offset
|
||||
|
@ -5,27 +5,45 @@ use crate::geom::Transform;
|
||||
|
||||
/// Transform a node without affecting layout.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct TransformNode<T: TransformKind> {
|
||||
pub struct TransformNode<const T: TransformKind> {
|
||||
/// Transformation to apply to the contents.
|
||||
pub kind: T,
|
||||
pub transform: Transform,
|
||||
/// The node whose contents should be transformed.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl<T: TransformKind> TransformNode<T> {
|
||||
impl<const 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<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 {
|
||||
kind: T::construct(args)?,
|
||||
transform,
|
||||
child: args.expect("body")?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TransformKind> Layout for TransformNode<T> {
|
||||
impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -33,14 +51,12 @@ impl<T: TransformKind> Layout for TransformNode<T> {
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<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);
|
||||
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||
let transform = Transform::translation(x, y)
|
||||
.pre_concat(matrix)
|
||||
.pre_concat(self.transform)
|
||||
.pre_concat(Transform::translation(-x, -y));
|
||||
|
||||
Arc::make_mut(frame).transform(transform);
|
||||
@ -51,54 +67,13 @@ impl<T: TransformKind> Layout for TransformNode<T> {
|
||||
}
|
||||
|
||||
/// Kinds of transformations.
|
||||
pub trait TransformKind: Debug + Hash + Sized + Sync + Send + 'static {
|
||||
fn construct(args: &mut Args) -> TypResult<Self>;
|
||||
fn matrix(&self) -> Transform;
|
||||
}
|
||||
pub type TransformKind = usize;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
pub const MOVE: TransformKind = 0;
|
||||
|
||||
/// 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_or_find("angle")?.unwrap_or_default()))
|
||||
}
|
||||
|
||||
fn matrix(&self) -> Transform {
|
||||
Transform::rotation(self.0)
|
||||
}
|
||||
}
|
||||
pub const ROTATE: TransformKind = 1;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
pub const SCALE: TransformKind = 2;
|
||||
|
Loading…
x
Reference in New Issue
Block a user