summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-09 13:42:52 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-09 13:42:52 +0100
commitfe21c4d399d291e75165b664762f0aa8bdc4724a (patch)
treea3ec954df6e66f6504f4416b37600cedf95dd7e1 /src/library
parent40b87d4066fe85cb3fde6cf84cd60d748273ae25 (diff)
Set Rules Episode III: Revenge of the packer
Diffstat (limited to 'src/library')
-rw-r--r--src/library/align.rs11
-rw-r--r--src/library/deco.rs4
-rw-r--r--src/library/flow.rs9
-rw-r--r--src/library/page.rs33
-rw-r--r--src/library/par.rs28
-rw-r--r--src/library/text.rs52
6 files changed, 80 insertions, 57 deletions
diff --git a/src/library/align.rs b/src/library/align.rs
index 76db7fc4..96a1c6c5 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -1,12 +1,19 @@
use super::prelude::*;
+use super::ParNode;
/// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let aligns = args.expect::<Spec<_>>("alignment")?;
let body = args.expect::<Node>("body")?;
- // TODO(set): Style paragraphs with x alignment.
- Ok(Value::block(body.into_block().aligned(aligns)))
+ let mut styles = Styles::new();
+ 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.
diff --git a/src/library/deco.rs b/src/library/deco.rs
index b1ca030a..d12f60b0 100644
--- a/src/library/deco.rs
+++ b/src/library/deco.rs
@@ -22,7 +22,7 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
let offset = args.named("offset")?;
let extent = args.named("extent")?.unwrap_or_default();
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 },
))))
}
@@ -37,7 +37,7 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
}
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.
diff --git a/src/library/flow.rs b/src/library/flow.rs
index dddd38a4..41760e51 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter};
use super::prelude::*;
-use super::{AlignNode, PlacedNode, Spacing};
+use super::{AlignNode, ParNode, PlacedNode, Spacing};
/// `flow`: A vertical flow of paragraphs and other layout nodes.
pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -158,6 +158,13 @@ impl<'a> FlowLayouter<'a> {
/// Layout a node.
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>() {
let frame = node.layout(ctx, &self.regions).remove(0);
if placed.out_of_flow() {
diff --git a/src/library/page.rs b/src/library/page.rs
index a4ad84f6..490eef66 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -49,7 +49,12 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
/// Layouts its child onto one or multiple pages.
#[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! {
PageNode,
@@ -77,30 +82,31 @@ properties! {
impl PageNode {
/// Layout the page run into a sequence of frames, one per page.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
- // TODO(set): Take styles as parameter.
- let styles = Styles::new();
+ // TODO(set): Use chaining.
+ 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
// that axis.
- let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
- let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
+ let width = ctx.styles.get(Self::WIDTH).unwrap_or(Length::inf());
+ let height = ctx.styles.get(Self::HEIGHT).unwrap_or(Length::inf());
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);
}
// Determine the margins.
- let class = styles.get(Self::CLASS);
+ let class = ctx.styles.get(Self::CLASS);
let default = class.default_margins();
let padding = Sides {
- left: styles.get(Self::LEFT).unwrap_or(default.left),
- right: styles.get(Self::RIGHT).unwrap_or(default.right),
- top: styles.get(Self::TOP).unwrap_or(default.top),
- bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom),
+ left: ctx.styles.get(Self::LEFT).unwrap_or(default.left),
+ right: ctx.styles.get(Self::RIGHT).unwrap_or(default.right),
+ top: ctx.styles.get(Self::TOP).unwrap_or(default.top),
+ bottom: ctx.styles.get(Self::BOTTOM).unwrap_or(default.bottom),
};
// Pad the child.
- let padded = PadNode { child: self.0.clone(), padding }.pack();
+ let padded = PadNode { child: self.node.clone(), padding }.pack();
// Layout the child.
let expand = size.map(Length::is_finite);
@@ -109,13 +115,14 @@ impl PageNode {
padded.layout(ctx, &regions).into_iter().map(|c| c.item).collect();
// 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 {
let shape = Shape::filled(Geometry::Rect(frame.size), fill);
Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
}
}
+ ctx.styles = prev;
frames
}
}
diff --git a/src/library/par.rs b/src/library/par.rs
index 21760225..e7433e3e 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -73,19 +73,16 @@ impl Layout for ParNode {
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- // TODO(set): Take styles as parameter.
- let styles = Styles::new();
-
// Collect all text into one string used for BiDi analysis.
let text = self.collect_text();
// 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);
// Prepare paragraph layout by building a representation on which we can
// 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.
layouter.layout(ctx, regions.clone())
@@ -119,8 +116,8 @@ impl ParNode {
fn strings(&self) -> impl Iterator<Item = &str> {
self.0.iter().map(|child| match child {
ParChild::Spacing(_) => " ",
- ParChild::Text(ref piece, ..) => &piece.0,
- ParChild::Node(..) => "\u{FFFC}",
+ ParChild::Text(ref node) => &node.text,
+ ParChild::Node(_) => "\u{FFFC}",
ParChild::Decorate(_) | ParChild::Undecorate => "",
})
}
@@ -132,7 +129,6 @@ pub enum ParChild {
/// Spacing between other nodes.
Spacing(Spacing),
/// 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),
/// Any child node and how to align it in its line.
Node(PackedNode),
@@ -146,7 +142,7 @@ impl Debug for ParChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
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::Decorate(deco) => write!(f, "Decorate({:?})", deco),
Self::Undecorate => write!(f, "Undecorate"),
@@ -193,7 +189,6 @@ impl<'a> ParLayouter<'a> {
ctx: &mut LayoutContext,
regions: &Regions,
bidi: BidiInfo<'a>,
- styles: &'a Styles,
) -> Self {
let mut items = vec![];
let mut ranges = vec![];
@@ -212,7 +207,7 @@ impl<'a> ParLayouter<'a> {
items.push(ParItem::Fractional(v));
ranges.push(range);
}
- ParChild::Text(_) => {
+ ParChild::Text(ref node) => {
// TODO: Also split by language and script.
let mut cursor = range.start;
for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) {
@@ -220,7 +215,8 @@ impl<'a> ParLayouter<'a> {
cursor += group.len();
let subrange = start .. cursor;
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));
ranges.push(subrange);
}
@@ -248,8 +244,8 @@ impl<'a> ParLayouter<'a> {
}
Self {
- align: styles.get(ParNode::ALIGN),
- leading: styles.get(ParNode::LEADING),
+ align: ctx.styles.get(ParNode::ALIGN),
+ leading: ctx.styles.get(ParNode::LEADING),
bidi,
items,
ranges,
@@ -426,7 +422,7 @@ impl<'a> LineLayout<'a> {
// empty string.
if !range.is_empty() || rest.is_empty() {
// Reshape that part.
- let reshaped = shaped.reshape(ctx, range);
+ let reshaped = shaped.reshape(&mut ctx.fonts, range);
last = Some(ParItem::Text(reshaped));
}
@@ -447,7 +443,7 @@ impl<'a> LineLayout<'a> {
// Reshape if necessary.
if range.len() < shaped.text.len() {
if !range.is_empty() {
- let reshaped = shaped.reshape(ctx, range);
+ let reshaped = shaped.reshape(&mut ctx.fonts, range);
first = Some(ParItem::Text(reshaped));
}
diff --git a/src/library/text.rs b/src/library/text.rs
index 01218087..e8bb6093 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -53,7 +53,12 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// A single run of text with the same style.
#[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! {
TextNode,
@@ -138,12 +143,12 @@ pub enum FontFamily {
impl Debug for FontFamily {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Self::Serif => "serif",
- Self::SansSerif => "sans-serif",
- Self::Monospace => "monospace",
- Self::Named(s) => s,
- })
+ match self {
+ Self::Serif => f.pad("serif"),
+ Self::SansSerif => f.pad("sans-serif"),
+ Self::Monospace => f.pad("monospace"),
+ Self::Named(s) => s.fmt(f),
+ }
}
}
@@ -329,28 +334,28 @@ castable! {
/// Shape text into [`ShapedText`].
pub fn shape<'a>(
- ctx: &mut LayoutContext,
+ fonts: &mut FontStore,
text: &'a str,
- styles: &'a Styles,
+ styles: Styles,
dir: Dir,
) -> ShapedText<'a> {
let mut glyphs = vec![];
if !text.is_empty() {
shape_segment(
- ctx.fonts,
+ fonts,
&mut glyphs,
0,
text,
- variant(styles),
- families(styles),
+ variant(&styles),
+ families(&styles),
None,
dir,
- &tags(styles),
+ &tags(&styles),
);
}
track(&mut glyphs, styles.get(TextNode::TRACKING));
- let (size, baseline) = measure(ctx, &glyphs, styles);
+ let (size, baseline) = measure(fonts, &glyphs, &styles);
ShapedText {
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
/// properties.
fn measure(
- ctx: &mut LayoutContext,
+ fonts: &mut FontStore,
glyphs: &[ShapedGlyph],
styles: &Styles,
) -> (Size, Length) {
@@ -529,14 +534,14 @@ fn measure(
// When there are no glyphs, we just use the vertical metrics of the
// first available font.
for family in families(styles) {
- if let Some(face_id) = ctx.fonts.select(family, variant(styles)) {
- expand(ctx.fonts.get(face_id));
+ if let Some(face_id) = fonts.select(family, variant(styles)) {
+ expand(fonts.get(face_id));
break;
}
}
} else {
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);
for glyph in group {
@@ -685,7 +690,8 @@ pub struct ShapedText<'a> {
/// The text direction.
pub dir: Dir,
/// The text's style properties.
- pub styles: &'a Styles,
+ // TODO(set): Go back to reference.
+ pub styles: Styles,
/// The font size.
pub size: Size,
/// The baseline from the top of the frame.
@@ -749,21 +755,21 @@ impl<'a> ShapedText<'a> {
/// shaping process if possible.
pub fn reshape(
&'a self,
- ctx: &mut LayoutContext,
+ fonts: &mut FontStore,
text_range: Range<usize>,
) -> ShapedText<'a> {
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 {
text: &self.text[text_range],
dir: self.dir,
- styles: self.styles,
+ styles: self.styles.clone(),
size,
baseline,
glyphs: Cow::Borrowed(glyphs),
}
} else {
- shape(ctx, &self.text[text_range], self.styles, self.dir)
+ shape(fonts, &self.text[text_range], self.styles.clone(), self.dir)
}
}