Page background and foreground

This commit is contained in:
Laurenz 2022-05-28 00:26:24 +02:00
parent 1a7ce3da02
commit 90be79dc86
6 changed files with 112 additions and 49 deletions

View File

@ -37,6 +37,34 @@ impl Frame {
self.baseline.unwrap_or(self.size.y)
}
/// The layer the next item will be added on. This corresponds to the number
/// of elements in the frame.
pub fn layer(&self) -> usize {
self.elements.len()
}
/// Whether the frame has comparatively few elements.
pub fn is_light(&self) -> bool {
self.elements.len() <= 5
}
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
self.elements.push((pos, element));
}
/// Add a frame.
///
/// Automatically decides whether to inline the frame or to include it as a
/// group based on the number of elements in the frame.
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
if self.elements.is_empty() || frame.as_ref().is_light() {
frame.inline(self, self.layer(), pos);
} else {
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
}
}
/// Add an element at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) {
self.elements.insert(0, (pos, element));
@ -50,15 +78,14 @@ impl Frame {
self.elements.splice(0 .. 0, insert);
}
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
self.elements.push((pos, element));
}
/// The layer the next item will be added on. This corresponds to the number
/// of elements in the frame.
pub fn layer(&self) -> usize {
self.elements.len()
/// Add a frame at a position in the background.
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
if self.elements.is_empty() || frame.as_ref().is_light() {
frame.inline(self, 0, pos);
} else {
self.elements
.insert(0, (pos, Element::Group(Group::new(frame.share()))));
}
}
/// Insert an element at the given layer in the frame.
@ -68,18 +95,6 @@ impl Frame {
self.elements.insert(layer, (pos, element));
}
/// Add a frame.
///
/// Automatically decides whether to inline the frame or to include it as a
/// group based on the number of elements in the frame.
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
if self.elements.is_empty() || frame.as_ref().elements.len() <= 5 {
frame.inline(self, pos);
} else {
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
}
}
/// Resize the frame to a new size, distributing new space according to the
/// given alignments.
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
@ -153,7 +168,7 @@ pub trait FrameRepr: AsRef<Frame> {
fn share(self) -> Arc<Frame>;
/// Inline `self` into the sink frame.
fn inline(self, sink: &mut Frame, offset: Point);
fn inline(self, sink: &mut Frame, layer: usize, offset: Point);
}
impl FrameRepr for Frame {
@ -161,16 +176,18 @@ impl FrameRepr for Frame {
Arc::new(self)
}
fn inline(self, sink: &mut Frame, offset: Point) {
fn inline(self, sink: &mut Frame, layer: usize, offset: Point) {
if offset.is_zero() {
if sink.elements.is_empty() {
sink.elements = self.elements;
} else {
sink.elements.extend(self.elements);
sink.elements.splice(layer .. layer, self.elements);
}
} else {
sink.elements
.extend(self.elements.into_iter().map(|(p, e)| (p + offset, e)));
sink.elements.splice(
layer .. layer,
self.elements.into_iter().map(|(p, e)| (p + offset, e)),
);
}
}
}
@ -180,12 +197,15 @@ impl FrameRepr for Arc<Frame> {
self
}
fn inline(self, sink: &mut Frame, offset: Point) {
fn inline(self, sink: &mut Frame, layer: usize, offset: Point) {
match Arc::try_unwrap(self) {
Ok(frame) => frame.inline(sink, offset),
Err(rc) => sink
.elements
.extend(rc.elements.iter().cloned().map(|(p, e)| (p + offset, e))),
Ok(frame) => frame.inline(sink, layer, offset),
Err(rc) => {
sink.elements.splice(
layer .. layer,
rc.elements.iter().cloned().map(|(p, e)| (p + offset, e)),
);
}
}
}
}
@ -198,10 +218,10 @@ impl FrameRepr for MaybeShared<Frame> {
}
}
fn inline(self, sink: &mut Frame, offset: Point) {
fn inline(self, sink: &mut Frame, layer: usize, offset: Point) {
match self {
Self::Owned(owned) => owned.inline(sink, offset),
Self::Shared(shared) => shared.inline(sink, offset),
Self::Owned(owned) => owned.inline(sink, layer, offset),
Self::Shared(shared) => shared.inline(sink, layer, offset),
}
}
}

