Set rules for everything
This commit is contained in:
parent
68503b9a07
commit
e089b6ea40
@ -5,7 +5,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Error, Result};
|
||||
use syn::{Error, Ident, Result};
|
||||
|
||||
/// Turn a node into a class.
|
||||
#[proc_macro_attribute]
|
||||
@ -24,15 +24,17 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
||||
let module = quote::format_ident!("{}_types", self_name);
|
||||
|
||||
let mut key_modules = vec![];
|
||||
let mut properties = vec![];
|
||||
let mut construct = None;
|
||||
let mut set = None;
|
||||
|
||||
for item in std::mem::take(&mut impl_block.items) {
|
||||
match item {
|
||||
syn::ImplItem::Const(mut item) => {
|
||||
key_modules.push(process_const(
|
||||
&mut item, params, self_ty, &self_name, &self_args,
|
||||
)?);
|
||||
let (property, module) =
|
||||
process_const(&mut item, params, self_ty, &self_name, &self_args)?;
|
||||
properties.push(property);
|
||||
key_modules.push(module);
|
||||
impl_block.items.push(syn::ImplItem::Const(item));
|
||||
}
|
||||
syn::ImplItem::Method(method) => {
|
||||
@ -49,17 +51,34 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
||||
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(())
|
||||
let set = set.unwrap_or_else(|| {
|
||||
let sets = properties.into_iter().filter(|p| !p.skip).map(|property| {
|
||||
let name = property.name;
|
||||
let string = name.to_string().replace("_", "-").to_lowercase();
|
||||
|
||||
let alternative = if property.variadic {
|
||||
quote! {
|
||||
.or_else(|| {
|
||||
let list: Vec<_> = args.all().collect();
|
||||
(!list.is_empty()).then(|| list)
|
||||
})
|
||||
}
|
||||
} else if property.shorthand {
|
||||
quote! { .or_else(|| args.find()) }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
quote! { styles.set_opt(Self::#name, args.named(#string)? #alternative); }
|
||||
});
|
||||
|
||||
parse_quote! {
|
||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||
#(#sets)*
|
||||
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
|
||||
// it from the outside.
|
||||
@ -87,6 +106,14 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
||||
})
|
||||
}
|
||||
|
||||
/// A style property.
|
||||
struct Property {
|
||||
name: Ident,
|
||||
shorthand: bool,
|
||||
variadic: bool,
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
/// Parse the name and generic type arguments of the node type.
|
||||
fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
|
||||
// Extract the node type for which we want to generate properties.
|
||||
@ -120,7 +147,7 @@ fn process_const(
|
||||
self_ty: &syn::Type,
|
||||
self_name: &str,
|
||||
self_args: &[&syn::Type],
|
||||
) -> Result<syn::ItemMod> {
|
||||
) -> Result<(Property, syn::ItemMod)> {
|
||||
// The module that will contain the `Key` type.
|
||||
let module_name = &item.ident;
|
||||
|
||||
@ -139,13 +166,16 @@ fn process_const(
|
||||
let default = &item.expr;
|
||||
|
||||
let mut folder = None;
|
||||
let mut nonfolding = Some(quote! {
|
||||
impl<#params> Nonfolding for Key<#key_args> {}
|
||||
});
|
||||
let mut property = Property {
|
||||
name: item.ident.clone(),
|
||||
shorthand: false,
|
||||
variadic: false,
|
||||
skip: false,
|
||||
};
|
||||
|
||||
// Look for a folding function like `#[fold(u64::add)]`.
|
||||
for attr in &item.attrs {
|
||||
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;
|
||||
@ -155,10 +185,30 @@ fn process_const(
|
||||
f(inner, outer)
|
||||
}
|
||||
});
|
||||
nonfolding = None;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
if property.shorthand && property.variadic {
|
||||
return Err(Error::new(
|
||||
property.name.span(),
|
||||
"shorthand and variadic are mutually exclusive",
|
||||
));
|
||||
}
|
||||
|
||||
let nonfolding = folder.is_none().then(|| {
|
||||
quote! {
|
||||
impl<#params> Nonfolding for Key<#key_args> {}
|
||||
}
|
||||
});
|
||||
|
||||
// Generate the module code.
|
||||
let module = parse_quote! {
|
||||
#[allow(non_snake_case)]
|
||||
@ -204,5 +254,5 @@ fn process_const(
|
||||
item.ty = parse_quote! { #module_name::Key<#key_args> };
|
||||
item.expr = parse_quote! { #module_name::Key(PhantomData) };
|
||||
|
||||
Ok(module)
|
||||
Ok((property, module))
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use super::{StyleChain, StyleVec, StyleVecBuilder};
|
||||
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
||||
pub struct CollapsingBuilder<'a, T> {
|
||||
builder: StyleVecBuilder<'a, T>,
|
||||
staged: Vec<(T, StyleChain<'a>, bool)>,
|
||||
staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
|
||||
last: Last,
|
||||
}
|
||||
|
||||
@ -29,9 +29,21 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> {
|
||||
/// and to its right, with no destructive items or weak items in between to
|
||||
/// its left and no destructive items in between to its right. There may be
|
||||
/// ignorant items in between in both directions.
|
||||
pub fn weak(&mut self, item: T, styles: StyleChain<'a>) {
|
||||
if self.last == Last::Supportive {
|
||||
self.staged.push((item, styles, true));
|
||||
pub fn weak(&mut self, item: T, strength: u8, styles: StyleChain<'a>) {
|
||||
if self.last != Last::Destructive {
|
||||
if self.last == Last::Weak {
|
||||
if let Some(i) = self
|
||||
.staged
|
||||
.iter()
|
||||
.position(|(.., prev)| prev.map_or(false, |p| p < strength))
|
||||
{
|
||||
self.staged.remove(i);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.staged.push((item, styles, Some(strength)));
|
||||
self.last = Last::Weak;
|
||||
}
|
||||
}
|
||||
@ -52,7 +64,7 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> {
|
||||
|
||||
/// Has no influence on other items.
|
||||
pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
|
||||
self.staged.push((item, styles, false));
|
||||
self.staged.push((item, styles, None));
|
||||
}
|
||||
|
||||
/// Return the finish style vec and the common prefix chain.
|
||||
@ -63,8 +75,8 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> {
|
||||
|
||||
/// Push the staged items, filtering out weak items if `supportive` is false.
|
||||
fn flush(&mut self, supportive: bool) {
|
||||
for (item, styles, weak) in self.staged.drain(..) {
|
||||
if !weak || supportive {
|
||||
for (item, styles, strength) in self.staged.drain(..) {
|
||||
if supportive || strength.is_none() {
|
||||
push_merging(&mut self.builder, item, styles);
|
||||
}
|
||||
}
|
||||
|
150
src/eval/mod.rs
150
src/eval/mod.rs
@ -14,6 +14,7 @@ mod collapse;
|
||||
mod func;
|
||||
mod ops;
|
||||
mod scope;
|
||||
mod show;
|
||||
mod template;
|
||||
|
||||
pub use array::*;
|
||||
@ -23,6 +24,7 @@ pub use collapse::*;
|
||||
pub use dict::*;
|
||||
pub use func::*;
|
||||
pub use scope::*;
|
||||
pub use show::*;
|
||||
pub use styles::*;
|
||||
pub use template::*;
|
||||
pub use value::*;
|
||||
@ -33,34 +35,23 @@ use std::io;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{FontStyle, Highlighter, Style as SynStyle, Theme, ThemeSet};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||
use crate::geom::{Angle, Color, Fractional, Length, Paint, Relative};
|
||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||
use crate::image::ImageStore;
|
||||
use crate::layout::Layout;
|
||||
use crate::library::{self, DecoLine, TextNode};
|
||||
use crate::library::{self};
|
||||
use crate::loading::Loader;
|
||||
use crate::parse;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::syntax;
|
||||
use crate::syntax::ast::*;
|
||||
use crate::syntax::{RedNode, Span, Spanned};
|
||||
use crate::syntax::{Span, Spanned};
|
||||
use crate::util::{EcoString, RefMutExt};
|
||||
use crate::Context;
|
||||
|
||||
static THEME: Lazy<Theme> =
|
||||
Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap());
|
||||
|
||||
static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines());
|
||||
|
||||
/// An evaluated module, ready for importing or conversion to a root layout
|
||||
/// tree.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Module {
|
||||
/// The top-level definitions that were bound in this module.
|
||||
pub scope: Scope,
|
||||
@ -194,17 +185,13 @@ fn eval_markup(
|
||||
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
||||
let tail = eval_markup(ctx, nodes)?;
|
||||
ctx.scopes.def_mut(wrap.binding().take(), tail);
|
||||
wrap.body().eval(ctx)?.show()
|
||||
wrap.body().eval(ctx)?.display()
|
||||
}
|
||||
_ => node.eval(ctx)?,
|
||||
});
|
||||
}
|
||||
|
||||
if seq.len() == 1 {
|
||||
Ok(seq.into_iter().next().unwrap())
|
||||
} else {
|
||||
Ok(Template::Sequence(seq))
|
||||
}
|
||||
Ok(Template::sequence(seq))
|
||||
}
|
||||
|
||||
impl Eval for MarkupNode {
|
||||
@ -223,7 +210,7 @@ impl Eval for MarkupNode {
|
||||
Self::Heading(heading) => heading.eval(ctx)?,
|
||||
Self::List(list) => list.eval(ctx)?,
|
||||
Self::Enum(enum_) => enum_.eval(ctx)?,
|
||||
Self::Expr(expr) => expr.eval(ctx)?.show(),
|
||||
Self::Expr(expr) => expr.eval(ctx)?.display(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -232,7 +219,7 @@ impl Eval for StrongNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(self.body().eval(ctx)?.styled(TextNode::STRONG, true))
|
||||
Ok(Template::show(library::StrongNode(self.body().eval(ctx)?)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +227,7 @@ impl Eval for EmphNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(self.body().eval(ctx)?.styled(TextNode::EMPH, true))
|
||||
Ok(Template::show(library::EmphNode(self.body().eval(ctx)?)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,104 +235,25 @@ impl Eval for RawNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let code = self.highlighted();
|
||||
Ok(if self.block { Template::Block(code.pack()) } else { code })
|
||||
let template = Template::show(library::RawNode {
|
||||
text: self.text.clone(),
|
||||
block: self.block,
|
||||
});
|
||||
Ok(match self.lang {
|
||||
Some(_) => template.styled(library::RawNode::LANG, self.lang.clone()),
|
||||
None => template,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RawNode {
|
||||
/// Styled template for a code block, with optional syntax highlighting.
|
||||
pub fn highlighted(&self) -> Template {
|
||||
let mut seq: Vec<Template> = vec![];
|
||||
|
||||
let syntax = if let Some(syntax) = self
|
||||
.lang
|
||||
.as_ref()
|
||||
.and_then(|token| SYNTAXES.find_syntax_by_token(&token))
|
||||
{
|
||||
Some(syntax)
|
||||
} else if matches!(
|
||||
self.lang.as_ref().map(|s| s.to_ascii_lowercase()).as_deref(),
|
||||
Some("typ" | "typst")
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
return Template::Text(self.text.clone()).monospaced();
|
||||
};
|
||||
|
||||
let foreground = THEME
|
||||
.settings
|
||||
.foreground
|
||||
.map(Color::from)
|
||||
.unwrap_or(Color::BLACK)
|
||||
.into();
|
||||
|
||||
match syntax {
|
||||
Some(syntax) => {
|
||||
let mut highlighter = HighlightLines::new(syntax, &THEME);
|
||||
for (i, line) in self.text.lines().enumerate() {
|
||||
if i != 0 {
|
||||
seq.push(Template::Linebreak);
|
||||
}
|
||||
|
||||
for (style, piece) in highlighter.highlight(line, &SYNTAXES) {
|
||||
seq.push(style_piece(piece, foreground, style));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let green = parse::parse(&self.text);
|
||||
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
||||
let highlighter = Highlighter::new(&THEME);
|
||||
|
||||
syntax::highlight_syntect(
|
||||
red.as_ref(),
|
||||
&highlighter,
|
||||
&mut |range, style| {
|
||||
seq.push(style_piece(&self.text[range], foreground, style));
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Template::Sequence(seq).monospaced()
|
||||
}
|
||||
}
|
||||
|
||||
/// Style a piece of text with a syntect style.
|
||||
fn style_piece(piece: &str, foreground: Paint, style: SynStyle) -> Template {
|
||||
let mut styles = StyleMap::new();
|
||||
|
||||
let paint = style.foreground.into();
|
||||
if paint != foreground {
|
||||
styles.set(TextNode::FILL, paint);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::BOLD) {
|
||||
styles.set(TextNode::STRONG, true);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::ITALIC) {
|
||||
styles.set(TextNode::EMPH, true);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||
styles.set(TextNode::LINES, vec![DecoLine::Underline.into()]);
|
||||
}
|
||||
|
||||
Template::Text(piece.into()).styled_with_map(styles)
|
||||
}
|
||||
|
||||
impl Eval for MathNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let text = Template::Text(self.formula.trim().into()).monospaced();
|
||||
Ok(if self.display {
|
||||
Template::Block(text.pack())
|
||||
} else {
|
||||
text
|
||||
})
|
||||
Ok(Template::show(library::MathNode {
|
||||
formula: self.formula.clone(),
|
||||
display: self.display,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,8 +261,8 @@ impl Eval for HeadingNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(Template::block(library::HeadingNode {
|
||||
child: self.body().eval(ctx)?.pack(),
|
||||
Ok(Template::show(library::HeadingNode {
|
||||
body: self.body().eval(ctx)?,
|
||||
level: self.level(),
|
||||
}))
|
||||
}
|
||||
@ -364,9 +272,9 @@ impl Eval for ListNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(Template::block(library::ListNode {
|
||||
Ok(Template::show(library::ListNode {
|
||||
child: self.body().eval(ctx)?.pack(),
|
||||
kind: library::Unordered,
|
||||
label: library::Unordered,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -375,9 +283,9 @@ impl Eval for EnumNode {
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
Ok(Template::block(library::ListNode {
|
||||
Ok(Template::show(library::ListNode {
|
||||
child: self.body().eval(ctx)?.pack(),
|
||||
kind: library::Ordered(self.number()),
|
||||
label: library::Ordered(self.number()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
96
src/eval/show.rs
Normal file
96
src/eval/show.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{StyleChain, Template};
|
||||
use crate::util::Prehashed;
|
||||
|
||||
/// A node that can be realized given some styles.
|
||||
pub trait Show {
|
||||
/// Realize the template in the given styles.
|
||||
fn show(&self, styles: StyleChain) -> Template;
|
||||
|
||||
/// Convert to a packed show node.
|
||||
fn pack(self) -> ShowNode
|
||||
where
|
||||
Self: Debug + Hash + Sized + Sync + Send + 'static,
|
||||
{
|
||||
ShowNode::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased showable node with a precomputed hash.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct ShowNode(Arc<Prehashed<dyn Bounds>>);
|
||||
|
||||
impl ShowNode {
|
||||
/// Pack any showable node.
|
||||
pub fn new<T>(node: T) -> Self
|
||||
where
|
||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
||||
{
|
||||
Self(Arc::new(Prehashed::new(node)))
|
||||
}
|
||||
|
||||
/// The type id of this node.
|
||||
pub fn id(&self) -> TypeId {
|
||||
self.0.as_any().type_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for ShowNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
self.0.show(styles)
|
||||
}
|
||||
|
||||
fn pack(self) -> ShowNode {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ShowNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ShowNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We cast to thin pointers for comparison because we don't want to
|
||||
// compare vtables (which can be different across codegen units).
|
||||
std::ptr::eq(
|
||||
Arc::as_ptr(&self.0) as *const (),
|
||||
Arc::as_ptr(&other.0) as *const (),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
trait Bounds: Show + Debug + Sync + Send + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn hash64(&self) -> u64;
|
||||
}
|
||||
|
||||
impl<T> Bounds for T
|
||||
where
|
||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn hash64(&self) -> u64 {
|
||||
// Also hash the TypeId since nodes with different types but
|
||||
// equal data should be different.
|
||||
let mut state = fxhash::FxHasher64::default();
|
||||
self.type_id().hash(&mut state);
|
||||
self.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for dyn Bounds {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.hash64());
|
||||
}
|
||||
}
|
@ -49,6 +49,11 @@ impl StyleMap {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether this map contains scoped styles.
|
||||
pub fn has_scoped(&self) -> bool {
|
||||
self.0.iter().any(|e| e.scoped)
|
||||
}
|
||||
|
||||
/// Make `self` the first link of the style chain `outer`.
|
||||
///
|
||||
/// The resulting style chain contains styles from `self` as well as
|
||||
@ -136,20 +141,6 @@ impl<'a> StyleChain<'a> {
|
||||
self.links().count()
|
||||
}
|
||||
|
||||
/// Convert to an owned style map.
|
||||
///
|
||||
/// Panics if the chain contains barrier links.
|
||||
pub fn to_map(self) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
for link in self.links() {
|
||||
match link {
|
||||
Link::Map(map) => suffix.apply(map),
|
||||
Link::Barrier(_) => panic!("chain contains barrier"),
|
||||
}
|
||||
}
|
||||
suffix
|
||||
}
|
||||
|
||||
/// Build a style map from the suffix (all links beyond the `len`) of the
|
||||
/// chain.
|
||||
///
|
||||
@ -170,6 +161,17 @@ impl<'a> StyleChain<'a> {
|
||||
pub fn pop(&mut self) {
|
||||
*self = self.outer.copied().unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Return the chain, but without the last link if that one contains only
|
||||
/// scoped styles. This is a hack.
|
||||
pub(crate) fn unscoped(mut self, node: TypeId) -> Self {
|
||||
if let Some(Link::Map(map)) = self.link {
|
||||
if map.0.iter().all(|e| e.scoped && e.is_of_id(node)) {
|
||||
self.pop();
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
|
@ -6,12 +6,15 @@ use std::ops::{Add, AddAssign};
|
||||
|
||||
use typed_arena::Arena;
|
||||
|
||||
use super::{CollapsingBuilder, Interruption, Property, StyleMap, StyleVecBuilder};
|
||||
use super::{
|
||||
CollapsingBuilder, Interruption, Property, Show, ShowNode, StyleMap, StyleVecBuilder,
|
||||
};
|
||||
use crate::diag::StrResult;
|
||||
use crate::layout::{Layout, PackedNode};
|
||||
use crate::layout::{Layout, LayoutNode};
|
||||
use crate::library::prelude::*;
|
||||
use crate::library::{
|
||||
FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, TextNode,
|
||||
DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind,
|
||||
TextNode, Underline,
|
||||
};
|
||||
use crate::util::EcoString;
|
||||
use crate::Context;
|
||||
@ -52,7 +55,7 @@ pub enum Template {
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// An inline-level node.
|
||||
Inline(PackedNode),
|
||||
Inline(LayoutNode),
|
||||
/// A paragraph break.
|
||||
Parbreak,
|
||||
/// A column break.
|
||||
@ -60,21 +63,23 @@ pub enum Template {
|
||||
/// Vertical spacing.
|
||||
Vertical(SpacingKind),
|
||||
/// A block-level node.
|
||||
Block(PackedNode),
|
||||
Block(LayoutNode),
|
||||
/// A page break.
|
||||
Pagebreak,
|
||||
/// A page node.
|
||||
Page(PageNode),
|
||||
/// A node that can be realized with styles.
|
||||
Show(ShowNode),
|
||||
/// A template with attached styles.
|
||||
Styled(Box<Self>, StyleMap),
|
||||
Styled(Arc<(Self, StyleMap)>),
|
||||
/// A sequence of multiple subtemplates.
|
||||
Sequence(Vec<Self>),
|
||||
Sequence(Arc<Vec<Self>>),
|
||||
}
|
||||
|
||||
impl Template {
|
||||
/// Create an empty template.
|
||||
pub fn new() -> Self {
|
||||
Self::Sequence(vec![])
|
||||
Self::sequence(vec![])
|
||||
}
|
||||
|
||||
/// Create a template from an inline-level node.
|
||||
@ -93,42 +98,63 @@ impl Template {
|
||||
Self::Block(node.pack())
|
||||
}
|
||||
|
||||
/// Layout this template into a collection of pages.
|
||||
pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> {
|
||||
let (mut ctx, styles) = LayoutContext::new(ctx);
|
||||
let (pages, shared) = Builder::build_pages(self);
|
||||
let styles = shared.chain(&styles);
|
||||
pages
|
||||
.iter()
|
||||
.flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&styles)))
|
||||
.collect()
|
||||
/// Create a template from a showable node.
|
||||
pub fn show<T>(node: T) -> Self
|
||||
where
|
||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
||||
{
|
||||
Self::Show(node.pack())
|
||||
}
|
||||
|
||||
/// Style this template with a single property.
|
||||
pub fn styled<P: Property>(mut self, key: P, value: P::Value) -> Self {
|
||||
if let Self::Styled(_, map) = &mut self {
|
||||
map.set(key, value);
|
||||
self
|
||||
} else {
|
||||
self.styled_with_map(StyleMap::with(key, value))
|
||||
if let Self::Styled(styled) = &mut self {
|
||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
||||
if !map.has_scoped() {
|
||||
map.set(key, value);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
self.styled_with_map(StyleMap::with(key, value))
|
||||
}
|
||||
|
||||
/// Style this template with a full style map.
|
||||
pub fn styled_with_map(mut self, styles: StyleMap) -> Self {
|
||||
if styles.is_empty() {
|
||||
self
|
||||
} else if let Self::Styled(_, map) = &mut self {
|
||||
map.apply(&styles);
|
||||
self
|
||||
} else {
|
||||
Self::Styled(Box::new(self), styles)
|
||||
return self;
|
||||
}
|
||||
|
||||
if let Self::Styled(styled) = &mut self {
|
||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
||||
if !styles.has_scoped() && !map.has_scoped() {
|
||||
map.apply(&styles);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::Styled(Arc::new((self, styles)))
|
||||
}
|
||||
|
||||
/// Style this template in monospace.
|
||||
pub fn monospaced(self) -> Self {
|
||||
self.styled(TextNode::MONOSPACE, true)
|
||||
self.styled(TextNode::MONOSPACED, true)
|
||||
}
|
||||
|
||||
/// Underline this template.
|
||||
pub fn underlined(self) -> Self {
|
||||
Self::show(DecoNode { kind: Underline, body: self })
|
||||
}
|
||||
|
||||
/// Create a new sequence template.
|
||||
pub fn sequence(seq: Vec<Self>) -> Self {
|
||||
if seq.len() == 1 {
|
||||
seq.into_iter().next().unwrap()
|
||||
} else {
|
||||
Self::Sequence(Arc::new(seq))
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeat this template `n` times.
|
||||
@ -136,7 +162,24 @@ impl Template {
|
||||
let count = usize::try_from(n)
|
||||
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
||||
|
||||
Ok(Self::Sequence(vec![self.clone(); count]))
|
||||
Ok(Self::sequence(vec![self.clone(); count]))
|
||||
}
|
||||
|
||||
/// Layout this template into a collection of pages.
|
||||
pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> {
|
||||
let style_arena = Arena::new();
|
||||
let template_arena = Arena::new();
|
||||
let (mut ctx, styles) = LayoutContext::new(ctx);
|
||||
|
||||
let mut builder = Builder::new(&style_arena, &template_arena, true);
|
||||
builder.process(self, styles);
|
||||
builder.finish_page(true, false, styles);
|
||||
|
||||
let (pages, shared) = builder.pages.unwrap().finish();
|
||||
pages
|
||||
.iter()
|
||||
.flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&shared)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,11 +211,17 @@ impl Debug for Template {
|
||||
}
|
||||
Self::Pagebreak => f.pad("Pagebreak"),
|
||||
Self::Page(page) => page.fmt(f),
|
||||
Self::Styled(sub, map) => {
|
||||
Self::Show(node) => {
|
||||
f.write_str("Show(")?;
|
||||
node.fmt(f)?;
|
||||
f.write_str(")")
|
||||
}
|
||||
Self::Styled(styled) => {
|
||||
let (sub, map) = styled.as_ref();
|
||||
map.fmt(f)?;
|
||||
sub.fmt(f)
|
||||
}
|
||||
Self::Sequence(seq) => f.debug_list().entries(seq).finish(),
|
||||
Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,20 +232,22 @@ impl Add for Template {
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self::Sequence(match (self, rhs) {
|
||||
(Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
|
||||
lhs.extend(rhs);
|
||||
let mutable = Arc::make_mut(&mut lhs);
|
||||
match Arc::try_unwrap(rhs) {
|
||||
Ok(vec) => mutable.extend(vec),
|
||||
Err(rc) => mutable.extend(rc.iter().cloned()),
|
||||
}
|
||||
lhs
|
||||
}
|
||||
(Self::Sequence(mut lhs), rhs) => {
|
||||
lhs.push(rhs);
|
||||
Arc::make_mut(&mut lhs).push(rhs);
|
||||
lhs
|
||||
}
|
||||
(lhs, Self::Sequence(mut rhs)) => {
|
||||
rhs.insert(0, lhs);
|
||||
Arc::make_mut(&mut rhs).insert(0, lhs);
|
||||
rhs
|
||||
}
|
||||
(lhs, rhs) => {
|
||||
vec![lhs, rhs]
|
||||
}
|
||||
(lhs, rhs) => Arc::new(vec![lhs, rhs]),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -209,7 +260,7 @@ impl AddAssign for Template {
|
||||
|
||||
impl Sum for Template {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
Self::Sequence(iter.collect())
|
||||
Self::sequence(iter.collect())
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,14 +271,21 @@ impl Layout for Template {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let (flow, shared) = Builder::build_flow(self);
|
||||
flow.layout(ctx, regions, shared.chain(&styles))
|
||||
let style_arena = Arena::new();
|
||||
let template_arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::new(&style_arena, &template_arena, false);
|
||||
builder.process(self, styles);
|
||||
builder.finish_par(styles);
|
||||
|
||||
let (flow, shared) = builder.flow.finish();
|
||||
FlowNode(flow).layout(ctx, regions, shared)
|
||||
}
|
||||
|
||||
fn pack(self) -> PackedNode {
|
||||
fn pack(self) -> LayoutNode {
|
||||
match self {
|
||||
Template::Block(node) => node,
|
||||
other => PackedNode::new(other),
|
||||
other => LayoutNode::new(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -235,7 +293,9 @@ impl Layout for Template {
|
||||
/// Builds a flow or page nodes from a template.
|
||||
struct Builder<'a> {
|
||||
/// An arena where intermediate style chains are stored.
|
||||
arena: &'a Arena<StyleChain<'a>>,
|
||||
style_arena: &'a Arena<StyleChain<'a>>,
|
||||
/// An arena where intermediate templates are stored.
|
||||
template_arena: &'a Arena<Template>,
|
||||
/// The already built page runs.
|
||||
pages: Option<StyleVecBuilder<'a, PageNode>>,
|
||||
/// The currently built flow.
|
||||
@ -247,34 +307,15 @@ struct Builder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Builder<'a> {
|
||||
/// Build page runs from a template.
|
||||
fn build_pages(template: &Template) -> (StyleVec<PageNode>, StyleMap) {
|
||||
let arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::prepare(&arena, true);
|
||||
builder.process(template, StyleChain::default());
|
||||
builder.finish_page(true, false, StyleChain::default());
|
||||
|
||||
let (pages, shared) = builder.pages.unwrap().finish();
|
||||
(pages, shared.to_map())
|
||||
}
|
||||
|
||||
/// Build a subflow from a template.
|
||||
fn build_flow(template: &Template) -> (FlowNode, StyleMap) {
|
||||
let arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::prepare(&arena, false);
|
||||
builder.process(template, StyleChain::default());
|
||||
builder.finish_par();
|
||||
|
||||
let (flow, shared) = builder.flow.finish();
|
||||
(FlowNode(flow), shared.to_map())
|
||||
}
|
||||
|
||||
/// Prepare the builder.
|
||||
fn prepare(arena: &'a Arena<StyleChain<'a>>, top: bool) -> Self {
|
||||
fn new(
|
||||
style_arena: &'a Arena<StyleChain<'a>>,
|
||||
template_arena: &'a Arena<Template>,
|
||||
top: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
arena,
|
||||
style_arena,
|
||||
template_arena,
|
||||
pages: top.then(|| StyleVecBuilder::new()),
|
||||
flow: CollapsingBuilder::new(),
|
||||
par: CollapsingBuilder::new(),
|
||||
@ -286,7 +327,7 @@ impl<'a> Builder<'a> {
|
||||
fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) {
|
||||
match template {
|
||||
Template::Space => {
|
||||
self.par.weak(ParChild::Text(' '.into()), styles);
|
||||
self.par.weak(ParChild::Text(' '.into()), 0, styles);
|
||||
}
|
||||
Template::Linebreak => {
|
||||
self.par.destructive(ParChild::Text('\n'.into()), styles);
|
||||
@ -306,15 +347,15 @@ impl<'a> Builder<'a> {
|
||||
self.par.supportive(ParChild::Node(node.clone()), styles);
|
||||
}
|
||||
Template::Parbreak => {
|
||||
self.finish_par();
|
||||
self.flow.weak(FlowChild::Parbreak, styles);
|
||||
self.finish_par(styles);
|
||||
self.flow.weak(FlowChild::Parbreak, 1, styles);
|
||||
}
|
||||
Template::Colbreak => {
|
||||
self.finish_par();
|
||||
self.finish_par(styles);
|
||||
self.flow.destructive(FlowChild::Colbreak, styles);
|
||||
}
|
||||
Template::Vertical(kind) => {
|
||||
self.finish_par();
|
||||
self.finish_par(styles);
|
||||
let child = FlowChild::Spacing(*kind);
|
||||
if kind.is_fractional() {
|
||||
self.flow.destructive(child, styles);
|
||||
@ -323,13 +364,14 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
Template::Block(node) => {
|
||||
self.finish_par();
|
||||
self.finish_par(styles);
|
||||
let child = FlowChild::Node(node.clone());
|
||||
if node.is::<PlaceNode>() {
|
||||
self.flow.ignorant(child, styles);
|
||||
} else {
|
||||
self.flow.supportive(child, styles);
|
||||
}
|
||||
self.finish_par(styles);
|
||||
}
|
||||
Template::Pagebreak => {
|
||||
self.finish_page(true, true, styles);
|
||||
@ -340,26 +382,32 @@ impl<'a> Builder<'a> {
|
||||
pages.push(page.clone(), styles);
|
||||
}
|
||||
}
|
||||
Template::Styled(sub, map) => {
|
||||
Template::Show(node) => {
|
||||
let template = self.template_arena.alloc(node.show(styles));
|
||||
self.process(template, styles.unscoped(node.id()));
|
||||
}
|
||||
Template::Styled(styled) => {
|
||||
let (sub, map) = styled.as_ref();
|
||||
let stored = self.style_arena.alloc(styles);
|
||||
let styles = map.chain(stored);
|
||||
|
||||
let interruption = map.interruption();
|
||||
match interruption {
|
||||
Some(Interruption::Page) => self.finish_page(false, true, styles),
|
||||
Some(Interruption::Par) => self.finish_par(),
|
||||
Some(Interruption::Par) => self.finish_par(styles),
|
||||
None => {}
|
||||
}
|
||||
|
||||
let outer = self.arena.alloc(styles);
|
||||
let styles = map.chain(outer);
|
||||
self.process(sub, styles);
|
||||
|
||||
match interruption {
|
||||
Some(Interruption::Page) => self.finish_page(true, false, styles),
|
||||
Some(Interruption::Par) => self.finish_par(),
|
||||
Some(Interruption::Par) => self.finish_par(styles),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Template::Sequence(seq) => {
|
||||
for sub in seq {
|
||||
for sub in seq.iter() {
|
||||
self.process(sub, styles);
|
||||
}
|
||||
}
|
||||
@ -367,17 +415,18 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
|
||||
/// Finish the currently built paragraph.
|
||||
fn finish_par(&mut self) {
|
||||
fn finish_par(&mut self, styles: StyleChain<'a>) {
|
||||
let (par, shared) = mem::take(&mut self.par).finish();
|
||||
if !par.is_empty() {
|
||||
let node = ParNode(par).pack();
|
||||
self.flow.supportive(FlowChild::Node(node), shared);
|
||||
}
|
||||
self.flow.weak(FlowChild::Leading, 0, styles);
|
||||
}
|
||||
|
||||
/// Finish the currently built page run.
|
||||
fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) {
|
||||
self.finish_par();
|
||||
self.finish_par(styles);
|
||||
if let Some(pages) = &mut self.pages {
|
||||
let (flow, shared) = mem::take(&mut self.flow).finish();
|
||||
if !flow.is_empty() || (keep_last && self.keep_next) {
|
||||
|
@ -115,7 +115,7 @@ impl Value {
|
||||
}
|
||||
|
||||
/// Return the display representation of the value.
|
||||
pub fn show(self) -> Template {
|
||||
pub fn display(self) -> Template {
|
||||
match self {
|
||||
Value::None => Template::new(),
|
||||
Value::Int(v) => Template::Text(format_eco!("{}", v)),
|
||||
|
@ -10,7 +10,7 @@ pub use constraints::*;
|
||||
pub use incremental::*;
|
||||
pub use regions::*;
|
||||
|
||||
use std::any::Any;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
@ -21,6 +21,7 @@ use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
||||
use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec};
|
||||
use crate::image::ImageStore;
|
||||
use crate::library::{AlignNode, Move, PadNode, TransformNode};
|
||||
use crate::util::Prehashed;
|
||||
use crate::Context;
|
||||
|
||||
/// A node that can be layouted into a sequence of regions.
|
||||
@ -37,11 +38,11 @@ pub trait Layout {
|
||||
) -> Vec<Constrained<Arc<Frame>>>;
|
||||
|
||||
/// Convert to a packed node.
|
||||
fn pack(self) -> PackedNode
|
||||
fn pack(self) -> LayoutNode
|
||||
where
|
||||
Self: Debug + Hash + Sized + Sync + Send + 'static,
|
||||
{
|
||||
PackedNode::new(self)
|
||||
LayoutNode::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,31 +76,26 @@ impl<'a> LayoutContext<'a> {
|
||||
}
|
||||
|
||||
/// A type-erased layouting node with a precomputed hash.
|
||||
#[derive(Clone)]
|
||||
pub struct PackedNode {
|
||||
/// The type-erased node.
|
||||
node: Arc<dyn Bounds>,
|
||||
/// A precomputed hash for the node.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: u64,
|
||||
}
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
||||
|
||||
impl PackedNode {
|
||||
impl LayoutNode {
|
||||
/// Pack any layoutable node.
|
||||
pub fn new<T>(node: T) -> Self
|
||||
where
|
||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: node.hash64(),
|
||||
node: Arc::new(node),
|
||||
}
|
||||
Self(Arc::new(Prehashed::new(node)))
|
||||
}
|
||||
|
||||
/// Check whether the contained node is a specific layout node.
|
||||
pub fn is<T: 'static>(&self) -> bool {
|
||||
self.node.as_any().is::<T>()
|
||||
self.0.as_any().is::<T>()
|
||||
}
|
||||
|
||||
/// The type id of this node.
|
||||
pub fn id(&self) -> TypeId {
|
||||
self.0.as_any().type_id()
|
||||
}
|
||||
|
||||
/// Try to downcast to a specific layout node.
|
||||
@ -107,7 +103,7 @@ impl PackedNode {
|
||||
where
|
||||
T: Layout + Debug + Hash + 'static,
|
||||
{
|
||||
self.node.as_any().downcast_ref()
|
||||
self.0.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
/// Force a size for this node.
|
||||
@ -165,7 +161,7 @@ impl PackedNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for PackedNode {
|
||||
impl Layout for LayoutNode {
|
||||
#[track_caller]
|
||||
fn layout(
|
||||
&self,
|
||||
@ -173,7 +169,7 @@ impl Layout for PackedNode {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let styles = styles.barred(self.node.as_any().type_id());
|
||||
let styles = styles.barred(self.id());
|
||||
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
return self.node.layout(ctx, regions, styles);
|
||||
@ -193,14 +189,14 @@ impl Layout for PackedNode {
|
||||
frames
|
||||
} else {
|
||||
ctx.level += 1;
|
||||
let frames = self.node.layout(ctx, regions, styles);
|
||||
let frames = self.0.layout(ctx, regions, styles);
|
||||
ctx.level -= 1;
|
||||
|
||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if !entry.check(regions) {
|
||||
eprintln!("node: {:#?}", self.node);
|
||||
eprintln!("node: {:#?}", self.0);
|
||||
eprintln!("regions: {regions:#?}");
|
||||
eprintln!(
|
||||
"constraints: {:#?}",
|
||||
@ -214,44 +210,34 @@ impl Layout for PackedNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn pack(self) -> PackedNode {
|
||||
fn pack(self) -> LayoutNode {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PackedNode {
|
||||
impl Default for LayoutNode {
|
||||
fn default() -> Self {
|
||||
EmptyNode.pack()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PackedNode {
|
||||
impl Debug for LayoutNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.node.fmt(f)
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PackedNode {
|
||||
impl PartialEq for LayoutNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We cast to thin pointers for comparison because we don't want to
|
||||
// compare vtables (which can be different across codegen units).
|
||||
std::ptr::eq(
|
||||
Arc::as_ptr(&self.node) as *const (),
|
||||
Arc::as_ptr(&other.node) as *const (),
|
||||
Arc::as_ptr(&self.0) as *const (),
|
||||
Arc::as_ptr(&other.0) as *const (),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for PackedNode {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// Hash the node.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
state.write_u64(self.hash);
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
state.write_u64(self.hash64());
|
||||
}
|
||||
}
|
||||
|
||||
trait Bounds: Layout + Debug + Sync + Send + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn hash64(&self) -> u64;
|
||||
@ -275,6 +261,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for dyn Bounds {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.hash64());
|
||||
}
|
||||
}
|
||||
|
||||
/// A layout node that produces an empty frame.
|
||||
///
|
||||
/// The packed version of this is returned by [`PackedNode::default`].
|
||||
@ -301,7 +293,7 @@ struct SizedNode {
|
||||
/// How to size the node horizontally and vertically.
|
||||
sizing: Spec<Option<Linear>>,
|
||||
/// The node to be sized.
|
||||
child: PackedNode,
|
||||
child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Layout for SizedNode {
|
||||
@ -355,7 +347,7 @@ struct FillNode {
|
||||
/// How to fill the frames resulting from the `child`.
|
||||
fill: Paint,
|
||||
/// The node to fill.
|
||||
child: PackedNode,
|
||||
child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Layout for FillNode {
|
||||
@ -380,7 +372,7 @@ struct StrokeNode {
|
||||
/// How to stroke the frames resulting from the `child`.
|
||||
stroke: Stroke,
|
||||
/// The node to stroke.
|
||||
child: PackedNode,
|
||||
child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Layout for StrokeNode {
|
||||
|
@ -9,14 +9,14 @@ pub struct AlignNode {
|
||||
/// How to align the node horizontally and vertically.
|
||||
pub aligns: Spec<Option<Align>>,
|
||||
/// The node to be aligned.
|
||||
pub child: PackedNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl AlignNode {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
let aligns: Spec<_> = args.find().unwrap_or_default();
|
||||
let body: PackedNode = args.expect("body")?;
|
||||
let body: LayoutNode = args.expect("body")?;
|
||||
Ok(Template::block(body.aligned(aligns)))
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub struct ColumnsNode {
|
||||
pub columns: NonZeroUsize,
|
||||
/// The child to be layouted into the columns. Most likely, this should be a
|
||||
/// flow or stack node.
|
||||
pub child: PackedNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -24,11 +24,6 @@ impl ColumnsNode {
|
||||
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 {
|
||||
|
@ -10,7 +10,7 @@ impl BoxNode {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
let width = args.named("width")?;
|
||||
let height = args.named("height")?;
|
||||
let body: PackedNode = args.find().unwrap_or_default();
|
||||
let body: LayoutNode = args.find().unwrap_or_default();
|
||||
Ok(Template::inline(body.sized(Spec::new(width, height))))
|
||||
}
|
||||
}
|
||||
|
@ -4,57 +4,67 @@ use super::prelude::*;
|
||||
use super::TextNode;
|
||||
|
||||
/// Typeset underline, striken-through or overlined text.
|
||||
pub struct DecoNode<L: LineKind>(pub L);
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct DecoNode<L: LineKind> {
|
||||
/// The kind of line.
|
||||
pub kind: L,
|
||||
/// The decorated contents.
|
||||
pub body: Template,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl<L: LineKind> DecoNode<L> {
|
||||
/// Stroke color of the line, defaults to the text color if `None`.
|
||||
#[shorthand]
|
||||
pub const STROKE: Option<Paint> = None;
|
||||
/// Thickness of the line's strokes (dependent on scaled font size), read
|
||||
/// from the font tables if `None`.
|
||||
#[shorthand]
|
||||
pub const THICKNESS: Option<Linear> = None;
|
||||
/// Position of the line relative to the baseline (dependent on scaled font
|
||||
/// size), read from the font tables if `None`.
|
||||
pub const OFFSET: Option<Linear> = None;
|
||||
/// Amount that the line will be longer or shorter than its associated text
|
||||
/// (dependent on scaled font size).
|
||||
pub const EXTENT: Linear = Linear::zero();
|
||||
/// Whether the line skips sections in which it would collide
|
||||
/// with the glyphs. Does not apply to strikethrough.
|
||||
pub const EVADE: bool = true;
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
let deco = Decoration {
|
||||
Ok(Template::show(Self {
|
||||
kind: L::default(),
|
||||
body: args.expect::<Template>("body")?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: LineKind> Show for DecoNode<L> {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
self.body.clone().styled(TextNode::LINES, vec![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(),
|
||||
evade: args.named("evade")?.unwrap_or(true),
|
||||
};
|
||||
Ok(args.expect::<Template>("body")?.styled(TextNode::LINES, vec![deco]))
|
||||
stroke: styles.get(Self::STROKE),
|
||||
thickness: styles.get(Self::THICKNESS),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: styles.get(Self::EVADE),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a line that is positioned over, under or on top of text.
|
||||
///
|
||||
/// For more details, see [`DecoNode`].
|
||||
#[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,
|
||||
/// Whether the line skips sections in which it would collide
|
||||
/// with the glyphs. Does not apply to strikethrough.
|
||||
pub evade: bool,
|
||||
}
|
||||
|
||||
impl From<DecoLine> for Decoration {
|
||||
fn from(line: DecoLine) -> Self {
|
||||
Self {
|
||||
line,
|
||||
stroke: None,
|
||||
thickness: None,
|
||||
offset: None,
|
||||
extent: Linear::zero(),
|
||||
evade: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of decorative line.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum DecoLine {
|
||||
@ -67,7 +77,7 @@ pub enum DecoLine {
|
||||
}
|
||||
|
||||
/// Different kinds of decorative lines for text.
|
||||
pub trait LineKind {
|
||||
pub trait LineKind: Debug + Default + Hash + Sync + Send + 'static {
|
||||
const LINE: DecoLine;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@ pub struct FlowNode(pub StyleVec<FlowChild>);
|
||||
/// A child of a flow node.
|
||||
#[derive(Hash)]
|
||||
pub enum FlowChild {
|
||||
/// Leading between other children.
|
||||
Leading,
|
||||
/// A paragraph / block break.
|
||||
Parbreak,
|
||||
/// A column / region break.
|
||||
@ -20,7 +22,7 @@ pub enum FlowChild {
|
||||
/// Vertical spacing between other children.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary block-level node.
|
||||
Node(PackedNode),
|
||||
Node(LayoutNode),
|
||||
}
|
||||
|
||||
impl Layout for FlowNode {
|
||||
@ -35,10 +37,17 @@ impl Layout for FlowNode {
|
||||
for (child, map) in self.0.iter() {
|
||||
let styles = map.chain(&styles);
|
||||
match child {
|
||||
FlowChild::Leading => {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::LEADING).resolve(em);
|
||||
layouter.layout_spacing(amount.into());
|
||||
}
|
||||
FlowChild::Parbreak => {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::SPACING).resolve(em);
|
||||
layouter.layout_spacing(SpacingKind::Linear(amount.into()));
|
||||
let leading = styles.get(ParNode::LEADING);
|
||||
let spacing = styles.get(ParNode::SPACING);
|
||||
let amount = (leading + spacing).resolve(em);
|
||||
layouter.layout_spacing(amount.into());
|
||||
}
|
||||
FlowChild::Colbreak => {
|
||||
layouter.finish_region();
|
||||
@ -72,6 +81,7 @@ impl Debug for FlowNode {
|
||||
impl Debug for FlowChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Leading => f.pad("Leading"),
|
||||
Self::Parbreak => f.pad("Parbreak"),
|
||||
Self::Colbreak => f.pad("Colbreak"),
|
||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||
@ -93,8 +103,6 @@ pub struct FlowLayouter {
|
||||
used: Size,
|
||||
/// The sum of fractional ratios in the current region.
|
||||
fr: Fractional,
|
||||
/// Whether to add leading before the next node.
|
||||
leading: bool,
|
||||
/// Spacing and layouted nodes.
|
||||
items: Vec<FlowItem>,
|
||||
/// Finished frames for previous regions.
|
||||
@ -129,7 +137,6 @@ impl FlowLayouter {
|
||||
full,
|
||||
used: Size::zero(),
|
||||
fr: Fractional::zero(),
|
||||
leading: false,
|
||||
items: vec![],
|
||||
finished: vec![],
|
||||
}
|
||||
@ -149,7 +156,6 @@ impl FlowLayouter {
|
||||
SpacingKind::Fractional(v) => {
|
||||
self.items.push(FlowItem::Fractional(v));
|
||||
self.fr += v;
|
||||
self.leading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,7 +164,7 @@ impl FlowLayouter {
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
ctx: &mut LayoutContext,
|
||||
node: &PackedNode,
|
||||
node: &LayoutNode,
|
||||
styles: StyleChain,
|
||||
) {
|
||||
// Don't even try layouting into a full region.
|
||||
@ -168,9 +174,7 @@ impl FlowLayouter {
|
||||
|
||||
// Placed nodes that are out of flow produce placed items which aren't
|
||||
// aligned later.
|
||||
let mut is_placed = false;
|
||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||
is_placed = true;
|
||||
if placed.out_of_flow() {
|
||||
let frame = node.layout(ctx, &self.regions, styles).remove(0);
|
||||
self.items.push(FlowItem::Placed(frame.item));
|
||||
@ -178,13 +182,6 @@ impl FlowLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
// Add leading.
|
||||
if self.leading {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let amount = styles.get(ParNode::LEADING).resolve(em);
|
||||
self.layout_spacing(SpacingKind::Linear(amount.into()));
|
||||
}
|
||||
|
||||
// How to align the node.
|
||||
let aligns = Spec::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
@ -210,8 +207,6 @@ impl FlowLayouter {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
self.leading = !is_placed;
|
||||
}
|
||||
|
||||
/// Finish the frame for one region.
|
||||
@ -264,7 +259,6 @@ impl FlowLayouter {
|
||||
self.full = self.regions.current;
|
||||
self.used = Size::zero();
|
||||
self.fr = Fractional::zero();
|
||||
self.leading = false;
|
||||
self.finished.push(output.constrain(cts));
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ pub struct GridNode {
|
||||
/// Defines sizing of gutter rows and columns between content.
|
||||
pub gutter: Spec<Vec<TrackSizing>>,
|
||||
/// The nodes to be arranged in a grid.
|
||||
pub children: Vec<PackedNode>,
|
||||
pub children: Vec<LayoutNode>,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -92,7 +92,7 @@ castable! {
|
||||
/// Performs grid layout.
|
||||
pub struct GridLayouter<'a> {
|
||||
/// The grid cells.
|
||||
cells: &'a [PackedNode],
|
||||
cells: &'a [LayoutNode],
|
||||
/// The column tracks including gutter tracks.
|
||||
cols: Vec<TrackSizing>,
|
||||
/// The row tracks including gutter tracks.
|
||||
@ -136,7 +136,7 @@ impl<'a> GridLayouter<'a> {
|
||||
pub fn new(
|
||||
tracks: Spec<&[TrackSizing]>,
|
||||
gutter: Spec<&[TrackSizing]>,
|
||||
cells: &'a [PackedNode],
|
||||
cells: &'a [LayoutNode],
|
||||
regions: &Regions,
|
||||
styles: StyleChain<'a>,
|
||||
) -> Self {
|
||||
@ -606,7 +606,7 @@ impl<'a> GridLayouter<'a> {
|
||||
///
|
||||
/// Returns `None` if it's a gutter cell.
|
||||
#[track_caller]
|
||||
fn cell(&self, x: usize, y: usize) -> Option<&'a PackedNode> {
|
||||
fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> {
|
||||
assert!(x < self.cols.len());
|
||||
assert!(y < self.rows.len());
|
||||
|
||||
|
@ -9,8 +9,8 @@ pub struct HeadingNode {
|
||||
/// The logical nesting depth of the section, starting from one. In the
|
||||
/// default style, this controls the text size of the heading.
|
||||
pub level: usize,
|
||||
/// The node that produces the heading's contents.
|
||||
pub child: PackedNode,
|
||||
/// The heading's contents.
|
||||
pub body: Template,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -23,70 +23,76 @@ impl HeadingNode {
|
||||
/// The fill color of text in the heading. Just the surrounding text color
|
||||
/// if `auto`.
|
||||
pub const FILL: Smart<Paint> = Smart::Auto;
|
||||
/// Whether text in the heading is strengthend.
|
||||
pub const STRONG: bool = true;
|
||||
/// Whether text in the heading is emphasized.
|
||||
pub const EMPH: bool = false;
|
||||
/// Whether the heading is underlined.
|
||||
pub const UNDERLINE: bool = false;
|
||||
/// The extra padding above the heading.
|
||||
pub const ABOVE: Length = Length::zero();
|
||||
/// The extra padding below the heading.
|
||||
pub const BELOW: Length = Length::zero();
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(Template::block(Self {
|
||||
child: args.expect("body")?,
|
||||
Ok(Template::show(Self {
|
||||
body: args.expect("body")?,
|
||||
level: args.named("level")?.unwrap_or(1),
|
||||
}))
|
||||
}
|
||||
|
||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||
styles.set_opt(Self::FAMILY, args.named("family")?);
|
||||
styles.set_opt(Self::SIZE, args.named("size")?);
|
||||
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||
styles.set_opt(Self::ABOVE, args.named("above")?);
|
||||
styles.set_opt(Self::BELOW, args.named("below")?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for HeadingNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
|
||||
impl Show for HeadingNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
let mut map = StyleMap::new();
|
||||
|
||||
let mut passed = StyleMap::new();
|
||||
passed.set(TextNode::STRONG, true);
|
||||
passed.set(
|
||||
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
|
||||
map.set(
|
||||
TextNode::SIZE,
|
||||
styles.get(Self::SIZE).unwrap_or(Relative::new(upscale).into()),
|
||||
);
|
||||
|
||||
if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) {
|
||||
passed.set(
|
||||
TextNode::FAMILY_LIST,
|
||||
map.set(
|
||||
TextNode::FAMILY,
|
||||
std::iter::once(family)
|
||||
.chain(styles.get_ref(TextNode::FAMILY_LIST))
|
||||
.chain(styles.get_ref(TextNode::FAMILY))
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Smart::Custom(fill) = styles.get(Self::FILL) {
|
||||
passed.set(TextNode::FILL, fill);
|
||||
map.set(TextNode::FILL, fill);
|
||||
}
|
||||
|
||||
let mut frames = self.child.layout(ctx, regions, passed.chain(&styles));
|
||||
if styles.get(Self::STRONG) {
|
||||
map.set(TextNode::STRONG, true);
|
||||
}
|
||||
|
||||
if styles.get(Self::EMPH) {
|
||||
map.set(TextNode::EMPH, true);
|
||||
}
|
||||
|
||||
let mut body = self.body.clone();
|
||||
if styles.get(Self::UNDERLINE) {
|
||||
body = body.underlined();
|
||||
}
|
||||
|
||||
let mut seq = vec![];
|
||||
|
||||
let above = styles.get(Self::ABOVE);
|
||||
let below = styles.get(Self::BELOW);
|
||||
|
||||
// FIXME: Constraints and region size.
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let frame = Arc::make_mut(frame);
|
||||
frame.size.y += above + below;
|
||||
frame.translate(Point::with_y(above));
|
||||
if !above.is_zero() {
|
||||
seq.push(Template::Vertical(above.into()));
|
||||
}
|
||||
|
||||
frames
|
||||
seq.push(body);
|
||||
|
||||
let below = styles.get(Self::BELOW);
|
||||
if !below.is_zero() {
|
||||
seq.push(Template::Vertical(below.into()));
|
||||
}
|
||||
|
||||
Template::block(Template::sequence(seq).styled_with_map(map))
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use super::prelude::*;
|
||||
|
||||
/// Hide a node without affecting layout.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct HideNode(pub PackedNode);
|
||||
pub struct HideNode(pub LayoutNode);
|
||||
|
||||
#[class]
|
||||
impl HideNode {
|
||||
|
@ -31,11 +31,6 @@ impl ImageNode {
|
||||
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 {
|
||||
|
@ -5,10 +5,22 @@ use super::TextNode;
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// Link text and other elements to an URL.
|
||||
pub struct LinkNode;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct LinkNode {
|
||||
/// The url the link points to.
|
||||
pub url: EcoString,
|
||||
/// How the link is represented.
|
||||
pub body: Template,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl LinkNode {
|
||||
/// The fill color of text in the link. Just the surrounding text color
|
||||
/// if `auto`.
|
||||
pub const FILL: Smart<Paint> = Smart::Auto;
|
||||
/// Whether to underline link.
|
||||
pub const UNDERLINE: bool = true;
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
let url = args.expect::<EcoString>("url")?;
|
||||
let body = args.find().unwrap_or_else(|| {
|
||||
@ -20,6 +32,24 @@ impl LinkNode {
|
||||
Template::Text(if shorter { text.into() } else { url.clone() })
|
||||
});
|
||||
|
||||
Ok(body.styled(TextNode::LINK, Some(url)))
|
||||
Ok(Template::show(Self { url, body }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for LinkNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::LINK, Some(self.url.clone()));
|
||||
|
||||
if let Smart::Custom(fill) = styles.get(Self::FILL) {
|
||||
map.set(TextNode::FILL, fill);
|
||||
}
|
||||
|
||||
let mut body = self.body.clone();
|
||||
if styles.get(Self::UNDERLINE) {
|
||||
body = body.underlined();
|
||||
}
|
||||
|
||||
body.styled_with_map(map)
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,15 @@ use super::{GridNode, TextNode, TrackSizing};
|
||||
|
||||
/// An unordered or ordered list.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct ListNode<L: ListKind> {
|
||||
/// The list labelling style -- unordered or ordered.
|
||||
pub kind: L,
|
||||
pub struct ListNode<L: ListLabel> {
|
||||
/// The list label -- unordered or ordered with index.
|
||||
pub label: L,
|
||||
/// The node that produces the item's body.
|
||||
pub child: PackedNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl<L: ListKind> ListNode<L> {
|
||||
impl<L: ListLabel> 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.
|
||||
@ -22,29 +22,19 @@ impl<L: ListKind> ListNode<L> {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(args
|
||||
.all()
|
||||
.map(|child: PackedNode| Template::block(Self { kind: L::default(), child }))
|
||||
.enumerate()
|
||||
.map(|(i, child)| Template::show(Self { label: L::new(1 + i), child }))
|
||||
.sum())
|
||||
}
|
||||
|
||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||
styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?);
|
||||
styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: ListKind> Layout for ListNode<L> {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
impl<L: ListLabel> 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 grid = GridNode {
|
||||
Template::block(GridNode {
|
||||
tracks: Spec::with_x(vec![
|
||||
TrackSizing::Linear(label_indent.into()),
|
||||
TrackSizing::Auto,
|
||||
@ -53,19 +43,20 @@ impl<L: ListKind> Layout for ListNode<L> {
|
||||
]),
|
||||
gutter: Spec::default(),
|
||||
children: vec![
|
||||
PackedNode::default(),
|
||||
Template::Text(self.kind.label()).pack(),
|
||||
PackedNode::default(),
|
||||
LayoutNode::default(),
|
||||
Template::Text(self.label.label()).pack(),
|
||||
LayoutNode::default(),
|
||||
self.child.clone(),
|
||||
],
|
||||
};
|
||||
|
||||
grid.layout(ctx, regions, styles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// How to label a list.
|
||||
pub trait ListKind: Debug + Default + Hash + Sync + Send + 'static {
|
||||
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;
|
||||
}
|
||||
@ -74,7 +65,11 @@ pub trait ListKind: Debug + Default + Hash + Sync + Send + 'static {
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Unordered;
|
||||
|
||||
impl ListKind for Unordered {
|
||||
impl ListLabel for Unordered {
|
||||
fn new(_: usize) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn label(&self) -> EcoString {
|
||||
'•'.into()
|
||||
}
|
||||
@ -84,7 +79,11 @@ impl ListKind for Unordered {
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Ordered(pub Option<usize>);
|
||||
|
||||
impl ListKind for Ordered {
|
||||
impl ListLabel for Ordered {
|
||||
fn new(number: usize) -> Self {
|
||||
Self(Some(number))
|
||||
}
|
||||
|
||||
fn label(&self) -> EcoString {
|
||||
format_eco!("{}.", self.0.unwrap_or(1))
|
||||
}
|
||||
|
32
src/library/math.rs
Normal file
32
src/library/math.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Mathematical formulas.
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
/// A mathematical formula.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct MathNode {
|
||||
/// The formula.
|
||||
pub formula: EcoString,
|
||||
/// Whether the formula is display-level.
|
||||
pub display: bool,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl MathNode {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(Template::show(Self {
|
||||
formula: args.expect("formula")?,
|
||||
display: args.named("display")?.unwrap_or(false),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for MathNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
let mut template = Template::Text(self.formula.trim().into());
|
||||
if self.display {
|
||||
template = Template::Block(template.pack());
|
||||
}
|
||||
template.monospaced()
|
||||
}
|
||||
}
|
@ -14,18 +14,20 @@ pub mod hide;
|
||||
pub mod image;
|
||||
pub mod link;
|
||||
pub mod list;
|
||||
pub mod math;
|
||||
pub mod pad;
|
||||
pub mod page;
|
||||
pub mod par;
|
||||
pub mod place;
|
||||
pub mod raw;
|
||||
pub mod shape;
|
||||
pub mod spacing;
|
||||
pub mod stack;
|
||||
pub mod table;
|
||||
pub mod text;
|
||||
pub mod transform;
|
||||
pub mod utility;
|
||||
|
||||
pub mod utility;
|
||||
pub use self::image::*;
|
||||
pub use align::*;
|
||||
pub use columns::*;
|
||||
@ -37,10 +39,12 @@ pub use heading::*;
|
||||
pub use hide::*;
|
||||
pub use link::*;
|
||||
pub use list::*;
|
||||
pub use math::*;
|
||||
pub use pad::*;
|
||||
pub use page::*;
|
||||
pub use par::*;
|
||||
pub use place::*;
|
||||
pub use raw::*;
|
||||
pub use shape::*;
|
||||
pub use spacing::*;
|
||||
pub use stack::*;
|
||||
@ -68,13 +72,13 @@ prelude! {
|
||||
|
||||
pub use crate::diag::{At, TypResult};
|
||||
pub use crate::eval::{
|
||||
Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain,
|
||||
StyleMap, StyleVec, Template, Value,
|
||||
Args, Construct, EvalContext, Merge, Property, Scope, Set, Show, ShowNode, Smart,
|
||||
StyleChain, StyleMap, StyleVec, Template, Value,
|
||||
};
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
pub use crate::layout::{
|
||||
Constrain, Constrained, Constraints, Layout, LayoutContext, PackedNode, Regions,
|
||||
Constrain, Constrained, Constraints, Layout, LayoutContext, LayoutNode, Regions,
|
||||
};
|
||||
pub use crate::syntax::{Span, Spanned};
|
||||
pub use crate::util::{EcoString, OptionExt};
|
||||
@ -95,6 +99,8 @@ pub fn new() -> Scope {
|
||||
std.def_class::<TextNode>("text");
|
||||
std.def_class::<StrongNode>("strong");
|
||||
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");
|
||||
@ -207,9 +213,9 @@ castable! {
|
||||
castable! {
|
||||
NonZeroUsize,
|
||||
Expected: "positive integer",
|
||||
Value::Int(int) => int
|
||||
Value::Int(int) => Value::Int(int)
|
||||
.cast::<usize>()?
|
||||
.try_into()
|
||||
.and_then(usize::try_into)
|
||||
.map_err(|_| "must be positive")?,
|
||||
}
|
||||
|
||||
@ -226,7 +232,7 @@ castable! {
|
||||
}
|
||||
|
||||
castable! {
|
||||
PackedNode,
|
||||
LayoutNode,
|
||||
Expected: "template",
|
||||
Value::Template(template) => template.pack(),
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ pub struct PadNode {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: PackedNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -19,7 +19,7 @@ impl PadNode {
|
||||
let top = args.named("top")?;
|
||||
let right = args.named("right")?;
|
||||
let bottom = args.named("bottom")?;
|
||||
let body: PackedNode = args.expect("body")?;
|
||||
let body: LayoutNode = args.expect("body")?;
|
||||
let padding = Sides::new(
|
||||
left.or(all).unwrap_or_default(),
|
||||
top.or(all).unwrap_or_default(),
|
||||
|
@ -8,16 +8,14 @@ use super::ColumnsNode;
|
||||
|
||||
/// Layouts its child onto one or multiple pages.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub struct PageNode(pub PackedNode);
|
||||
pub struct PageNode(pub LayoutNode);
|
||||
|
||||
#[class]
|
||||
impl PageNode {
|
||||
/// The unflipped width of the page.
|
||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::default().width());
|
||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width());
|
||||
/// The unflipped height of the page.
|
||||
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::default().height());
|
||||
/// The class of paper. Defines the default margins.
|
||||
pub const CLASS: PaperClass = Paper::default().class();
|
||||
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height());
|
||||
/// Whether the page is flipped into landscape orientation.
|
||||
pub const FLIPPED: bool = false;
|
||||
/// The left margin.
|
||||
@ -39,20 +37,12 @@ impl PageNode {
|
||||
|
||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
||||
styles.set(Self::CLASS, paper.class());
|
||||
styles.set(Self::WIDTH, Smart::Custom(paper.width()));
|
||||
styles.set(Self::HEIGHT, Smart::Custom(paper.height()));
|
||||
}
|
||||
|
||||
if let Some(width) = args.named("width")? {
|
||||
styles.set(Self::CLASS, PaperClass::Custom);
|
||||
styles.set(Self::WIDTH, width);
|
||||
}
|
||||
|
||||
if let Some(height) = args.named("height")? {
|
||||
styles.set(Self::CLASS, PaperClass::Custom);
|
||||
styles.set(Self::HEIGHT, height);
|
||||
}
|
||||
styles.set_opt(Self::WIDTH, args.named("width")?);
|
||||
styles.set_opt(Self::HEIGHT, args.named("height")?);
|
||||
|
||||
let margins = args.named("margins")?;
|
||||
styles.set_opt(Self::LEFT, args.named("left")?.or(margins));
|
||||
@ -80,14 +70,18 @@ impl PageNode {
|
||||
std::mem::swap(&mut size.x, &mut size.y);
|
||||
}
|
||||
|
||||
let mut min = width.min(height);
|
||||
if min.is_infinite() {
|
||||
min = Paper::A4.width();
|
||||
}
|
||||
|
||||
// Determine the margins.
|
||||
let class = styles.get(Self::CLASS);
|
||||
let default = class.default_margins();
|
||||
let default = Linear::from(0.1190 * min);
|
||||
let padding = Sides {
|
||||
left: styles.get(Self::LEFT).unwrap_or(default.left),
|
||||
right: styles.get(Self::RIGHT).unwrap_or(default.right),
|
||||
top: styles.get(Self::TOP).unwrap_or(default.top),
|
||||
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
||||
left: styles.get(Self::LEFT).unwrap_or(default),
|
||||
right: styles.get(Self::RIGHT).unwrap_or(default),
|
||||
top: styles.get(Self::TOP).unwrap_or(default),
|
||||
bottom: styles.get(Self::BOTTOM).unwrap_or(default),
|
||||
};
|
||||
|
||||
let mut child = self.0.clone();
|
||||
@ -138,8 +132,6 @@ impl PagebreakNode {
|
||||
/// Specification of a paper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Paper {
|
||||
/// The broad class this paper belongs to.
|
||||
class: PaperClass,
|
||||
/// The width of the paper in millimeters.
|
||||
width: f64,
|
||||
/// The height of the paper in millimeters.
|
||||
@ -147,11 +139,6 @@ pub struct Paper {
|
||||
}
|
||||
|
||||
impl Paper {
|
||||
/// The class of the paper.
|
||||
pub fn class(self) -> PaperClass {
|
||||
self.class
|
||||
}
|
||||
|
||||
/// The width of the paper.
|
||||
pub fn width(self) -> Length {
|
||||
Length::mm(self.width)
|
||||
@ -163,24 +150,14 @@ impl Paper {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Paper {
|
||||
fn default() -> Self {
|
||||
Paper::A4
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines paper constants and a paper parsing implementation.
|
||||
macro_rules! papers {
|
||||
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
|
||||
($(($var:ident: $width:expr, $height: expr, $($pats:tt)*))*) => {
|
||||
/// Predefined papers.
|
||||
///
|
||||
/// Each paper is parsable from its name in kebab-case.
|
||||
impl Paper {
|
||||
$(pub const $var: Self = Self {
|
||||
class: PaperClass::$class,
|
||||
width: $width,
|
||||
height: $height,
|
||||
};)*
|
||||
$(pub const $var: Self = Self { width: $width, height: $height };)*
|
||||
}
|
||||
|
||||
impl FromStr for Paper {
|
||||
@ -206,149 +183,149 @@ macro_rules! papers {
|
||||
papers! {
|
||||
// ---------------------------------------------------------------------- //
|
||||
// ISO 216 A Series
|
||||
(A0: Base, 841.0, 1189.0, "a0")
|
||||
(A1: Base, 594.0, 841.0, "a1")
|
||||
(A2: Base, 420.0, 594.0, "a2")
|
||||
(A3: Base, 297.0, 420.0, "a3")
|
||||
(A4: Base, 210.0, 297.0, "a4")
|
||||
(A5: Base, 148.0, 210.0, "a5")
|
||||
(A6: Book, 105.0, 148.0, "a6")
|
||||
(A7: Base, 74.0, 105.0, "a7")
|
||||
(A8: Base, 52.0, 74.0, "a8")
|
||||
(A9: Base, 37.0, 52.0, "a9")
|
||||
(A10: Base, 26.0, 37.0, "a10")
|
||||
(A11: Base, 18.0, 26.0, "a11")
|
||||
(A0: 841.0, 1189.0, "a0")
|
||||
(A1: 594.0, 841.0, "a1")
|
||||
(A2: 420.0, 594.0, "a2")
|
||||
(A3: 297.0, 420.0, "a3")
|
||||
(A4: 210.0, 297.0, "a4")
|
||||
(A5: 148.0, 210.0, "a5")
|
||||
(A6: 105.0, 148.0, "a6")
|
||||
(A7: 74.0, 105.0, "a7")
|
||||
(A8: 52.0, 74.0, "a8")
|
||||
(A9: 37.0, 52.0, "a9")
|
||||
(A10: 26.0, 37.0, "a10")
|
||||
(A11: 18.0, 26.0, "a11")
|
||||
|
||||
// ISO 216 B Series
|
||||
(ISO_B1: Base, 707.0, 1000.0, "iso-b1")
|
||||
(ISO_B2: Base, 500.0, 707.0, "iso-b2")
|
||||
(ISO_B3: Base, 353.0, 500.0, "iso-b3")
|
||||
(ISO_B4: Base, 250.0, 353.0, "iso-b4")
|
||||
(ISO_B5: Book, 176.0, 250.0, "iso-b5")
|
||||
(ISO_B6: Book, 125.0, 176.0, "iso-b6")
|
||||
(ISO_B7: Base, 88.0, 125.0, "iso-b7")
|
||||
(ISO_B8: Base, 62.0, 88.0, "iso-b8")
|
||||
(ISO_B1: 707.0, 1000.0, "iso-b1")
|
||||
(ISO_B2: 500.0, 707.0, "iso-b2")
|
||||
(ISO_B3: 353.0, 500.0, "iso-b3")
|
||||
(ISO_B4: 250.0, 353.0, "iso-b4")
|
||||
(ISO_B5: 176.0, 250.0, "iso-b5")
|
||||
(ISO_B6: 125.0, 176.0, "iso-b6")
|
||||
(ISO_B7: 88.0, 125.0, "iso-b7")
|
||||
(ISO_B8: 62.0, 88.0, "iso-b8")
|
||||
|
||||
// ISO 216 C Series
|
||||
(ISO_C3: Base, 324.0, 458.0, "iso-c3")
|
||||
(ISO_C4: Base, 229.0, 324.0, "iso-c4")
|
||||
(ISO_C5: Base, 162.0, 229.0, "iso-c5")
|
||||
(ISO_C6: Base, 114.0, 162.0, "iso-c6")
|
||||
(ISO_C7: Base, 81.0, 114.0, "iso-c7")
|
||||
(ISO_C8: Base, 57.0, 81.0, "iso-c8")
|
||||
(ISO_C3: 324.0, 458.0, "iso-c3")
|
||||
(ISO_C4: 229.0, 324.0, "iso-c4")
|
||||
(ISO_C5: 162.0, 229.0, "iso-c5")
|
||||
(ISO_C6: 114.0, 162.0, "iso-c6")
|
||||
(ISO_C7: 81.0, 114.0, "iso-c7")
|
||||
(ISO_C8: 57.0, 81.0, "iso-c8")
|
||||
|
||||
// DIN D Series (extension to ISO)
|
||||
(DIN_D3: Base, 272.0, 385.0, "din-d3")
|
||||
(DIN_D4: Base, 192.0, 272.0, "din-d4")
|
||||
(DIN_D5: Base, 136.0, 192.0, "din-d5")
|
||||
(DIN_D6: Base, 96.0, 136.0, "din-d6")
|
||||
(DIN_D7: Base, 68.0, 96.0, "din-d7")
|
||||
(DIN_D8: Base, 48.0, 68.0, "din-d8")
|
||||
(DIN_D3: 272.0, 385.0, "din-d3")
|
||||
(DIN_D4: 192.0, 272.0, "din-d4")
|
||||
(DIN_D5: 136.0, 192.0, "din-d5")
|
||||
(DIN_D6: 96.0, 136.0, "din-d6")
|
||||
(DIN_D7: 68.0, 96.0, "din-d7")
|
||||
(DIN_D8: 48.0, 68.0, "din-d8")
|
||||
|
||||
// SIS (used in academia)
|
||||
(SIS_G5: Base, 169.0, 239.0, "sis-g5")
|
||||
(SIS_E5: Base, 115.0, 220.0, "sis-e5")
|
||||
(SIS_G5: 169.0, 239.0, "sis-g5")
|
||||
(SIS_E5: 115.0, 220.0, "sis-e5")
|
||||
|
||||
// ANSI Extensions
|
||||
(ANSI_A: Base, 216.0, 279.0, "ansi-a")
|
||||
(ANSI_B: Base, 279.0, 432.0, "ansi-b")
|
||||
(ANSI_C: Base, 432.0, 559.0, "ansi-c")
|
||||
(ANSI_D: Base, 559.0, 864.0, "ansi-d")
|
||||
(ANSI_E: Base, 864.0, 1118.0, "ansi-e")
|
||||
(ANSI_A: 216.0, 279.0, "ansi-a")
|
||||
(ANSI_B: 279.0, 432.0, "ansi-b")
|
||||
(ANSI_C: 432.0, 559.0, "ansi-c")
|
||||
(ANSI_D: 559.0, 864.0, "ansi-d")
|
||||
(ANSI_E: 864.0, 1118.0, "ansi-e")
|
||||
|
||||
// ANSI Architectural Paper
|
||||
(ARCH_A: Base, 229.0, 305.0, "arch-a")
|
||||
(ARCH_B: Base, 305.0, 457.0, "arch-b")
|
||||
(ARCH_C: Base, 457.0, 610.0, "arch-c")
|
||||
(ARCH_D: Base, 610.0, 914.0, "arch-d")
|
||||
(ARCH_E1: Base, 762.0, 1067.0, "arch-e1")
|
||||
(ARCH_E: Base, 914.0, 1219.0, "arch-e")
|
||||
(ARCH_A: 229.0, 305.0, "arch-a")
|
||||
(ARCH_B: 305.0, 457.0, "arch-b")
|
||||
(ARCH_C: 457.0, 610.0, "arch-c")
|
||||
(ARCH_D: 610.0, 914.0, "arch-d")
|
||||
(ARCH_E1: 762.0, 1067.0, "arch-e1")
|
||||
(ARCH_E: 914.0, 1219.0, "arch-e")
|
||||
|
||||
// JIS B Series
|
||||
(JIS_B0: Base, 1030.0, 1456.0, "jis-b0")
|
||||
(JIS_B1: Base, 728.0, 1030.0, "jis-b1")
|
||||
(JIS_B2: Base, 515.0, 728.0, "jis-b2")
|
||||
(JIS_B3: Base, 364.0, 515.0, "jis-b3")
|
||||
(JIS_B4: Base, 257.0, 364.0, "jis-b4")
|
||||
(JIS_B5: Base, 182.0, 257.0, "jis-b5")
|
||||
(JIS_B6: Base, 128.0, 182.0, "jis-b6")
|
||||
(JIS_B7: Base, 91.0, 128.0, "jis-b7")
|
||||
(JIS_B8: Base, 64.0, 91.0, "jis-b8")
|
||||
(JIS_B9: Base, 45.0, 64.0, "jis-b9")
|
||||
(JIS_B10: Base, 32.0, 45.0, "jis-b10")
|
||||
(JIS_B11: Base, 22.0, 32.0, "jis-b11")
|
||||
(JIS_B0: 1030.0, 1456.0, "jis-b0")
|
||||
(JIS_B1: 728.0, 1030.0, "jis-b1")
|
||||
(JIS_B2: 515.0, 728.0, "jis-b2")
|
||||
(JIS_B3: 364.0, 515.0, "jis-b3")
|
||||
(JIS_B4: 257.0, 364.0, "jis-b4")
|
||||
(JIS_B5: 182.0, 257.0, "jis-b5")
|
||||
(JIS_B6: 128.0, 182.0, "jis-b6")
|
||||
(JIS_B7: 91.0, 128.0, "jis-b7")
|
||||
(JIS_B8: 64.0, 91.0, "jis-b8")
|
||||
(JIS_B9: 45.0, 64.0, "jis-b9")
|
||||
(JIS_B10: 32.0, 45.0, "jis-b10")
|
||||
(JIS_B11: 22.0, 32.0, "jis-b11")
|
||||
|
||||
// SAC D Series
|
||||
(SAC_D0: Base, 764.0, 1064.0, "sac-d0")
|
||||
(SAC_D1: Base, 532.0, 760.0, "sac-d1")
|
||||
(SAC_D2: Base, 380.0, 528.0, "sac-d2")
|
||||
(SAC_D3: Base, 264.0, 376.0, "sac-d3")
|
||||
(SAC_D4: Base, 188.0, 260.0, "sac-d4")
|
||||
(SAC_D5: Base, 130.0, 184.0, "sac-d5")
|
||||
(SAC_D6: Base, 92.0, 126.0, "sac-d6")
|
||||
(SAC_D0: 764.0, 1064.0, "sac-d0")
|
||||
(SAC_D1: 532.0, 760.0, "sac-d1")
|
||||
(SAC_D2: 380.0, 528.0, "sac-d2")
|
||||
(SAC_D3: 264.0, 376.0, "sac-d3")
|
||||
(SAC_D4: 188.0, 260.0, "sac-d4")
|
||||
(SAC_D5: 130.0, 184.0, "sac-d5")
|
||||
(SAC_D6: 92.0, 126.0, "sac-d6")
|
||||
|
||||
// ISO 7810 ID
|
||||
(ISO_ID_1: Base, 85.6, 53.98, "iso-id-1")
|
||||
(ISO_ID_2: Base, 74.0, 105.0, "iso-id-2")
|
||||
(ISO_ID_3: Base, 88.0, 125.0, "iso-id-3")
|
||||
(ISO_ID_1: 85.6, 53.98, "iso-id-1")
|
||||
(ISO_ID_2: 74.0, 105.0, "iso-id-2")
|
||||
(ISO_ID_3: 88.0, 125.0, "iso-id-3")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Asia
|
||||
(ASIA_F4: Base, 210.0, 330.0, "asia-f4")
|
||||
(ASIA_F4: 210.0, 330.0, "asia-f4")
|
||||
|
||||
// Japan
|
||||
(JP_SHIROKU_BAN_4: Base, 264.0, 379.0, "jp-shiroku-ban-4")
|
||||
(JP_SHIROKU_BAN_5: Base, 189.0, 262.0, "jp-shiroku-ban-5")
|
||||
(JP_SHIROKU_BAN_6: Base, 127.0, 188.0, "jp-shiroku-ban-6")
|
||||
(JP_KIKU_4: Base, 227.0, 306.0, "jp-kiku-4")
|
||||
(JP_KIKU_5: Base, 151.0, 227.0, "jp-kiku-5")
|
||||
(JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card")
|
||||
(JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4")
|
||||
(JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5")
|
||||
(JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6")
|
||||
(JP_KIKU_4: 227.0, 306.0, "jp-kiku-4")
|
||||
(JP_KIKU_5: 151.0, 227.0, "jp-kiku-5")
|
||||
(JP_BUSINESS_CARD: 91.0, 55.0, "jp-business-card")
|
||||
|
||||
// China
|
||||
(CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card")
|
||||
(CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card")
|
||||
|
||||
// Europe
|
||||
(EU_BUSINESS_CARD: Base, 85.0, 55.0, "eu-business-card")
|
||||
(EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card")
|
||||
|
||||
// French Traditional (AFNOR)
|
||||
(FR_TELLIERE: Base, 340.0, 440.0, "fr-tellière")
|
||||
(FR_COURONNE_ECRITURE: Base, 360.0, 460.0, "fr-couronne-écriture")
|
||||
(FR_COURONNE_EDITION: Base, 370.0, 470.0, "fr-couronne-édition")
|
||||
(FR_RAISIN: Base, 500.0, 650.0, "fr-raisin")
|
||||
(FR_CARRE: Base, 450.0, 560.0, "fr-carré")
|
||||
(FR_JESUS: Base, 560.0, 760.0, "fr-jésus")
|
||||
(FR_TELLIERE: 340.0, 440.0, "fr-tellière")
|
||||
(FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture")
|
||||
(FR_COURONNE_EDITION: 370.0, 470.0, "fr-couronne-édition")
|
||||
(FR_RAISIN: 500.0, 650.0, "fr-raisin")
|
||||
(FR_CARRE: 450.0, 560.0, "fr-carré")
|
||||
(FR_JESUS: 560.0, 760.0, "fr-jésus")
|
||||
|
||||
// United Kingdom Imperial
|
||||
(UK_BRIEF: Base, 406.4, 342.9, "uk-brief")
|
||||
(UK_DRAFT: Base, 254.0, 406.4, "uk-draft")
|
||||
(UK_FOOLSCAP: Base, 203.2, 330.2, "uk-foolscap")
|
||||
(UK_QUARTO: Base, 203.2, 254.0, "uk-quarto")
|
||||
(UK_CROWN: Base, 508.0, 381.0, "uk-crown")
|
||||
(UK_BOOK_A: Book, 111.0, 178.0, "uk-book-a")
|
||||
(UK_BOOK_B: Book, 129.0, 198.0, "uk-book-b")
|
||||
(UK_BRIEF: 406.4, 342.9, "uk-brief")
|
||||
(UK_DRAFT: 254.0, 406.4, "uk-draft")
|
||||
(UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap")
|
||||
(UK_QUARTO: 203.2, 254.0, "uk-quarto")
|
||||
(UK_CROWN: 508.0, 381.0, "uk-crown")
|
||||
(UK_BOOK_A: 111.0, 178.0, "uk-book-a")
|
||||
(UK_BOOK_B: 129.0, 198.0, "uk-book-b")
|
||||
|
||||
// Unites States
|
||||
(US_LETTER: US, 215.9, 279.4, "us-letter")
|
||||
(US_LEGAL: US, 215.9, 355.6, "us-legal")
|
||||
(US_TABLOID: US, 279.4, 431.8, "us-tabloid")
|
||||
(US_EXECUTIVE: US, 184.15, 266.7, "us-executive")
|
||||
(US_FOOLSCAP_FOLIO: US, 215.9, 342.9, "us-foolscap-folio")
|
||||
(US_STATEMENT: US, 139.7, 215.9, "us-statement")
|
||||
(US_LEDGER: US, 431.8, 279.4, "us-ledger")
|
||||
(US_OFICIO: US, 215.9, 340.36, "us-oficio")
|
||||
(US_GOV_LETTER: US, 203.2, 266.7, "us-gov-letter")
|
||||
(US_GOV_LEGAL: US, 215.9, 330.2, "us-gov-legal")
|
||||
(US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card")
|
||||
(US_DIGEST: Book, 139.7, 215.9, "us-digest")
|
||||
(US_TRADE: Book, 152.4, 228.6, "us-trade")
|
||||
(US_LETTER: 215.9, 279.4, "us-letter")
|
||||
(US_LEGAL: 215.9, 355.6, "us-legal")
|
||||
(US_TABLOID: 279.4, 431.8, "us-tabloid")
|
||||
(US_EXECUTIVE: 84.15, 266.7, "us-executive")
|
||||
(US_FOOLSCAP_FOLIO: 215.9, 342.9, "us-foolscap-folio")
|
||||
(US_STATEMENT: 139.7, 215.9, "us-statement")
|
||||
(US_LEDGER: 431.8, 279.4, "us-ledger")
|
||||
(US_OFICIO: 215.9, 340.36, "us-oficio")
|
||||
(US_GOV_LETTER: 203.2, 266.7, "us-gov-letter")
|
||||
(US_GOV_LEGAL: 215.9, 330.2, "us-gov-legal")
|
||||
(US_BUSINESS_CARD: 88.9, 50.8, "us-business-card")
|
||||
(US_DIGEST: 139.7, 215.9, "us-digest")
|
||||
(US_TRADE: 152.4, 228.6, "us-trade")
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Other
|
||||
(NEWSPAPER_COMPACT: Newspaper, 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: Newspaper, 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: Newspaper, 381.0, 578.0, "newspaper-broadsheet")
|
||||
(PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9")
|
||||
(PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3")
|
||||
(NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet")
|
||||
(PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9")
|
||||
(PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3")
|
||||
}
|
||||
|
||||
castable! {
|
||||
@ -357,31 +334,6 @@ castable! {
|
||||
Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
|
||||
}
|
||||
|
||||
/// Defines default margins for a class of related papers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PaperClass {
|
||||
Custom,
|
||||
Base,
|
||||
US,
|
||||
Newspaper,
|
||||
Book,
|
||||
}
|
||||
|
||||
impl PaperClass {
|
||||
/// The default margins for this page class.
|
||||
fn default_margins(self) -> Sides<Linear> {
|
||||
let f = |r| Relative::new(r).into();
|
||||
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
|
||||
match self {
|
||||
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
|
||||
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
|
||||
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error when parsing a [`Paper`] from a string fails.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct PaperError;
|
||||
|
@ -22,7 +22,7 @@ pub enum ParChild {
|
||||
/// Horizontal spacing between other children.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary inline-level node.
|
||||
Node(PackedNode),
|
||||
Node(LayoutNode),
|
||||
}
|
||||
|
||||
#[class]
|
||||
|
@ -5,7 +5,7 @@ use super::AlignNode;
|
||||
|
||||
/// Place a node at an absolute position.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PlaceNode(pub PackedNode);
|
||||
pub struct PlaceNode(pub LayoutNode);
|
||||
|
||||
#[class]
|
||||
impl PlaceNode {
|
||||
@ -13,7 +13,7 @@ impl PlaceNode {
|
||||
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")?;
|
||||
let body: LayoutNode = args.expect("body")?;
|
||||
Ok(Template::block(Self(
|
||||
body.moved(Point::new(tx, ty)).aligned(aligns),
|
||||
)))
|
||||
|
117
src/library/raw.rs
Normal file
117
src/library/raw.rs
Normal file
@ -0,0 +1,117 @@
|
||||
//! Monospaced text and code.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::library::TextNode;
|
||||
use crate::source::SourceId;
|
||||
use crate::syntax::{self, RedNode};
|
||||
|
||||
/// The lazily-loaded theme used for syntax highlighting.
|
||||
static THEME: Lazy<Theme> =
|
||||
Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap());
|
||||
|
||||
/// The lazily-loaded syntect syntax definitions.
|
||||
static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines());
|
||||
|
||||
/// Monospaced text with optional syntax highlighting.
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct RawNode {
|
||||
/// The raw text.
|
||||
pub text: EcoString,
|
||||
/// Whether the node is block-level.
|
||||
pub block: bool,
|
||||
}
|
||||
|
||||
#[class]
|
||||
impl RawNode {
|
||||
/// The language to syntax-highlight in.
|
||||
pub const LANG: Option<EcoString> = None;
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(Template::show(Self {
|
||||
text: args.expect("text")?,
|
||||
block: args.named("block")?.unwrap_or(false),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for RawNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
let lang = styles.get_ref(Self::LANG).as_ref();
|
||||
let foreground = THEME
|
||||
.settings
|
||||
.foreground
|
||||
.map(Color::from)
|
||||
.unwrap_or(Color::BLACK)
|
||||
.into();
|
||||
|
||||
let mut template = if matches!(
|
||||
lang.map(|s| s.to_lowercase()).as_deref(),
|
||||
Some("typ" | "typst")
|
||||
) {
|
||||
let mut seq = vec![];
|
||||
let green = crate::parse::parse(&self.text);
|
||||
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
||||
let highlighter = Highlighter::new(&THEME);
|
||||
|
||||
syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |range, style| {
|
||||
seq.push(styled(&self.text[range], foreground, style));
|
||||
});
|
||||
|
||||
Template::sequence(seq)
|
||||
} else if let Some(syntax) =
|
||||
lang.and_then(|token| SYNTAXES.find_syntax_by_token(&token))
|
||||
{
|
||||
let mut seq = vec![];
|
||||
let mut highlighter = HighlightLines::new(syntax, &THEME);
|
||||
for (i, line) in self.text.lines().enumerate() {
|
||||
if i != 0 {
|
||||
seq.push(Template::Linebreak);
|
||||
}
|
||||
|
||||
for (style, piece) in highlighter.highlight(line, &SYNTAXES) {
|
||||
seq.push(styled(piece, foreground, style));
|
||||
}
|
||||
}
|
||||
|
||||
Template::sequence(seq)
|
||||
} else {
|
||||
Template::Text(self.text.clone())
|
||||
};
|
||||
|
||||
if self.block {
|
||||
template = Template::Block(template.pack());
|
||||
}
|
||||
|
||||
template.monospaced()
|
||||
}
|
||||
}
|
||||
|
||||
/// Style a piece of text with a syntect style.
|
||||
fn styled(piece: &str, foreground: Paint, style: Style) -> Template {
|
||||
let mut styles = StyleMap::new();
|
||||
let mut body = Template::Text(piece.into());
|
||||
|
||||
let paint = style.foreground.into();
|
||||
if paint != foreground {
|
||||
styles.set(TextNode::FILL, paint);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::BOLD) {
|
||||
styles.set(TextNode::STRONG, true);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::ITALIC) {
|
||||
styles.set(TextNode::EMPH, true);
|
||||
}
|
||||
|
||||
if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||
body = body.underlined();
|
||||
}
|
||||
|
||||
body.styled_with_map(styles)
|
||||
}
|
@ -11,7 +11,7 @@ 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<PackedNode>,
|
||||
pub child: Option<LayoutNode>,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -50,14 +50,6 @@ impl<S: ShapeKind> ShapeNode<S> {
|
||||
.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<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
|
@ -38,6 +38,12 @@ impl SpacingKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for SpacingKind {
|
||||
fn from(length: Length) -> Self {
|
||||
Self::Linear(length.into())
|
||||
}
|
||||
}
|
||||
|
||||
castable! {
|
||||
SpacingKind,
|
||||
Expected: "linear or fractional",
|
||||
|
@ -64,7 +64,7 @@ pub enum StackChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(SpacingKind),
|
||||
/// An arbitrary node.
|
||||
Node(PackedNode),
|
||||
Node(LayoutNode),
|
||||
}
|
||||
|
||||
impl Debug for StackChild {
|
||||
@ -166,7 +166,7 @@ impl StackLayouter {
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
ctx: &mut LayoutContext,
|
||||
node: &PackedNode,
|
||||
node: &LayoutNode,
|
||||
styles: StyleChain,
|
||||
) {
|
||||
if self.regions.is_full() {
|
||||
|
@ -11,7 +11,7 @@ pub struct TableNode {
|
||||
/// Defines sizing of gutter rows and columns between content.
|
||||
pub gutter: Spec<Vec<TrackSizing>>,
|
||||
/// The nodes to be arranged in the table.
|
||||
pub children: Vec<PackedNode>,
|
||||
pub children: Vec<LayoutNode>,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -33,7 +33,7 @@ impl TableNode {
|
||||
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(Template::block(Self {
|
||||
Ok(Template::show(Self {
|
||||
tracks: Spec::new(columns, rows),
|
||||
gutter: Spec::new(
|
||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||
@ -54,13 +54,8 @@ impl TableNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for TableNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
impl Show for TableNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
let primary = styles.get(Self::PRIMARY);
|
||||
let secondary = styles.get(Self::SECONDARY);
|
||||
let thickness = styles.get(Self::THICKNESS);
|
||||
@ -90,12 +85,10 @@ impl Layout for TableNode {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let grid = GridNode {
|
||||
Template::block(GridNode {
|
||||
tracks: self.tracks.clone(),
|
||||
gutter: self.gutter.clone(),
|
||||
children,
|
||||
};
|
||||
|
||||
grid.layout(ctx, regions, styles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,14 @@ pub struct TextNode;
|
||||
#[class]
|
||||
impl TextNode {
|
||||
/// A prioritized sequence of font families.
|
||||
pub const FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif];
|
||||
#[variadic]
|
||||
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::SansSerif];
|
||||
/// The serif font family/families.
|
||||
pub const SERIF_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")];
|
||||
pub const SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")];
|
||||
/// The sans-serif font family/families.
|
||||
pub const SANS_SERIF_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")];
|
||||
pub const SANS_SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")];
|
||||
/// The monospace font family/families.
|
||||
pub const MONOSPACE_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")];
|
||||
pub const MONOSPACE: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")];
|
||||
/// Whether to allow font fallback when the primary font list contains no
|
||||
/// match.
|
||||
pub const FALLBACK: bool = true;
|
||||
@ -41,23 +42,12 @@ impl TextNode {
|
||||
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
||||
/// The width of the glyphs.
|
||||
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
||||
/// Whether the font weight should be increased by 300.
|
||||
#[fold(bool::bitxor)]
|
||||
pub const STRONG: bool = false;
|
||||
/// Whether the the font style should be inverted.
|
||||
#[fold(bool::bitxor)]
|
||||
pub const EMPH: bool = false;
|
||||
/// Whether a monospace font should be preferred.
|
||||
pub const MONOSPACE: bool = false;
|
||||
/// The glyph fill color.
|
||||
#[shorthand]
|
||||
pub const FILL: Paint = Color::BLACK.into();
|
||||
/// Decorative lines.
|
||||
#[fold(|a, b| a.into_iter().chain(b).collect())]
|
||||
pub const LINES: Vec<Decoration> = vec![];
|
||||
/// An URL the text should link to.
|
||||
pub const LINK: Option<EcoString> = None;
|
||||
|
||||
/// The size of the glyphs.
|
||||
#[shorthand]
|
||||
#[fold(Linear::compose)]
|
||||
pub const SIZE: Linear = Length::pt(11.0).into();
|
||||
/// The amount of space that should be added between characters.
|
||||
@ -94,73 +84,64 @@ impl TextNode {
|
||||
/// Raw OpenType features to apply.
|
||||
pub const FEATURES: Vec<(Tag, u32)> = vec![];
|
||||
|
||||
/// Whether the font weight should be increased by 300.
|
||||
#[skip]
|
||||
#[fold(bool::bitxor)]
|
||||
pub const STRONG: bool = false;
|
||||
/// Whether the the font style should be inverted.
|
||||
#[skip]
|
||||
#[fold(bool::bitxor)]
|
||||
pub const EMPH: bool = false;
|
||||
/// Whether a monospace font should be preferred.
|
||||
#[skip]
|
||||
pub const MONOSPACED: bool = false;
|
||||
/// Decorative lines.
|
||||
#[skip]
|
||||
#[fold(|a, b| a.into_iter().chain(b).collect())]
|
||||
pub const LINES: Vec<Decoration> = vec![];
|
||||
/// An URL the text should link to.
|
||||
#[skip]
|
||||
pub const LINK: Option<EcoString> = None;
|
||||
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
// The text constructor is special: It doesn't create a text node.
|
||||
// Instead, it leaves the passed argument structurally unchanged, but
|
||||
// styles all text in it.
|
||||
args.expect("body")
|
||||
}
|
||||
|
||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||
let list = args.named("family")?.or_else(|| {
|
||||
let families: Vec<_> = args.all().collect();
|
||||
(!families.is_empty()).then(|| families)
|
||||
});
|
||||
|
||||
styles.set_opt(Self::FAMILY_LIST, list);
|
||||
styles.set_opt(Self::SERIF_LIST, args.named("serif")?);
|
||||
styles.set_opt(Self::SANS_SERIF_LIST, args.named("sans-serif")?);
|
||||
styles.set_opt(Self::MONOSPACE_LIST, args.named("monospace")?);
|
||||
styles.set_opt(Self::FALLBACK, args.named("fallback")?);
|
||||
styles.set_opt(Self::STYLE, args.named("style")?);
|
||||
styles.set_opt(Self::WEIGHT, args.named("weight")?);
|
||||
styles.set_opt(Self::STRETCH, args.named("stretch")?);
|
||||
styles.set_opt(Self::FILL, args.named("fill")?.or_else(|| args.find()));
|
||||
styles.set_opt(Self::SIZE, args.named("size")?.or_else(|| args.find()));
|
||||
styles.set_opt(Self::TRACKING, args.named("tracking")?.map(Em::new));
|
||||
styles.set_opt(Self::TOP_EDGE, args.named("top-edge")?);
|
||||
styles.set_opt(Self::BOTTOM_EDGE, args.named("bottom-edge")?);
|
||||
styles.set_opt(Self::KERNING, args.named("kerning")?);
|
||||
styles.set_opt(Self::SMALLCAPS, args.named("smallcaps")?);
|
||||
styles.set_opt(Self::ALTERNATES, args.named("alternates")?);
|
||||
styles.set_opt(Self::STYLISTIC_SET, args.named("stylistic-set")?);
|
||||
styles.set_opt(Self::LIGATURES, args.named("ligatures")?);
|
||||
styles.set_opt(
|
||||
Self::DISCRETIONARY_LIGATURES,
|
||||
args.named("discretionary-ligatures")?,
|
||||
);
|
||||
styles.set_opt(
|
||||
Self::HISTORICAL_LIGATURES,
|
||||
args.named("historical-ligatures")?,
|
||||
);
|
||||
styles.set_opt(Self::NUMBER_TYPE, args.named("number-type")?);
|
||||
styles.set_opt(Self::NUMBER_WIDTH, args.named("number-width")?);
|
||||
styles.set_opt(Self::NUMBER_POSITION, args.named("number-position")?);
|
||||
styles.set_opt(Self::SLASHED_ZERO, args.named("slashed-zero")?);
|
||||
styles.set_opt(Self::FRACTIONS, args.named("fractions")?);
|
||||
styles.set_opt(Self::FEATURES, args.named("features")?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Strong text, rendered in boldface.
|
||||
pub struct StrongNode;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct StrongNode(pub Template);
|
||||
|
||||
#[class]
|
||||
impl StrongNode {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(args.expect::<Template>("body")?.styled(TextNode::STRONG, true))
|
||||
Ok(Template::show(Self(args.expect("body")?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for StrongNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
self.0.clone().styled(TextNode::STRONG, true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Emphasized text, rendered with an italic face.
|
||||
pub struct EmphNode;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct EmphNode(pub Template);
|
||||
|
||||
#[class]
|
||||
impl EmphNode {
|
||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
|
||||
Ok(args.expect::<Template>("body")?.styled(TextNode::EMPH, true))
|
||||
Ok(Template::show(Self(args.expect("body")?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Show for EmphNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
self.0.clone().styled(TextNode::EMPH, true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +255,12 @@ castable! {
|
||||
Value::Relative(v) => Self::from_ratio(v.get() as f32),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Em,
|
||||
Expected: "float",
|
||||
Value::Float(v) => Self::new(v),
|
||||
}
|
||||
|
||||
castable! {
|
||||
VerticalFontMetric,
|
||||
Expected: "linear or string",
|
||||
@ -644,18 +631,18 @@ fn variant(styles: StyleChain) -> FontVariant {
|
||||
|
||||
/// Resolve a prioritized iterator over the font families.
|
||||
fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||
let head = if styles.get(TextNode::MONOSPACE) {
|
||||
styles.get_ref(TextNode::MONOSPACE_LIST).as_slice()
|
||||
let head = if styles.get(TextNode::MONOSPACED) {
|
||||
styles.get_ref(TextNode::MONOSPACE).as_slice()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
let core = styles.get_ref(TextNode::FAMILY_LIST).iter().flat_map(move |family| {
|
||||
let core = styles.get_ref(TextNode::FAMILY).iter().flat_map(move |family| {
|
||||
match family {
|
||||
FontFamily::Named(name) => std::slice::from_ref(name),
|
||||
FontFamily::Serif => styles.get_ref(TextNode::SERIF_LIST),
|
||||
FontFamily::SansSerif => styles.get_ref(TextNode::SANS_SERIF_LIST),
|
||||
FontFamily::Monospace => styles.get_ref(TextNode::MONOSPACE_LIST),
|
||||
FontFamily::Serif => styles.get_ref(TextNode::SERIF),
|
||||
FontFamily::SansSerif => styles.get_ref(TextNode::SANS_SERIF),
|
||||
FontFamily::Monospace => styles.get_ref(TextNode::MONOSPACE),
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9,7 +9,7 @@ pub struct TransformNode<T: TransformKind> {
|
||||
/// Transformation to apply to the contents.
|
||||
pub kind: T,
|
||||
/// The node whose contents should be transformed.
|
||||
pub child: PackedNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
#[class]
|
||||
@ -23,11 +23,6 @@ impl<T: TransformKind> TransformNode<T> {
|
||||
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> {
|
||||
|
@ -3,9 +3,11 @@
|
||||
#[macro_use]
|
||||
mod eco_string;
|
||||
mod mac_roman;
|
||||
mod prehashed;
|
||||
|
||||
pub use eco_string::EcoString;
|
||||
pub use mac_roman::decode_mac_roman;
|
||||
pub use prehashed::Prehashed;
|
||||
|
||||
use std::cell::RefMut;
|
||||
use std::cmp::Ordering;
|
||||
|
72
src/util/prehashed.rs
Normal file
72
src/util/prehashed.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A wrapper around a type that precomputes its hash.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Prehashed<T: ?Sized> {
|
||||
/// The precomputed hash.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: u64,
|
||||
/// The wrapped item.
|
||||
item: T,
|
||||
}
|
||||
|
||||
impl<T: Hash + 'static> Prehashed<T> {
|
||||
/// Compute an item's hash and wrap it.
|
||||
pub fn new(item: T) -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: {
|
||||
// Also hash the TypeId because the type might be converted
|
||||
// through an unsized coercion.
|
||||
let mut state = fxhash::FxHasher64::default();
|
||||
item.type_id().hash(&mut state);
|
||||
item.hash(&mut state);
|
||||
state.finish()
|
||||
},
|
||||
item,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the wrapped value.
|
||||
pub fn into_iter(self) -> T {
|
||||
self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for Prehashed<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + ?Sized> Debug for Prehashed<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.item.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + ?Sized> Hash for Prehashed<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// Hash the node.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
state.write_u64(self.hash);
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
self.item.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + ?Sized> Eq for Prehashed<T> {}
|
||||
|
||||
impl<T: PartialEq + ?Sized> PartialEq for Prehashed<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
return self.hash == other.hash;
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
self.item.eq(&other.item)
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
@ -2,6 +2,7 @@
|
||||
|
||||
---
|
||||
// Ensure that constructor styles aren't passed down the tree.
|
||||
// The inner list should have no extra indent.
|
||||
#set par(leading: 2pt)
|
||||
#list(
|
||||
body-indent: 20pt,
|
||||
@ -11,7 +12,22 @@
|
||||
|
||||
---
|
||||
// Ensure that constructor styles win, but not over outer styles.
|
||||
// The outer paragraph should be right-aligned,
|
||||
// but the B should be center-aligned.
|
||||
#set par(align: center)
|
||||
#par(align: right)[
|
||||
A #rect(width: 2cm, fill: conifer, padding: 4pt)[B]
|
||||
]
|
||||
|
||||
---
|
||||
// The inner rectangle should also be yellow here.
|
||||
// (and therefore invisible)
|
||||
[#set rect(fill: yellow);#text(100%, rect(padding: 5pt, rect()))]
|
||||
|
||||
---
|
||||
// The inner rectangle should not be yellow here.
|
||||
A #rect(fill: yellow, padding: 5pt, rect()) B
|
||||
|
||||
---
|
||||
// The inner list should not be indented extra.
|
||||
[#set text(100%);#list(label-indent: 20pt, list[A])]
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Test hyperlinking.
|
||||
|
||||
---
|
||||
// Link without body.
|
||||
#link("https://example.com/")
|
||||
|
||||
@ -8,22 +11,25 @@
|
||||
This link appears #link("https://google.com/")[in the middle of] a paragraph.
|
||||
|
||||
// Prefix is trimmed.
|
||||
Contact #link("mailto:hi@typst.app") or call #link("tel:123") for more information.
|
||||
Contact #link("mailto:hi@typst.app") or
|
||||
call #link("tel:123") for more information.
|
||||
|
||||
---
|
||||
// Styled with underline and color.
|
||||
#let link(url, body) = link(url, text(fill: rgb("283663"), underline(body)))
|
||||
You could also make the #link("https://html5zombo.com/")[link look way more typical.]
|
||||
#set link(fill: rgb("283663"))
|
||||
You could also make the
|
||||
#link("https://html5zombo.com/")[link look way more typical.]
|
||||
|
||||
---
|
||||
// Transformed link.
|
||||
#set page(height: 60pt)
|
||||
#let link = link("https://typst.app/")[LINK]
|
||||
My cool #move(x: 0.7cm, y: 0.7cm, rotate(10deg, scale(200%, link)))
|
||||
#set link(underline: false)
|
||||
#let mylink = link("https://typst.app/")[LINK]
|
||||
My cool #move(x: 0.7cm, y: 0.7cm, rotate(10deg, scale(200%, mylink)))
|
||||
|
||||
---
|
||||
// Link containing a block.
|
||||
#link("https://example.com/", block[
|
||||
#link("https://example.com/", underline: false, block[
|
||||
My cool rhino
|
||||
#move(x: 10pt, image("../../res/rhino.png", width: 1cm))
|
||||
])
|
||||
|
@ -29,18 +29,6 @@ You
|
||||
// Test that paragraphs break due to incompatibility has correct spacing.
|
||||
A #set par(spacing: 0pt, leading: 0pt); B #parbreak() C
|
||||
|
||||
---
|
||||
// Test that paragraph breaks due to block nodes have the correct spacing.
|
||||
#set par(spacing: 10pt)
|
||||
- A
|
||||
|
||||
#set par(leading: 0pt)
|
||||
- B
|
||||
- C
|
||||
#set par(leading: 5pt)
|
||||
- D
|
||||
- E
|
||||
|
||||
---
|
||||
// Test weird metrics.
|
||||
#set par(spacing: 100%, leading: 0pt)
|
||||
|
Loading…
x
Reference in New Issue
Block a user