mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Style chains
This commit is contained in:
parent
5d5d8a21cf
commit
f174134aa2
@ -47,7 +47,7 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
|||||||
mod #module {
|
mod #module {
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use crate::eval::{Property, StyleId};
|
use crate::eval::{Nonfolding, Property, StyleId};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#impl_block
|
#impl_block
|
||||||
@ -106,17 +106,24 @@ fn process_const(
|
|||||||
// initialization value of the const.
|
// initialization value of the const.
|
||||||
let default = &item.expr;
|
let default = &item.expr;
|
||||||
|
|
||||||
|
let mut folder = None;
|
||||||
|
let mut nonfolding = Some(quote! {
|
||||||
|
impl<T: 'static> Nonfolding for Key<T> {}
|
||||||
|
});
|
||||||
|
|
||||||
// Look for a folding function like `#[fold(u64::add)]`.
|
// Look for a folding function like `#[fold(u64::add)]`.
|
||||||
let mut combinator = None;
|
|
||||||
for attr in &item.attrs {
|
for attr in &item.attrs {
|
||||||
if attr.path.is_ident("fold") {
|
if attr.path.is_ident("fold") {
|
||||||
let fold: syn::Expr = attr.parse_args()?;
|
let func: syn::Expr = attr.parse_args()?;
|
||||||
combinator = Some(quote! {
|
folder = Some(quote! {
|
||||||
fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
const FOLDABLE: bool = true;
|
||||||
let f: fn(Self::Value, Self::Value) -> Self::Value = #fold;
|
|
||||||
|
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
||||||
|
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
|
||||||
f(inner, outer)
|
f(inner, outer)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
nonfolding = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,8 +143,10 @@ fn process_const(
|
|||||||
&*LAZY
|
&*LAZY
|
||||||
}
|
}
|
||||||
|
|
||||||
#combinator
|
#folder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nonfolding
|
||||||
};
|
};
|
||||||
|
|
||||||
// The module that will contain the `Key` type.
|
// The module that will contain the `Key` type.
|
||||||
|
@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{Args, EvalContext, Node, Styles};
|
use super::{Args, EvalContext, Node, StyleMap};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ impl Class {
|
|||||||
/// This parses both property and data arguments (in this order) and styles
|
/// This parses both property and data arguments (in this order) and styles
|
||||||
/// the node constructed from the data with the style properties.
|
/// the node constructed from the data with the style properties.
|
||||||
pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
let mut styles = Styles::new();
|
let mut styles = StyleMap::new();
|
||||||
self.set(args, &mut styles)?;
|
self.set(args, &mut styles)?;
|
||||||
let node = self.0.shim.construct(ctx, args)?;
|
let node = self.0.shim.construct(ctx, args)?;
|
||||||
Ok(node.styled(styles))
|
Ok(node.styled(styles))
|
||||||
@ -76,7 +76,7 @@ impl Class {
|
|||||||
///
|
///
|
||||||
/// This parses property arguments and writes the resulting styles into the
|
/// This parses property arguments and writes the resulting styles into the
|
||||||
/// given style map. There are no further side effects.
|
/// given style map. There are no further side effects.
|
||||||
pub fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
self.0.shim.set(args, styles)
|
self.0.shim.set(args, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,14 +113,14 @@ pub trait Construct {
|
|||||||
pub trait Set {
|
pub trait Set {
|
||||||
/// Parse the arguments and insert style properties of this class into the
|
/// Parse the arguments and insert style properties of this class into the
|
||||||
/// given style map.
|
/// given style map.
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()>;
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewires the operations available on a class in an object-safe way. This is
|
/// Rewires the operations available on a class in an object-safe way. This is
|
||||||
/// only implemented by the zero-sized `Shim` struct.
|
/// only implemented by the zero-sized `Shim` struct.
|
||||||
trait Bounds {
|
trait Bounds {
|
||||||
fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
|
fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
|
||||||
fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()>;
|
fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Shim<T>(PhantomData<T>);
|
struct Shim<T>(PhantomData<T>);
|
||||||
@ -133,7 +133,7 @@ where
|
|||||||
T::construct(ctx, args)
|
T::construct(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
T::set(args, styles)
|
T::set(args, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ pub struct EvalContext<'a> {
|
|||||||
/// The active scopes.
|
/// The active scopes.
|
||||||
pub scopes: Scopes<'a>,
|
pub scopes: Scopes<'a>,
|
||||||
/// The active styles.
|
/// The active styles.
|
||||||
pub styles: Styles,
|
pub styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EvalContext<'a> {
|
impl<'a> EvalContext<'a> {
|
||||||
@ -99,7 +99,7 @@ impl<'a> EvalContext<'a> {
|
|||||||
route: vec![source],
|
route: vec![source],
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
scopes: Scopes::new(Some(&ctx.std)),
|
scopes: Scopes::new(Some(&ctx.std)),
|
||||||
styles: Styles::new(),
|
styles: StyleMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::iter::Sum;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
use super::Styles;
|
use super::StyleMap;
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::SpecAxis;
|
use crate::geom::SpecAxis;
|
||||||
use crate::layout::{Layout, PackedNode, RootNode};
|
use crate::layout::{Layout, PackedNode, RootNode};
|
||||||
@ -63,7 +63,7 @@ pub enum Node {
|
|||||||
/// nesting doesn't hurt since we can just recurse into the nested sequences
|
/// nesting doesn't hurt since we can just recurse into the nested sequences
|
||||||
/// during packing. Also, in theory, this allows better complexity when
|
/// during packing. Also, in theory, this allows better complexity when
|
||||||
/// adding (large) sequence nodes (just like for a text rope).
|
/// adding (large) sequence nodes (just like for a text rope).
|
||||||
Sequence(Vec<(Self, Styles)>),
|
Sequence(Vec<(Self, StyleMap)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@ -89,7 +89,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Style this node.
|
/// Style this node.
|
||||||
pub fn styled(self, styles: Styles) -> Self {
|
pub fn styled(self, styles: StyleMap) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Inline(inline) => Self::Inline(inline.styled(styles)),
|
Self::Inline(inline) => Self::Inline(inline.styled(styles)),
|
||||||
Self::Block(block) => Self::Block(block.styled(styles)),
|
Self::Block(block) => Self::Block(block.styled(styles)),
|
||||||
@ -100,7 +100,7 @@ impl Node {
|
|||||||
|
|
||||||
/// Style this node in monospace.
|
/// Style this node in monospace.
|
||||||
pub fn monospaced(self) -> Self {
|
pub fn monospaced(self) -> Self {
|
||||||
self.styled(Styles::one(TextNode::MONOSPACE, true))
|
self.styled(StyleMap::with(TextNode::MONOSPACE, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lift to a type-erased block-level node.
|
/// Lift to a type-erased block-level node.
|
||||||
@ -109,7 +109,7 @@ impl Node {
|
|||||||
packed
|
packed
|
||||||
} else {
|
} else {
|
||||||
let mut packer = Packer::new(false);
|
let mut packer = Packer::new(false);
|
||||||
packer.walk(self, Styles::new());
|
packer.walk(self, StyleMap::new());
|
||||||
packer.into_block()
|
packer.into_block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ impl Node {
|
|||||||
/// Lift to a root layout tree node.
|
/// Lift to a root layout tree node.
|
||||||
pub fn into_root(self) -> RootNode {
|
pub fn into_root(self) -> RootNode {
|
||||||
let mut packer = Packer::new(true);
|
let mut packer = Packer::new(true);
|
||||||
packer.walk(self, Styles::new());
|
packer.walk(self, StyleMap::new());
|
||||||
packer.into_root()
|
packer.into_root()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ impl Node {
|
|||||||
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
||||||
|
|
||||||
// TODO(style): Make more efficient.
|
// TODO(style): Make more efficient.
|
||||||
Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count]))
|
Ok(Self::Sequence(vec![(self.clone(), StyleMap::new()); count]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ impl Add for Node {
|
|||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
// TODO(style): Make more efficient.
|
// TODO(style): Make more efficient.
|
||||||
Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())])
|
Self::Sequence(vec![(self, StyleMap::new()), (rhs, StyleMap::new())])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ impl AddAssign for Node {
|
|||||||
|
|
||||||
impl Sum for Node {
|
impl Sum for Node {
|
||||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||||
Self::Sequence(iter.map(|n| (n, Styles::new())).collect())
|
Self::Sequence(iter.map(|n| (n, StyleMap::new())).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ impl Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Consider a node with the given styles.
|
/// Consider a node with the given styles.
|
||||||
fn walk(&mut self, node: Node, styles: Styles) {
|
fn walk(&mut self, node: Node, styles: StyleMap) {
|
||||||
match node {
|
match node {
|
||||||
Node::Space => {
|
Node::Space => {
|
||||||
// A text space is "soft", meaning that it can be eaten up by
|
// A text space is "soft", meaning that it can be eaten up by
|
||||||
@ -321,7 +321,7 @@ impl Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Advance to the next paragraph.
|
/// Advance to the next paragraph.
|
||||||
fn parbreak(&mut self, break_styles: Option<Styles>) {
|
fn parbreak(&mut self, break_styles: Option<StyleMap>) {
|
||||||
// Erase any styles that will be inherited anyway.
|
// Erase any styles that will be inherited anyway.
|
||||||
let Builder { mut children, styles, .. } = mem::take(&mut self.par);
|
let Builder { mut children, styles, .. } = mem::take(&mut self.par);
|
||||||
for child in &mut children {
|
for child in &mut children {
|
||||||
@ -366,7 +366,7 @@ impl Packer {
|
|||||||
|
|
||||||
/// Break to a new paragraph if the `styles` contain paragraph styles that
|
/// Break to a new paragraph if the `styles` contain paragraph styles that
|
||||||
/// are incompatible with the current paragraph.
|
/// are incompatible with the current paragraph.
|
||||||
fn make_par_compatible(&mut self, styles: &Styles) {
|
fn make_par_compatible(&mut self, styles: &StyleMap) {
|
||||||
if self.par.children.is_empty() {
|
if self.par.children.is_empty() {
|
||||||
self.par.styles = styles.clone();
|
self.par.styles = styles.clone();
|
||||||
return;
|
return;
|
||||||
@ -383,7 +383,7 @@ impl Packer {
|
|||||||
|
|
||||||
/// Break to a new page if the `styles` contain page styles that are
|
/// Break to a new page if the `styles` contain page styles that are
|
||||||
/// incompatible with the current flow.
|
/// incompatible with the current flow.
|
||||||
fn make_flow_compatible(&mut self, styles: &Styles) {
|
fn make_flow_compatible(&mut self, styles: &StyleMap) {
|
||||||
if self.flow.children.is_empty() && self.par.children.is_empty() {
|
if self.flow.children.is_empty() && self.par.children.is_empty() {
|
||||||
self.flow.styles = styles.clone();
|
self.flow.styles = styles.clone();
|
||||||
return;
|
return;
|
||||||
@ -402,7 +402,7 @@ impl Packer {
|
|||||||
/// Container for building a flow or paragraph.
|
/// Container for building a flow or paragraph.
|
||||||
struct Builder<T> {
|
struct Builder<T> {
|
||||||
/// The intersection of the style properties of all `children`.
|
/// The intersection of the style properties of all `children`.
|
||||||
styles: Styles,
|
styles: StyleMap,
|
||||||
/// The accumulated flow or paragraph children.
|
/// The accumulated flow or paragraph children.
|
||||||
children: Vec<T>,
|
children: Vec<T>,
|
||||||
/// The kind of thing that was last added.
|
/// The kind of thing that was last added.
|
||||||
@ -412,7 +412,7 @@ struct Builder<T> {
|
|||||||
impl<T> Default for Builder<T> {
|
impl<T> Default for Builder<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
styles: Styles::new(),
|
styles: StyleMap::new(),
|
||||||
children: vec![],
|
children: vec![],
|
||||||
last: Last::None,
|
last: Last::None,
|
||||||
}
|
}
|
||||||
|
@ -10,23 +10,21 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone, Hash)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct Styles {
|
pub struct StyleMap(Vec<Entry>);
|
||||||
map: Vec<(StyleId, Entry)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Styles {
|
impl StyleMap {
|
||||||
/// Create a new, empty style map.
|
/// Create a new, empty style map.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { map: vec![] }
|
Self(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this map contains no styles.
|
/// Whether this map contains no styles.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.map.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a style map with a single property-value pair.
|
/// Create a style map from a single property-value pair.
|
||||||
pub fn one<P: Property>(key: P, value: P::Value) -> Self {
|
pub fn with<P: Property>(key: P, value: P::Value) -> Self {
|
||||||
let mut styles = Self::new();
|
let mut styles = Self::new();
|
||||||
styles.set(key, value);
|
styles.set(key, value);
|
||||||
styles
|
styles
|
||||||
@ -34,17 +32,16 @@ impl Styles {
|
|||||||
|
|
||||||
/// Set the value for a style property.
|
/// Set the value for a style property.
|
||||||
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
|
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
|
||||||
let id = StyleId::of::<P>();
|
for entry in &mut self.0 {
|
||||||
for pair in &mut self.map {
|
if entry.is::<P>() {
|
||||||
if pair.0 == id {
|
let prev = entry.downcast::<P::Value>().unwrap();
|
||||||
let prev = pair.1.downcast::<P::Value>().unwrap();
|
let folded = P::fold(value, prev.clone());
|
||||||
let folded = P::combine(value, prev.clone());
|
*entry = Entry::new(key, folded);
|
||||||
pair.1 = Entry::new(key, folded);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.map.push((id, Entry::new(key, value)));
|
self.0.push(Entry::new(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a value for a style property if it is `Some(_)`.
|
/// Set a value for a style property if it is `Some(_)`.
|
||||||
@ -54,81 +51,57 @@ impl Styles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle a boolean style property.
|
/// Toggle a boolean style property, removing it if it exists and inserting
|
||||||
|
/// it with `true` if it doesn't.
|
||||||
pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) {
|
pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) {
|
||||||
let id = StyleId::of::<P>();
|
for (i, entry) in self.0.iter_mut().enumerate() {
|
||||||
for (i, pair) in self.map.iter_mut().enumerate() {
|
if entry.is::<P>() {
|
||||||
if pair.0 == id {
|
self.0.swap_remove(i);
|
||||||
self.map.swap_remove(i);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.map.push((id, Entry::new(key, true)));
|
self.0.push(Entry::new(key, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a copyable style property.
|
/// Make `self` the first link of the style chain `outer`.
|
||||||
///
|
///
|
||||||
/// Returns the property's default value if the map does not contain an
|
/// The resulting style chain contains styles from `self` as well as
|
||||||
/// entry for it.
|
/// `outer`. The ones from `self` take precedence over the ones from
|
||||||
pub fn get<P: Property>(&self, key: P) -> P::Value
|
/// `outer`. For folded properties `self` contributes the inner value.
|
||||||
where
|
pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> {
|
||||||
P::Value: Copy,
|
if self.is_empty() {
|
||||||
{
|
// No need to chain an empty map.
|
||||||
self.get_direct(key)
|
*outer
|
||||||
.map(|&v| P::combine(v, P::default()))
|
} else {
|
||||||
.unwrap_or_else(P::default)
|
StyleChain { inner: self, outer: Some(outer) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a style property.
|
/// Apply styles from `outer` in-place. The resulting style map is
|
||||||
///
|
/// equivalent to the style chain created by
|
||||||
/// Returns a reference to the property's default value if the map does not
|
/// `self.chain(StyleChain::new(outer))`.
|
||||||
/// contain an entry for it.
|
|
||||||
pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
|
|
||||||
self.get_direct(key).unwrap_or_else(|| P::default_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to a style directly in this map (no default value).
|
|
||||||
fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> {
|
|
||||||
self.map
|
|
||||||
.iter()
|
|
||||||
.find(|pair| pair.0 == StyleId::of::<P>())
|
|
||||||
.and_then(|pair| pair.1.downcast())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new styles combining `self` with `outer`.
|
|
||||||
///
|
|
||||||
/// Properties from `self` take precedence over the ones from `outer`.
|
|
||||||
pub fn chain(&self, outer: &Self) -> Self {
|
|
||||||
let mut styles = self.clone();
|
|
||||||
styles.apply(outer);
|
|
||||||
styles
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply styles from `outer` in-place.
|
|
||||||
///
|
|
||||||
/// Properties from `self` take precedence over the ones from `outer`.
|
|
||||||
pub fn apply(&mut self, outer: &Self) {
|
pub fn apply(&mut self, outer: &Self) {
|
||||||
'outer: for pair in &outer.map {
|
'outer: for outer in &outer.0 {
|
||||||
for (id, entry) in &mut self.map {
|
for inner in &mut self.0 {
|
||||||
if pair.0 == *id {
|
if inner.style_id() == outer.style_id() {
|
||||||
entry.apply(&pair.1);
|
inner.fold(outer);
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.map.push(pair.clone());
|
self.0.push(outer.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keep only those styles that are not also in `other`.
|
/// Keep only those styles that are not also in `other`.
|
||||||
pub fn erase(&mut self, other: &Self) {
|
pub fn erase(&mut self, other: &Self) {
|
||||||
self.map.retain(|a| other.map.iter().all(|b| a != b));
|
self.0.retain(|a| other.0.iter().all(|b| a != b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keep only those styles that are also in `other`.
|
/// Keep only those styles that are also in `other`.
|
||||||
pub fn intersect(&mut self, other: &Self) {
|
pub fn intersect(&mut self, other: &Self) {
|
||||||
self.map.retain(|a| other.map.iter().any(|b| a == b));
|
self.0.retain(|a| other.0.iter().any(|b| a == b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether two style maps are equal when filtered down to the given
|
/// Whether two style maps are equal when filtered down to the given
|
||||||
@ -137,113 +110,136 @@ impl Styles {
|
|||||||
where
|
where
|
||||||
F: Fn(StyleId) -> bool,
|
F: Fn(StyleId) -> bool,
|
||||||
{
|
{
|
||||||
// TODO(style): Filtered length + one direction equal should suffice.
|
let f = |entry: &&Entry| filter(entry.style_id());
|
||||||
let f = |e: &&(StyleId, Entry)| filter(e.0);
|
self.0.iter().filter(f).count() == other.0.iter().filter(f).count()
|
||||||
self.map.iter().filter(f).all(|pair| other.map.contains(pair))
|
&& self.0.iter().filter(f).all(|pair| other.0.contains(pair))
|
||||||
&& other.map.iter().filter(f).all(|pair| self.map.contains(pair))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Styles {
|
impl Debug for StyleMap {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
for entry in &self.0 {
|
||||||
for pair in &self.map {
|
writeln!(f, "{:#?}", entry)?;
|
||||||
writeln!(f, "{:#?}", pair.1)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
f.write_str("Styles ")?;
|
|
||||||
f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Styles {
|
impl PartialEq for StyleMap {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.compatible(other, |_| true)
|
self.compatible(other, |_| true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entry for a single style property.
|
/// A chain of style maps, similar to a linked list.
|
||||||
#[derive(Clone)]
|
///
|
||||||
pub(crate) struct Entry(Rc<dyn Bounds>);
|
/// A style chain allows to conceptually merge (and fold) properties from
|
||||||
|
/// multiple style maps in a node hierarchy in a non-allocating way. Rather than
|
||||||
|
/// eagerly merging the maps, each access walks the hierarchy from the innermost
|
||||||
|
/// to the outermost map, trying to find a match and then folding it with
|
||||||
|
/// matches further up the chain.
|
||||||
|
#[derive(Clone, Copy, Hash)]
|
||||||
|
pub struct StyleChain<'a> {
|
||||||
|
inner: &'a StyleMap,
|
||||||
|
outer: Option<&'a Self>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl<'a> StyleChain<'a> {
|
||||||
fn new<P: Property>(key: P, value: P::Value) -> Self {
|
/// Start a new style chain with a root map.
|
||||||
Self(Rc::new((key, value)))
|
pub fn new(map: &'a StyleMap) -> Self {
|
||||||
|
Self { inner: map, outer: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downcast<T: 'static>(&self) -> Option<&T> {
|
/// Get the (folded) value of a copyable style property.
|
||||||
self.0.as_any().downcast_ref()
|
///
|
||||||
|
/// Returns the property's default value if no map in the chain contains an
|
||||||
|
/// entry for it.
|
||||||
|
pub fn get<P>(self, key: P) -> P::Value
|
||||||
|
where
|
||||||
|
P: Property,
|
||||||
|
P::Value: Copy,
|
||||||
|
{
|
||||||
|
// This exists separately to `get_cloned` for `Copy` types so that
|
||||||
|
// people don't just naively use `get` / `get_cloned` where they should
|
||||||
|
// use `get_ref`.
|
||||||
|
self.get_cloned(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply(&mut self, outer: &Self) {
|
/// Get a reference to a style property's value.
|
||||||
*self = self.0.combine(outer);
|
///
|
||||||
|
/// This is naturally only possible for properties that don't need folding.
|
||||||
|
/// Prefer `get` if possible or resort to `get_cloned` for non-`Copy`
|
||||||
|
/// properties that need folding.
|
||||||
|
///
|
||||||
|
/// Returns a reference to the property's default value if no map in the
|
||||||
|
/// chain contains an entry for it.
|
||||||
|
pub fn get_ref<P>(self, key: P) -> &'a P::Value
|
||||||
|
where
|
||||||
|
P: Property + Nonfolding,
|
||||||
|
{
|
||||||
|
if let Some(value) = self.get_locally(key) {
|
||||||
|
value
|
||||||
|
} else if let Some(outer) = self.outer {
|
||||||
|
outer.get_ref(key)
|
||||||
|
} else {
|
||||||
|
P::default_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the (folded) value of any style property.
|
||||||
|
///
|
||||||
|
/// While this works for all properties, you should prefer `get` or
|
||||||
|
/// `get_ref` where possible. This is only needed for non-`Copy` properties
|
||||||
|
/// that need folding.
|
||||||
|
pub fn get_cloned<P>(self, key: P) -> P::Value
|
||||||
|
where
|
||||||
|
P: Property,
|
||||||
|
{
|
||||||
|
if let Some(value) = self.get_locally(key).cloned() {
|
||||||
|
if P::FOLDABLE {
|
||||||
|
if let Some(outer) = self.outer {
|
||||||
|
P::fold(value, outer.get_cloned(key))
|
||||||
|
} else {
|
||||||
|
P::fold(value, P::default())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
} else if let Some(outer) = self.outer {
|
||||||
|
outer.get_cloned(key)
|
||||||
|
} else {
|
||||||
|
P::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a property directly in the most local map.
|
||||||
|
fn get_locally<P: Property>(&self, _: P) -> Option<&'a P::Value> {
|
||||||
|
self.inner
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.is::<P>())
|
||||||
|
.and_then(|entry| entry.downcast())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Entry {
|
impl Debug for StyleChain<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.0.dyn_fmt(f)
|
self.inner.fmt(f)?;
|
||||||
}
|
if let Some(outer) = self.outer {
|
||||||
}
|
outer.fmt(f)?;
|
||||||
|
|
||||||
impl PartialEq for Entry {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.dyn_eq(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Entry {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u64(self.0.hash64());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Bounds: 'static {
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
|
|
||||||
fn dyn_eq(&self, other: &Entry) -> bool;
|
|
||||||
fn hash64(&self) -> u64;
|
|
||||||
fn combine(&self, outer: &Entry) -> Entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `P` is always zero-sized. We only implement the trait for a pair of key and
|
|
||||||
// associated value so that `P` is a constrained type parameter that we can use
|
|
||||||
// in `dyn_fmt` to access the property's name. This way, we can effectively
|
|
||||||
// store the property's name in its vtable instead of having an actual runtime
|
|
||||||
// string somewhere in `Entry`.
|
|
||||||
impl<P: Property> Bounds for (P, P::Value) {
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
&self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
if f.alternate() {
|
|
||||||
write!(f, "#[{} = {:?}]", P::NAME, self.1)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}: {:?}", P::NAME, self.1)
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dyn_eq(&self, other: &Entry) -> bool {
|
/// A unique identifier for a style property.
|
||||||
if let Some(other) = other.downcast::<P::Value>() {
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
&self.1 == other
|
pub struct StyleId(TypeId);
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash64(&self) -> u64 {
|
impl StyleId {
|
||||||
// No need to hash the TypeId since there's only one
|
/// The style id of the property.
|
||||||
// valid value type per property.
|
pub fn of<P: Property>() -> Self {
|
||||||
fxhash::hash64(&self.1)
|
Self(TypeId::of::<P>())
|
||||||
}
|
|
||||||
|
|
||||||
fn combine(&self, outer: &Entry) -> Entry {
|
|
||||||
let outer = outer.downcast::<P::Value>().unwrap();
|
|
||||||
let combined = P::combine(self.1.clone(), outer.clone());
|
|
||||||
Entry::new(self.0, combined)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,23 +266,111 @@ pub trait Property: Copy + 'static {
|
|||||||
/// recreated all the time.
|
/// recreated all the time.
|
||||||
fn default_ref() -> &'static Self::Value;
|
fn default_ref() -> &'static Self::Value;
|
||||||
|
|
||||||
|
/// Whether the property needs folding.
|
||||||
|
const FOLDABLE: bool = false;
|
||||||
|
|
||||||
/// Fold the property with an outer value.
|
/// Fold the property with an outer value.
|
||||||
///
|
///
|
||||||
/// For example, this would combine a relative font size with an outer
|
/// For example, this would fold a relative font size with an outer
|
||||||
/// absolute font size.
|
/// absolute font size.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
|
||||||
inner
|
inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a style property.
|
/// Marker trait that indicates that a property doesn't need folding.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
pub trait Nonfolding {}
|
||||||
pub struct StyleId(TypeId);
|
|
||||||
|
|
||||||
impl StyleId {
|
/// An entry for a single style property.
|
||||||
/// The style id of the property.
|
#[derive(Clone)]
|
||||||
pub fn of<P: Property>() -> Self {
|
struct Entry(Rc<dyn Bounds>);
|
||||||
Self(TypeId::of::<P>())
|
|
||||||
|
impl Entry {
|
||||||
|
fn new<P: Property>(key: P, value: P::Value) -> Self {
|
||||||
|
Self(Rc::new((key, value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_id(&self) -> StyleId {
|
||||||
|
self.0.style_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is<P: Property>(&self) -> bool {
|
||||||
|
self.style_id() == StyleId::of::<P>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.0.as_any().downcast_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold(&mut self, outer: &Self) {
|
||||||
|
*self = self.0.fold(outer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Entry {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.0.dyn_fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Entry {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.dyn_eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Entry {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_u64(self.0.hash64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait is implemented for pairs of zero-sized property keys and their
|
||||||
|
/// value types below. Although it is zero-sized, the property `P` must be part
|
||||||
|
/// of the implementing type so that we can use it in the methods (it must be a
|
||||||
|
/// constrained type parameter).
|
||||||
|
trait Bounds: 'static {
|
||||||
|
fn style_id(&self) -> StyleId;
|
||||||
|
fn fold(&self, outer: &Entry) -> Entry;
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
|
||||||
|
fn dyn_eq(&self, other: &Entry) -> bool;
|
||||||
|
fn hash64(&self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Property> Bounds for (P, P::Value) {
|
||||||
|
fn style_id(&self) -> StyleId {
|
||||||
|
StyleId::of::<P>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold(&self, outer: &Entry) -> Entry {
|
||||||
|
let outer = outer.downcast::<P::Value>().unwrap();
|
||||||
|
let combined = P::fold(self.1.clone(), outer.clone());
|
||||||
|
Entry::new(self.0, combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "#[{} = {:?}]", P::NAME, self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_eq(&self, other: &Entry) -> bool {
|
||||||
|
self.style_id() == other.style_id()
|
||||||
|
&& if let Some(other) = other.downcast::<P::Value>() {
|
||||||
|
&self.1 == other
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash64(&self) -> u64 {
|
||||||
|
let mut state = fxhash::FxHasher64::default();
|
||||||
|
self.style_id().hash(&mut state);
|
||||||
|
self.1.hash(&mut state);
|
||||||
|
state.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::eval::Styles;
|
use crate::eval::{StyleChain, StyleMap};
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform};
|
use crate::geom::{Align, Linear, Point, Sides, Size, Spec, Transform};
|
||||||
@ -30,8 +30,8 @@ pub struct RootNode(pub Vec<PageNode>);
|
|||||||
impl RootNode {
|
impl RootNode {
|
||||||
/// Layout the document into a sequence of frames, one per page.
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
pub fn layout(&self, ctx: &mut Context) -> Vec<Rc<Frame>> {
|
pub fn layout(&self, ctx: &mut Context) -> Vec<Rc<Frame>> {
|
||||||
let mut ctx = LayoutContext::new(ctx);
|
let (mut ctx, styles) = LayoutContext::new(ctx);
|
||||||
self.0.iter().flat_map(|node| node.layout(&mut ctx)).collect()
|
self.0.iter().flat_map(|node| node.layout(&mut ctx, styles)).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +52,7 @@ pub trait Layout {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>>;
|
) -> Vec<Constrained<Rc<Frame>>>;
|
||||||
|
|
||||||
/// Convert to a packed node.
|
/// Convert to a packed node.
|
||||||
@ -63,7 +64,7 @@ pub trait Layout {
|
|||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
hash: self.hash64(),
|
hash: self.hash64(),
|
||||||
node: Rc::new(self),
|
node: Rc::new(self),
|
||||||
styles: Styles::new(),
|
styles: StyleMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,26 +78,23 @@ pub struct LayoutContext<'a> {
|
|||||||
/// Caches layouting artifacts.
|
/// Caches layouting artifacts.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub layouts: &'a mut LayoutCache,
|
pub layouts: &'a mut LayoutCache,
|
||||||
/// The inherited style properties.
|
|
||||||
// TODO(style): This probably shouldn't be here.
|
|
||||||
pub styles: Styles,
|
|
||||||
/// How deeply nested the current layout tree position is.
|
/// How deeply nested the current layout tree position is.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub level: usize,
|
level: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayoutContext<'a> {
|
impl<'a> LayoutContext<'a> {
|
||||||
/// Create a new layout context.
|
/// Create a new layout context.
|
||||||
pub fn new(ctx: &'a mut Context) -> Self {
|
fn new(ctx: &'a mut Context) -> (Self, StyleChain<'a>) {
|
||||||
Self {
|
let this = Self {
|
||||||
fonts: &mut ctx.fonts,
|
fonts: &mut ctx.fonts,
|
||||||
images: &mut ctx.images,
|
images: &mut ctx.images,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
layouts: &mut ctx.layouts,
|
layouts: &mut ctx.layouts,
|
||||||
styles: ctx.styles.clone(),
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
level: 0,
|
level: 0,
|
||||||
}
|
};
|
||||||
|
(this, StyleChain::new(&ctx.styles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +109,7 @@ impl Layout for EmptyNode {
|
|||||||
&self,
|
&self,
|
||||||
_: &mut LayoutContext,
|
_: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
_: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let size = regions.expand.select(regions.current, Size::zero());
|
let size = regions.expand.select(regions.current, Size::zero());
|
||||||
let mut cts = Constraints::new(regions.expand);
|
let mut cts = Constraints::new(regions.expand);
|
||||||
@ -128,7 +127,7 @@ pub struct PackedNode {
|
|||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
hash: u64,
|
hash: u64,
|
||||||
/// The node's styles.
|
/// The node's styles.
|
||||||
pub styles: Styles,
|
pub styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackedNode {
|
impl PackedNode {
|
||||||
@ -146,7 +145,7 @@ impl PackedNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Style the node with styles from a style map.
|
/// Style the node with styles from a style map.
|
||||||
pub fn styled(mut self, styles: Styles) -> Self {
|
pub fn styled(mut self, styles: StyleMap) -> Self {
|
||||||
if self.styles.is_empty() {
|
if self.styles.is_empty() {
|
||||||
self.styles = styles;
|
self.styles = styles;
|
||||||
} else {
|
} else {
|
||||||
@ -206,22 +205,25 @@ impl Layout for PackedNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
let chained = self.styles.chain(&styles);
|
||||||
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
return self.layout_impl(ctx, regions);
|
return self.node.layout(ctx, regions, chained);
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
let hash = {
|
let hash = {
|
||||||
let mut state = fxhash::FxHasher64::default();
|
let mut state = fxhash::FxHasher64::default();
|
||||||
self.hash(&mut state);
|
self.hash(&mut state);
|
||||||
ctx.styles.hash(&mut state);
|
styles.hash(&mut state);
|
||||||
state.finish()
|
state.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
ctx.layouts.get(hash, regions).unwrap_or_else(|| {
|
ctx.layouts.get(hash, regions).unwrap_or_else(|| {
|
||||||
ctx.level += 1;
|
ctx.level += 1;
|
||||||
let frames = self.layout_impl(ctx, regions);
|
let frames = self.node.layout(ctx, regions, chained);
|
||||||
ctx.level -= 1;
|
ctx.level -= 1;
|
||||||
|
|
||||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||||
@ -250,21 +252,6 @@ impl Layout for PackedNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackedNode {
|
|
||||||
/// Layout the node without checking the cache.
|
|
||||||
fn layout_impl(
|
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
let new = self.styles.chain(&ctx.styles);
|
|
||||||
let prev = std::mem::replace(&mut ctx.styles, new);
|
|
||||||
let frames = self.node.layout(ctx, regions);
|
|
||||||
ctx.styles = prev;
|
|
||||||
frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PackedNode {
|
impl Default for PackedNode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
EmptyNode.pack()
|
EmptyNode.pack()
|
||||||
|
10
src/lib.rs
10
src/lib.rs
@ -55,7 +55,7 @@ pub mod syntax;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Eval, EvalContext, Module, Scope, Styles};
|
use crate::eval::{Eval, EvalContext, Module, Scope, StyleMap};
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
@ -80,7 +80,7 @@ pub struct Context {
|
|||||||
/// The standard library scope.
|
/// The standard library scope.
|
||||||
std: Scope,
|
std: Scope,
|
||||||
/// The default styles.
|
/// The default styles.
|
||||||
styles: Styles,
|
styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -100,7 +100,7 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A read-only reference to the styles.
|
/// A read-only reference to the styles.
|
||||||
pub fn styles(&self) -> &Styles {
|
pub fn styles(&self) -> &StyleMap {
|
||||||
&self.styles
|
&self.styles
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ impl Context {
|
|||||||
/// This struct is created by [`Context::builder`].
|
/// This struct is created by [`Context::builder`].
|
||||||
pub struct ContextBuilder {
|
pub struct ContextBuilder {
|
||||||
std: Option<Scope>,
|
std: Option<Scope>,
|
||||||
styles: Option<Styles>,
|
styles: Option<StyleMap>,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
policy: EvictionPolicy,
|
policy: EvictionPolicy,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
@ -156,7 +156,7 @@ impl ContextBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The default properties for page size, font selection and so on.
|
/// The default properties for page size, font selection and so on.
|
||||||
pub fn styles(mut self, styles: Styles) -> Self {
|
pub fn styles(mut self, styles: StyleMap) -> Self {
|
||||||
self.styles = Some(styles);
|
self.styles = Some(styles);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
let aligns: Spec<_> = args.find().unwrap_or_default();
|
let aligns: Spec<_> = args.find().unwrap_or_default();
|
||||||
let body: PackedNode = args.expect("body")?;
|
let body: PackedNode = args.expect("body")?;
|
||||||
|
|
||||||
let mut styles = Styles::new();
|
let mut styles = StyleMap::new();
|
||||||
if let Some(align) = aligns.x {
|
if let Some(align) = aligns.x {
|
||||||
styles.set(ParNode::ALIGN, align);
|
styles.set(ParNode::ALIGN, align);
|
||||||
}
|
}
|
||||||
@ -50,13 +50,14 @@ impl Layout for AlignNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// The child only needs to expand along an axis if there's no alignment.
|
// The child only needs to expand along an axis if there's no alignment.
|
||||||
let mut pod = regions.clone();
|
let mut pod = regions.clone();
|
||||||
pod.expand &= self.aligns.map_is_none();
|
pod.expand &= self.aligns.map_is_none();
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod, styles);
|
||||||
|
|
||||||
for ((current, base), Constrained { item: frame, cts }) in
|
for ((current, base), Constrained { item: frame, cts }) in
|
||||||
regions.iter().zip(&mut frames)
|
regions.iter().zip(&mut frames)
|
||||||
|
@ -34,6 +34,7 @@ impl Layout for ColumnsNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let columns = self.columns.get();
|
let columns = self.columns.get();
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ impl Layout for ColumnsNode {
|
|||||||
// much sense. Note that this line assumes that no infinitely wide
|
// much sense. Note that this line assumes that no infinitely wide
|
||||||
// region will follow if the first region's width is finite.
|
// region will follow if the first region's width is finite.
|
||||||
if regions.current.x.is_infinite() {
|
if regions.current.x.is_infinite() {
|
||||||
return self.child.layout(ctx, regions);
|
return self.child.layout(ctx, regions, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gutter width for each region. (Can be different because the relative
|
// Gutter width for each region. (Can be different because the relative
|
||||||
@ -81,9 +82,9 @@ impl Layout for ColumnsNode {
|
|||||||
pod.backlog = sizes.into_iter();
|
pod.backlog = sizes.into_iter();
|
||||||
|
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
let mut frames = self.child.layout(ctx, &pod).into_iter();
|
let mut frames = self.child.layout(ctx, &pod, styles).into_iter();
|
||||||
|
|
||||||
let dir = ctx.styles.get(ParNode::DIR);
|
let dir = styles.get(ParNode::DIR);
|
||||||
let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
|
let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
|
||||||
|
|
||||||
// Stitch together the columns for each region.
|
// Stitch together the columns for each region.
|
||||||
|
@ -17,8 +17,9 @@ impl Layout for FlowNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
FlowLayouter::new(self, regions.clone()).layout(ctx)
|
FlowLayouter::new(self, regions.clone(), styles).layout(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,14 +38,14 @@ pub enum FlowChild {
|
|||||||
/// An arbitrary node.
|
/// An arbitrary node.
|
||||||
Node(PackedNode),
|
Node(PackedNode),
|
||||||
/// A paragraph/block break.
|
/// A paragraph/block break.
|
||||||
Break(Styles),
|
Break(StyleMap),
|
||||||
/// Skip the rest of the region and move to the next.
|
/// Skip the rest of the region and move to the next.
|
||||||
Skip,
|
Skip,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlowChild {
|
impl FlowChild {
|
||||||
/// A reference to the child's styles.
|
/// A reference to the child's styles.
|
||||||
pub fn styles(&self) -> Option<&Styles> {
|
pub fn styles(&self) -> Option<&StyleMap> {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(node) => Some(&node.styles),
|
Self::Spacing(node) => Some(&node.styles),
|
||||||
Self::Node(node) => Some(&node.styles),
|
Self::Node(node) => Some(&node.styles),
|
||||||
@ -54,7 +55,7 @@ impl FlowChild {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A mutable reference to the child's styles.
|
/// A mutable reference to the child's styles.
|
||||||
pub fn styles_mut(&mut self) -> Option<&mut Styles> {
|
pub fn styles_mut(&mut self) -> Option<&mut StyleMap> {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(node) => Some(&mut node.styles),
|
Self::Spacing(node) => Some(&mut node.styles),
|
||||||
Self::Node(node) => Some(&mut node.styles),
|
Self::Node(node) => Some(&mut node.styles),
|
||||||
@ -86,6 +87,8 @@ struct FlowLayouter<'a> {
|
|||||||
children: &'a [FlowChild],
|
children: &'a [FlowChild],
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
|
/// The inherited styles.
|
||||||
|
styles: StyleChain<'a>,
|
||||||
/// Whether the flow should expand to fill the region.
|
/// Whether the flow should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The full size of `regions.current` that was available before we started
|
/// The full size of `regions.current` that was available before we started
|
||||||
@ -115,7 +118,7 @@ enum FlowItem {
|
|||||||
|
|
||||||
impl<'a> FlowLayouter<'a> {
|
impl<'a> FlowLayouter<'a> {
|
||||||
/// Create a new flow layouter.
|
/// Create a new flow layouter.
|
||||||
fn new(flow: &'a FlowNode, mut regions: Regions) -> Self {
|
fn new(flow: &'a FlowNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.current;
|
let full = regions.current;
|
||||||
|
|
||||||
@ -125,6 +128,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
Self {
|
Self {
|
||||||
children: &flow.0,
|
children: &flow.0,
|
||||||
regions,
|
regions,
|
||||||
|
styles,
|
||||||
expand,
|
expand,
|
||||||
full,
|
full,
|
||||||
used: Size::zero(),
|
used: Size::zero(),
|
||||||
@ -149,7 +153,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
self.layout_node(ctx, node);
|
self.layout_node(ctx, node);
|
||||||
}
|
}
|
||||||
FlowChild::Break(styles) => {
|
FlowChild::Break(styles) => {
|
||||||
let chain = styles.chain(&ctx.styles);
|
let chain = styles.chain(&self.styles);
|
||||||
let em = chain.get(TextNode::SIZE).abs;
|
let em = chain.get(TextNode::SIZE).abs;
|
||||||
let amount = chain.get(ParNode::SPACING).resolve(em);
|
let amount = chain.get(ParNode::SPACING).resolve(em);
|
||||||
self.layout_absolute(amount.into());
|
self.layout_absolute(amount.into());
|
||||||
@ -191,7 +195,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = node.downcast::<PlacedNode>() {
|
if let Some(placed) = node.downcast::<PlacedNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
let frame = node.layout(ctx, &self.regions).remove(0);
|
let frame = node.layout(ctx, &self.regions, self.styles).remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame.item));
|
self.items.push(FlowItem::Placed(frame.item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -202,7 +206,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// For non-expanding paragraphs it is crucial that we align the
|
// For non-expanding paragraphs it is crucial that we align the
|
||||||
// whole paragraph according to its internal alignment.
|
// whole paragraph according to its internal alignment.
|
||||||
if node.is::<ParNode>() {
|
if node.is::<ParNode>() {
|
||||||
node.styles.chain(&ctx.styles).get(ParNode::ALIGN)
|
node.styles.chain(&self.styles).get(ParNode::ALIGN)
|
||||||
} else {
|
} else {
|
||||||
Align::Left
|
Align::Left
|
||||||
},
|
},
|
||||||
@ -212,7 +216,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions);
|
let frames = node.layout(ctx, &self.regions, self.styles);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
|
@ -35,9 +35,10 @@ impl Layout for GridNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let mut layouter = GridLayouter::new(self, regions.clone());
|
let mut layouter = GridLayouter::new(self, regions.clone(), styles);
|
||||||
|
|
||||||
// Determine all column sizes.
|
// Determine all column sizes.
|
||||||
layouter.measure_columns(ctx);
|
layouter.measure_columns(ctx);
|
||||||
@ -93,6 +94,8 @@ struct GridLayouter<'a> {
|
|||||||
rows: Vec<TrackSizing>,
|
rows: Vec<TrackSizing>,
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
|
/// The inherited styles.
|
||||||
|
styles: StyleChain<'a>,
|
||||||
/// Resolved column sizes.
|
/// Resolved column sizes.
|
||||||
rcols: Vec<Length>,
|
rcols: Vec<Length>,
|
||||||
/// Rows in the current region.
|
/// Rows in the current region.
|
||||||
@ -123,7 +126,7 @@ enum Row {
|
|||||||
|
|
||||||
impl<'a> GridLayouter<'a> {
|
impl<'a> GridLayouter<'a> {
|
||||||
/// Prepare grid layout by unifying content and gutter tracks.
|
/// Prepare grid layout by unifying content and gutter tracks.
|
||||||
fn new(grid: &'a GridNode, mut regions: Regions) -> Self {
|
fn new(grid: &'a GridNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
|
|
||||||
@ -175,6 +178,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
cols,
|
cols,
|
||||||
rows,
|
rows,
|
||||||
regions,
|
regions,
|
||||||
|
styles,
|
||||||
rcols,
|
rcols,
|
||||||
lrows,
|
lrows,
|
||||||
expand,
|
expand,
|
||||||
@ -296,7 +300,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pod.base.y = v.resolve(self.regions.base.y);
|
pod.base.y = v.resolve(self.regions.base.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = node.layout(ctx, &pod).remove(0).item;
|
let frame = node.layout(ctx, &pod, self.styles).remove(0).item;
|
||||||
resolved.set_max(frame.size.x);
|
resolved.set_max(frame.size.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,8 +391,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pod.base.x = self.regions.base.x;
|
pod.base.x = self.regions.base.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sizes =
|
let mut sizes = node
|
||||||
node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.y);
|
.layout(ctx, &pod, self.styles)
|
||||||
|
.into_iter()
|
||||||
|
.map(|frame| frame.item.size.y);
|
||||||
|
|
||||||
// For each region, we want to know the maximum height any
|
// For each region, we want to know the maximum height any
|
||||||
// column requires.
|
// column requires.
|
||||||
@ -479,7 +485,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.select(self.regions.base, size);
|
.select(self.regions.base, size);
|
||||||
|
|
||||||
let pod = Regions::one(size, base, Spec::splat(true));
|
let pod = Regions::one(size, base, Spec::splat(true));
|
||||||
let frame = node.layout(ctx, &pod).remove(0);
|
let frame = node.layout(ctx, &pod, self.styles).remove(0);
|
||||||
output.push_frame(pos, frame.item);
|
output.push_frame(pos, frame.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,7 +528,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the layouted frames into the individual output frames.
|
// Push the layouted frames into the individual output frames.
|
||||||
let frames = node.layout(ctx, &pod);
|
let frames = node.layout(ctx, &pod, self.styles);
|
||||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||||
output.push_frame(pos, frame.item);
|
output.push_frame(pos, frame.item);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ impl Construct for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Set for HeadingNode {
|
impl Set for HeadingNode {
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
styles.set_opt(Self::FAMILY, args.named("family")?);
|
styles.set_opt(Self::FAMILY, args.named("family")?);
|
||||||
styles.set_opt(Self::FILL, args.named("fill")?);
|
styles.set_opt(Self::FILL, args.named("fill")?);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -44,23 +44,28 @@ impl Layout for HeadingNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
|
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
|
||||||
ctx.styles.set(TextNode::STRONG, true);
|
|
||||||
ctx.styles.set(TextNode::SIZE, Relative::new(upscale).into());
|
|
||||||
|
|
||||||
if let Smart::Custom(family) = ctx.styles.get_ref(Self::FAMILY) {
|
let mut local = StyleMap::new();
|
||||||
let list = std::iter::once(family)
|
local.set(TextNode::STRONG, true);
|
||||||
.chain(ctx.styles.get_ref(TextNode::FAMILY_LIST))
|
local.set(TextNode::SIZE, Relative::new(upscale).into());
|
||||||
.cloned()
|
|
||||||
.collect();
|
if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) {
|
||||||
ctx.styles.set(TextNode::FAMILY_LIST, list);
|
local.set(
|
||||||
|
TextNode::FAMILY_LIST,
|
||||||
|
std::iter::once(family)
|
||||||
|
.chain(styles.get_ref(TextNode::FAMILY_LIST))
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Smart::Custom(fill) = ctx.styles.get(Self::FILL) {
|
if let Smart::Custom(fill) = styles.get(Self::FILL) {
|
||||||
ctx.styles.set(TextNode::FILL, fill);
|
local.set(TextNode::FILL, fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.child.layout(ctx, regions)
|
self.child.layout(ctx, regions, local.chain(&styles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ impl Layout for ImageNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let img = ctx.images.get(self.id);
|
let img = ctx.images.get(self.id);
|
||||||
let pxw = img.width() as f64;
|
let pxw = img.width() as f64;
|
||||||
@ -89,7 +90,7 @@ impl Layout for ImageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = ctx.styles.get_ref(LinkNode::URL) {
|
if let Some(url) = styles.get_ref(LinkNode::URL) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(Value::Node(
|
Ok(Value::Node(
|
||||||
body.styled(Styles::one(LinkNode::URL, Some(url))),
|
body.styled(StyleMap::with(LinkNode::URL, Some(url))),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ impl<L: Labelling> Construct for ListNode<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<L: Labelling> Set for ListNode<L> {
|
impl<L: Labelling> Set for ListNode<L> {
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?);
|
styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?);
|
||||||
styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?);
|
styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -44,10 +44,11 @@ impl<L: Labelling> Layout for ListNode<L> {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let em = ctx.styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE).abs;
|
||||||
let label_indent = ctx.styles.get(Self::LABEL_INDENT).resolve(em);
|
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
|
||||||
let body_indent = ctx.styles.get(Self::BODY_INDENT).resolve(em);
|
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
|
||||||
|
|
||||||
let columns = vec![
|
let columns = vec![
|
||||||
TrackSizing::Linear(label_indent.into()),
|
TrackSizing::Linear(label_indent.into()),
|
||||||
@ -68,7 +69,7 @@ impl<L: Labelling> Layout for ListNode<L> {
|
|||||||
gutter: Spec::default(),
|
gutter: Spec::default(),
|
||||||
children,
|
children,
|
||||||
}
|
}
|
||||||
.layout(ctx, regions)
|
.layout(ctx, regions, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ prelude! {
|
|||||||
|
|
||||||
pub use crate::diag::{At, TypResult};
|
pub use crate::diag::{At, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value,
|
Args, Construct, EvalContext, Node, Property, Set, Smart, StyleChain, StyleMap,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -34,10 +34,11 @@ impl Layout for PadNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let pod = regions.map(|size| shrink(size, self.padding));
|
let pod = regions.map(|size| shrink(size, self.padding));
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod, styles);
|
||||||
|
|
||||||
for ((current, base), Constrained { item: frame, cts }) in
|
for ((current, base), Constrained { item: frame, cts }) in
|
||||||
regions.iter().zip(&mut frames)
|
regions.iter().zip(&mut frames)
|
||||||
|
@ -12,7 +12,7 @@ pub struct PageNode {
|
|||||||
/// The node producing the content.
|
/// The node producing the content.
|
||||||
pub child: PackedNode,
|
pub child: PackedNode,
|
||||||
/// The page's styles.
|
/// The page's styles.
|
||||||
pub styles: Styles,
|
pub styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[properties]
|
#[properties]
|
||||||
@ -44,14 +44,14 @@ impl PageNode {
|
|||||||
impl Construct for PageNode {
|
impl Construct for PageNode {
|
||||||
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
||||||
Ok(Node::Page(Self {
|
Ok(Node::Page(Self {
|
||||||
styles: Styles::new(),
|
|
||||||
child: args.expect("body")?,
|
child: args.expect("body")?,
|
||||||
|
styles: StyleMap::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Set for PageNode {
|
impl Set for PageNode {
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
|
||||||
styles.set(Self::CLASS, paper.class());
|
styles.set(Self::CLASS, paper.class());
|
||||||
styles.set(Self::WIDTH, Smart::Custom(paper.width()));
|
styles.set(Self::WIDTH, Smart::Custom(paper.width()));
|
||||||
@ -85,41 +85,40 @@ impl Set for PageNode {
|
|||||||
|
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
/// Style the node with styles from a style map.
|
/// Style the node with styles from a style map.
|
||||||
pub fn styled(mut self, styles: Styles) -> Self {
|
pub fn styled(mut self, styles: StyleMap) -> Self {
|
||||||
self.styles.apply(&styles);
|
self.styles.apply(&styles);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the page run into a sequence of frames, one per page.
|
/// Layout the page run into a sequence of frames, one per page.
|
||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
pub fn layout(&self, ctx: &mut LayoutContext, styles: StyleChain) -> Vec<Rc<Frame>> {
|
||||||
let prev = ctx.styles.clone();
|
let styles = self.styles.chain(&styles);
|
||||||
ctx.styles = self.styles.chain(&ctx.styles);
|
|
||||||
|
|
||||||
// When one of the lengths is infinite the page fits its content along
|
// When one of the lengths is infinite the page fits its content along
|
||||||
// that axis.
|
// that axis.
|
||||||
let width = ctx.styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||||
let height = ctx.styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
||||||
let mut size = Size::new(width, height);
|
let mut size = Size::new(width, height);
|
||||||
if ctx.styles.get(Self::FLIPPED) {
|
if styles.get(Self::FLIPPED) {
|
||||||
std::mem::swap(&mut size.x, &mut size.y);
|
std::mem::swap(&mut size.x, &mut size.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the margins.
|
// Determine the margins.
|
||||||
let class = ctx.styles.get(Self::CLASS);
|
let class = styles.get(Self::CLASS);
|
||||||
let default = class.default_margins();
|
let default = class.default_margins();
|
||||||
let padding = Sides {
|
let padding = Sides {
|
||||||
left: ctx.styles.get(Self::LEFT).unwrap_or(default.left),
|
left: styles.get(Self::LEFT).unwrap_or(default.left),
|
||||||
right: ctx.styles.get(Self::RIGHT).unwrap_or(default.right),
|
right: styles.get(Self::RIGHT).unwrap_or(default.right),
|
||||||
top: ctx.styles.get(Self::TOP).unwrap_or(default.top),
|
top: styles.get(Self::TOP).unwrap_or(default.top),
|
||||||
bottom: ctx.styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Realize columns with columns node.
|
// Realize columns with columns node.
|
||||||
let columns = ctx.styles.get(Self::COLUMNS);
|
let columns = styles.get(Self::COLUMNS);
|
||||||
let child = if columns.get() > 1 {
|
let child = if columns.get() > 1 {
|
||||||
ColumnsNode {
|
ColumnsNode {
|
||||||
columns,
|
columns,
|
||||||
gutter: ctx.styles.get(Self::COLUMN_GUTTER),
|
gutter: styles.get(Self::COLUMN_GUTTER),
|
||||||
child: self.child.clone(),
|
child: self.child.clone(),
|
||||||
}
|
}
|
||||||
.pack()
|
.pack()
|
||||||
@ -133,18 +132,20 @@ impl PageNode {
|
|||||||
// Layout the child.
|
// Layout the child.
|
||||||
let expand = size.map(Length::is_finite);
|
let expand = size.map(Length::is_finite);
|
||||||
let regions = Regions::repeat(size, size, expand);
|
let regions = Regions::repeat(size, size, expand);
|
||||||
let mut frames: Vec<_> =
|
let mut frames: Vec<_> = child
|
||||||
child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
.layout(ctx, ®ions, styles)
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.item)
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Add background fill if requested.
|
// Add background fill if requested.
|
||||||
if let Some(fill) = ctx.styles.get(Self::FILL) {
|
if let Some(fill) = styles.get(Self::FILL) {
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let shape = Shape::filled(Geometry::Rect(frame.size), fill);
|
let shape = Shape::filled(Geometry::Rect(frame.size), fill);
|
||||||
Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.styles = prev;
|
|
||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ impl Construct for ParNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Set for ParNode {
|
impl Set for ParNode {
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
let spacing = args.named("spacing")?;
|
let spacing = args.named("spacing")?;
|
||||||
let leading = args.named("leading")?;
|
let leading = args.named("leading")?;
|
||||||
|
|
||||||
@ -78,17 +78,18 @@ impl Layout for ParNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// Collect all text into one string used for BiDi analysis.
|
// Collect all text into one string used for BiDi analysis.
|
||||||
let text = self.collect_text();
|
let text = self.collect_text();
|
||||||
|
|
||||||
// Find out the BiDi embedding levels.
|
// Find out the BiDi embedding levels.
|
||||||
let level = Level::from_dir(ctx.styles.get(Self::DIR));
|
let level = Level::from_dir(styles.get(Self::DIR));
|
||||||
let bidi = BidiInfo::new(&text, level);
|
let bidi = BidiInfo::new(&text, level);
|
||||||
|
|
||||||
// Prepare paragraph layout by building a representation on which we can
|
// Prepare paragraph layout by building a representation on which we can
|
||||||
// do line breaking without layouting each and every line from scratch.
|
// do line breaking without layouting each and every line from scratch.
|
||||||
let layouter = ParLayouter::new(self, ctx, regions, bidi);
|
let layouter = ParLayouter::new(self, ctx, regions, &styles, bidi);
|
||||||
|
|
||||||
// Find suitable linebreaks.
|
// Find suitable linebreaks.
|
||||||
layouter.layout(ctx, regions.clone())
|
layouter.layout(ctx, regions.clone())
|
||||||
@ -147,12 +148,12 @@ pub enum ParChild {
|
|||||||
|
|
||||||
impl ParChild {
|
impl ParChild {
|
||||||
/// Create a text child.
|
/// Create a text child.
|
||||||
pub fn text(text: impl Into<EcoString>, styles: Styles) -> Self {
|
pub fn text(text: impl Into<EcoString>, styles: StyleMap) -> Self {
|
||||||
Self::Text(TextNode { text: text.into(), styles })
|
Self::Text(TextNode { text: text.into(), styles })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to the child's styles.
|
/// A reference to the child's styles.
|
||||||
pub fn styles(&self) -> &Styles {
|
pub fn styles(&self) -> &StyleMap {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(node) => &node.styles,
|
Self::Spacing(node) => &node.styles,
|
||||||
Self::Text(node) => &node.styles,
|
Self::Text(node) => &node.styles,
|
||||||
@ -161,7 +162,7 @@ impl ParChild {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A mutable reference to the child's styles.
|
/// A mutable reference to the child's styles.
|
||||||
pub fn styles_mut(&mut self) -> &mut Styles {
|
pub fn styles_mut(&mut self) -> &mut StyleMap {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(node) => &mut node.styles,
|
Self::Spacing(node) => &mut node.styles,
|
||||||
Self::Text(node) => &mut node.styles,
|
Self::Text(node) => &mut node.styles,
|
||||||
@ -226,6 +227,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
par: &'a ParNode,
|
par: &'a ParNode,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: &'a StyleChain<'a>,
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
@ -253,7 +255,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
cursor += group.len();
|
cursor += group.len();
|
||||||
let subrange = start .. cursor;
|
let subrange = start .. cursor;
|
||||||
let text = &bidi.text[subrange.clone()];
|
let text = &bidi.text[subrange.clone()];
|
||||||
let styles = node.styles.chain(&ctx.styles);
|
let styles = node.styles.chain(styles);
|
||||||
let shaped = shape(ctx.fonts, text, styles, level.dir());
|
let shaped = shape(ctx.fonts, text, styles, level.dir());
|
||||||
items.push(ParItem::Text(shaped));
|
items.push(ParItem::Text(shaped));
|
||||||
ranges.push(subrange);
|
ranges.push(subrange);
|
||||||
@ -262,16 +264,16 @@ impl<'a> ParLayouter<'a> {
|
|||||||
ParChild::Node(node) => {
|
ParChild::Node(node) => {
|
||||||
let size = Size::new(regions.current.x, regions.base.y);
|
let size = Size::new(regions.current.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||||
let frame = node.layout(ctx, &pod).remove(0);
|
let frame = node.layout(ctx, &pod, *styles).remove(0);
|
||||||
items.push(ParItem::Frame(Rc::take(frame.item)));
|
items.push(ParItem::Frame(Rc::take(frame.item)));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let em = ctx.styles.get(TextNode::SIZE).abs;
|
let em = styles.get(TextNode::SIZE).abs;
|
||||||
let align = ctx.styles.get(ParNode::ALIGN);
|
let align = styles.get(ParNode::ALIGN);
|
||||||
let leading = ctx.styles.get(ParNode::LEADING).resolve(em);
|
let leading = styles.get(ParNode::LEADING).resolve(em);
|
||||||
|
|
||||||
Self { align, leading, bidi, items, ranges }
|
Self { align, leading, bidi, items, ranges }
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ impl Layout for PlacedNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let out_of_flow = self.out_of_flow();
|
let out_of_flow = self.out_of_flow();
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ impl Layout for PlacedNode {
|
|||||||
Regions::one(regions.base, regions.base, expand)
|
Regions::one(regions.base, regions.base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.0.layout(ctx, &pod);
|
let mut frames = self.0.layout(ctx, &pod, styles);
|
||||||
let Constrained { item: frame, cts } = &mut frames[0];
|
let Constrained { item: frame, cts } = &mut frames[0];
|
||||||
|
|
||||||
// If expansion is off, zero all sizes so that we don't take up any
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
|
@ -106,11 +106,12 @@ impl Layout for ShapeNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames;
|
let mut frames;
|
||||||
if let Some(child) = &self.child {
|
if let Some(child) = &self.child {
|
||||||
let mut pod = Regions::one(regions.current, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.current, regions.base, regions.expand);
|
||||||
frames = child.layout(ctx, &pod);
|
frames = child.layout(ctx, &pod, styles);
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
// the result is really a square or circle.
|
// the result is really a square or circle.
|
||||||
@ -126,7 +127,7 @@ impl Layout for ShapeNode {
|
|||||||
|
|
||||||
pod.current = Size::splat(length);
|
pod.current = Size::splat(length);
|
||||||
pod.expand = Spec::splat(true);
|
pod.expand = Spec::splat(true);
|
||||||
frames = child.layout(ctx, &pod);
|
frames = child.layout(ctx, &pod, styles);
|
||||||
frames[0].cts = Constraints::tight(regions);
|
frames[0].cts = Constraints::tight(regions);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -169,7 +170,7 @@ impl Layout for ShapeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = ctx.styles.get_ref(LinkNode::URL) {
|
if let Some(url) = styles.get_ref(LinkNode::URL) {
|
||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ impl Layout for SizedNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let is_auto = self.sizing.map_is_none();
|
let is_auto = self.sizing.map_is_none();
|
||||||
let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
|
let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
|
||||||
@ -51,7 +52,7 @@ impl Layout for SizedNode {
|
|||||||
Regions::one(size, base, expand)
|
Regions::one(size, base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.child.layout(ctx, &pod);
|
let mut frames = self.child.layout(ctx, &pod, styles);
|
||||||
let Constrained { item: frame, cts } = &mut frames[0];
|
let Constrained { item: frame, cts } = &mut frames[0];
|
||||||
|
|
||||||
// Ensure frame size matches regions size if expansion is on.
|
// Ensure frame size matches regions size if expansion is on.
|
||||||
|
@ -24,7 +24,7 @@ pub struct SpacingNode {
|
|||||||
/// The kind of spacing.
|
/// The kind of spacing.
|
||||||
pub kind: SpacingKind,
|
pub kind: SpacingKind,
|
||||||
/// The spacing's styles.
|
/// The spacing's styles.
|
||||||
pub styles: Styles,
|
pub styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SpacingNode {
|
impl Debug for SpacingNode {
|
||||||
|
@ -28,8 +28,9 @@ impl Layout for StackNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
StackLayouter::new(self, regions.clone()).layout(ctx)
|
StackLayouter::new(self, regions.clone(), styles).layout(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ pub enum StackChild {
|
|||||||
|
|
||||||
impl From<SpacingKind> for StackChild {
|
impl From<SpacingKind> for StackChild {
|
||||||
fn from(kind: SpacingKind) -> Self {
|
fn from(kind: SpacingKind) -> Self {
|
||||||
Self::Spacing(SpacingNode { kind, styles: Styles::new() })
|
Self::Spacing(SpacingNode { kind, styles: StyleMap::new() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,8 @@ struct StackLayouter<'a> {
|
|||||||
spacing: Option<SpacingKind>,
|
spacing: Option<SpacingKind>,
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
|
/// The inherited styles.
|
||||||
|
styles: StyleChain<'a>,
|
||||||
/// Whether the stack should expand to fill the region.
|
/// Whether the stack should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The full size of `regions.current` that was available before we started
|
/// The full size of `regions.current` that was available before we started
|
||||||
@ -106,7 +109,7 @@ enum StackItem {
|
|||||||
|
|
||||||
impl<'a> StackLayouter<'a> {
|
impl<'a> StackLayouter<'a> {
|
||||||
/// Create a new stack layouter.
|
/// Create a new stack layouter.
|
||||||
fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
|
fn new(stack: &'a StackNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
||||||
let dir = stack.dir;
|
let dir = stack.dir;
|
||||||
let axis = dir.axis();
|
let axis = dir.axis();
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
@ -121,6 +124,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
axis,
|
axis,
|
||||||
spacing: stack.spacing,
|
spacing: stack.spacing,
|
||||||
regions,
|
regions,
|
||||||
|
styles,
|
||||||
expand,
|
expand,
|
||||||
full,
|
full,
|
||||||
used: Gen::zero(),
|
used: Gen::zero(),
|
||||||
@ -190,7 +194,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
.and_then(|node| node.aligns.get(self.axis))
|
.and_then(|node| node.aligns.get(self.axis))
|
||||||
.unwrap_or(self.dir.start().into());
|
.unwrap_or(self.dir.start().into());
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions);
|
let frames = node.layout(ctx, &self.regions, self.styles);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
|
@ -23,7 +23,7 @@ pub struct TextNode {
|
|||||||
/// The run's text.
|
/// The run's text.
|
||||||
pub text: EcoString,
|
pub text: EcoString,
|
||||||
/// The run's styles.
|
/// The run's styles.
|
||||||
pub styles: Styles,
|
pub styles: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[properties]
|
#[properties]
|
||||||
@ -108,7 +108,7 @@ impl Construct for TextNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Set for TextNode {
|
impl Set for TextNode {
|
||||||
fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()> {
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
let list = args.named("family")?.or_else(|| {
|
let list = args.named("family")?.or_else(|| {
|
||||||
let families: Vec<_> = args.all().collect();
|
let families: Vec<_> = args.all().collect();
|
||||||
(!families.is_empty()).then(|| families)
|
(!families.is_empty()).then(|| families)
|
||||||
@ -412,7 +412,7 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
|||||||
let body: Node = args.expect("body")?;
|
let body: Node = args.expect("body")?;
|
||||||
let deco = LineDecoration { kind, stroke, thickness, offset, extent };
|
let deco = LineDecoration { kind, stroke, thickness, offset, extent };
|
||||||
Ok(Value::Node(
|
Ok(Value::Node(
|
||||||
body.styled(Styles::one(TextNode::LINES, vec![deco])),
|
body.styled(StyleMap::with(TextNode::LINES, vec![deco])),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,7 +449,7 @@ pub enum LineKind {
|
|||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
fonts: &mut FontStore,
|
fonts: &mut FontStore,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
styles: Styles,
|
styles: StyleChain<'a>,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
let mut glyphs = vec![];
|
let mut glyphs = vec![];
|
||||||
@ -459,16 +459,16 @@ pub fn shape<'a>(
|
|||||||
&mut glyphs,
|
&mut glyphs,
|
||||||
0,
|
0,
|
||||||
text,
|
text,
|
||||||
variant(&styles),
|
variant(styles),
|
||||||
families(&styles),
|
families(styles),
|
||||||
None,
|
None,
|
||||||
dir,
|
dir,
|
||||||
&tags(&styles),
|
&tags(styles),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
track(&mut glyphs, styles.get(TextNode::TRACKING));
|
track(&mut glyphs, styles.get(TextNode::TRACKING));
|
||||||
let (size, baseline) = measure(fonts, &glyphs, &styles);
|
let (size, baseline) = measure(fonts, &glyphs, styles);
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
text,
|
text,
|
||||||
@ -627,7 +627,7 @@ fn track(glyphs: &mut [ShapedGlyph], tracking: Em) {
|
|||||||
fn measure(
|
fn measure(
|
||||||
fonts: &mut FontStore,
|
fonts: &mut FontStore,
|
||||||
glyphs: &[ShapedGlyph],
|
glyphs: &[ShapedGlyph],
|
||||||
styles: &Styles,
|
styles: StyleChain,
|
||||||
) -> (Size, Length) {
|
) -> (Size, Length) {
|
||||||
let mut width = Length::zero();
|
let mut width = Length::zero();
|
||||||
let mut top = Length::zero();
|
let mut top = Length::zero();
|
||||||
@ -667,7 +667,7 @@ fn measure(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the font variant with `STRONG` and `EMPH` factored in.
|
/// Resolve the font variant with `STRONG` and `EMPH` factored in.
|
||||||
fn variant(styles: &Styles) -> FontVariant {
|
fn variant(styles: StyleChain) -> FontVariant {
|
||||||
let mut variant = FontVariant::new(
|
let mut variant = FontVariant::new(
|
||||||
styles.get(TextNode::STYLE),
|
styles.get(TextNode::STYLE),
|
||||||
styles.get(TextNode::WEIGHT),
|
styles.get(TextNode::WEIGHT),
|
||||||
@ -690,7 +690,7 @@ fn variant(styles: &Styles) -> FontVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a prioritized iterator over the font families.
|
/// Resolve a prioritized iterator over the font families.
|
||||||
fn families(styles: &Styles) -> impl Iterator<Item = &str> + Clone {
|
fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||||
let head = if styles.get(TextNode::MONOSPACE) {
|
let head = if styles.get(TextNode::MONOSPACE) {
|
||||||
styles.get_ref(TextNode::MONOSPACE_LIST).as_slice()
|
styles.get_ref(TextNode::MONOSPACE_LIST).as_slice()
|
||||||
} else {
|
} else {
|
||||||
@ -719,7 +719,7 @@ fn families(styles: &Styles) -> impl Iterator<Item = &str> + Clone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect the tags of the OpenType features to apply.
|
/// Collect the tags of the OpenType features to apply.
|
||||||
fn tags(styles: &Styles) -> Vec<Feature> {
|
fn tags(styles: StyleChain) -> Vec<Feature> {
|
||||||
let mut tags = vec![];
|
let mut tags = vec![];
|
||||||
let mut feat = |tag, value| {
|
let mut feat = |tag, value| {
|
||||||
tags.push(Feature::new(Tag::from_bytes(tag), value, ..));
|
tags.push(Feature::new(Tag::from_bytes(tag), value, ..));
|
||||||
@ -803,8 +803,7 @@ pub struct ShapedText<'a> {
|
|||||||
/// The text direction.
|
/// The text direction.
|
||||||
pub dir: Dir,
|
pub dir: Dir,
|
||||||
/// The text's style properties.
|
/// The text's style properties.
|
||||||
// TODO(style): Go back to reference.
|
pub styles: StyleChain<'a>,
|
||||||
pub styles: Styles,
|
|
||||||
/// The font size.
|
/// The font size.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// The baseline from the top of the frame.
|
/// The baseline from the top of the frame.
|
||||||
@ -858,7 +857,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
frame.push(pos, Element::Text(text));
|
frame.push(pos, Element::Text(text));
|
||||||
|
|
||||||
// Apply line decorations.
|
// Apply line decorations.
|
||||||
for line in self.styles.get_ref(TextNode::LINES) {
|
for line in self.styles.get_cloned(TextNode::LINES) {
|
||||||
let face = fonts.get(face_id);
|
let face = fonts.get(face_id);
|
||||||
let metrics = match line.kind {
|
let metrics = match line.kind {
|
||||||
LineKind::Underline => face.underline,
|
LineKind::Underline => face.underline,
|
||||||
@ -905,7 +904,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
text_range: Range<usize>,
|
text_range: Range<usize>,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
let (size, baseline) = measure(fonts, glyphs, &self.styles);
|
let (size, baseline) = measure(fonts, glyphs, self.styles);
|
||||||
Self {
|
Self {
|
||||||
text: &self.text[text_range],
|
text: &self.text[text_range],
|
||||||
dir: self.dir,
|
dir: self.dir,
|
||||||
|
@ -53,8 +53,9 @@ impl Layout for TransformNode {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions);
|
let mut frames = self.child.layout(ctx, regions, styles);
|
||||||
|
|
||||||
for Constrained { item: frame, .. } in &mut frames {
|
for Constrained { item: frame, .. } in &mut frames {
|
||||||
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||||
|
@ -11,7 +11,7 @@ use usvg::FitTo;
|
|||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use typst::diag::Error;
|
use typst::diag::Error;
|
||||||
use typst::eval::{Smart, Styles, Value};
|
use typst::eval::{Smart, StyleMap, Value};
|
||||||
use typst::font::Face;
|
use typst::font::Face;
|
||||||
use typst::frame::{Element, Frame, Geometry, Shape, Stroke, Text};
|
use typst::frame::{Element, Frame, Geometry, Shape, Stroke, Text};
|
||||||
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Size, Transform};
|
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Size, Transform};
|
||||||
@ -70,7 +70,7 @@ fn main() {
|
|||||||
// Set page width to 120pt with 10pt margins, so that the inner page is
|
// Set page width to 120pt with 10pt margins, so that the inner page is
|
||||||
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
||||||
// that it multiplies to nice round numbers.
|
// that it multiplies to nice round numbers.
|
||||||
let mut styles = Styles::new();
|
let mut styles = StyleMap::new();
|
||||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0)));
|
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0)));
|
||||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||||
styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user