mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Page background and foreground
This commit is contained in:
parent
1a7ce3da02
commit
90be79dc86
88
src/frame.rs
88
src/frame.rs
@ -37,6 +37,34 @@ impl Frame {
|
|||||||
self.baseline.unwrap_or(self.size.y)
|
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.
|
/// Add an element at a position in the background.
|
||||||
pub fn prepend(&mut self, pos: Point, element: Element) {
|
pub fn prepend(&mut self, pos: Point, element: Element) {
|
||||||
self.elements.insert(0, (pos, element));
|
self.elements.insert(0, (pos, element));
|
||||||
@ -50,15 +78,14 @@ impl Frame {
|
|||||||
self.elements.splice(0 .. 0, insert);
|
self.elements.splice(0 .. 0, insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an element at a position in the foreground.
|
/// Add a frame at a position in the background.
|
||||||
pub fn push(&mut self, pos: Point, element: Element) {
|
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
self.elements.push((pos, element));
|
if self.elements.is_empty() || frame.as_ref().is_light() {
|
||||||
}
|
frame.inline(self, 0, pos);
|
||||||
|
} else {
|
||||||
/// The layer the next item will be added on. This corresponds to the number
|
self.elements
|
||||||
/// of elements in the frame.
|
.insert(0, (pos, Element::Group(Group::new(frame.share()))));
|
||||||
pub fn layer(&self) -> usize {
|
}
|
||||||
self.elements.len()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert an element at the given layer in the frame.
|
/// Insert an element at the given layer in the frame.
|
||||||
@ -68,18 +95,6 @@ impl Frame {
|
|||||||
self.elements.insert(layer, (pos, element));
|
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
|
/// Resize the frame to a new size, distributing new space according to the
|
||||||
/// given alignments.
|
/// given alignments.
|
||||||
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
||||||
@ -153,7 +168,7 @@ pub trait FrameRepr: AsRef<Frame> {
|
|||||||
fn share(self) -> Arc<Frame>;
|
fn share(self) -> Arc<Frame>;
|
||||||
|
|
||||||
/// Inline `self` into the sink 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 {
|
impl FrameRepr for Frame {
|
||||||
@ -161,16 +176,18 @@ impl FrameRepr for Frame {
|
|||||||
Arc::new(self)
|
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 offset.is_zero() {
|
||||||
if sink.elements.is_empty() {
|
if sink.elements.is_empty() {
|
||||||
sink.elements = self.elements;
|
sink.elements = self.elements;
|
||||||
} else {
|
} else {
|
||||||
sink.elements.extend(self.elements);
|
sink.elements.splice(layer .. layer, self.elements);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sink.elements
|
sink.elements.splice(
|
||||||
.extend(self.elements.into_iter().map(|(p, e)| (p + offset, e)));
|
layer .. layer,
|
||||||
|
self.elements.into_iter().map(|(p, e)| (p + offset, e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,12 +197,15 @@ impl FrameRepr for Arc<Frame> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inline(self, sink: &mut Frame, offset: Point) {
|
fn inline(self, sink: &mut Frame, layer: usize, offset: Point) {
|
||||||
match Arc::try_unwrap(self) {
|
match Arc::try_unwrap(self) {
|
||||||
Ok(frame) => frame.inline(sink, offset),
|
Ok(frame) => frame.inline(sink, layer, offset),
|
||||||
Err(rc) => sink
|
Err(rc) => {
|
||||||
.elements
|
sink.elements.splice(
|
||||||
.extend(rc.elements.iter().cloned().map(|(p, e)| (p + offset, e))),
|
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 {
|
match self {
|
||||||
Self::Owned(owned) => owned.inline(sink, offset),
|
Self::Owned(owned) => owned.inline(sink, layer, offset),
|
||||||
Self::Shared(shared) => shared.inline(sink, offset),
|
Self::Shared(shared) => shared.inline(sink, layer, offset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,12 @@ impl PageNode {
|
|||||||
/// The page's footer.
|
/// The page's footer.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const FOOTER: Marginal = Marginal::None;
|
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> {
|
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::Page(Self(args.expect("body")?)))
|
Ok(Content::Page(Self(args.expect("body")?)))
|
||||||
@ -95,22 +101,29 @@ impl PageNode {
|
|||||||
|
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get(Self::FOOTER);
|
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 {
|
for frame in &mut frames {
|
||||||
let size = frame.size;
|
let size = frame.size;
|
||||||
let padding = padding.resolve(styles).relative_to(size);
|
let pad = padding.resolve(styles).relative_to(size);
|
||||||
for (y, h, marginal) in [
|
let pw = size.x - pad.left - pad.right;
|
||||||
(Length::zero(), padding.top, header),
|
let py = size.y - pad.bottom;
|
||||||
(size.y - padding.bottom, padding.bottom, footer),
|
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)? {
|
if let Some(content) = marginal.resolve(ctx, page)? {
|
||||||
let pos = Point::new(padding.left, y);
|
let pod = Regions::one(area, area, Spec::splat(true));
|
||||||
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 sub = content.layout(ctx, &pod, styles)?.remove(0);
|
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)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum Marginal {
|
pub enum Marginal {
|
||||||
/// Nothing,
|
/// 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 |
@ -32,16 +32,43 @@
|
|||||||
..{
|
..{
|
||||||
let seen = ()
|
let seen = ()
|
||||||
for item in all {
|
for item in all {
|
||||||
if item.value not in seen {
|
if item.value in seen { continue }
|
||||||
seen.push(item.value)
|
(num(all, item.value), item.value)
|
||||||
(num(all, item.value), item.value)
|
seen.push(item.value)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))}
|
))}
|
||||||
|
|
||||||
As shown in #cite("abc") and #cite("def") and #cite("abc") ...
|
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`.
|
// Test that `all` contains `me`.
|
||||||
// Ref: false
|
// Ref: false
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
text(0.8em)[_Chapter 1_]
|
text(0.8em)[_Chapter 1_]
|
||||||
}),
|
}),
|
||||||
footer: page => v(5pt) + align(center)[\~ #page \~],
|
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
|
But, soft! what light through yonder window breaks? It is the east, and Juliet
|
||||||
|
Loading…
x
Reference in New Issue
Block a user