Rework style chains

This commit is contained in:
Laurenz 2022-04-07 10:50:39 +02:00
parent 20b4d590b3
commit 3d52387eea
23 changed files with 785 additions and 644 deletions

View File

@ -1,8 +1,8 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::{quote, quote_spanned};
use syn::parse_quote;
use syn::punctuated::Punctuated;
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"))?;
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 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.
fn parse_self(
self_ty: &syn::Type,
@ -153,12 +145,21 @@ fn process_const(
self_name: &str,
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
) -> Result<(Property, syn::ItemMod)> {
let property = parse_property(item)?;
// The display name, e.g. `TextNode::STRONG`.
let name = format!("{}::{}", self_name, &item.ident);
// The type of the property's value is what the user of our macro wrote
// as type of the const ...
let value_ty = &item.ty;
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..
let key = quote! { Key<#value_ty, #self_args> };
@ -172,50 +173,41 @@ fn process_const(
_ => true,
});
// The default value of the property is what the user wrote as
// initialization value of the const.
let default = &item.expr;
let mut fold = None;
let mut property = Property {
name: item.ident.clone(),
shorthand: false,
variadic: false,
skip: false,
};
// Ensure that the type is either `Copy` or that the property is referenced
// or that the property isn't copy but can't be referenced because it needs
// folding.
let get;
let mut copy = None;
for attr in std::mem::take(&mut item.attrs) {
match attr.path.get_ident().map(ToString::to_string).as_deref() {
Some("fold") => {
// Look for a folding function like `#[fold(u64::add)]`.
let func: syn::Expr = attr.parse_args()?;
fold = Some(quote! {
const FOLDING: bool = true;
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
f(inner, outer)
}
});
if property.referenced {
get = quote! {
values.next().unwrap_or_else(|| {
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
&*LAZY
})
};
} else if property.fold {
get = quote! {
match values.next().cloned() {
Some(inner) => eval::Fold::fold(inner, Self::get(values)),
None => #default,
}
Some("shorthand") => property.shorthand = true,
Some("variadic") => property.variadic = true,
Some("skip") => property.skip = true,
_ => item.attrs.push(attr),
}
}
};
} else {
get = quote! {
values.next().copied().unwrap_or(#default)
};
if property.shorthand && property.variadic {
return Err(Error::new(
property.name.span(),
"shorthand and variadic are mutually exclusive",
));
copy = Some(quote_spanned! { item.ty.span() =>
const _: fn() -> () = || {
fn type_must_be_copy_or_fold_or_referenced<T: Copy>() {}
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.
let module_name = &item.ident;
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 Output = #output_ty;
const NAME: &'static str = #name;
fn node_id() -> TypeId {
fn node() -> TypeId {
TypeId::of::<#self_ty>()
}
fn default() -> Self::Value {
#default
fn get(mut values: impl Iterator<Item = &'a Self::Value>) -> Self::Output {
#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))
}
/// 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)
}

View File

@ -102,17 +102,15 @@ impl Content {
}
/// 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 Some((_, map)) = Arc::get_mut(styled) {
if !map.has_scoped() {
map.set(key, value);
return self;
}
map.apply(key, value);
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.
@ -123,10 +121,8 @@ impl Content {
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;
}
map.apply_map(&styles);
return self;
}
}
@ -161,7 +157,7 @@ impl Content {
let tpa = Arena::new();
let styles = ctx.styles.clone();
let styles = StyleChain::new(&styles);
let styles = StyleChain::with_root(&styles);
let mut builder = Builder::new(&sya, &tpa, true);
builder.process(ctx, self, styles)?;

View File

@ -1,4 +1,3 @@
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
@ -52,7 +51,7 @@ impl Func {
show: if T::SHOWABLE {
Some(|recipe, span| {
let mut styles = StyleMap::new();
styles.set_recipe(TypeId::of::<T>(), recipe, span);
styles.set_recipe::<T>(recipe, span);
styles
})
} else {

View File

@ -1,12 +1,12 @@
//! Layouting infrastructure.
use std::any::{Any, TypeId};
use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc;
use super::{Barrier, StyleChain};
use crate::diag::TypResult;
use crate::eval::StyleChain;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
use crate::library::graphics::MoveNode;
@ -18,7 +18,7 @@ use crate::Context;
///
/// Layout return one frame per used region alongside constraints that define
/// 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.
fn layout(
&self,
@ -144,12 +144,12 @@ impl LayoutNode {
/// Check whether the contained node is a specific layout node.
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.
pub fn id(&self) -> TypeId {
self.0.as_any().type_id()
/// A barrier for the node.
pub fn barrier(&self) -> Barrier {
(**self.0).barrier()
}
/// Try to downcast to a specific layout node.
@ -157,7 +157,7 @@ impl LayoutNode {
where
T: Layout + Debug + Hash + 'static,
{
self.0.as_any().downcast_ref()
(**self.0).as_any().downcast_ref()
}
/// Force a size for this node.
@ -223,7 +223,7 @@ impl Layout for LayoutNode {
styles: StyleChain,
) -> TypResult<Vec<Arc<Frame>>> {
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()
}
@ -253,6 +253,7 @@ impl PartialEq for LayoutNode {
trait Bounds: Layout + Debug + Sync + Send + 'static {
fn as_any(&self) -> &dyn Any;
fn barrier(&self) -> Barrier;
}
impl<T> Bounds for T
@ -262,6 +263,10 @@ where
fn as_any(&self) -> &dyn Any {
self
}
fn barrier(&self) -> Barrier {
Barrier::new::<T>()
}
}
/// A layout node that produces an empty frame.

View File

@ -9,7 +9,7 @@ use crate::util::Prehashed;
use crate::Context;
/// A node that can be realized given some styles.
pub trait Show {
pub trait Show: 'static {
/// Realize this node in the given styles.
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content>;

File diff suppressed because it is too large Load Diff

View File

@ -359,12 +359,12 @@ impl Dynamic {
/// Whether the wrapped type is `T`.
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.
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.

View File

@ -82,7 +82,7 @@ impl Layout for ImageNode {
}
// 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);
}

View File

@ -132,7 +132,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
}
// 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);
}

View File

@ -37,12 +37,12 @@ impl Layout for FlowNode {
let styles = map.chain(&styles);
match child {
FlowChild::Leading => {
let em = styles.get(TextNode::SIZE).abs;
let em = styles.get(TextNode::SIZE);
let amount = styles.get(ParNode::LEADING).resolve(em);
layouter.layout_spacing(amount.into());
}
FlowChild::Parbreak => {
let em = styles.get(TextNode::SIZE).abs;
let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = styles.get(ParNode::SPACING);
let amount = (leading + spacing).resolve(em);

View File

@ -28,8 +28,10 @@ impl PageNode {
/// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
/// The page's header.
#[property(referenced)]
pub const HEADER: Marginal = Marginal::None;
/// The page's footer.
#[property(referenced)]
pub const FOOTER: Marginal = Marginal::None;
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 mut frames = child.layout(ctx, &regions, styles)?;
let header = styles.get_ref(Self::HEADER);
let footer = styles.get_ref(Self::FOOTER);
let header = styles.get(Self::HEADER);
let footer = styles.get(Self::FOOTER);
// Realize header and footer.
for frame in &mut frames {

View File

@ -15,6 +15,7 @@ pub struct MathNode {
#[node(showable)]
impl MathNode {
/// The raw text's font family. Just the normal text family if `auto`.
#[property(referenced)]
pub const FAMILY: Smart<FontFamily> =
Smart::Custom(FontFamily::new("Latin Modern Math"));
@ -28,16 +29,14 @@ impl MathNode {
impl Show for MathNode {
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
.show(self, ctx, [
Value::Str(self.formula.clone()),
Value::Bool(self.display),
])?
.show::<Self, _>(ctx, args)?
.unwrap_or_else(|| Content::Text(self.formula.trim().into()));
let mut map = StyleMap::new();
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
map.set_family(family, styles);
if let Smart::Custom(family) = styles.get(Self::FAMILY) {
map.set_family(family.clone(), styles);
}
content = content.styled_with_map(map);

View File

@ -9,8 +9,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{
Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node,
Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
Node, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
};
pub use crate::frame::*;
pub use crate::geom::*;

View File

@ -1,5 +1,5 @@
use crate::library::prelude::*;
use crate::library::text::{FontFamily, TextNode};
use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
/// A section heading.
#[derive(Debug, Hash)]
@ -14,25 +14,34 @@ pub struct HeadingNode {
#[node(showable)]
impl HeadingNode {
/// The heading's font family. Just the normal text family if `auto`.
#[property(referenced)]
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::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);
/// 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);
Relative::new(upscale).into()
FontSize(Relative::new(upscale).into())
});
/// Whether text in the heading is strengthend.
#[property(referenced)]
pub const STRONG: Leveled<bool> = Leveled::Value(true);
/// Whether text in the heading is emphasized.
#[property(referenced)]
pub const EMPH: Leveled<bool> = Leveled::Value(false);
/// Whether the heading is underlined.
#[property(referenced)]
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
/// The extra padding above the heading.
#[property(referenced)]
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
/// The extra padding below the heading.
#[property(referenced)]
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
/// Whether the heading is block-level.
#[property(referenced)]
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
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> {
macro_rules! resolve {
($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
.show(self, ctx, [
Value::Int(self.level as i64),
Value::Content(self.body.clone()),
])?
.show::<Self, _>(ctx, args)?
.unwrap_or_else(|| self.body.clone());
let mut map = StyleMap::new();
@ -71,11 +81,11 @@ impl Show for HeadingNode {
}
if resolve!(Self::STRONG) {
map.set(TextNode::STRONG, true);
map.set(TextNode::STRONG, Toggle);
}
if resolve!(Self::EMPH) {
map.set(TextNode::EMPH, true);
map.set(TextNode::EMPH, Toggle);
}
let mut seq = vec![];
@ -116,15 +126,15 @@ pub enum Leveled<T> {
Func(Func, Span),
}
impl<T: Cast> Leveled<T> {
impl<T: Cast + Clone> Leveled<T> {
/// 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 {
Self::Value(value) => value,
Self::Value(value) => value.clone(),
Self::Mapping(mapping) => mapping(level),
Self::Func(func, span) => {
let args = Args::from_values(span, [Value::Int(level as i64)]);
func.call(ctx, args)?.cast().at(span)?
let args = Args::from_values(*span, [Value::Int(level as i64)]);
func.call(ctx, args)?.cast().at(*span)?
}
})
}

View File

@ -31,6 +31,7 @@ pub type EnumNode = ListNode<ORDERED>;
#[node(showable)]
impl<const L: ListKind> ListNode<L> {
/// How the list is labelled.
#[property(referenced)]
pub const LABEL: Label = Label::Default;
/// The spacing between the list items of a non-wide list.
pub const SPACING: Linear = Linear::zero();
@ -58,17 +59,14 @@ impl<const L: ListKind> ListNode<L> {
impl<const L: ListKind> Show for ListNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
let content = if let Some(content) = styles.show(
self,
ctx,
self.items.iter().map(|item| Value::Content((*item.body).clone())),
)? {
let args = self.items.iter().map(|item| Value::Content((*item.body).clone()));
let content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content
} else {
let mut children = vec![];
let mut number = self.start;
let label = styles.get_ref(Self::LABEL);
let label = styles.get(Self::LABEL);
for item in &self.items {
number = item.number.unwrap_or(number);
@ -79,7 +77,7 @@ impl<const L: ListKind> Show for ListNode<L> {
number += 1;
}
let em = styles.get(TextNode::SIZE).abs;
let em = styles.get(TextNode::SIZE);
let leading = styles.get(ParNode::LEADING);
let spacing = if self.wide {
styles.get(ParNode::SPACING)

View File

@ -55,11 +55,8 @@ impl TableNode {
impl Show for TableNode {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
if let Some(content) = styles.show(
self,
ctx,
self.children.iter().map(|child| Value::Content(child.clone())),
)? {
let args = self.children.iter().map(|child| Value::Content(child.clone()));
if let Some(content) = styles.show::<Self, _>(ctx, args)? {
return Ok(content);
}

View File

@ -21,11 +21,11 @@ pub type OverlineNode = DecoNode<OVERLINE>;
#[node(showable)]
impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`.
#[shorthand]
#[property(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]
#[property(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`.
@ -45,16 +45,16 @@ impl<const L: DecoLine> DecoNode<L> {
impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
Ok(styles
.show(self, ctx, [Value::Content(self.0.clone())])?
.show::<Self, _>(ctx, [Value::Content(self.0.clone())])?
.unwrap_or_else(|| {
self.0.clone().styled(TextNode::LINES, vec![Decoration {
self.0.clone().styled(TextNode::DECO, Decoration {
line: L,
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),
}])
})
}))
}
}

View File

@ -29,14 +29,13 @@ impl LinkNode {
impl Show for LinkNode {
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
.show(self, ctx, [
Value::Str(self.url.clone()),
match &self.body {
Some(body) => Value::Content(body.clone()),
None => Value::None,
},
])?
.show::<Self, _>(ctx, args)?
.or_else(|| self.body.clone())
.unwrap_or_else(|| {
let url = &self.url;

View File

@ -13,7 +13,6 @@ pub use raw::*;
pub use shaping::*;
use std::borrow::Cow;
use std::ops::BitXor;
use ttf_parser::Tag;
@ -28,7 +27,7 @@ pub struct TextNode;
#[node]
impl TextNode {
/// A prioritized sequence of font families.
#[variadic]
#[property(referenced, variadic)]
pub const FAMILY: Vec<FontFamily> = vec![FontFamily::new("IBM Plex Sans")];
/// Whether to allow font fallback when the primary font list contains no
/// match.
@ -40,14 +39,13 @@ impl TextNode {
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
/// The width of the glyphs.
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.
#[shorthand]
#[property(shorthand)]
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.
pub const TRACKING: Em = Em::zero();
/// The ratio by which spaces should be stretched.
@ -84,26 +82,24 @@ impl TextNode {
/// Whether to convert fractions. ("frac")
pub const FRACTIONS: bool = false;
/// Raw OpenType features to apply.
#[property(fold)]
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;
#[property(hidden, fold)]
pub const STRONG: Toggle = false;
/// Whether the the font style should be inverted.
#[skip]
#[fold(bool::bitxor)]
pub const EMPH: bool = false;
/// The case transformation that should be applied to the next.
#[skip]
#[property(hidden, fold)]
pub const EMPH: Toggle = false;
/// A case transformation that should be applied to the text.
#[property(hidden)]
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.
#[skip]
#[property(hidden, referenced)]
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> {
// 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".
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct FontFamily(EcoString);
@ -228,6 +186,26 @@ castable! {
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! {
Em,
Expected: "float",
@ -353,6 +331,15 @@ castable! {
.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.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
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)))
}
}

View File

@ -28,6 +28,7 @@ pub enum ParChild {
#[node]
impl ParNode {
/// An ISO 639-1 language code.
#[property(referenced)]
pub const LANG: Option<EcoString> = None;
/// The direction for text and inline objects.
pub const DIR: Dir = Dir::LTR;
@ -611,7 +612,7 @@ fn breakpoints<'a>(
let mut lang = None;
if styles.get(ParNode::HYPHENATE).unwrap_or(styles.get(ParNode::JUSTIFY)) {
lang = styles
.get_ref(ParNode::LANG)
.get(ParNode::LANG)
.as_ref()
.and_then(|iso| iso.as_bytes().try_into().ok())
.and_then(hypher::Lang::from_iso);
@ -768,7 +769,7 @@ fn stack(
regions: &Regions,
styles: StyleChain,
) -> 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 align = styles.get(ParNode::ALIGN);
let justify = styles.get(ParNode::JUSTIFY);
@ -832,7 +833,7 @@ fn commit(
if let Some(glyph) = text.glyphs.first() {
if text.styles.get(TextNode::OVERHANG) {
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);
offset -= amount;
remaining += amount;
@ -847,7 +848,7 @@ fn commit(
&& (reordered.len() > 1 || text.glyphs.len() > 1)
{
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);
remaining += amount;
}

View File

@ -3,7 +3,7 @@ use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
use syntect::parsing::SyntaxSet;
use super::{FontFamily, TextNode};
use super::{FontFamily, TextNode, Toggle};
use crate::library::prelude::*;
use crate::source::SourceId;
use crate::syntax::{self, RedNode};
@ -27,8 +27,10 @@ pub struct RawNode {
#[node(showable)]
impl RawNode {
/// 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"));
/// The language to syntax-highlight in.
#[property(referenced)]
pub const LANG: Option<EcoString> = None;
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
@ -41,7 +43,7 @@ impl RawNode {
impl Show for RawNode {
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
.settings
.foreground
@ -49,14 +51,16 @@ impl Show for RawNode {
.unwrap_or(Color::BLACK)
.into();
let mut content = if let Some(content) = styles.show(self, ctx, [
let args = [
Value::Str(self.text.clone()),
match lang {
Some(lang) => Value::Str(lang.clone()),
None => Value::None,
},
Value::Bool(self.block),
])? {
];
let mut content = if let Some(content) = styles.show::<Self, _>(ctx, args)? {
content
} else if matches!(
lang.map(|s| s.to_lowercase()).as_deref(),
@ -93,8 +97,8 @@ impl Show for RawNode {
};
let mut map = StyleMap::new();
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
map.set_family(family, styles);
if let Smart::Custom(family) = styles.get(Self::FAMILY) {
map.set_family(family.clone(), styles);
}
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) {
styles.set(TextNode::STRONG, true);
styles.set(TextNode::STRONG, Toggle);
}
if style.font_style.contains(FontStyle::ITALIC) {
styles.set(TextNode::EMPH, true);
styles.set(TextNode::EMPH, Toggle);
}
if style.font_style.contains(FontStyle::UNDERLINE) {

View File

@ -77,7 +77,7 @@ impl<'a> ShapedText<'a> {
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
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 glyphs = group
.iter()
@ -99,7 +99,7 @@ impl<'a> ShapedText<'a> {
let width = text.width();
// 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);
}
@ -108,7 +108,7 @@ impl<'a> ShapedText<'a> {
}
// 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);
}
@ -127,7 +127,7 @@ impl<'a> ShapedText<'a> {
.filter(|g| g.is_space())
.map(|g| g.x_advance)
.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
@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> {
/// Push a hyphen to end of the text.
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);
families(self.styles).find_map(|family| {
let face_id = fonts.select(family, variant)?;
@ -467,7 +467,7 @@ fn measure(
let mut top = 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 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 { &[] };
styles
.get_ref(TextNode::FAMILY)
.get(TextNode::FAMILY)
.iter()
.map(|family| family.as_str())
.chain(tail.iter().copied())
@ -609,7 +609,7 @@ fn tags(styles: StyleChain) -> Vec<Feature> {
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, ..))
}

View File

@ -13,7 +13,7 @@ use typst::eval::{Smart, StyleMap, Value};
use typst::frame::{Element, Frame};
use typst::geom::{Length, RgbaColor};
use typst::library::layout::PageNode;
use typst::library::text::TextNode;
use typst::library::text::{FontSize, TextNode};
use typst::loading::FsLoader;
use typst::parse::Scanner;
use typst::source::SourceFile;
@ -67,7 +67,7 @@ fn main() {
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::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.
let mut std = typst::library::new();