mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Rework style chains
This commit is contained in:
parent
20b4d590b3
commit
3d52387eea
@ -1,8 +1,8 @@
|
|||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
|
||||||
use quote::quote;
|
use quote::{quote, quote_spanned};
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
@ -54,7 +54,7 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
|
|||||||
construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?;
|
construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?;
|
||||||
|
|
||||||
let set = set.unwrap_or_else(|| {
|
let set = set.unwrap_or_else(|| {
|
||||||
let sets = properties.into_iter().filter(|p| !p.skip).map(|property| {
|
let sets = properties.into_iter().filter(|p| !p.hidden).map(|property| {
|
||||||
let name = property.name;
|
let name = property.name;
|
||||||
let string = name.to_string().replace("_", "-").to_lowercase();
|
let string = name.to_string().replace("_", "-").to_lowercase();
|
||||||
|
|
||||||
@ -116,14 +116,6 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A style property.
|
|
||||||
struct Property {
|
|
||||||
name: Ident,
|
|
||||||
shorthand: bool,
|
|
||||||
variadic: bool,
|
|
||||||
skip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse the name and generic type arguments of the node type.
|
/// Parse the name and generic type arguments of the node type.
|
||||||
fn parse_self(
|
fn parse_self(
|
||||||
self_ty: &syn::Type,
|
self_ty: &syn::Type,
|
||||||
@ -153,12 +145,21 @@ fn process_const(
|
|||||||
self_name: &str,
|
self_name: &str,
|
||||||
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
|
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
|
||||||
) -> Result<(Property, syn::ItemMod)> {
|
) -> Result<(Property, syn::ItemMod)> {
|
||||||
|
let property = parse_property(item)?;
|
||||||
|
|
||||||
// The display name, e.g. `TextNode::STRONG`.
|
// The display name, e.g. `TextNode::STRONG`.
|
||||||
let name = format!("{}::{}", self_name, &item.ident);
|
let name = format!("{}::{}", self_name, &item.ident);
|
||||||
|
|
||||||
// The type of the property's value is what the user of our macro wrote
|
// The type of the property's value is what the user of our macro wrote
|
||||||
// as type of the const ...
|
// as type of the const ...
|
||||||
let value_ty = &item.ty;
|
let value_ty = &item.ty;
|
||||||
|
let output_ty = if property.fold {
|
||||||
|
parse_quote!(<#value_ty as eval::Fold>::Output)
|
||||||
|
} else if property.referenced {
|
||||||
|
parse_quote!(&'a #value_ty)
|
||||||
|
} else {
|
||||||
|
value_ty.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// ... but the real type of the const becomes this..
|
// ... but the real type of the const becomes this..
|
||||||
let key = quote! { Key<#value_ty, #self_args> };
|
let key = quote! { Key<#value_ty, #self_args> };
|
||||||
@ -172,50 +173,41 @@ fn process_const(
|
|||||||
_ => true,
|
_ => true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// The default value of the property is what the user wrote as
|
|
||||||
// initialization value of the const.
|
|
||||||
let default = &item.expr;
|
let default = &item.expr;
|
||||||
|
|
||||||
let mut fold = None;
|
// Ensure that the type is either `Copy` or that the property is referenced
|
||||||
let mut property = Property {
|
// or that the property isn't copy but can't be referenced because it needs
|
||||||
name: item.ident.clone(),
|
// folding.
|
||||||
shorthand: false,
|
let get;
|
||||||
variadic: false,
|
let mut copy = None;
|
||||||
skip: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
for attr in std::mem::take(&mut item.attrs) {
|
if property.referenced {
|
||||||
match attr.path.get_ident().map(ToString::to_string).as_deref() {
|
get = quote! {
|
||||||
Some("fold") => {
|
values.next().unwrap_or_else(|| {
|
||||||
// Look for a folding function like `#[fold(u64::add)]`.
|
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
|
||||||
let func: syn::Expr = attr.parse_args()?;
|
&*LAZY
|
||||||
fold = Some(quote! {
|
})
|
||||||
const FOLDING: bool = true;
|
};
|
||||||
|
} else if property.fold {
|
||||||
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
get = quote! {
|
||||||
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
|
match values.next().cloned() {
|
||||||
f(inner, outer)
|
Some(inner) => eval::Fold::fold(inner, Self::get(values)),
|
||||||
}
|
None => #default,
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Some("shorthand") => property.shorthand = true,
|
};
|
||||||
Some("variadic") => property.variadic = true,
|
} else {
|
||||||
Some("skip") => property.skip = true,
|
get = quote! {
|
||||||
_ => item.attrs.push(attr),
|
values.next().copied().unwrap_or(#default)
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if property.shorthand && property.variadic {
|
copy = Some(quote_spanned! { item.ty.span() =>
|
||||||
return Err(Error::new(
|
const _: fn() -> () = || {
|
||||||
property.name.span(),
|
fn type_must_be_copy_or_fold_or_referenced<T: Copy>() {}
|
||||||
"shorthand and variadic are mutually exclusive",
|
type_must_be_copy_or_fold_or_referenced::<#value_ty>();
|
||||||
));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let referencable = fold.is_none().then(|| {
|
|
||||||
quote! { impl<#params> eval::Referencable for #key {} }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate the module code.
|
// Generate the module code.
|
||||||
let module_name = &item.ident;
|
let module_name = &item.ident;
|
||||||
let module = parse_quote! {
|
let module = parse_quote! {
|
||||||
@ -232,28 +224,21 @@ fn process_const(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<#params> eval::Key for #key {
|
impl<'a, #params> eval::Key<'a> for #key {
|
||||||
type Value = #value_ty;
|
type Value = #value_ty;
|
||||||
|
type Output = #output_ty;
|
||||||
const NAME: &'static str = #name;
|
const NAME: &'static str = #name;
|
||||||
|
|
||||||
fn node_id() -> TypeId {
|
fn node() -> TypeId {
|
||||||
TypeId::of::<#self_ty>()
|
TypeId::of::<#self_ty>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default() -> Self::Value {
|
fn get(mut values: impl Iterator<Item = &'a Self::Value>) -> Self::Output {
|
||||||
#default
|
#get
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_ref() -> &'static Self::Value {
|
|
||||||
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
|
|
||||||
&*LAZY
|
|
||||||
}
|
|
||||||
|
|
||||||
#fold
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#referencable
|
#copy
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -263,3 +248,64 @@ fn process_const(
|
|||||||
|
|
||||||
Ok((property, module))
|
Ok((property, module))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A style property.
|
||||||
|
struct Property {
|
||||||
|
name: Ident,
|
||||||
|
hidden: bool,
|
||||||
|
referenced: bool,
|
||||||
|
shorthand: bool,
|
||||||
|
variadic: bool,
|
||||||
|
fold: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a style property attribute.
|
||||||
|
fn parse_property(item: &mut syn::ImplItemConst) -> Result<Property> {
|
||||||
|
let mut property = Property {
|
||||||
|
name: item.ident.clone(),
|
||||||
|
hidden: false,
|
||||||
|
referenced: false,
|
||||||
|
shorthand: false,
|
||||||
|
variadic: false,
|
||||||
|
fold: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(idx) = item
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.position(|attr| attr.path.get_ident().map_or(false, |name| name == "property"))
|
||||||
|
{
|
||||||
|
let attr = item.attrs.remove(idx);
|
||||||
|
for token in attr.parse_args::<TokenStream2>()? {
|
||||||
|
match token {
|
||||||
|
TokenTree::Ident(ident) => match ident.to_string().as_str() {
|
||||||
|
"hidden" => property.hidden = true,
|
||||||
|
"shorthand" => property.shorthand = true,
|
||||||
|
"referenced" => property.referenced = true,
|
||||||
|
"variadic" => property.variadic = true,
|
||||||
|
"fold" => property.fold = true,
|
||||||
|
_ => return Err(Error::new(ident.span(), "invalid attribute")),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(_) => {}
|
||||||
|
_ => return Err(Error::new(token.span(), "invalid token")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = property.name.span();
|
||||||
|
if property.shorthand && property.variadic {
|
||||||
|
return Err(Error::new(
|
||||||
|
span,
|
||||||
|
"shorthand and variadic are mutually exclusive",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.referenced && property.fold {
|
||||||
|
return Err(Error::new(
|
||||||
|
span,
|
||||||
|
"referenced and fold are mutually exclusive",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(property)
|
||||||
|
}
|
||||||
|
@ -102,17 +102,15 @@ impl Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a single style property.
|
/// Style this content with a single style property.
|
||||||
pub fn styled<P: Key>(mut self, key: P, value: P::Value) -> Self {
|
pub fn styled<'k, K: Key<'k>>(mut self, key: K, value: K::Value) -> Self {
|
||||||
if let Self::Styled(styled) = &mut self {
|
if let Self::Styled(styled) = &mut self {
|
||||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
if let Some((_, map)) = Arc::get_mut(styled) {
|
||||||
if !map.has_scoped() {
|
map.apply(key, value);
|
||||||
map.set(key, value);
|
return self;
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.styled_with_map(StyleMap::with(key, value))
|
Self::Styled(Arc::new((self, StyleMap::with(key, value))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a full style map.
|
/// Style this content with a full style map.
|
||||||
@ -123,10 +121,8 @@ impl Content {
|
|||||||
|
|
||||||
if let Self::Styled(styled) = &mut self {
|
if let Self::Styled(styled) = &mut self {
|
||||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
if let Some((_, map)) = Arc::get_mut(styled) {
|
||||||
if !styles.has_scoped() && !map.has_scoped() {
|
map.apply_map(&styles);
|
||||||
map.apply(&styles);
|
return self;
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +157,7 @@ impl Content {
|
|||||||
let tpa = Arena::new();
|
let tpa = Arena::new();
|
||||||
|
|
||||||
let styles = ctx.styles.clone();
|
let styles = ctx.styles.clone();
|
||||||
let styles = StyleChain::new(&styles);
|
let styles = StyleChain::with_root(&styles);
|
||||||
|
|
||||||
let mut builder = Builder::new(&sya, &tpa, true);
|
let mut builder = Builder::new(&sya, &tpa, true);
|
||||||
builder.process(ctx, self, styles)?;
|
builder.process(ctx, self, styles)?;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::any::TypeId;
|
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -52,7 +51,7 @@ impl Func {
|
|||||||
show: if T::SHOWABLE {
|
show: if T::SHOWABLE {
|
||||||
Some(|recipe, span| {
|
Some(|recipe, span| {
|
||||||
let mut styles = StyleMap::new();
|
let mut styles = StyleMap::new();
|
||||||
styles.set_recipe(TypeId::of::<T>(), recipe, span);
|
styles.set_recipe::<T>(recipe, span);
|
||||||
styles
|
styles
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
//! Layouting infrastructure.
|
//! Layouting infrastructure.
|
||||||
|
|
||||||
use std::any::{Any, TypeId};
|
use std::any::Any;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{Barrier, StyleChain};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::StyleChain;
|
|
||||||
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
||||||
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
|
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
|
||||||
use crate::library::graphics::MoveNode;
|
use crate::library::graphics::MoveNode;
|
||||||
@ -18,7 +18,7 @@ use crate::Context;
|
|||||||
///
|
///
|
||||||
/// Layout return one frame per used region alongside constraints that define
|
/// Layout return one frame per used region alongside constraints that define
|
||||||
/// whether the result is reusable in other regions.
|
/// whether the result is reusable in other regions.
|
||||||
pub trait Layout {
|
pub trait Layout: 'static {
|
||||||
/// Layout this node into the given regions, producing constrained frames.
|
/// Layout this node into the given regions, producing constrained frames.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -144,12 +144,12 @@ impl LayoutNode {
|
|||||||
|
|
||||||
/// Check whether the contained node is a specific layout node.
|
/// Check whether the contained node is a specific layout node.
|
||||||
pub fn is<T: 'static>(&self) -> bool {
|
pub fn is<T: 'static>(&self) -> bool {
|
||||||
self.0.as_any().is::<T>()
|
(**self.0).as_any().is::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type id of this node.
|
/// A barrier for the node.
|
||||||
pub fn id(&self) -> TypeId {
|
pub fn barrier(&self) -> Barrier {
|
||||||
self.0.as_any().type_id()
|
(**self.0).barrier()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to downcast to a specific layout node.
|
/// Try to downcast to a specific layout node.
|
||||||
@ -157,7 +157,7 @@ impl LayoutNode {
|
|||||||
where
|
where
|
||||||
T: Layout + Debug + Hash + 'static,
|
T: Layout + Debug + Hash + 'static,
|
||||||
{
|
{
|
||||||
self.0.as_any().downcast_ref()
|
(**self.0).as_any().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force a size for this node.
|
/// Force a size for this node.
|
||||||
@ -223,7 +223,7 @@ impl Layout for LayoutNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
|
ctx.query((self, regions, styles), |ctx, (node, regions, styles)| {
|
||||||
node.0.layout(ctx, regions, styles.barred(node.id()))
|
node.0.layout(ctx, regions, node.barrier().chain(&styles))
|
||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
@ -253,6 +253,7 @@ impl PartialEq for LayoutNode {
|
|||||||
|
|
||||||
trait Bounds: Layout + Debug + Sync + Send + 'static {
|
trait Bounds: Layout + Debug + Sync + Send + 'static {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn barrier(&self) -> Barrier;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Bounds for T
|
impl<T> Bounds for T
|
||||||
@ -262,6 +263,10 @@ where
|
|||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn barrier(&self) -> Barrier {
|
||||||
|
Barrier::new::<T>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout node that produces an empty frame.
|
/// A layout node that produces an empty frame.
|
||||||
|
@ -9,7 +9,7 @@ use crate::util::Prehashed;
|
|||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// A node that can be realized given some styles.
|
/// A node that can be realized given some styles.
|
||||||
pub trait Show {
|
pub trait Show: 'static {
|
||||||
/// Realize this node in the given styles.
|
/// Realize this node in the given styles.
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -359,12 +359,12 @@ impl Dynamic {
|
|||||||
|
|
||||||
/// Whether the wrapped type is `T`.
|
/// Whether the wrapped type is `T`.
|
||||||
pub fn is<T: Type + 'static>(&self) -> bool {
|
pub fn is<T: Type + 'static>(&self) -> bool {
|
||||||
self.0.as_any().is::<T>()
|
(*self.0).as_any().is::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to downcast to a reference to a specific type.
|
/// Try to downcast to a reference to a specific type.
|
||||||
pub fn downcast<T: Type + 'static>(&self) -> Option<&T> {
|
pub fn downcast<T: Type + 'static>(&self) -> Option<&T> {
|
||||||
self.0.as_any().downcast_ref()
|
(*self.0).as_any().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the stored value's type.
|
/// The name of the stored value's type.
|
||||||
|
@ -82,7 +82,7 @@ impl Layout for ImageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get_ref(TextNode::LINK) {
|
if let Some(url) = styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get_ref(TextNode::LINK) {
|
if let Some(url) = styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,12 @@ impl Layout for FlowNode {
|
|||||||
let styles = map.chain(&styles);
|
let styles = map.chain(&styles);
|
||||||
match child {
|
match child {
|
||||||
FlowChild::Leading => {
|
FlowChild::Leading => {
|
||||||
let em = styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE);
|
||||||
let amount = styles.get(ParNode::LEADING).resolve(em);
|
let amount = styles.get(ParNode::LEADING).resolve(em);
|
||||||
layouter.layout_spacing(amount.into());
|
layouter.layout_spacing(amount.into());
|
||||||
}
|
}
|
||||||
FlowChild::Parbreak => {
|
FlowChild::Parbreak => {
|
||||||
let em = styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE);
|
||||||
let leading = styles.get(ParNode::LEADING);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let spacing = styles.get(ParNode::SPACING);
|
let spacing = styles.get(ParNode::SPACING);
|
||||||
let amount = (leading + spacing).resolve(em);
|
let amount = (leading + spacing).resolve(em);
|
||||||
|
@ -28,8 +28,10 @@ impl PageNode {
|
|||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
||||||
/// The page's header.
|
/// The page's header.
|
||||||
|
#[property(referenced)]
|
||||||
pub const HEADER: Marginal = Marginal::None;
|
pub const HEADER: Marginal = Marginal::None;
|
||||||
/// The page's footer.
|
/// The page's footer.
|
||||||
|
#[property(referenced)]
|
||||||
pub const FOOTER: Marginal = Marginal::None;
|
pub const FOOTER: Marginal = Marginal::None;
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
@ -116,8 +118,8 @@ impl PageNode {
|
|||||||
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
||||||
let mut frames = child.layout(ctx, ®ions, styles)?;
|
let mut frames = child.layout(ctx, ®ions, styles)?;
|
||||||
|
|
||||||
let header = styles.get_ref(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get_ref(Self::FOOTER);
|
let footer = styles.get(Self::FOOTER);
|
||||||
|
|
||||||
// Realize header and footer.
|
// Realize header and footer.
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
|
@ -15,6 +15,7 @@ pub struct MathNode {
|
|||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl MathNode {
|
impl MathNode {
|
||||||
/// The raw text's font family. Just the normal text family if `auto`.
|
/// The raw text's font family. Just the normal text family if `auto`.
|
||||||
|
#[property(referenced)]
|
||||||
pub const FAMILY: Smart<FontFamily> =
|
pub const FAMILY: Smart<FontFamily> =
|
||||||
Smart::Custom(FontFamily::new("Latin Modern Math"));
|
Smart::Custom(FontFamily::new("Latin Modern Math"));
|
||||||
|
|
||||||
@ -28,16 +29,14 @@ impl MathNode {
|
|||||||
|
|
||||||
impl Show for MathNode {
|
impl Show for MathNode {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
|
let args = [Value::Str(self.formula.clone()), Value::Bool(self.display)];
|
||||||
let mut content = styles
|
let mut content = styles
|
||||||
.show(self, ctx, [
|
.show::<Self, _>(ctx, args)?
|
||||||
Value::Str(self.formula.clone()),
|
|
||||||
Value::Bool(self.display),
|
|
||||||
])?
|
|
||||||
.unwrap_or_else(|| Content::Text(self.formula.trim().into()));
|
.unwrap_or_else(|| Content::Text(self.formula.trim().into()));
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
|
if let Smart::Custom(family) = styles.get(Self::FAMILY) {
|
||||||
map.set_family(family, styles);
|
map.set_family(family.clone(), styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.styled_with_map(map);
|
content = content.styled_with_map(map);
|
||||||
|
@ -9,8 +9,8 @@ pub use typst_macros::node;
|
|||||||
|
|
||||||
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node,
|
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
|
||||||
Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
|
Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{FontFamily, TextNode};
|
use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -14,25 +14,34 @@ pub struct HeadingNode {
|
|||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
/// The heading's font family. Just the normal text family if `auto`.
|
/// The heading's font family. Just the normal text family if `auto`.
|
||||||
|
#[property(referenced)]
|
||||||
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto);
|
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto);
|
||||||
/// The color of text in the heading. Just the normal text color if `auto`.
|
/// The color of text in the heading. Just the normal text color if `auto`.
|
||||||
|
#[property(referenced)]
|
||||||
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
|
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
|
||||||
/// The size of text in the heading.
|
/// The size of text in the heading.
|
||||||
pub const SIZE: Leveled<Linear> = Leveled::Mapping(|level| {
|
#[property(referenced)]
|
||||||
|
pub const SIZE: Leveled<FontSize> = Leveled::Mapping(|level| {
|
||||||
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
|
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
|
||||||
Relative::new(upscale).into()
|
FontSize(Relative::new(upscale).into())
|
||||||
});
|
});
|
||||||
/// Whether text in the heading is strengthend.
|
/// Whether text in the heading is strengthend.
|
||||||
|
#[property(referenced)]
|
||||||
pub const STRONG: Leveled<bool> = Leveled::Value(true);
|
pub const STRONG: Leveled<bool> = Leveled::Value(true);
|
||||||
/// Whether text in the heading is emphasized.
|
/// Whether text in the heading is emphasized.
|
||||||
|
#[property(referenced)]
|
||||||
pub const EMPH: Leveled<bool> = Leveled::Value(false);
|
pub const EMPH: Leveled<bool> = Leveled::Value(false);
|
||||||
/// Whether the heading is underlined.
|
/// Whether the heading is underlined.
|
||||||
|
#[property(referenced)]
|
||||||
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
|
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
|
||||||
/// The extra padding above the heading.
|
/// The extra padding above the heading.
|
||||||
|
#[property(referenced)]
|
||||||
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
|
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
|
||||||
/// The extra padding below the heading.
|
/// The extra padding below the heading.
|
||||||
|
#[property(referenced)]
|
||||||
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
|
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
|
||||||
/// Whether the heading is block-level.
|
/// Whether the heading is block-level.
|
||||||
|
#[property(referenced)]
|
||||||
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
|
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
@ -47,16 +56,17 @@ impl Show for HeadingNode {
|
|||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
macro_rules! resolve {
|
macro_rules! resolve {
|
||||||
($key:expr) => {
|
($key:expr) => {
|
||||||
styles.get_cloned($key).resolve(ctx, self.level)?
|
styles.get($key).resolve(ctx, self.level)?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the user recipe.
|
let args = [
|
||||||
|
Value::Int(self.level as i64),
|
||||||
|
Value::Content(self.body.clone()),
|
||||||
|
];
|
||||||
|
|
||||||
let mut body = styles
|
let mut body = styles
|
||||||
.show(self, ctx, [
|
.show::<Self, _>(ctx, args)?
|
||||||
Value::Int(self.level as i64),
|
|
||||||
Value::Content(self.body.clone()),
|
|
||||||
])?
|
|
||||||
.unwrap_or_else(|| self.body.clone());
|
.unwrap_or_else(|| self.body.clone());
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
@ -71,11 +81,11 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resolve!(Self::STRONG) {
|
if resolve!(Self::STRONG) {
|
||||||
map.set(TextNode::STRONG, true);
|
map.set(TextNode::STRONG, Toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolve!(Self::EMPH) {
|
if resolve!(Self::EMPH) {
|
||||||
map.set(TextNode::EMPH, true);
|
map.set(TextNode::EMPH, Toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
@ -116,15 +126,15 @@ pub enum Leveled<T> {
|
|||||||
Func(Func, Span),
|
Func(Func, Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Cast> Leveled<T> {
|
impl<T: Cast + Clone> Leveled<T> {
|
||||||
/// Resolve the value based on the level.
|
/// Resolve the value based on the level.
|
||||||
pub fn resolve(self, ctx: &mut Context, level: usize) -> TypResult<T> {
|
pub fn resolve(&self, ctx: &mut Context, level: usize) -> TypResult<T> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Value(value) => value,
|
Self::Value(value) => value.clone(),
|
||||||
Self::Mapping(mapping) => mapping(level),
|
Self::Mapping(mapping) => mapping(level),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
let args = Args::from_values(span, [Value::Int(level as i64)]);
|
let args = Args::from_values(*span, [Value::Int(level as i64)]);
|
||||||
func.call(ctx, args)?.cast().at(span)?
|
func.call(ctx, args)?.cast().at(*span)?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ pub type EnumNode = ListNode<ORDERED>;
|
|||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl<const L: ListKind> ListNode<L> {
|
impl<const L: ListKind> ListNode<L> {
|
||||||
/// How the list is labelled.
|
/// How the list is labelled.
|
||||||
|
#[property(referenced)]
|
||||||
pub const LABEL: Label = Label::Default;
|
pub const LABEL: Label = Label::Default;
|
||||||
/// The spacing between the list items of a non-wide list.
|
/// The spacing between the list items of a non-wide list.
|
||||||
pub const SPACING: Linear = Linear::zero();
|
pub const SPACING: Linear = Linear::zero();
|
||||||
@ -58,17 +59,14 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
|
|
||||||
impl<const L: ListKind> Show for ListNode<L> {
|
impl<const L: ListKind> Show for ListNode<L> {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
let content = if let Some(content) = styles.show(
|
let args = self.items.iter().map(|item| Value::Content((*item.body).clone()));
|
||||||
self,
|
let content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
|
||||||
ctx,
|
|
||||||
self.items.iter().map(|item| Value::Content((*item.body).clone())),
|
|
||||||
)? {
|
|
||||||
content
|
content
|
||||||
} else {
|
} else {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
let mut number = self.start;
|
let mut number = self.start;
|
||||||
|
|
||||||
let label = styles.get_ref(Self::LABEL);
|
let label = styles.get(Self::LABEL);
|
||||||
|
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
number = item.number.unwrap_or(number);
|
number = item.number.unwrap_or(number);
|
||||||
@ -79,7 +77,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
number += 1;
|
number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let em = styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE);
|
||||||
let leading = styles.get(ParNode::LEADING);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let spacing = if self.wide {
|
let spacing = if self.wide {
|
||||||
styles.get(ParNode::SPACING)
|
styles.get(ParNode::SPACING)
|
||||||
|
@ -55,11 +55,8 @@ impl TableNode {
|
|||||||
|
|
||||||
impl Show for TableNode {
|
impl Show for TableNode {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
if let Some(content) = styles.show(
|
let args = self.children.iter().map(|child| Value::Content(child.clone()));
|
||||||
self,
|
if let Some(content) = styles.show::<Self, _>(ctx, args)? {
|
||||||
ctx,
|
|
||||||
self.children.iter().map(|child| Value::Content(child.clone())),
|
|
||||||
)? {
|
|
||||||
return Ok(content);
|
return Ok(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ pub type OverlineNode = DecoNode<OVERLINE>;
|
|||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl<const L: DecoLine> DecoNode<L> {
|
impl<const L: DecoLine> DecoNode<L> {
|
||||||
/// Stroke color of the line, defaults to the text color if `None`.
|
/// Stroke color of the line, defaults to the text color if `None`.
|
||||||
#[shorthand]
|
#[property(shorthand)]
|
||||||
pub const STROKE: Option<Paint> = None;
|
pub const STROKE: Option<Paint> = None;
|
||||||
/// Thickness of the line's strokes (dependent on scaled font size), read
|
/// Thickness of the line's strokes (dependent on scaled font size), read
|
||||||
/// from the font tables if `None`.
|
/// from the font tables if `None`.
|
||||||
#[shorthand]
|
#[property(shorthand)]
|
||||||
pub const THICKNESS: Option<Linear> = None;
|
pub const THICKNESS: Option<Linear> = None;
|
||||||
/// Position of the line relative to the baseline (dependent on scaled font
|
/// Position of the line relative to the baseline (dependent on scaled font
|
||||||
/// size), read from the font tables if `None`.
|
/// size), read from the font tables if `None`.
|
||||||
@ -45,16 +45,16 @@ impl<const L: DecoLine> DecoNode<L> {
|
|||||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
Ok(styles
|
Ok(styles
|
||||||
.show(self, ctx, [Value::Content(self.0.clone())])?
|
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
self.0.clone().styled(TextNode::DECO, Decoration {
|
||||||
line: L,
|
line: L,
|
||||||
stroke: styles.get(Self::STROKE),
|
stroke: styles.get(Self::STROKE),
|
||||||
thickness: styles.get(Self::THICKNESS),
|
thickness: styles.get(Self::THICKNESS),
|
||||||
offset: styles.get(Self::OFFSET),
|
offset: styles.get(Self::OFFSET),
|
||||||
extent: styles.get(Self::EXTENT),
|
extent: styles.get(Self::EXTENT),
|
||||||
evade: styles.get(Self::EVADE),
|
evade: styles.get(Self::EVADE),
|
||||||
}])
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,14 +29,13 @@ impl LinkNode {
|
|||||||
|
|
||||||
impl Show for LinkNode {
|
impl Show for LinkNode {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
|
let args = [Value::Str(self.url.clone()), match &self.body {
|
||||||
|
Some(body) => Value::Content(body.clone()),
|
||||||
|
None => Value::None,
|
||||||
|
}];
|
||||||
|
|
||||||
let mut body = styles
|
let mut body = styles
|
||||||
.show(self, ctx, [
|
.show::<Self, _>(ctx, args)?
|
||||||
Value::Str(self.url.clone()),
|
|
||||||
match &self.body {
|
|
||||||
Some(body) => Value::Content(body.clone()),
|
|
||||||
None => Value::None,
|
|
||||||
},
|
|
||||||
])?
|
|
||||||
.or_else(|| self.body.clone())
|
.or_else(|| self.body.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
let url = &self.url;
|
let url = &self.url;
|
||||||
|
@ -13,7 +13,6 @@ pub use raw::*;
|
|||||||
pub use shaping::*;
|
pub use shaping::*;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::BitXor;
|
|
||||||
|
|
||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ pub struct TextNode;
|
|||||||
#[node]
|
#[node]
|
||||||
impl TextNode {
|
impl TextNode {
|
||||||
/// A prioritized sequence of font families.
|
/// A prioritized sequence of font families.
|
||||||
#[variadic]
|
#[property(referenced, variadic)]
|
||||||
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
|
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
|
||||||
/// Whether to allow font fallback when the primary font list contains no
|
/// Whether to allow font fallback when the primary font list contains no
|
||||||
/// match.
|
/// match.
|
||||||
@ -40,14 +39,13 @@ impl TextNode {
|
|||||||
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
||||||
/// The width of the glyphs.
|
/// The width of the glyphs.
|
||||||
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
||||||
|
/// The size of the glyphs.
|
||||||
|
#[property(shorthand, fold)]
|
||||||
|
pub const SIZE: FontSize = Length::pt(11.0);
|
||||||
/// The glyph fill color.
|
/// The glyph fill color.
|
||||||
#[shorthand]
|
#[property(shorthand)]
|
||||||
pub const FILL: Paint = Color::BLACK.into();
|
pub const FILL: Paint = Color::BLACK.into();
|
||||||
|
|
||||||
/// 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.
|
/// The amount of space that should be added between characters.
|
||||||
pub const TRACKING: Em = Em::zero();
|
pub const TRACKING: Em = Em::zero();
|
||||||
/// The ratio by which spaces should be stretched.
|
/// The ratio by which spaces should be stretched.
|
||||||
@ -84,26 +82,24 @@ impl TextNode {
|
|||||||
/// Whether to convert fractions. ("frac")
|
/// Whether to convert fractions. ("frac")
|
||||||
pub const FRACTIONS: bool = false;
|
pub const FRACTIONS: bool = false;
|
||||||
/// Raw OpenType features to apply.
|
/// Raw OpenType features to apply.
|
||||||
|
#[property(fold)]
|
||||||
pub const FEATURES: Vec<(Tag, u32)> = vec![];
|
pub const FEATURES: Vec<(Tag, u32)> = vec![];
|
||||||
|
|
||||||
/// Whether the font weight should be increased by 300.
|
/// Whether the font weight should be increased by 300.
|
||||||
#[skip]
|
#[property(hidden, fold)]
|
||||||
#[fold(bool::bitxor)]
|
pub const STRONG: Toggle = false;
|
||||||
pub const STRONG: bool = false;
|
|
||||||
/// Whether the the font style should be inverted.
|
/// Whether the the font style should be inverted.
|
||||||
#[skip]
|
#[property(hidden, fold)]
|
||||||
#[fold(bool::bitxor)]
|
pub const EMPH: Toggle = false;
|
||||||
pub const EMPH: bool = false;
|
/// A case transformation that should be applied to the text.
|
||||||
/// The case transformation that should be applied to the next.
|
#[property(hidden)]
|
||||||
#[skip]
|
|
||||||
pub const CASE: Option<Case> = None;
|
pub const CASE: Option<Case> = None;
|
||||||
/// 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.
|
/// An URL the text should link to.
|
||||||
#[skip]
|
#[property(hidden, referenced)]
|
||||||
pub const LINK: Option<EcoString> = None;
|
pub const LINK: Option<EcoString> = None;
|
||||||
|
/// Decorative lines.
|
||||||
|
#[property(hidden, fold)]
|
||||||
|
pub const DECO: Decoration = vec![];
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
// The text constructor is special: It doesn't create a text node.
|
// The text constructor is special: It doesn't create a text node.
|
||||||
@ -113,44 +109,6 @@ impl TextNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strong text, rendered in boldface.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct StrongNode(pub Content);
|
|
||||||
|
|
||||||
#[node(showable)]
|
|
||||||
impl StrongNode {
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for StrongNode {
|
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
|
||||||
Ok(styles
|
|
||||||
.show(self, ctx, [Value::Content(self.0.clone())])?
|
|
||||||
.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, true)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emphasized text, rendered with an italic face.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct EmphNode(pub Content);
|
|
||||||
|
|
||||||
#[node(showable)]
|
|
||||||
impl EmphNode {
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for EmphNode {
|
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
|
||||||
Ok(styles
|
|
||||||
.show(self, ctx, [Value::Content(self.0.clone())])?
|
|
||||||
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, true)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A font family like "Arial".
|
/// A font family like "Arial".
|
||||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FontFamily(EcoString);
|
pub struct FontFamily(EcoString);
|
||||||
@ -228,6 +186,26 @@ castable! {
|
|||||||
Value::Relative(v) => Self::from_ratio(v.get() as f32),
|
Value::Relative(v) => Self::from_ratio(v.get() as f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The size of text.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct FontSize(pub Linear);
|
||||||
|
|
||||||
|
impl Fold for FontSize {
|
||||||
|
type Output = Length;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.0.rel.resolve(outer) + self.0.abs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
FontSize,
|
||||||
|
Expected: "linear",
|
||||||
|
Value::Length(v) => Self(v.into()),
|
||||||
|
Value::Relative(v) => Self(v.into()),
|
||||||
|
Value::Linear(v) => Self(v),
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Em,
|
Em,
|
||||||
Expected: "float",
|
Expected: "float",
|
||||||
@ -353,6 +331,15 @@ castable! {
|
|||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Fold for Vec<(Tag, u32)> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.extend(outer);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A case transformation on text.
|
/// A case transformation on text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Case {
|
pub enum Case {
|
||||||
@ -371,3 +358,62 @@ impl Case {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A toggle that turns on and off alternatingly if folded.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Toggle;
|
||||||
|
|
||||||
|
impl Fold for Toggle {
|
||||||
|
type Output = bool;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
!outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Decoration {
|
||||||
|
type Output = Vec<Self>;
|
||||||
|
|
||||||
|
fn fold(self, mut outer: Self::Output) -> Self::Output {
|
||||||
|
outer.insert(0, self);
|
||||||
|
outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strong text, rendered in boldface.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct StrongNode(pub Content);
|
||||||
|
|
||||||
|
#[node(showable)]
|
||||||
|
impl StrongNode {
|
||||||
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for StrongNode {
|
||||||
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
|
Ok(styles
|
||||||
|
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
|
||||||
|
.unwrap_or_else(|| self.0.clone().styled(TextNode::STRONG, Toggle)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emphasized text, rendered with an italic face.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct EmphNode(pub Content);
|
||||||
|
|
||||||
|
#[node(showable)]
|
||||||
|
impl EmphNode {
|
||||||
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
|
Ok(Content::show(Self(args.expect("body")?)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for EmphNode {
|
||||||
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
|
Ok(styles
|
||||||
|
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
|
||||||
|
.unwrap_or_else(|| self.0.clone().styled(TextNode::EMPH, Toggle)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ pub enum ParChild {
|
|||||||
#[node]
|
#[node]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
/// An ISO 639-1 language code.
|
/// An ISO 639-1 language code.
|
||||||
|
#[property(referenced)]
|
||||||
pub const LANG: Option<EcoString> = None;
|
pub const LANG: Option<EcoString> = None;
|
||||||
/// The direction for text and inline objects.
|
/// The direction for text and inline objects.
|
||||||
pub const DIR: Dir = Dir::LTR;
|
pub const DIR: Dir = Dir::LTR;
|
||||||
@ -611,7 +612,7 @@ fn breakpoints<'a>(
|
|||||||
let mut lang = None;
|
let mut lang = None;
|
||||||
if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) {
|
if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) {
|
||||||
lang = styles
|
lang = styles
|
||||||
.get_ref(ParNode::LANG)
|
.get(ParNode::LANG)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|iso| iso.as_bytes().try_into().ok())
|
.and_then(|iso| iso.as_bytes().try_into().ok())
|
||||||
.and_then(hypher::Lang::from_iso);
|
.and_then(hypher::Lang::from_iso);
|
||||||
@ -768,7 +769,7 @@ fn stack(
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Arc<Frame>> {
|
) -> Vec<Arc<Frame>> {
|
||||||
let em = styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE);
|
||||||
let leading = styles.get(ParNode::LEADING).resolve(em);
|
let leading = styles.get(ParNode::LEADING).resolve(em);
|
||||||
let align = styles.get(ParNode::ALIGN);
|
let align = styles.get(ParNode::ALIGN);
|
||||||
let justify = styles.get(ParNode::JUSTIFY);
|
let justify = styles.get(ParNode::JUSTIFY);
|
||||||
@ -832,7 +833,7 @@ fn commit(
|
|||||||
if let Some(glyph) = text.glyphs.first() {
|
if let Some(glyph) = text.glyphs.first() {
|
||||||
if text.styles.get(TextNode::OVERHANG) {
|
if text.styles.get(TextNode::OVERHANG) {
|
||||||
let start = text.dir.is_positive();
|
let start = text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE).abs;
|
let em = text.styles.get(TextNode::SIZE);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
||||||
offset -= amount;
|
offset -= amount;
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
@ -847,7 +848,7 @@ fn commit(
|
|||||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||||
{
|
{
|
||||||
let start = !text.dir.is_positive();
|
let start = !text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE).abs;
|
let em = text.styles.get(TextNode::SIZE);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use syntect::easy::HighlightLines;
|
|||||||
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
|
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
use super::{FontFamily, TextNode};
|
use super::{FontFamily, TextNode, Toggle};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::syntax::{self, RedNode};
|
use crate::syntax::{self, RedNode};
|
||||||
@ -27,8 +27,10 @@ pub struct RawNode {
|
|||||||
#[node(showable)]
|
#[node(showable)]
|
||||||
impl RawNode {
|
impl RawNode {
|
||||||
/// The raw text's font family. Just the normal text family if `none`.
|
/// The raw text's font family. Just the normal text family if `none`.
|
||||||
|
#[property(referenced)]
|
||||||
pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono"));
|
pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono"));
|
||||||
/// The language to syntax-highlight in.
|
/// The language to syntax-highlight in.
|
||||||
|
#[property(referenced)]
|
||||||
pub const LANG: Option<EcoString> = None;
|
pub const LANG: Option<EcoString> = None;
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
@ -41,7 +43,7 @@ impl RawNode {
|
|||||||
|
|
||||||
impl Show for RawNode {
|
impl Show for RawNode {
|
||||||
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
||||||
let lang = styles.get_ref(Self::LANG).as_ref();
|
let lang = styles.get(Self::LANG).as_ref();
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
.foreground
|
.foreground
|
||||||
@ -49,14 +51,16 @@ impl Show for RawNode {
|
|||||||
.unwrap_or(Color::BLACK)
|
.unwrap_or(Color::BLACK)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut content = if let Some(content) = styles.show(self, ctx, [
|
let args = [
|
||||||
Value::Str(self.text.clone()),
|
Value::Str(self.text.clone()),
|
||||||
match lang {
|
match lang {
|
||||||
Some(lang) => Value::Str(lang.clone()),
|
Some(lang) => Value::Str(lang.clone()),
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
},
|
},
|
||||||
Value::Bool(self.block),
|
Value::Bool(self.block),
|
||||||
])? {
|
];
|
||||||
|
|
||||||
|
let mut content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
|
||||||
content
|
content
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
lang.map(|s| s.to_lowercase()).as_deref(),
|
lang.map(|s| s.to_lowercase()).as_deref(),
|
||||||
@ -93,8 +97,8 @@ impl Show for RawNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
|
if let Smart::Custom(family) = styles.get(Self::FAMILY) {
|
||||||
map.set_family(family, styles);
|
map.set_family(family.clone(), styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.styled_with_map(map);
|
content = content.styled_with_map(map);
|
||||||
@ -118,11 +122,11 @@ fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if style.font_style.contains(FontStyle::BOLD) {
|
if style.font_style.contains(FontStyle::BOLD) {
|
||||||
styles.set(TextNode::STRONG, true);
|
styles.set(TextNode::STRONG, Toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if style.font_style.contains(FontStyle::ITALIC) {
|
if style.font_style.contains(FontStyle::ITALIC) {
|
||||||
styles.set(TextNode::EMPH, true);
|
styles.set(TextNode::EMPH, Toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if style.font_style.contains(FontStyle::UNDERLINE) {
|
if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||||
|
@ -77,7 +77,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
||||||
let pos = Point::new(offset, self.baseline);
|
let pos = Point::new(offset, self.baseline);
|
||||||
|
|
||||||
let size = self.styles.get(TextNode::SIZE).abs;
|
let size = self.styles.get(TextNode::SIZE);
|
||||||
let fill = self.styles.get(TextNode::FILL);
|
let fill = self.styles.get(TextNode::FILL);
|
||||||
let glyphs = group
|
let glyphs = group
|
||||||
.iter()
|
.iter()
|
||||||
@ -99,7 +99,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
let width = text.width();
|
let width = text.width();
|
||||||
|
|
||||||
// Apply line decorations.
|
// Apply line decorations.
|
||||||
for deco in self.styles.get_cloned(TextNode::LINES) {
|
for deco in self.styles.get(TextNode::DECO) {
|
||||||
decorate(&mut frame, &deco, fonts, &text, pos, width);
|
decorate(&mut frame, &deco, fonts, &text, pos, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = self.styles.get_ref(TextNode::LINK) {
|
if let Some(url) = self.styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
.filter(|g| g.is_space())
|
.filter(|g| g.is_space())
|
||||||
.map(|g| g.x_advance)
|
.map(|g| g.x_advance)
|
||||||
.sum::<Em>()
|
.sum::<Em>()
|
||||||
.resolve(self.styles.get(TextNode::SIZE).abs)
|
.resolve(self.styles.get(TextNode::SIZE))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reshape a range of the shaped text, reusing information from this
|
/// Reshape a range of the shaped text, reusing information from this
|
||||||
@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
|
|
||||||
/// Push a hyphen to end of the text.
|
/// Push a hyphen to end of the text.
|
||||||
pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
|
pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
|
||||||
let size = self.styles.get(TextNode::SIZE).abs;
|
let size = self.styles.get(TextNode::SIZE);
|
||||||
let variant = variant(self.styles);
|
let variant = variant(self.styles);
|
||||||
families(self.styles).find_map(|family| {
|
families(self.styles).find_map(|family| {
|
||||||
let face_id = fonts.select(family, variant)?;
|
let face_id = fonts.select(family, variant)?;
|
||||||
@ -467,7 +467,7 @@ fn measure(
|
|||||||
let mut top = Length::zero();
|
let mut top = Length::zero();
|
||||||
let mut bottom = Length::zero();
|
let mut bottom = Length::zero();
|
||||||
|
|
||||||
let size = styles.get(TextNode::SIZE).abs;
|
let size = styles.get(TextNode::SIZE);
|
||||||
let top_edge = styles.get(TextNode::TOP_EDGE);
|
let top_edge = styles.get(TextNode::TOP_EDGE);
|
||||||
let bottom_edge = styles.get(TextNode::BOTTOM_EDGE);
|
let bottom_edge = styles.get(TextNode::BOTTOM_EDGE);
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
|||||||
|
|
||||||
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
|
let tail = if styles.get(TextNode::FALLBACK) { FALLBACKS } else { &[] };
|
||||||
styles
|
styles
|
||||||
.get_ref(TextNode::FAMILY)
|
.get(TextNode::FAMILY)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|family| family.as_str())
|
.map(|family| family.as_str())
|
||||||
.chain(tail.iter().copied())
|
.chain(tail.iter().copied())
|
||||||
@ -609,7 +609,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
|
|||||||
feat(b"frac", 1);
|
feat(b"frac", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for &(tag, value) in styles.get_ref(TextNode::FEATURES).iter() {
|
for (tag, value) in styles.get(TextNode::FEATURES) {
|
||||||
tags.push(Feature::new(tag, value, ..))
|
tags.push(Feature::new(tag, value, ..))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ use typst::eval::{Smart, StyleMap, Value};
|
|||||||
use typst::frame::{Element, Frame};
|
use typst::frame::{Element, Frame};
|
||||||
use typst::geom::{Length, RgbaColor};
|
use typst::geom::{Length, RgbaColor};
|
||||||
use typst::library::layout::PageNode;
|
use typst::library::layout::PageNode;
|
||||||
use typst::library::text::TextNode;
|
use typst::library::text::{FontSize, TextNode};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
use typst::source::SourceFile;
|
use typst::source::SourceFile;
|
||||||
@ -67,7 +67,7 @@ fn main() {
|
|||||||
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(TextNode::SIZE, Length::pt(10.0).into());
|
styles.set(TextNode::SIZE, FontSize(Length::pt(10.0).into()));
|
||||||
|
|
||||||
// Hook up an assert function into the global scope.
|
// Hook up an assert function into the global scope.
|
||||||
let mut std = typst::library::new();
|
let mut std = typst::library::new();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user