mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Lift styles out of individual nodes
This commit is contained in:
parent
f7e8624b4c
commit
5fd9c0b0d7
@ -1,6 +1,4 @@
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use super::{Args, EvalContext, Node, StyleMap};
|
use super::{Args, EvalContext, Node, StyleMap};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
@ -36,12 +34,10 @@ use crate::util::EcoString;
|
|||||||
/// [`TextNode`]: crate::library::TextNode
|
/// [`TextNode`]: crate::library::TextNode
|
||||||
/// [`set`]: Self::set
|
/// [`set`]: Self::set
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Class(Rc<Inner<dyn Bounds>>);
|
pub struct Class {
|
||||||
|
|
||||||
/// The unsized structure behind the [`Rc`].
|
|
||||||
struct Inner<T: ?Sized> {
|
|
||||||
name: EcoString,
|
name: EcoString,
|
||||||
shim: T,
|
construct: fn(&mut EvalContext, &mut Args) -> TypResult<Node>,
|
||||||
|
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Class {
|
impl Class {
|
||||||
@ -50,15 +46,16 @@ impl Class {
|
|||||||
where
|
where
|
||||||
T: Construct + Set + 'static,
|
T: Construct + Set + 'static,
|
||||||
{
|
{
|
||||||
// By specializing the shim to `T`, its vtable will contain T's
|
Self {
|
||||||
// `Construct` and `Set` impls (through the `Bounds` trait), enabling us
|
name,
|
||||||
// to use them in the class's methods.
|
construct: T::construct,
|
||||||
Self(Rc::new(Inner { name, shim: Shim::<T>(PhantomData) }))
|
set: T::set,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the class.
|
/// The name of the class.
|
||||||
pub fn name(&self) -> &EcoString {
|
pub fn name(&self) -> &EcoString {
|
||||||
&self.0.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an instance of the class.
|
/// Construct an instance of the class.
|
||||||
@ -68,7 +65,7 @@ impl Class {
|
|||||||
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 = StyleMap::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.construct)(ctx, args)?;
|
||||||
Ok(node.styled(styles))
|
Ok(node.styled(styles))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +74,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 StyleMap) -> TypResult<()> {
|
pub fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
||||||
self.0.shim.set(args, styles)
|
(self.set)(args, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,12 +88,7 @@ impl Debug for Class {
|
|||||||
|
|
||||||
impl PartialEq for Class {
|
impl PartialEq for Class {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
// We cast to thin pointers for comparison because we don't want to
|
self.name == other.name
|
||||||
// compare vtables (there can be duplicate vtables across codegen units).
|
|
||||||
std::ptr::eq(
|
|
||||||
Rc::as_ptr(&self.0) as *const (),
|
|
||||||
Rc::as_ptr(&other.0) as *const (),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,25 +107,3 @@ pub trait Set {
|
|||||||
/// given style map.
|
/// given style map.
|
||||||
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
|
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewires the operations available on a class in an object-safe way. This is
|
|
||||||
/// only implemented by the zero-sized `Shim` struct.
|
|
||||||
trait Bounds {
|
|
||||||
fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
|
|
||||||
fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Shim<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T> Bounds for Shim<T>
|
|
||||||
where
|
|
||||||
T: Construct + Set,
|
|
||||||
{
|
|
||||||
fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
|
|
||||||
T::construct(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(&self, args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
|
|
||||||
T::set(args, styles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -171,7 +171,7 @@ impl Eval for Markup {
|
|||||||
let upper = nodes.size_hint().1.unwrap_or_default();
|
let upper = nodes.size_hint().1.unwrap_or_default();
|
||||||
let mut seq = Vec::with_capacity(upper);
|
let mut seq = Vec::with_capacity(upper);
|
||||||
for piece in nodes {
|
for piece in nodes {
|
||||||
seq.push((piece.eval(ctx)?, ctx.styles.clone()));
|
seq.push(Styled::new(piece.eval(ctx)?, ctx.styles.clone()));
|
||||||
}
|
}
|
||||||
ctx.styles = prev;
|
ctx.styles = prev;
|
||||||
Ok(Node::Sequence(seq))
|
Ok(Node::Sequence(seq))
|
||||||
|
113
src/eval/node.rs
113
src/eval/node.rs
@ -5,13 +5,12 @@ use std::iter::Sum;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
use super::StyleMap;
|
use super::{StyleMap, Styled};
|
||||||
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};
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind,
|
FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, TextNode,
|
||||||
SpacingNode, TextNode,
|
|
||||||
};
|
};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -50,20 +49,17 @@ pub enum Node {
|
|||||||
///
|
///
|
||||||
/// For example, the Typst template `[Hi *you!*]` would result in the
|
/// For example, the Typst template `[Hi *you!*]` would result in the
|
||||||
/// sequence:
|
/// sequence:
|
||||||
/// ```ignore
|
/// - `Text("Hi")` with empty style map,
|
||||||
/// Sequence([
|
/// - `Space` with empty style map,
|
||||||
/// (Text("Hi"), {}),
|
/// - `Text("you!")` with `TextNode::STRONG` set to `true`.
|
||||||
/// (Space, {}),
|
///
|
||||||
/// (Text("you!"), { TextNode::STRONG: true }),
|
|
||||||
/// ])
|
|
||||||
/// ```
|
|
||||||
/// A sequence may contain nested sequences (meaning this variant
|
/// A sequence may contain nested sequences (meaning this variant
|
||||||
/// effectively allows nodes to form trees). All nested sequences can
|
/// effectively allows nodes to form trees). All nested sequences can
|
||||||
/// equivalently be represented as a single flat sequence, but allowing
|
/// equivalently be represented as a single flat sequence, but allowing
|
||||||
/// 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, StyleMap)>),
|
Sequence(Vec<Styled<Self>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@ -90,12 +86,7 @@ impl Node {
|
|||||||
|
|
||||||
/// Style this node.
|
/// Style this node.
|
||||||
pub fn styled(self, styles: StyleMap) -> Self {
|
pub fn styled(self, styles: StyleMap) -> Self {
|
||||||
match self {
|
Self::Sequence(vec![Styled::new(self, styles)])
|
||||||
Self::Inline(inline) => Self::Inline(inline.styled(styles)),
|
|
||||||
Self::Block(block) => Self::Block(block.styled(styles)),
|
|
||||||
Self::Page(page) => Self::Page(page.styled(styles)),
|
|
||||||
other => Self::Sequence(vec![(other, styles)]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this node in monospace.
|
/// Style this node in monospace.
|
||||||
@ -127,7 +118,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(), StyleMap::new()); count]))
|
Ok(Self::Sequence(vec![Styled::bare(self.clone()); count]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +133,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, StyleMap::new()), (rhs, StyleMap::new())])
|
Self::Sequence(vec![Styled::bare(self), Styled::bare(rhs)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +145,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, StyleMap::new())).collect())
|
Self::Sequence(iter.map(|n| Styled::bare(n)).collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,11 +154,11 @@ struct Packer {
|
|||||||
/// Whether this packer produces a root node.
|
/// Whether this packer produces a root node.
|
||||||
top: bool,
|
top: bool,
|
||||||
/// The accumulated page nodes.
|
/// The accumulated page nodes.
|
||||||
pages: Vec<PageNode>,
|
pages: Vec<Styled<PageNode>>,
|
||||||
/// The accumulated flow children.
|
/// The accumulated flow children.
|
||||||
flow: Builder<FlowChild>,
|
flow: Builder<Styled<FlowChild>>,
|
||||||
/// The accumulated paragraph children.
|
/// The accumulated paragraph children.
|
||||||
par: Builder<ParChild>,
|
par: Builder<Styled<ParChild>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packer {
|
impl Packer {
|
||||||
@ -199,12 +190,12 @@ impl Packer {
|
|||||||
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
|
||||||
// adjacent line breaks or explicit spacings.
|
// adjacent line breaks or explicit spacings.
|
||||||
self.par.last.soft(ParChild::text(' ', styles));
|
self.par.last.soft(Styled::new(ParChild::text(' '), styles));
|
||||||
}
|
}
|
||||||
Node::Linebreak => {
|
Node::Linebreak => {
|
||||||
// A line break eats up surrounding text spaces.
|
// A line break eats up surrounding text spaces.
|
||||||
self.par.last.hard();
|
self.par.last.hard();
|
||||||
self.push_inline(ParChild::text('\n', styles));
|
self.push_inline(Styled::new(ParChild::text('\n'), styles));
|
||||||
self.par.last.hard();
|
self.par.last.hard();
|
||||||
}
|
}
|
||||||
Node::Parbreak => {
|
Node::Parbreak => {
|
||||||
@ -219,7 +210,7 @@ impl Packer {
|
|||||||
// discards the paragraph break.
|
// discards the paragraph break.
|
||||||
self.parbreak(None);
|
self.parbreak(None);
|
||||||
self.make_flow_compatible(&styles);
|
self.make_flow_compatible(&styles);
|
||||||
self.flow.children.push(FlowChild::Skip);
|
self.flow.children.push(Styled::new(FlowChild::Skip, styles));
|
||||||
self.flow.last.hard();
|
self.flow.last.hard();
|
||||||
}
|
}
|
||||||
Node::Pagebreak => {
|
Node::Pagebreak => {
|
||||||
@ -230,13 +221,13 @@ impl Packer {
|
|||||||
self.flow.styles = styles;
|
self.flow.styles = styles;
|
||||||
}
|
}
|
||||||
Node::Text(text) => {
|
Node::Text(text) => {
|
||||||
self.push_inline(ParChild::text(text, styles));
|
self.push_inline(Styled::new(ParChild::text(text), styles));
|
||||||
}
|
}
|
||||||
Node::Spacing(SpecAxis::Horizontal, kind) => {
|
Node::Spacing(SpecAxis::Horizontal, kind) => {
|
||||||
// Just like a line break, explicit horizontal spacing eats up
|
// Just like a line break, explicit horizontal spacing eats up
|
||||||
// surrounding text spaces.
|
// surrounding text spaces.
|
||||||
self.par.last.hard();
|
self.par.last.hard();
|
||||||
self.push_inline(ParChild::Spacing(SpacingNode { kind, styles }));
|
self.push_inline(Styled::new(ParChild::Spacing(kind), styles));
|
||||||
self.par.last.hard();
|
self.par.last.hard();
|
||||||
}
|
}
|
||||||
Node::Spacing(SpecAxis::Vertical, kind) => {
|
Node::Spacing(SpecAxis::Vertical, kind) => {
|
||||||
@ -244,57 +235,56 @@ impl Packer {
|
|||||||
// discards the paragraph break.
|
// discards the paragraph break.
|
||||||
self.parbreak(None);
|
self.parbreak(None);
|
||||||
self.make_flow_compatible(&styles);
|
self.make_flow_compatible(&styles);
|
||||||
self.flow
|
self.flow.children.push(Styled::new(FlowChild::Spacing(kind), styles));
|
||||||
.children
|
|
||||||
.push(FlowChild::Spacing(SpacingNode { kind, styles }));
|
|
||||||
self.flow.last.hard();
|
self.flow.last.hard();
|
||||||
}
|
}
|
||||||
Node::Inline(inline) => {
|
Node::Inline(inline) => {
|
||||||
self.push_inline(ParChild::Node(inline.styled(styles)));
|
self.push_inline(Styled::new(ParChild::Node(inline), styles));
|
||||||
}
|
}
|
||||||
Node::Block(block) => {
|
Node::Block(block) => {
|
||||||
self.push_block(block.styled(styles));
|
self.push_block(Styled::new(block, styles));
|
||||||
}
|
}
|
||||||
Node::Page(page) => {
|
Node::Page(page) => {
|
||||||
if self.top {
|
if self.top {
|
||||||
self.pagebreak();
|
self.pagebreak();
|
||||||
self.pages.push(page.styled(styles));
|
self.pages.push(Styled::new(page, styles));
|
||||||
} else {
|
} else {
|
||||||
let flow = page.child.styled(page.styles);
|
self.push_block(Styled::new(page.0, styles));
|
||||||
self.push_block(flow.styled(styles));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node::Sequence(list) => {
|
Node::Sequence(list) => {
|
||||||
// For a list of nodes, we apply the list's styles to each node
|
// For a list of nodes, we apply the list's styles to each node
|
||||||
// individually.
|
// individually.
|
||||||
for (node, mut inner) in list {
|
for mut node in list {
|
||||||
inner.apply(&styles);
|
node.map.apply(&styles);
|
||||||
self.walk(node, inner);
|
self.walk(node.item, node.map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert an inline-level element into the current paragraph.
|
/// Insert an inline-level element into the current paragraph.
|
||||||
fn push_inline(&mut self, child: ParChild) {
|
fn push_inline(&mut self, child: Styled<ParChild>) {
|
||||||
if let Some(child) = self.par.last.any() {
|
if let Some(styled) = self.par.last.any() {
|
||||||
self.push_coalescing(child);
|
self.push_coalescing(styled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The node must be both compatible with the current page and the
|
// The node must be both compatible with the current page and the
|
||||||
// current paragraph.
|
// current paragraph.
|
||||||
self.make_flow_compatible(child.styles());
|
self.make_flow_compatible(&child.map);
|
||||||
self.make_par_compatible(child.styles());
|
self.make_par_compatible(&child.map);
|
||||||
self.push_coalescing(child);
|
self.push_coalescing(child);
|
||||||
self.par.last.any();
|
self.par.last.any();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a paragraph child, coalescing text nodes with compatible styles.
|
/// Push a paragraph child, coalescing text nodes with compatible styles.
|
||||||
fn push_coalescing(&mut self, child: ParChild) {
|
fn push_coalescing(&mut self, child: Styled<ParChild>) {
|
||||||
if let ParChild::Text(right) = &child {
|
if let ParChild::Text(right) = &child.item {
|
||||||
if let Some(ParChild::Text(left)) = self.par.children.last_mut() {
|
if let Some(Styled { item: ParChild::Text(left), map }) =
|
||||||
if left.styles.compatible(&right.styles, TextNode::has_property) {
|
self.par.children.last_mut()
|
||||||
left.text.push_str(&right.text);
|
{
|
||||||
|
if child.map.compatible(map, TextNode::has_property) {
|
||||||
|
left.0.push_str(&right.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,13 +294,13 @@ impl Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a block-level element into the current flow.
|
/// Insert a block-level element into the current flow.
|
||||||
fn push_block(&mut self, node: PackedNode) {
|
fn push_block(&mut self, node: Styled<PackedNode>) {
|
||||||
let placed = node.is::<PlacedNode>();
|
let placed = node.item.is::<PlacedNode>();
|
||||||
|
|
||||||
self.parbreak(None);
|
self.parbreak(None);
|
||||||
self.make_flow_compatible(&node.styles);
|
self.make_flow_compatible(&node.map);
|
||||||
self.flow.children.extend(self.flow.last.any());
|
self.flow.children.extend(self.flow.last.any());
|
||||||
self.flow.children.push(FlowChild::Node(node));
|
self.flow.children.push(node.map(FlowChild::Node));
|
||||||
self.parbreak(None);
|
self.parbreak(None);
|
||||||
|
|
||||||
// Prevent paragraph spacing between the placed node and the paragraph
|
// Prevent paragraph spacing between the placed node and the paragraph
|
||||||
@ -324,8 +314,8 @@ impl Packer {
|
|||||||
fn parbreak(&mut self, break_styles: Option<StyleMap>) {
|
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 Styled { map, .. } in &mut children {
|
||||||
child.styles_mut().erase(&styles);
|
map.erase(&styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For explicit paragraph breaks, `break_styles` is already `Some(_)`.
|
// For explicit paragraph breaks, `break_styles` is already `Some(_)`.
|
||||||
@ -338,13 +328,13 @@ impl Packer {
|
|||||||
// The paragraph's children are all compatible with the page, so the
|
// The paragraph's children are all compatible with the page, so the
|
||||||
// paragraph is too, meaning we don't need to check or intersect
|
// paragraph is too, meaning we don't need to check or intersect
|
||||||
// anything here.
|
// anything here.
|
||||||
let par = ParNode(children).pack().styled(styles);
|
let par = ParNode(children).pack();
|
||||||
self.flow.children.extend(self.flow.last.any());
|
self.flow.children.extend(self.flow.last.any());
|
||||||
self.flow.children.push(FlowChild::Node(par));
|
self.flow.children.push(Styled::new(FlowChild::Node(par), styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert paragraph spacing.
|
// Insert paragraph spacing.
|
||||||
self.flow.last.soft(FlowChild::Break(break_styles));
|
self.flow.last.soft(Styled::new(FlowChild::Break, break_styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advance to the next page.
|
/// Advance to the next page.
|
||||||
@ -354,13 +344,12 @@ impl Packer {
|
|||||||
|
|
||||||
// Take the flow and erase any styles that will be inherited anyway.
|
// Take the flow and erase any styles that will be inherited anyway.
|
||||||
let Builder { mut children, styles, .. } = mem::take(&mut self.flow);
|
let Builder { mut children, styles, .. } = mem::take(&mut self.flow);
|
||||||
for local in children.iter_mut().filter_map(FlowChild::styles_mut) {
|
for Styled { map, .. } in &mut children {
|
||||||
local.erase(&styles);
|
map.erase(&styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
let flow = FlowNode(children).pack();
|
let flow = FlowNode(children).pack();
|
||||||
let page = PageNode { child: flow, styles };
|
self.pages.push(Styled::new(PageNode(flow), styles));
|
||||||
self.pages.push(page);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,42 @@ use std::rc::Rc;
|
|||||||
// - Store map in `Option` to make empty maps non-allocating
|
// - Store map in `Option` to make empty maps non-allocating
|
||||||
// - Store small properties inline
|
// - Store small properties inline
|
||||||
|
|
||||||
|
/// An item with associated styles.
|
||||||
|
#[derive(PartialEq, Clone, Hash)]
|
||||||
|
pub struct Styled<T> {
|
||||||
|
/// The item to apply styles to.
|
||||||
|
pub item: T,
|
||||||
|
/// The associated style map.
|
||||||
|
pub map: StyleMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Styled<T> {
|
||||||
|
/// Create a new instance from an item and a style map.
|
||||||
|
pub fn new(item: T, map: StyleMap) -> Self {
|
||||||
|
Self { item, map }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new instance with empty style map.
|
||||||
|
pub fn bare(item: T) -> Self {
|
||||||
|
Self { item, map: StyleMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map the item with `f`.
|
||||||
|
pub fn map<F, U>(self, f: F) -> Styled<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(T) -> U,
|
||||||
|
{
|
||||||
|
Styled { item: f(self.item), map: self.map }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for Styled<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
self.map.fmt(f)?;
|
||||||
|
self.item.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone, Hash)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct StyleMap(Vec<Entry>);
|
pub struct StyleMap(Vec<Entry>);
|
||||||
|
@ -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::{StyleChain, StyleMap};
|
use crate::eval::{StyleChain, Styled};
|
||||||
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};
|
||||||
@ -25,13 +25,16 @@ use crate::Context;
|
|||||||
|
|
||||||
/// The root layout node, a document consisting of top-level page runs.
|
/// The root layout node, a document consisting of top-level page runs.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct RootNode(pub Vec<PageNode>);
|
pub struct RootNode(pub Vec<Styled<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, styles) = LayoutContext::new(ctx);
|
let (mut ctx, styles) = LayoutContext::new(ctx);
|
||||||
self.0.iter().flat_map(|node| node.layout(&mut ctx, styles)).collect()
|
self.0
|
||||||
|
.iter()
|
||||||
|
.flat_map(|styled| styled.item.layout(&mut ctx, styled.map.chain(&styles)))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +67,6 @@ 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: StyleMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +120,7 @@ impl Layout for EmptyNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A packed layouting node with style properties and a precomputed hash.
|
/// A packed layouting node with a precomputed hash.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PackedNode {
|
pub struct PackedNode {
|
||||||
/// The type-erased node.
|
/// The type-erased node.
|
||||||
@ -126,8 +128,6 @@ pub struct PackedNode {
|
|||||||
/// A precomputed hash for the node.
|
/// A precomputed hash for the node.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
hash: u64,
|
hash: u64,
|
||||||
/// The node's styles.
|
|
||||||
pub styles: StyleMap,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackedNode {
|
impl PackedNode {
|
||||||
@ -144,16 +144,6 @@ impl PackedNode {
|
|||||||
self.node.as_any().downcast_ref()
|
self.node.as_any().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style the node with styles from a style map.
|
|
||||||
pub fn styled(mut self, styles: StyleMap) -> Self {
|
|
||||||
if self.styles.is_empty() {
|
|
||||||
self.styles = styles;
|
|
||||||
} else {
|
|
||||||
self.styles.apply(&styles);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Force a size for this node.
|
/// Force a size for this node.
|
||||||
pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self {
|
pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self {
|
||||||
if sizing.any(Option::is_some) {
|
if sizing.any(Option::is_some) {
|
||||||
@ -207,10 +197,8 @@ impl Layout for PackedNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
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.node.layout(ctx, regions, chained);
|
return self.node.layout(ctx, regions, styles);
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
let hash = {
|
let hash = {
|
||||||
@ -223,7 +211,7 @@ impl Layout for PackedNode {
|
|||||||
#[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.node.layout(ctx, regions, chained);
|
let frames = self.node.layout(ctx, regions, styles);
|
||||||
ctx.level -= 1;
|
ctx.level -= 1;
|
||||||
|
|
||||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||||
@ -260,9 +248,6 @@ impl Default for PackedNode {
|
|||||||
|
|
||||||
impl Debug for PackedNode {
|
impl Debug for PackedNode {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
|
||||||
self.styles.fmt(f)?;
|
|
||||||
}
|
|
||||||
self.node.fmt(f)
|
self.node.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,9 +268,6 @@ impl Hash for PackedNode {
|
|||||||
state.write_u64(self.hash);
|
state.write_u64(self.hash);
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
state.write_u64(self.hash64());
|
state.write_u64(self.hash64());
|
||||||
|
|
||||||
// Hash the styles.
|
|
||||||
self.styles.hash(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, ParNode, PlacedNode, SpacingKind, SpacingNode, TextNode};
|
use super::{AlignNode, ParNode, PlacedNode, SpacingKind, TextNode};
|
||||||
|
|
||||||
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
/// A vertical flow of content consisting of paragraphs and other layout nodes.
|
||||||
///
|
///
|
||||||
/// This node is reponsible for layouting both the top-level content flow and
|
/// This node is reponsible for layouting both the top-level content flow and
|
||||||
/// the contents of boxes.
|
/// the contents of boxes.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct FlowNode(pub Vec<FlowChild>);
|
pub struct FlowNode(pub Vec<Styled<FlowChild>>);
|
||||||
|
|
||||||
impl Layout for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
@ -19,7 +19,7 @@ impl Layout for FlowNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
FlowLayouter::new(self, regions.clone(), styles).layout(ctx)
|
FlowLayouter::new(self, regions.clone()).layout(ctx, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,50 +33,23 @@ impl Debug for FlowNode {
|
|||||||
/// A child of a flow node.
|
/// A child of a flow node.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum FlowChild {
|
pub enum FlowChild {
|
||||||
/// Vertical spacing between other children.
|
|
||||||
Spacing(SpacingNode),
|
|
||||||
/// An arbitrary node.
|
|
||||||
Node(PackedNode),
|
|
||||||
/// A paragraph/block break.
|
/// A paragraph/block break.
|
||||||
Break(StyleMap),
|
Break,
|
||||||
/// Skip the rest of the region and move to the next.
|
/// Skip the rest of the region and move to the next.
|
||||||
Skip,
|
Skip,
|
||||||
}
|
/// Vertical spacing between other children.
|
||||||
|
Spacing(SpacingKind),
|
||||||
impl FlowChild {
|
/// An arbitrary node.
|
||||||
/// A reference to the child's styles.
|
Node(PackedNode),
|
||||||
pub fn styles(&self) -> Option<&StyleMap> {
|
|
||||||
match self {
|
|
||||||
Self::Spacing(node) => Some(&node.styles),
|
|
||||||
Self::Node(node) => Some(&node.styles),
|
|
||||||
Self::Break(styles) => Some(styles),
|
|
||||||
Self::Skip => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mutable reference to the child's styles.
|
|
||||||
pub fn styles_mut(&mut self) -> Option<&mut StyleMap> {
|
|
||||||
match self {
|
|
||||||
Self::Spacing(node) => Some(&mut node.styles),
|
|
||||||
Self::Node(node) => Some(&mut node.styles),
|
|
||||||
Self::Break(styles) => Some(styles),
|
|
||||||
Self::Skip => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FlowChild {
|
impl Debug for FlowChild {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Break => f.pad("Break"),
|
||||||
|
Self::Skip => f.pad("Skip"),
|
||||||
Self::Spacing(node) => node.fmt(f),
|
Self::Spacing(node) => node.fmt(f),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Node(node) => node.fmt(f),
|
||||||
Self::Break(styles) => {
|
|
||||||
if f.alternate() {
|
|
||||||
styles.fmt(f)?;
|
|
||||||
}
|
|
||||||
write!(f, "Break")
|
|
||||||
}
|
|
||||||
Self::Skip => f.pad("Skip"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,11 +57,9 @@ impl Debug for FlowChild {
|
|||||||
/// Performs flow layout.
|
/// Performs flow layout.
|
||||||
struct FlowLayouter<'a> {
|
struct FlowLayouter<'a> {
|
||||||
/// The flow node to layout.
|
/// The flow node to layout.
|
||||||
children: &'a [FlowChild],
|
children: &'a [Styled<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
|
||||||
@ -118,7 +89,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, styles: StyleChain<'a>) -> Self {
|
fn new(flow: &'a FlowNode, mut regions: Regions) -> Self {
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.current;
|
let full = regions.current;
|
||||||
|
|
||||||
@ -128,7 +99,6 @@ 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(),
|
||||||
@ -139,28 +109,32 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout all children.
|
/// Layout all children.
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
fn layout(
|
||||||
for child in self.children {
|
mut self,
|
||||||
match child {
|
ctx: &mut LayoutContext,
|
||||||
FlowChild::Spacing(node) => {
|
styles: StyleChain,
|
||||||
self.layout_spacing(node.kind);
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
}
|
for styled in self.children {
|
||||||
FlowChild::Node(node) => {
|
let styles = styled.map.chain(&styles);
|
||||||
if self.regions.is_full() {
|
match styled.item {
|
||||||
self.finish_region();
|
FlowChild::Break => {
|
||||||
}
|
let em = styles.get(TextNode::SIZE).abs;
|
||||||
|
let amount = styles.get(ParNode::SPACING).resolve(em);
|
||||||
self.layout_node(ctx, node);
|
|
||||||
}
|
|
||||||
FlowChild::Break(styles) => {
|
|
||||||
let chain = styles.chain(&self.styles);
|
|
||||||
let em = chain.get(TextNode::SIZE).abs;
|
|
||||||
let amount = chain.get(ParNode::SPACING).resolve(em);
|
|
||||||
self.layout_absolute(amount.into());
|
self.layout_absolute(amount.into());
|
||||||
}
|
}
|
||||||
FlowChild::Skip => {
|
FlowChild::Skip => {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
}
|
}
|
||||||
|
FlowChild::Spacing(kind) => {
|
||||||
|
self.layout_spacing(kind);
|
||||||
|
}
|
||||||
|
FlowChild::Node(ref node) => {
|
||||||
|
if self.regions.is_full() {
|
||||||
|
self.finish_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout_node(ctx, node, styles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,12 +164,17 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
/// Layout a node.
|
||||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
fn layout_node(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
node: &PackedNode,
|
||||||
|
styles: StyleChain,
|
||||||
|
) {
|
||||||
// Placed nodes that are out of flow produce placed items which aren't
|
// Placed nodes that are out of flow produce placed items which aren't
|
||||||
// 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, self.styles).remove(0);
|
let frame = node.layout(ctx, &self.regions, styles).remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame.item));
|
self.items.push(FlowItem::Placed(frame.item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -204,9 +183,9 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// How to align the node.
|
// How to align the node.
|
||||||
let aligns = Spec::new(
|
let aligns = Spec::new(
|
||||||
// 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 as it is itself aligned.
|
||||||
if node.is::<ParNode>() {
|
if node.is::<ParNode>() {
|
||||||
node.styles.chain(&self.styles).get(ParNode::ALIGN)
|
styles.get(ParNode::ALIGN)
|
||||||
} else {
|
} else {
|
||||||
Align::Left
|
Align::Left
|
||||||
},
|
},
|
||||||
@ -216,7 +195,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, self.styles);
|
let frames = node.layout(ctx, &self.regions, 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.
|
||||||
|
@ -62,7 +62,7 @@ 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, StyleChain, StyleMap,
|
Args, Construct, EvalContext, Node, Property, Set, Smart, StyleChain, StyleMap,
|
||||||
Value,
|
Styled, Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -8,12 +8,7 @@ use super::{ColumnsNode, PadNode};
|
|||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct PageNode {
|
pub struct PageNode(pub PackedNode);
|
||||||
/// The node producing the content.
|
|
||||||
pub child: PackedNode,
|
|
||||||
/// The page's styles.
|
|
||||||
pub styles: StyleMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[properties]
|
#[properties]
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
@ -43,10 +38,7 @@ 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(args.expect("body")?)))
|
||||||
child: args.expect("body")?,
|
|
||||||
styles: StyleMap::new(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,16 +76,8 @@ impl Set for PageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
/// Style the node with styles from a style map.
|
|
||||||
pub fn styled(mut self, styles: StyleMap) -> Self {
|
|
||||||
self.styles.apply(&styles);
|
|
||||||
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, styles: StyleChain) -> Vec<Rc<Frame>> {
|
pub fn layout(&self, ctx: &mut LayoutContext, styles: StyleChain) -> Vec<Rc<Frame>> {
|
||||||
let styles = self.styles.chain(&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 = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||||
@ -113,21 +97,21 @@ impl PageNode {
|
|||||||
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut child = self.0.clone();
|
||||||
|
|
||||||
// Realize columns with columns node.
|
// Realize columns with columns node.
|
||||||
let columns = styles.get(Self::COLUMNS);
|
let columns = styles.get(Self::COLUMNS);
|
||||||
let child = if columns.get() > 1 {
|
if columns.get() > 1 {
|
||||||
ColumnsNode {
|
child = ColumnsNode {
|
||||||
columns,
|
columns,
|
||||||
gutter: styles.get(Self::COLUMN_GUTTER),
|
gutter: styles.get(Self::COLUMN_GUTTER),
|
||||||
child: self.child.clone(),
|
child: self.0.clone(),
|
||||||
}
|
}
|
||||||
.pack()
|
.pack();
|
||||||
} else {
|
}
|
||||||
self.child.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Realize margins with padding node.
|
// Realize margins with padding node.
|
||||||
let child = PadNode { child, padding }.pack();
|
child = PadNode { child, padding }.pack();
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let expand = size.map(Length::is_finite);
|
let expand = size.map(Length::is_finite);
|
||||||
@ -152,11 +136,8 @@ impl PageNode {
|
|||||||
|
|
||||||
impl Debug for PageNode {
|
impl Debug for PageNode {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
|
||||||
self.styles.fmt(f)?;
|
|
||||||
}
|
|
||||||
f.write_str("Page(")?;
|
f.write_str("Page(")?;
|
||||||
self.child.fmt(f)?;
|
self.0.fmt(f)?;
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@ use unicode_bidi::{BidiInfo, Level};
|
|||||||
use xi_unicode::LineBreakIterator;
|
use xi_unicode::LineBreakIterator;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
|
use super::{shape, ShapedText, SpacingKind, TextNode};
|
||||||
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
||||||
|
|
||||||
/// A node that arranges its children into a paragraph.
|
/// A node that arranges its children into a paragraph.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct ParNode(pub Vec<ParChild>);
|
pub struct ParNode(pub Vec<Styled<ParChild>>);
|
||||||
|
|
||||||
#[properties]
|
#[properties]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
@ -120,9 +120,9 @@ impl ParNode {
|
|||||||
|
|
||||||
/// The string representation of each child.
|
/// The string representation of each child.
|
||||||
fn strings(&self) -> impl Iterator<Item = &str> {
|
fn strings(&self) -> impl Iterator<Item = &str> {
|
||||||
self.0.iter().map(|child| match child {
|
self.0.iter().map(|styled| match &styled.item {
|
||||||
ParChild::Spacing(_) => " ",
|
ParChild::Spacing(_) => " ",
|
||||||
ParChild::Text(ref node) => &node.text,
|
ParChild::Text(node) => &node.0,
|
||||||
ParChild::Node(_) => "\u{FFFC}",
|
ParChild::Node(_) => "\u{FFFC}",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ impl Debug for ParNode {
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum ParChild {
|
pub enum ParChild {
|
||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(SpacingNode),
|
Spacing(SpacingKind),
|
||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
Text(TextNode),
|
Text(TextNode),
|
||||||
/// Any child node and how to align it in its line.
|
/// Any child node and how to align it in its line.
|
||||||
@ -148,26 +148,8 @@ pub enum ParChild {
|
|||||||
|
|
||||||
impl ParChild {
|
impl ParChild {
|
||||||
/// Create a text child.
|
/// Create a text child.
|
||||||
pub fn text(text: impl Into<EcoString>, styles: StyleMap) -> Self {
|
pub fn text(text: impl Into<EcoString>) -> Self {
|
||||||
Self::Text(TextNode { text: text.into(), styles })
|
Self::Text(TextNode(text.into()))
|
||||||
}
|
|
||||||
|
|
||||||
/// A reference to the child's styles.
|
|
||||||
pub fn styles(&self) -> &StyleMap {
|
|
||||||
match self {
|
|
||||||
Self::Spacing(node) => &node.styles,
|
|
||||||
Self::Text(node) => &node.styles,
|
|
||||||
Self::Node(node) => &node.styles,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mutable reference to the child's styles.
|
|
||||||
pub fn styles_mut(&mut self) -> &mut StyleMap {
|
|
||||||
match self {
|
|
||||||
Self::Spacing(node) => &mut node.styles,
|
|
||||||
Self::Text(node) => &mut node.styles,
|
|
||||||
Self::Node(node) => &mut node.styles,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,9 +216,10 @@ impl<'a> ParLayouter<'a> {
|
|||||||
let mut ranges = vec![];
|
let mut ranges = vec![];
|
||||||
|
|
||||||
// Layout the children and collect them into items.
|
// Layout the children and collect them into items.
|
||||||
for (range, child) in par.ranges().zip(&par.0) {
|
for (range, styled) in par.ranges().zip(&par.0) {
|
||||||
match child {
|
let styles = styled.map.chain(styles);
|
||||||
ParChild::Spacing(node) => match node.kind {
|
match styled.item {
|
||||||
|
ParChild::Spacing(kind) => match kind {
|
||||||
SpacingKind::Linear(v) => {
|
SpacingKind::Linear(v) => {
|
||||||
let resolved = v.resolve(regions.current.x);
|
let resolved = v.resolve(regions.current.x);
|
||||||
items.push(ParItem::Absolute(resolved));
|
items.push(ParItem::Absolute(resolved));
|
||||||
@ -247,7 +230,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ParChild::Text(node) => {
|
ParChild::Text(_) => {
|
||||||
// TODO: Also split by language and script.
|
// TODO: Also split by language and script.
|
||||||
let mut cursor = range.start;
|
let mut cursor = range.start;
|
||||||
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
|
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
|
||||||
@ -255,16 +238,15 @@ 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(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParChild::Node(node) => {
|
ParChild::Node(ref 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, *styles).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);
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,6 @@ pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explicit spacing in a flow or paragraph.
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub struct SpacingNode {
|
|
||||||
/// The kind of spacing.
|
|
||||||
pub kind: SpacingKind,
|
|
||||||
/// The spacing's styles.
|
|
||||||
pub styles: StyleMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for SpacingNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
if f.alternate() {
|
|
||||||
self.styles.fmt(f)?;
|
|
||||||
}
|
|
||||||
write!(f, "{:?}", self.kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kinds of spacing.
|
/// Kinds of spacing.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum SpacingKind {
|
pub enum SpacingKind {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Side-by-side layout of nodes along an axis.
|
//! Side-by-side layout of nodes along an axis.
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, SpacingKind, SpacingNode};
|
use super::{AlignNode, SpacingKind};
|
||||||
|
|
||||||
/// `stack`: Stack children along an axis.
|
/// `stack`: Stack children along an axis.
|
||||||
pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -38,17 +38,11 @@ impl Layout for StackNode {
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum StackChild {
|
pub enum StackChild {
|
||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(SpacingNode),
|
Spacing(SpacingKind),
|
||||||
/// An arbitrary node.
|
/// An arbitrary node.
|
||||||
Node(PackedNode),
|
Node(PackedNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SpacingKind> for StackChild {
|
|
||||||
fn from(kind: SpacingKind) -> Self {
|
|
||||||
Self::Spacing(SpacingNode { kind, styles: StyleMap::new() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for StackChild {
|
impl Debug for StackChild {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@ -61,10 +55,10 @@ impl Debug for StackChild {
|
|||||||
castable! {
|
castable! {
|
||||||
StackChild,
|
StackChild,
|
||||||
Expected: "linear, fractional or template",
|
Expected: "linear, fractional or template",
|
||||||
Value::Length(v) => SpacingKind::Linear(v.into()).into(),
|
Value::Length(v) => Self::Spacing(SpacingKind::Linear(v.into())),
|
||||||
Value::Relative(v) => SpacingKind::Linear(v.into()).into(),
|
Value::Relative(v) => Self::Spacing(SpacingKind::Linear(v.into())),
|
||||||
Value::Linear(v) => SpacingKind::Linear(v).into(),
|
Value::Linear(v) => Self::Spacing(SpacingKind::Linear(v)),
|
||||||
Value::Fractional(v) => SpacingKind::Fractional(v).into(),
|
Value::Fractional(v) => Self::Spacing(SpacingKind::Fractional(v)),
|
||||||
Value::Node(v) => Self::Node(v.into_block()),
|
Value::Node(v) => Self::Node(v.into_block()),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,12 +134,12 @@ impl<'a> StackLayouter<'a> {
|
|||||||
let mut deferred = None;
|
let mut deferred = None;
|
||||||
|
|
||||||
for child in self.children {
|
for child in self.children {
|
||||||
match child {
|
match *child {
|
||||||
StackChild::Spacing(node) => {
|
StackChild::Spacing(kind) => {
|
||||||
self.layout_spacing(node.kind);
|
self.layout_spacing(kind);
|
||||||
deferred = None;
|
deferred = None;
|
||||||
}
|
}
|
||||||
StackChild::Node(node) => {
|
StackChild::Node(ref node) => {
|
||||||
if let Some(kind) = deferred {
|
if let Some(kind) = deferred {
|
||||||
self.layout_spacing(kind);
|
self.layout_spacing(kind);
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,7 @@ use crate::util::{EcoString, SliceExt};
|
|||||||
|
|
||||||
/// A single run of text with the same style.
|
/// A single run of text with the same style.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct TextNode {
|
pub struct TextNode(pub EcoString);
|
||||||
/// The run's text.
|
|
||||||
pub text: EcoString,
|
|
||||||
/// The run's styles.
|
|
||||||
pub styles: StyleMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[properties]
|
#[properties]
|
||||||
impl TextNode {
|
impl TextNode {
|
||||||
@ -154,10 +149,7 @@ impl Set for TextNode {
|
|||||||
|
|
||||||
impl Debug for TextNode {
|
impl Debug for TextNode {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
write!(f, "Text({:?})", self.0)
|
||||||
self.styles.fmt(f)?;
|
|
||||||
}
|
|
||||||
write!(f, "Text({:?})", self.text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user