mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Set Rules Episode III: Revenge of the packer
This commit is contained in:
parent
40b87d4066
commit
fe21c4d399
@ -117,14 +117,16 @@ impl<'a> EvalContext<'a> {
|
|||||||
|
|
||||||
// Prepare the new context.
|
// Prepare the new context.
|
||||||
let new_scopes = Scopes::new(self.scopes.base);
|
let new_scopes = Scopes::new(self.scopes.base);
|
||||||
let old_scopes = mem::replace(&mut self.scopes, new_scopes);
|
let prev_scopes = mem::replace(&mut self.scopes, new_scopes);
|
||||||
|
let prev_styles = mem::take(&mut self.styles);
|
||||||
self.route.push(id);
|
self.route.push(id);
|
||||||
|
|
||||||
// Evaluate the module.
|
// Evaluate the module.
|
||||||
let template = ast.eval(self).trace(|| Tracepoint::Import, span)?;
|
let template = ast.eval(self).trace(|| Tracepoint::Import, span)?;
|
||||||
|
|
||||||
// Restore the old context.
|
// Restore the old context.
|
||||||
let new_scopes = mem::replace(&mut self.scopes, old_scopes);
|
let new_scopes = mem::replace(&mut self.scopes, prev_scopes);
|
||||||
|
self.styles = prev_styles;
|
||||||
self.route.pop().unwrap();
|
self.route.pop().unwrap();
|
||||||
|
|
||||||
// Save the evaluated module.
|
// Save the evaluated module.
|
||||||
@ -160,11 +162,13 @@ impl Eval for Markup {
|
|||||||
type Output = Node;
|
type Output = Node;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let mut result = Node::new();
|
let prev = mem::take(&mut ctx.styles);
|
||||||
|
let mut seq = vec![];
|
||||||
for piece in self.nodes() {
|
for piece in self.nodes() {
|
||||||
result += piece.eval(ctx)?;
|
seq.push((piece.eval(ctx)?, ctx.styles.clone()));
|
||||||
}
|
}
|
||||||
Ok(result)
|
ctx.styles = prev;
|
||||||
|
Ok(Node::Sequence(seq))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +194,7 @@ impl Eval for MarkupNode {
|
|||||||
Self::Heading(heading) => heading.eval(ctx)?,
|
Self::Heading(heading) => heading.eval(ctx)?,
|
||||||
Self::List(list) => list.eval(ctx)?,
|
Self::List(list) => list.eval(ctx)?,
|
||||||
Self::Enum(enum_) => enum_.eval(ctx)?,
|
Self::Enum(enum_) => enum_.eval(ctx)?,
|
||||||
Self::Expr(expr) => expr.eval(ctx)?.display(),
|
Self::Expr(expr) => expr.eval(ctx)?.show(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,8 +203,7 @@ impl Eval for RawNode {
|
|||||||
type Output = Node;
|
type Output = Node;
|
||||||
|
|
||||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
// TODO(set): Styled in monospace.
|
let text = Node::Text(self.text.clone()).monospaced();
|
||||||
let text = Node::Text(self.text.clone());
|
|
||||||
Ok(if self.block {
|
Ok(if self.block {
|
||||||
Node::Block(text.into_block())
|
Node::Block(text.into_block())
|
||||||
} else {
|
} else {
|
||||||
@ -213,8 +216,7 @@ impl Eval for MathNode {
|
|||||||
type Output = Node;
|
type Output = Node;
|
||||||
|
|
||||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
// TODO(set): Styled in monospace.
|
let text = Node::Text(self.formula.clone()).monospaced();
|
||||||
let text = Node::Text(self.formula.clone());
|
|
||||||
Ok(if self.display {
|
Ok(if self.display {
|
||||||
Node::Block(text.into_block())
|
Node::Block(text.into_block())
|
||||||
} else {
|
} else {
|
||||||
@ -227,8 +229,14 @@ impl Eval for HeadingNode {
|
|||||||
type Output = Node;
|
type Output = Node;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
// TODO(set): Styled appropriately.
|
// TODO(set): Relative font size.
|
||||||
Ok(Node::Block(self.body().eval(ctx)?.into_block()))
|
let upscale = (1.6 - 0.1 * self.level() as f64).max(0.75);
|
||||||
|
let mut styles = Styles::new();
|
||||||
|
styles.set(TextNode::STRONG, true);
|
||||||
|
styles.set(TextNode::SIZE, upscale * Length::pt(11.0));
|
||||||
|
Ok(Node::Block(
|
||||||
|
self.body().eval(ctx)?.into_block().styled(styles),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
223
src/eval/node.rs
223
src/eval/node.rs
@ -1,9 +1,10 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
|
use super::Styles;
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::SpecAxis;
|
use crate::geom::SpecAxis;
|
||||||
use crate::layout::{Layout, PackedNode};
|
use crate::layout::{Layout, PackedNode};
|
||||||
@ -16,8 +17,9 @@ use crate::util::EcoString;
|
|||||||
/// A partial representation of a layout node.
|
/// A partial representation of a layout node.
|
||||||
///
|
///
|
||||||
/// A node is a composable intermediate representation that can be converted
|
/// A node is a composable intermediate representation that can be converted
|
||||||
/// into a proper layout node by lifting it to the block or page level.
|
/// into a proper layout node by lifting it to a block-level or document node.
|
||||||
#[derive(Clone)]
|
// TODO(set): Fix Debug impl leaking into user-facing repr.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
/// A word space.
|
/// A word space.
|
||||||
Space,
|
Space,
|
||||||
@ -36,13 +38,13 @@ pub enum Node {
|
|||||||
/// A block node.
|
/// A block node.
|
||||||
Block(PackedNode),
|
Block(PackedNode),
|
||||||
/// A sequence of nodes (which may themselves contain sequences).
|
/// A sequence of nodes (which may themselves contain sequences).
|
||||||
Seq(Vec<Self>),
|
Sequence(Vec<(Self, Styles)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
/// Create an empty node.
|
/// Create an empty node.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::Seq(vec![])
|
Self::Sequence(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an inline-level node.
|
/// Create an inline-level node.
|
||||||
@ -61,8 +63,22 @@ impl Node {
|
|||||||
Self::Block(node.pack())
|
Self::Block(node.pack())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decoration this node.
|
/// Style this node.
|
||||||
pub fn decorate(self, _: Decoration) -> Self {
|
pub fn styled(self, styles: Styles) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Inline(inline) => Self::Inline(inline.styled(styles)),
|
||||||
|
Self::Block(block) => Self::Block(block.styled(styles)),
|
||||||
|
other => Self::Sequence(vec![(other, styles)]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Style this node in monospace.
|
||||||
|
pub fn monospaced(self) -> Self {
|
||||||
|
self.styled(Styles::one(TextNode::MONOSPACE, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decorate this node.
|
||||||
|
pub fn decorated(self, _: Decoration) -> Self {
|
||||||
// TODO(set): Actually decorate.
|
// TODO(set): Actually decorate.
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -73,7 +89,7 @@ impl Node {
|
|||||||
packed
|
packed
|
||||||
} else {
|
} else {
|
||||||
let mut packer = NodePacker::new();
|
let mut packer = NodePacker::new();
|
||||||
packer.walk(self);
|
packer.walk(self, Styles::new());
|
||||||
packer.into_block()
|
packer.into_block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +97,7 @@ impl Node {
|
|||||||
/// Lift to a document node, the root of the layout tree.
|
/// Lift to a document node, the root of the layout tree.
|
||||||
pub fn into_document(self) -> DocumentNode {
|
pub fn into_document(self) -> DocumentNode {
|
||||||
let mut packer = NodePacker::new();
|
let mut packer = NodePacker::new();
|
||||||
packer.walk(self);
|
packer.walk(self, Styles::new());
|
||||||
packer.into_document()
|
packer.into_document()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,13 +107,7 @@ impl Node {
|
|||||||
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
.map_err(|_| format!("cannot repeat this template {} times", n))?;
|
||||||
|
|
||||||
// TODO(set): Make more efficient.
|
// TODO(set): Make more efficient.
|
||||||
Ok(Self::Seq(vec![self.clone(); count]))
|
Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count]))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Node {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad("<node>")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +129,7 @@ impl Add for Node {
|
|||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
// TODO(set): Make more efficient.
|
// TODO(set): Make more efficient.
|
||||||
Self::Seq(vec![self, rhs])
|
Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,86 +139,211 @@ impl AddAssign for Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Packs a `Node` into a flow or whole document.
|
/// Packs a [`Node`] into a flow or whole document.
|
||||||
struct NodePacker {
|
struct NodePacker {
|
||||||
|
/// The accumulated page nodes.
|
||||||
document: Vec<PageNode>,
|
document: Vec<PageNode>,
|
||||||
|
/// The common style properties of all items on the current page.
|
||||||
|
page_styles: Styles,
|
||||||
|
/// The accumulated flow children.
|
||||||
flow: Vec<FlowChild>,
|
flow: Vec<FlowChild>,
|
||||||
|
/// The accumulated paragraph children.
|
||||||
par: Vec<ParChild>,
|
par: Vec<ParChild>,
|
||||||
|
/// The common style properties of all items in the current paragraph.
|
||||||
|
par_styles: Styles,
|
||||||
|
/// The kind of thing that was last added to the current paragraph.
|
||||||
|
last: Last,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of the last thing that was pushed into the paragraph.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Last {
|
||||||
|
None,
|
||||||
|
Spacing,
|
||||||
|
Newline,
|
||||||
|
Space,
|
||||||
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodePacker {
|
impl NodePacker {
|
||||||
|
/// Start a new node-packing session.
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
document: vec![],
|
document: vec![],
|
||||||
|
page_styles: Styles::new(),
|
||||||
flow: vec![],
|
flow: vec![],
|
||||||
par: vec![],
|
par: vec![],
|
||||||
|
par_styles: Styles::new(),
|
||||||
|
last: Last::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish up and return the resulting flow.
|
||||||
fn into_block(mut self) -> PackedNode {
|
fn into_block(mut self) -> PackedNode {
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
FlowNode(self.flow).pack()
|
FlowNode(self.flow).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish up and return the resulting document.
|
||||||
fn into_document(mut self) -> DocumentNode {
|
fn into_document(mut self) -> DocumentNode {
|
||||||
self.parbreak();
|
self.pagebreak(true);
|
||||||
self.pagebreak();
|
|
||||||
DocumentNode(self.document)
|
DocumentNode(self.document)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk(&mut self, node: Node) {
|
/// Consider a node with the given styles.
|
||||||
|
fn walk(&mut self, node: Node, styles: Styles) {
|
||||||
match node {
|
match node {
|
||||||
Node::Space => {
|
Node::Space => {
|
||||||
self.push_inline(ParChild::Text(TextNode(' '.into())));
|
// Only insert a space if the previous thing was actual content.
|
||||||
|
if self.last == Last::Other {
|
||||||
|
self.push_text(' '.into(), styles);
|
||||||
|
self.last = Last::Space;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Node::Linebreak => {
|
Node::Linebreak => {
|
||||||
self.push_inline(ParChild::Text(TextNode('\n'.into())));
|
self.trim();
|
||||||
|
self.push_text('\n'.into(), styles);
|
||||||
|
self.last = Last::Newline;
|
||||||
}
|
}
|
||||||
Node::Parbreak => {
|
Node::Parbreak => {
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
}
|
}
|
||||||
Node::Pagebreak => {
|
Node::Pagebreak => {
|
||||||
self.pagebreak();
|
self.pagebreak(true);
|
||||||
|
self.page_styles = styles;
|
||||||
}
|
}
|
||||||
Node::Text(text) => {
|
Node::Text(text) => {
|
||||||
self.push_inline(ParChild::Text(TextNode(text)));
|
self.push_text(text, styles);
|
||||||
|
}
|
||||||
|
Node::Spacing(SpecAxis::Horizontal, amount) => {
|
||||||
|
self.push_inline(ParChild::Spacing(amount), styles);
|
||||||
|
self.last = Last::Spacing;
|
||||||
|
}
|
||||||
|
Node::Spacing(SpecAxis::Vertical, amount) => {
|
||||||
|
self.push_block(FlowChild::Spacing(amount), styles);
|
||||||
}
|
}
|
||||||
Node::Spacing(axis, amount) => match axis {
|
|
||||||
SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)),
|
|
||||||
SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)),
|
|
||||||
},
|
|
||||||
Node::Inline(inline) => {
|
Node::Inline(inline) => {
|
||||||
self.push_inline(ParChild::Node(inline));
|
self.push_inline(ParChild::Node(inline), styles);
|
||||||
}
|
}
|
||||||
Node::Block(block) => {
|
Node::Block(block) => {
|
||||||
self.push_block(FlowChild::Node(block));
|
self.push_block(FlowChild::Node(block), styles);
|
||||||
}
|
}
|
||||||
Node::Seq(list) => {
|
Node::Sequence(list) => {
|
||||||
for node in list {
|
for (node, mut inner) in list {
|
||||||
self.walk(node);
|
inner.apply(&styles);
|
||||||
|
self.walk(node, inner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a trailing space.
|
||||||
|
fn trim(&mut self) {
|
||||||
|
if self.last == Last::Space {
|
||||||
|
self.par.pop();
|
||||||
|
self.last = Last::Other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance to the next paragraph.
|
||||||
fn parbreak(&mut self) {
|
fn parbreak(&mut self) {
|
||||||
|
self.trim();
|
||||||
|
|
||||||
let children = mem::take(&mut self.par);
|
let children = mem::take(&mut self.par);
|
||||||
|
let styles = mem::take(&mut self.par_styles);
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
self.flow.push(FlowChild::Node(ParNode(children).pack()));
|
// The paragraph's children are all compatible with the page, so the
|
||||||
}
|
// paragraph is too, meaning we don't need to check or intersect
|
||||||
|
// anything here.
|
||||||
|
let node = ParNode(children).pack().styled(styles);
|
||||||
|
self.flow.push(FlowChild::Node(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pagebreak(&mut self) {
|
self.last = Last::None;
|
||||||
let children = mem::take(&mut self.flow);
|
|
||||||
self.document.push(PageNode(FlowNode(children).pack()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_inline(&mut self, child: ParChild) {
|
/// Advance to the next page.
|
||||||
self.par.push(child);
|
fn pagebreak(&mut self, keep: bool) {
|
||||||
}
|
|
||||||
|
|
||||||
fn push_block(&mut self, child: FlowChild) {
|
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
|
let children = mem::take(&mut self.flow);
|
||||||
|
let styles = mem::take(&mut self.page_styles);
|
||||||
|
if keep || !children.is_empty() {
|
||||||
|
let node = PageNode { node: FlowNode(children).pack(), styles };
|
||||||
|
self.document.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert text into the current paragraph.
|
||||||
|
fn push_text(&mut self, text: EcoString, styles: Styles) {
|
||||||
|
// TODO(set): Join compatible text nodes. Take care with space
|
||||||
|
// coalescing.
|
||||||
|
let node = TextNode { text, styles: Styles::new() };
|
||||||
|
self.push_inline(ParChild::Text(node), styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert an inline-level element into the current paragraph.
|
||||||
|
fn push_inline(&mut self, mut child: ParChild, styles: Styles) {
|
||||||
|
match &mut child {
|
||||||
|
ParChild::Spacing(_) => {}
|
||||||
|
ParChild::Text(node) => node.styles.apply(&styles),
|
||||||
|
ParChild::Node(node) => node.styles.apply(&styles),
|
||||||
|
ParChild::Decorate(_) => {}
|
||||||
|
ParChild::Undecorate => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The node must be both compatible with the current page and the
|
||||||
|
// current paragraph.
|
||||||
|
self.make_page_compatible(&styles);
|
||||||
|
self.make_par_compatible(&styles);
|
||||||
|
self.par.push(child);
|
||||||
|
self.last = Last::Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a block-level element into the current flow.
|
||||||
|
fn push_block(&mut self, mut child: FlowChild, styles: Styles) {
|
||||||
|
self.parbreak();
|
||||||
|
|
||||||
|
match &mut child {
|
||||||
|
FlowChild::Spacing(_) => {}
|
||||||
|
FlowChild::Node(node) => node.styles.apply(&styles),
|
||||||
|
}
|
||||||
|
|
||||||
|
// The node must be compatible with the current page.
|
||||||
|
self.make_page_compatible(&styles);
|
||||||
self.flow.push(child);
|
self.flow.push(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Break to a new paragraph if the `styles` contain paragraph styles that
|
||||||
|
/// are incompatible with the current paragraph.
|
||||||
|
fn make_par_compatible(&mut self, styles: &Styles) {
|
||||||
|
if self.par.is_empty() {
|
||||||
|
self.par_styles = styles.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.par_styles.compatible(&styles, ParNode::has_property) {
|
||||||
|
self.parbreak();
|
||||||
|
self.par_styles = styles.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.par_styles.intersect(&styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Break to a new page if the `styles` contain page styles that are
|
||||||
|
/// incompatible with the current page.
|
||||||
|
fn make_page_compatible(&mut self, styles: &Styles) {
|
||||||
|
if self.flow.is_empty() && self.par.is_empty() {
|
||||||
|
self.page_styles = styles.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.page_styles.compatible(&styles, PageNode::has_property) {
|
||||||
|
self.pagebreak(false);
|
||||||
|
self.page_styles = styles.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.page_styles.intersect(styles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ impl Scope {
|
|||||||
|
|
||||||
impl Debug for Scope {
|
impl Debug for Scope {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("Scope ")?;
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(self.values.iter().map(|(k, v)| (k, v.borrow())))
|
.entries(self.values.iter().map(|(k, v)| (k, v.borrow())))
|
||||||
.finish()
|
.finish()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
// Possible optimizations:
|
// Possible optimizations:
|
||||||
@ -9,20 +9,48 @@ use std::rc::Rc;
|
|||||||
// - Store small properties inline
|
// - Store small properties inline
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct Styles {
|
pub struct Styles {
|
||||||
map: HashMap<TypeId, Rc<dyn Any>>,
|
pub(crate) map: Vec<(StyleId, Entry)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styles {
|
impl Styles {
|
||||||
/// Create a new, empty style map.
|
/// Create a new, empty style map.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { map: HashMap::new() }
|
Self { map: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a style map with a single property-value pair.
|
||||||
|
pub fn one<P: Property>(key: P, value: P::Value) -> Self
|
||||||
|
where
|
||||||
|
P::Value: Debug + Hash + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
let mut styles = Self::new();
|
||||||
|
styles.set(key, value);
|
||||||
|
styles
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this map contains no styles.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.map.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the value for a style property.
|
/// Set the value for a style property.
|
||||||
pub fn set<P: Property>(&mut self, _: P, value: P::Value) {
|
pub fn set<P: Property>(&mut self, key: P, value: P::Value)
|
||||||
self.map.insert(TypeId::of::<P>(), Rc::new(value));
|
where
|
||||||
|
P::Value: Debug + Hash + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
let id = StyleId::of::<P>();
|
||||||
|
let entry = Entry::new(key, value);
|
||||||
|
|
||||||
|
for pair in &mut self.map {
|
||||||
|
if pair.0 == id {
|
||||||
|
pair.1 = entry;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.map.push((id, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a copyable style property.
|
/// Get the value of a copyable style property.
|
||||||
@ -33,7 +61,7 @@ impl Styles {
|
|||||||
where
|
where
|
||||||
P::Value: Copy,
|
P::Value: Copy,
|
||||||
{
|
{
|
||||||
self.get_inner(key).copied().unwrap_or_else(P::default)
|
self.get_direct(key).copied().unwrap_or_else(P::default)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a style property.
|
/// Get a reference to a style property.
|
||||||
@ -41,30 +69,147 @@ impl Styles {
|
|||||||
/// Returns a reference to the property's default value if the map does not
|
/// Returns a reference to the property's default value if the map does not
|
||||||
/// contain an entry for it.
|
/// contain an entry for it.
|
||||||
pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
|
pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
|
||||||
self.get_inner(key).unwrap_or_else(|| P::default_ref())
|
self.get_direct(key).unwrap_or_else(|| P::default_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a style directly in this map.
|
/// Get a reference to a style directly in this map (no default value).
|
||||||
fn get_inner<P: Property>(&self, _: P) -> Option<&P::Value> {
|
pub fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> {
|
||||||
self.map
|
self.map
|
||||||
.get(&TypeId::of::<P>())
|
.iter()
|
||||||
.and_then(|boxed| boxed.downcast_ref())
|
.find(|pair| pair.0 == StyleId::of::<P>())
|
||||||
|
.and_then(|pair| pair.1.downcast())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply styles from `outer` in-place.
|
||||||
|
///
|
||||||
|
/// Properties from `self` take precedence over the ones from `outer`.
|
||||||
|
pub fn apply(&mut self, outer: &Self) {
|
||||||
|
for pair in &outer.map {
|
||||||
|
if self.map.iter().all(|&(id, _)| pair.0 != id) {
|
||||||
|
self.map.push(pair.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keep only those styles that are also in `other`.
|
||||||
|
pub fn intersect(&mut self, other: &Self) {
|
||||||
|
self.map.retain(|a| other.map.iter().any(|b| a == b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether two style maps are equal when filtered down to the given
|
||||||
|
/// properties.
|
||||||
|
pub fn compatible<F>(&self, other: &Self, filter: F) -> bool
|
||||||
|
where
|
||||||
|
F: Fn(StyleId) -> bool,
|
||||||
|
{
|
||||||
|
let f = |e: &&(StyleId, Entry)| filter(e.0);
|
||||||
|
self.map.iter().filter(f).all(|pair| other.map.contains(pair))
|
||||||
|
&& other.map.iter().filter(f).all(|pair| self.map.contains(pair))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Styles {
|
impl Debug for Styles {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
// TODO(set): Better debug printing possible?
|
f.write_str("Styles ")?;
|
||||||
f.pad("Styles(..)")
|
f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stylistic property keys.
|
/// An entry for a single style property.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Entry {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: &'static str,
|
||||||
|
value: Rc<dyn Bounds>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
fn new<P: Property>(_: P, value: P::Value) -> Self
|
||||||
|
where
|
||||||
|
P::Value: Debug + Hash + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: P::NAME,
|
||||||
|
value: Rc::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.value.as_any().downcast_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Entry {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
write!(f, "{}: ", self.name)?;
|
||||||
|
write!(f, "{:?}", &self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Entry {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.value.dyn_eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Entry {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_u64(self.value.hash64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Bounds: Debug + 'static {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn dyn_eq(&self, other: &Entry) -> bool;
|
||||||
|
fn hash64(&self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Bounds for T
|
||||||
|
where
|
||||||
|
T: Debug + Hash + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_eq(&self, other: &Entry) -> bool {
|
||||||
|
if let Some(other) = other.downcast::<Self>() {
|
||||||
|
self == other
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash64(&self) -> u64 {
|
||||||
|
// No need to hash the TypeId since there's only one
|
||||||
|
// valid value type per property.
|
||||||
|
fxhash::hash64(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Style property keys.
|
||||||
|
///
|
||||||
|
/// This trait is not intended to be implemented manually, but rather through
|
||||||
|
/// the `properties!` macro.
|
||||||
pub trait Property: 'static {
|
pub trait Property: 'static {
|
||||||
/// The type of this property, for example, this could be
|
/// The type of this property, for example, this could be
|
||||||
/// [`Length`](crate::geom::Length) for a `WIDTH` property.
|
/// [`Length`](crate::geom::Length) for a `WIDTH` property.
|
||||||
type Value;
|
type Value;
|
||||||
|
|
||||||
|
/// The name of the property, used for debug printing.
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
/// The default value of the property.
|
/// The default value of the property.
|
||||||
fn default() -> Self::Value;
|
fn default() -> Self::Value;
|
||||||
|
|
||||||
@ -76,14 +221,18 @@ pub trait Property: 'static {
|
|||||||
fn default_ref() -> &'static Self::Value;
|
fn default_ref() -> &'static Self::Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! set {
|
/// A unique identifier for a style property.
|
||||||
($ctx:expr, $target:expr => $source:expr) => {
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
if let Some(v) = $source {
|
pub struct StyleId(TypeId);
|
||||||
$ctx.styles.set($target, v);
|
|
||||||
|
impl StyleId {
|
||||||
|
/// The style id of the property.
|
||||||
|
pub fn of<P: Property>() -> Self {
|
||||||
|
Self(TypeId::of::<P>())
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate the property keys for a node.
|
||||||
macro_rules! properties {
|
macro_rules! properties {
|
||||||
($node:ty, $(
|
($node:ty, $(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
@ -92,10 +241,10 @@ macro_rules! properties {
|
|||||||
// TODO(set): Fix possible name clash.
|
// TODO(set): Fix possible name clash.
|
||||||
mod properties {
|
mod properties {
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use $crate::eval::{Property, StyleId};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
$(#[allow(non_snake_case)] mod $name {
|
$(#[allow(non_snake_case)] mod $name {
|
||||||
use $crate::eval::Property;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -104,6 +253,10 @@ macro_rules! properties {
|
|||||||
impl Property for Key<$type> {
|
impl Property for Key<$type> {
|
||||||
type Value = $type;
|
type Value = $type;
|
||||||
|
|
||||||
|
const NAME: &'static str = concat!(
|
||||||
|
stringify!($node), "::", stringify!($name)
|
||||||
|
);
|
||||||
|
|
||||||
fn default() -> Self::Value {
|
fn default() -> Self::Value {
|
||||||
$default
|
$default
|
||||||
}
|
}
|
||||||
@ -116,9 +269,24 @@ macro_rules! properties {
|
|||||||
})*
|
})*
|
||||||
|
|
||||||
impl $node {
|
impl $node {
|
||||||
|
/// Check whether the property with the given type id belongs to
|
||||||
|
/// `Self`.
|
||||||
|
pub fn has_property(id: StyleId) -> bool {
|
||||||
|
false || $(id == StyleId::of::<$name::Key<$type>>())||*
|
||||||
|
}
|
||||||
|
|
||||||
$($(#[$attr])* pub const $name: $name::Key<$type>
|
$($(#[$attr])* pub const $name: $name::Key<$type>
|
||||||
= $name::Key(PhantomData);)*
|
= $name::Key(PhantomData);)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a style property to a value if the value is `Some`.
|
||||||
|
macro_rules! set {
|
||||||
|
($ctx:expr, $target:expr => $value:expr) => {
|
||||||
|
if let Some(v) = $value {
|
||||||
|
$ctx.styles.set($target, v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -108,8 +108,8 @@ impl Value {
|
|||||||
format_eco!("{:?}", self)
|
format_eco!("{:?}", self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the display representation of a value in form of a node.
|
/// Return the display representation of the value.
|
||||||
pub fn display(self) -> Node {
|
pub fn show(self) -> Node {
|
||||||
match self {
|
match self {
|
||||||
Value::None => Node::new(),
|
Value::None => Node::new(),
|
||||||
Value::Int(v) => Node::Text(format_eco!("{}", v)),
|
Value::Int(v) => Node::Text(format_eco!("{}", v)),
|
||||||
@ -118,8 +118,7 @@ impl Value {
|
|||||||
Value::Node(v) => v,
|
Value::Node(v) => v,
|
||||||
// For values which can't be shown "naturally", we print the
|
// For values which can't be shown "naturally", we print the
|
||||||
// representation in monospace.
|
// representation in monospace.
|
||||||
// TODO(set): Styled in monospace.
|
v => Node::Text(v.repr()).monospaced(),
|
||||||
v => Node::Text(v.repr()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +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::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Align, Linear, Point, Sides, Spec, Transform};
|
use crate::geom::{Align, Linear, Point, Sides, Spec, Transform};
|
||||||
@ -37,6 +38,8 @@ 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.
|
||||||
|
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,
|
pub level: usize,
|
||||||
@ -50,6 +53,7 @@ impl<'a> LayoutContext<'a> {
|
|||||||
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,
|
||||||
}
|
}
|
||||||
@ -75,13 +79,9 @@ pub trait Layout {
|
|||||||
{
|
{
|
||||||
PackedNode {
|
PackedNode {
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
hash: {
|
hash: self.hash64(),
|
||||||
let mut state = fxhash::FxHasher64::default();
|
|
||||||
self.type_id().hash(&mut state);
|
|
||||||
self.hash(&mut state);
|
|
||||||
state.finish()
|
|
||||||
},
|
|
||||||
node: Rc::new(self),
|
node: Rc::new(self),
|
||||||
|
styles: Styles::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,8 +89,12 @@ pub trait Layout {
|
|||||||
/// A packed layouting node with precomputed hash.
|
/// A packed layouting node with precomputed hash.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PackedNode {
|
pub struct PackedNode {
|
||||||
|
/// The type-erased node.
|
||||||
node: Rc<dyn Bounds>,
|
node: Rc<dyn Bounds>,
|
||||||
|
/// The node's styles.
|
||||||
|
pub styles: Styles,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
|
/// A precomputed hash.
|
||||||
hash: u64,
|
hash: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +107,16 @@ 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: Styles) -> 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) {
|
||||||
@ -156,12 +170,12 @@ impl Layout for PackedNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
return self.node.layout(ctx, regions);
|
return self.layout_impl(ctx, regions);
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
|
ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
|
||||||
ctx.level += 1;
|
ctx.level += 1;
|
||||||
let frames = self.node.layout(ctx, regions);
|
let frames = self.layout_impl(ctx, regions);
|
||||||
ctx.level -= 1;
|
ctx.level -= 1;
|
||||||
|
|
||||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||||
@ -190,12 +204,27 @@ 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 Hash for PackedNode {
|
impl Hash for PackedNode {
|
||||||
fn hash<H: Hasher>(&self, _state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
_state.write_u64(self.hash);
|
state.write_u64(self.hash);
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
unimplemented!()
|
state.write_u64(self.hash64());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,13 +236,23 @@ impl Debug for PackedNode {
|
|||||||
|
|
||||||
trait Bounds: Layout + Debug + 'static {
|
trait Bounds: Layout + Debug + 'static {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn hash64(&self) -> u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Bounds for T
|
impl<T> Bounds for T
|
||||||
where
|
where
|
||||||
T: Layout + Debug + 'static,
|
T: Layout + Hash + Debug + 'static,
|
||||||
{
|
{
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash64(&self) -> u64 {
|
||||||
|
// Also hash the TypeId since nodes with different types but
|
||||||
|
// equal data should be different.
|
||||||
|
let mut state = fxhash::FxHasher64::default();
|
||||||
|
self.type_id().hash(&mut state);
|
||||||
|
self.hash(&mut state);
|
||||||
|
state.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
use super::ParNode;
|
||||||
|
|
||||||
/// `align`: Configure the alignment along the layouting axes.
|
/// `align`: Configure the alignment along the layouting axes.
|
||||||
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let aligns = args.expect::<Spec<_>>("alignment")?;
|
let aligns = args.expect::<Spec<_>>("alignment")?;
|
||||||
let body = args.expect::<Node>("body")?;
|
let body = args.expect::<Node>("body")?;
|
||||||
|
|
||||||
// TODO(set): Style paragraphs with x alignment.
|
let mut styles = Styles::new();
|
||||||
Ok(Value::block(body.into_block().aligned(aligns)))
|
if let Some(align) = aligns.x {
|
||||||
|
styles.set(ParNode::ALIGN, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::block(
|
||||||
|
body.into_block().styled(styles).aligned(aligns),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that aligns its child.
|
/// A node that aligns its child.
|
||||||
|
@ -22,7 +22,7 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
|||||||
let offset = args.named("offset")?;
|
let offset = args.named("offset")?;
|
||||||
let extent = args.named("extent")?.unwrap_or_default();
|
let extent = args.named("extent")?.unwrap_or_default();
|
||||||
let body: Node = args.expect("body")?;
|
let body: Node = args.expect("body")?;
|
||||||
Ok(Value::Node(body.decorate(Decoration::Line(
|
Ok(Value::Node(body.decorated(Decoration::Line(
|
||||||
LineDecoration { kind, stroke, thickness, offset, extent },
|
LineDecoration { kind, stroke, thickness, offset, extent },
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
Node::Text(text.into())
|
Node::Text(text.into())
|
||||||
});
|
});
|
||||||
Ok(Value::Node(body.decorate(Decoration::Link(url))))
|
Ok(Value::Node(body.decorated(Decoration::Link(url))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A decoration for a frame.
|
/// A decoration for a frame.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{AlignNode, PlacedNode, Spacing};
|
use super::{AlignNode, ParNode, PlacedNode, Spacing};
|
||||||
|
|
||||||
/// `flow`: A vertical flow of paragraphs and other layout nodes.
|
/// `flow`: A vertical flow of paragraphs and other layout nodes.
|
||||||
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -158,6 +158,13 @@ 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) {
|
||||||
|
// Add paragraph spacing.
|
||||||
|
// TODO(set): Handle edge cases.
|
||||||
|
if !self.items.is_empty() {
|
||||||
|
let spacing = node.styles.chain(&ctx.styles).get(ParNode::SPACING);
|
||||||
|
self.layout_absolute(spacing.into());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(placed) = node.downcast::<PlacedNode>() {
|
if let Some(placed) = node.downcast::<PlacedNode>() {
|
||||||
let frame = node.layout(ctx, &self.regions).remove(0);
|
let frame = node.layout(ctx, &self.regions).remove(0);
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
|
@ -49,7 +49,12 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
|||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PageNode(pub PackedNode);
|
pub struct PageNode {
|
||||||
|
/// The node producing the content.
|
||||||
|
pub node: PackedNode,
|
||||||
|
/// The page's styles.
|
||||||
|
pub styles: Styles,
|
||||||
|
}
|
||||||
|
|
||||||
properties! {
|
properties! {
|
||||||
PageNode,
|
PageNode,
|
||||||
@ -77,30 +82,31 @@ properties! {
|
|||||||
impl PageNode {
|
impl PageNode {
|
||||||
/// 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) -> Vec<Rc<Frame>> {
|
||||||
// TODO(set): Take styles as parameter.
|
// TODO(set): Use chaining.
|
||||||
let styles = Styles::new();
|
let prev = std::mem::replace(&mut ctx.styles, self.styles.clone());
|
||||||
|
ctx.styles.apply(&prev);
|
||||||
|
|
||||||
// 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 = ctx.styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||||
let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
let height = ctx.styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
||||||
let mut size = Size::new(width, height);
|
let mut size = Size::new(width, height);
|
||||||
if styles.get(Self::FLIPPED) {
|
if ctx.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 = styles.get(Self::CLASS);
|
let class = ctx.styles.get(Self::CLASS);
|
||||||
let default = class.default_margins();
|
let default = class.default_margins();
|
||||||
let padding = Sides {
|
let padding = Sides {
|
||||||
left: styles.get(Self::LEFT).unwrap_or(default.left),
|
left: ctx.styles.get(Self::LEFT).unwrap_or(default.left),
|
||||||
right: styles.get(Self::RIGHT).unwrap_or(default.right),
|
right: ctx.styles.get(Self::RIGHT).unwrap_or(default.right),
|
||||||
top: styles.get(Self::TOP).unwrap_or(default.top),
|
top: ctx.styles.get(Self::TOP).unwrap_or(default.top),
|
||||||
bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
bottom: ctx.styles.get(Self::BOTTOM).unwrap_or(default.bottom),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pad the child.
|
// Pad the child.
|
||||||
let padded = PadNode { child: self.0.clone(), padding }.pack();
|
let padded = PadNode { child: self.node.clone(), padding }.pack();
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let expand = size.map(Length::is_finite);
|
let expand = size.map(Length::is_finite);
|
||||||
@ -109,13 +115,14 @@ impl PageNode {
|
|||||||
padded.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
padded.layout(ctx, ®ions).into_iter().map(|c| c.item).collect();
|
||||||
|
|
||||||
// Add background fill if requested.
|
// Add background fill if requested.
|
||||||
if let Some(fill) = styles.get(Self::FILL) {
|
if let Some(fill) = ctx.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,19 +73,16 @@ impl Layout for ParNode {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// TODO(set): Take styles as parameter.
|
|
||||||
let styles = Styles::new();
|
|
||||||
|
|
||||||
// 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 default_level = Level::from_dir(styles.get(Self::DIR));
|
let default_level = Level::from_dir(ctx.styles.get(Self::DIR));
|
||||||
let bidi = BidiInfo::new(&text, default_level);
|
let bidi = BidiInfo::new(&text, default_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, &styles);
|
let layouter = ParLayouter::new(self, ctx, regions, bidi);
|
||||||
|
|
||||||
// Find suitable linebreaks.
|
// Find suitable linebreaks.
|
||||||
layouter.layout(ctx, regions.clone())
|
layouter.layout(ctx, regions.clone())
|
||||||
@ -119,8 +116,8 @@ impl ParNode {
|
|||||||
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(|child| match child {
|
||||||
ParChild::Spacing(_) => " ",
|
ParChild::Spacing(_) => " ",
|
||||||
ParChild::Text(ref piece, ..) => &piece.0,
|
ParChild::Text(ref node) => &node.text,
|
||||||
ParChild::Node(..) => "\u{FFFC}",
|
ParChild::Node(_) => "\u{FFFC}",
|
||||||
ParChild::Decorate(_) | ParChild::Undecorate => "",
|
ParChild::Decorate(_) | ParChild::Undecorate => "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -132,7 +129,6 @@ pub enum ParChild {
|
|||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
// TODO(set): A single text run may also have its own style.
|
|
||||||
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.
|
||||||
Node(PackedNode),
|
Node(PackedNode),
|
||||||
@ -146,7 +142,7 @@ impl Debug for ParChild {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
||||||
Self::Text(text) => write!(f, "Text({:?})", text),
|
Self::Text(node) => write!(f, "Text({:?})", node.text),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Node(node) => node.fmt(f),
|
||||||
Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
|
Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
|
||||||
Self::Undecorate => write!(f, "Undecorate"),
|
Self::Undecorate => write!(f, "Undecorate"),
|
||||||
@ -193,7 +189,6 @@ impl<'a> ParLayouter<'a> {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
styles: &'a Styles,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
let mut ranges = vec![];
|
let mut ranges = vec![];
|
||||||
@ -212,7 +207,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
items.push(ParItem::Fractional(v));
|
items.push(ParItem::Fractional(v));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
ParChild::Text(_) => {
|
ParChild::Text(ref node) => {
|
||||||
// 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) {
|
||||||
@ -220,7 +215,8 @@ 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 shaped = shape(ctx, text, styles, level.dir());
|
let styles = node.styles.chain(&ctx.styles);
|
||||||
|
let shaped = shape(&mut ctx.fonts, text, styles, level.dir());
|
||||||
items.push(ParItem::Text(shaped));
|
items.push(ParItem::Text(shaped));
|
||||||
ranges.push(subrange);
|
ranges.push(subrange);
|
||||||
}
|
}
|
||||||
@ -248,8 +244,8 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
align: styles.get(ParNode::ALIGN),
|
align: ctx.styles.get(ParNode::ALIGN),
|
||||||
leading: styles.get(ParNode::LEADING),
|
leading: ctx.styles.get(ParNode::LEADING),
|
||||||
bidi,
|
bidi,
|
||||||
items,
|
items,
|
||||||
ranges,
|
ranges,
|
||||||
@ -426,7 +422,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
// empty string.
|
// empty string.
|
||||||
if !range.is_empty() || rest.is_empty() {
|
if !range.is_empty() || rest.is_empty() {
|
||||||
// Reshape that part.
|
// Reshape that part.
|
||||||
let reshaped = shaped.reshape(ctx, range);
|
let reshaped = shaped.reshape(&mut ctx.fonts, range);
|
||||||
last = Some(ParItem::Text(reshaped));
|
last = Some(ParItem::Text(reshaped));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +443,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
// Reshape if necessary.
|
// Reshape if necessary.
|
||||||
if range.len() < shaped.text.len() {
|
if range.len() < shaped.text.len() {
|
||||||
if !range.is_empty() {
|
if !range.is_empty() {
|
||||||
let reshaped = shaped.reshape(ctx, range);
|
let reshaped = shaped.reshape(&mut ctx.fonts, range);
|
||||||
first = Some(ParItem::Text(reshaped));
|
first = Some(ParItem::Text(reshaped));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,12 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
|
|
||||||
/// A single run of text with the same style.
|
/// A single run of text with the same style.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct TextNode(pub EcoString);
|
pub struct TextNode {
|
||||||
|
/// The run's text.
|
||||||
|
pub text: EcoString,
|
||||||
|
/// The run's styles.
|
||||||
|
pub styles: Styles,
|
||||||
|
}
|
||||||
|
|
||||||
properties! {
|
properties! {
|
||||||
TextNode,
|
TextNode,
|
||||||
@ -138,12 +143,12 @@ pub enum FontFamily {
|
|||||||
|
|
||||||
impl Debug for FontFamily {
|
impl Debug for FontFamily {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.pad(match self {
|
match self {
|
||||||
Self::Serif => "serif",
|
Self::Serif => f.pad("serif"),
|
||||||
Self::SansSerif => "sans-serif",
|
Self::SansSerif => f.pad("sans-serif"),
|
||||||
Self::Monospace => "monospace",
|
Self::Monospace => f.pad("monospace"),
|
||||||
Self::Named(s) => s,
|
Self::Named(s) => s.fmt(f),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,28 +334,28 @@ castable! {
|
|||||||
|
|
||||||
/// Shape text into [`ShapedText`].
|
/// Shape text into [`ShapedText`].
|
||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
ctx: &mut LayoutContext,
|
fonts: &mut FontStore,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
styles: &'a Styles,
|
styles: Styles,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
let mut glyphs = vec![];
|
let mut glyphs = vec![];
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
shape_segment(
|
shape_segment(
|
||||||
ctx.fonts,
|
fonts,
|
||||||
&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(ctx, &glyphs, styles);
|
let (size, baseline) = measure(fonts, &glyphs, &styles);
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
text,
|
text,
|
||||||
@ -507,7 +512,7 @@ fn track(glyphs: &mut [ShapedGlyph], tracking: Em) {
|
|||||||
/// Measure the size and baseline of a run of shaped glyphs with the given
|
/// Measure the size and baseline of a run of shaped glyphs with the given
|
||||||
/// properties.
|
/// properties.
|
||||||
fn measure(
|
fn measure(
|
||||||
ctx: &mut LayoutContext,
|
fonts: &mut FontStore,
|
||||||
glyphs: &[ShapedGlyph],
|
glyphs: &[ShapedGlyph],
|
||||||
styles: &Styles,
|
styles: &Styles,
|
||||||
) -> (Size, Length) {
|
) -> (Size, Length) {
|
||||||
@ -529,14 +534,14 @@ fn measure(
|
|||||||
// When there are no glyphs, we just use the vertical metrics of the
|
// When there are no glyphs, we just use the vertical metrics of the
|
||||||
// first available font.
|
// first available font.
|
||||||
for family in families(styles) {
|
for family in families(styles) {
|
||||||
if let Some(face_id) = ctx.fonts.select(family, variant(styles)) {
|
if let Some(face_id) = fonts.select(family, variant(styles)) {
|
||||||
expand(ctx.fonts.get(face_id));
|
expand(fonts.get(face_id));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
|
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
|
||||||
let face = ctx.fonts.get(face_id);
|
let face = fonts.get(face_id);
|
||||||
expand(face);
|
expand(face);
|
||||||
|
|
||||||
for glyph in group {
|
for glyph in group {
|
||||||
@ -685,7 +690,8 @@ 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.
|
||||||
pub styles: &'a Styles,
|
// TODO(set): Go back to reference.
|
||||||
|
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.
|
||||||
@ -749,21 +755,21 @@ impl<'a> ShapedText<'a> {
|
|||||||
/// shaping process if possible.
|
/// shaping process if possible.
|
||||||
pub fn reshape(
|
pub fn reshape(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctx: &mut LayoutContext,
|
fonts: &mut FontStore,
|
||||||
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(ctx, 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,
|
||||||
styles: self.styles,
|
styles: self.styles.clone(),
|
||||||
size,
|
size,
|
||||||
baseline,
|
baseline,
|
||||||
glyphs: Cow::Borrowed(glyphs),
|
glyphs: Cow::Borrowed(glyphs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shape(ctx, &self.text[text_range], self.styles, self.dir)
|
shape(fonts, &self.text[text_range], self.styles.clone(), self.dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user