View File

@ -34,6 +34,12 @@ impl PageNode {
/// The page's footer.
#[property(referenced)]
pub const FOOTER: Marginal = Marginal::None;
/// Content in the page's background.
#[property(referenced)]
pub const BACKGROUND: Marginal = Marginal::None;
/// Content in the page's foreground.
#[property(referenced)]
pub const FOREGROUND: Marginal = Marginal::None;
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
Ok(Content::Page(Self(args.expect("body")?)))
@ -95,22 +101,29 @@ impl PageNode {
let header = styles.get(Self::HEADER);
let footer = styles.get(Self::FOOTER);
let foreground = styles.get(Self::FOREGROUND);
let background = styles.get(Self::BACKGROUND);
// Realize header and footer.
// Realize overlays.
for frame in &mut frames {
let size = frame.size;
let padding = padding.resolve(styles).relative_to(size);
for (y, h, marginal) in [
(Length::zero(), padding.top, header),
(size.y - padding.bottom, padding.bottom, footer),
let pad = padding.resolve(styles).relative_to(size);
let pw = size.x - pad.left - pad.right;
let py = size.y - pad.bottom;
for (marginal, pos, area) in [
(header, Point::with_x(pad.left), Size::new(pw, pad.top)),
(footer, Point::new(pad.left, py), Size::new(pw, pad.bottom)),
(foreground, Point::zero(), size),
(background, Point::zero(), size),
] {
if let Some(content) = marginal.resolve(ctx, page)? {
let pos = Point::new(padding.left, y);
let w = size.x - padding.left - padding.right;
let area = Size::new(w, h);
let pod = Regions::one(area, area, area.map(Length::is_finite));
let pod = Regions::one(area, area, Spec::splat(true));
let sub = content.layout(ctx, &pod, styles)?.remove(0);
Arc::make_mut(frame).push_frame(pos, sub);
if std::ptr::eq(marginal, background) {
Arc::make_mut(frame).prepend_frame(pos, sub);
} else {
Arc::make_mut(frame).push_frame(pos, sub);
}
}
}
@ -140,7 +153,7 @@ impl PagebreakNode {
}
}
/// A header or footer definition.
/// A header, footer, foreground or background definition.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Marginal {
/// Nothing,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -32,16 +32,43 @@
..{
let seen = ()
for item in all {
if item.value not in seen {
seen.push(item.value)
(num(all, item.value), item.value)
}
if item.value in seen { continue }
(num(all, item.value), item.value)
seen.push(item.value)
}
}
))}
As shown in #cite("abc") and #cite("def") and #cite("abc") ...
---
// Test lovely sidebar.
#let lovely = group("lovely")
#let words = ("Juliet", "soft", "fair", "maid")
#let regex = regex(words.map(p => "(" + p + ")").join("|"))
#show word: regex as underline(word) + lovely.entry(_ => {})
#set page(
paper: "a8",
margins: (left: 25pt, rest: 15pt),
foreground: lovely.all(entries => {
let seen = ()
for y in entries.map(it => it.y) {
if y in seen { continue }
let line = entries.filter(it => it.y == y)
for i, it in line {
let x = 10pt - 4pt * (line.len() - i - 1)
place(dx: x, dy: it.y - 8pt, [💗])
}
seen.push(y)
}
}),
)
But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
pale with grief, That thou her maid art far more fair than she: Be not her maid,
since she is envious.
---
// Test that `all` contains `me`.
// Ref: false

View File

@ -7,6 +7,9 @@
text(0.8em)[_Chapter 1_]
}),
footer: page => v(5pt) + align(center)[\~ #page \~],
background: n => if n <= 2 {
place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
}
)
But, soft! what light through yonder window breaks? It is the east, and Juliet