Add bleed support to page layout

This commit is contained in:
Gabriel Araújo 2025-05-30 23:31:45 -03:00
parent d3caedd813
commit daa9399124
7 changed files with 100 additions and 10 deletions

View File

@ -15,6 +15,7 @@ pub fn finalize(
LayoutedPage {
inner,
mut margin,
bleed,
binding,
two_sided,
header,
@ -34,33 +35,36 @@ pub fn finalize(
}
// Create a frame for the full page.
let mut frame = Frame::hard(inner.size() + margin.sum_by_axis());
let mut frame =
Frame::hard(inner.size() + margin.sum_by_axis() + bleed.sum_by_axis());
// Add tags.
for tag in tags.drain(..) {
frame.push(Point::zero(), FrameItem::Tag(tag));
}
let bleed_start = Point::new(bleed.left, bleed.top);
// Add the "before" marginals. The order in which we push things here is
// important as it affects the relative ordering of introspectable elements
// and thus how counters resolve.
if let Some(background) = background {
frame.push_frame(Point::zero(), background);
frame.push_frame(bleed_start, background);
}
if let Some(header) = header {
frame.push_frame(Point::with_x(margin.left), header);
frame.push_frame(bleed_start + Point::with_x(margin.left), header);
}
// Add the inner contents.
frame.push_frame(Point::new(margin.left, margin.top), inner);
frame.push_frame(bleed_start + Point::new(margin.left, margin.top), inner);
// Add the "after" marginals.
if let Some(footer) = footer {
let y = frame.height() - footer.height();
frame.push_frame(Point::new(margin.left, y), footer);
let y = frame.height() - footer.height() - bleed.bottom;
frame.push_frame(Point::new(margin.left + bleed.left, y), footer);
}
if let Some(foreground) = foreground {
frame.push_frame(Point::zero(), foreground);
frame.push_frame(bleed_start + Point::zero(), foreground);
}
// Apply counter updates from within the page to the manual page counter.
@ -70,5 +74,5 @@ pub fn finalize(
let number = counter.logical();
counter.step();
Ok(Page { frame, fill, numbering, supplement, number })
Ok(Page { frame, bleed, fill, numbering, supplement, number })
}

View File

@ -28,6 +28,7 @@ use crate::flow::{layout_flow, FlowMode};
pub struct LayoutedPage {
pub inner: Frame,
pub margin: Sides<Abs>,
pub bleed: Sides<Abs>,
pub binding: Binding,
pub two_sided: bool,
pub header: Option<Frame>,
@ -123,6 +124,12 @@ fn layout_page_run_impl(
.resolve(styles)
.relative_to(size);
let bleed = PageElem::bleed_in(styles)
.sides
.map(|side| side.and_then(Smart::custom).unwrap_or(Rel::zero()))
.resolve(styles)
.relative_to(size);
let fill = PageElem::fill_in(styles);
let foreground = PageElem::foreground_in(styles);
let background = PageElem::background_in(styles);
@ -215,6 +222,7 @@ fn layout_page_run_impl(
background: layout_marginal(background, full_size, mid)?,
foreground: layout_marginal(foreground, full_size, mid)?,
margin,
bleed,
binding,
two_sided,
});

View File

@ -148,6 +148,46 @@ pub struct PageElem {
#[ghost]
pub margin: Margin,
/// The page's bleed margin.
///
/// The bleed is a part of the content that extends beyond the edge of the
/// final trimmed page. It ensures that there are no unprinted edges in the
/// final product, even if there's a slight misalignment during trimming.
///
/// - `{auto}`: The bleed is set to 0mm on each side.
/// - A single length: The same bleed on all sides.
/// - A dictionary: With a dictionary, the bleed can be set individually.
/// The dictionary can contain the following keys in order of precedence:
/// - `top`: The top bleed.
/// - `right`: The right bleed.
/// - `bottom`: The bottom bleed.
/// - `left`: The left bleed.
/// - `inside`: The bleed at the inner side of the page (where the
/// [binding]($page.binding) is).
/// - `outside`: The bleed at the outer side of the page (opposite to the
/// [binding]($page.binding)).
/// - `x`: The horizontal bleeds.
/// - `y`: The vertical bleeds.
/// - `rest`: The bleeds on all sides except those for which the
/// dictionary explicitly sets a size.
///
/// The values for `left` and `right` are mutually exclusive with
/// the values for `inside` and `outside`.
///
/// On PDF output, if bleed is different of zero, it sets a TrimBox and a
/// BleedBox for the page.
///
/// ```example
/// #set page(
/// width: 3cm,
/// height: 4cm,
/// bleed: 5mm,
/// background: rect(width: 100%, height: 100%, fill: aqua),
/// )
/// ```
#[ghost]
pub bleed: Margin,
/// On which side the pages will be bound.
///
/// - `{auto}`: Equivalent to `left` if the [text direction]($text.dir)
@ -467,6 +507,8 @@ pub struct PagedDocument {
pub struct Page {
/// The frame that defines the page.
pub frame: Frame,
/// The bleed amount to be added on each side of the page.
pub bleed: Sides<Abs>,
/// How the page is filled.
///
/// - When `None`, the background is transparent.

View File

@ -7,7 +7,7 @@ use krilla::configure::{Configuration, ValidationError, Validator};
use krilla::destination::{NamedDestination, XyzDestination};
use krilla::embed::EmbedError;
use krilla::error::KrillaError;
use krilla::geom::PathBuilder;
use krilla::geom::{PathBuilder, Rect};
use krilla::page::{PageLabel, PageSettings};
use krilla::surface::Surface;
use krilla::{Document, SerializeSettings};
@ -16,7 +16,7 @@ use typst_library::diag::{bail, error, SourceDiagnostic, SourceResult};
use typst_library::foundations::{NativeElement, Repr};
use typst_library::introspection::Location;
use typst_library::layout::{
Abs, Frame, FrameItem, GroupItem, PagedDocument, Size, Transform,
Abs, Frame, FrameItem, GroupItem, PagedDocument, Sides, Size, Transform,
};
use typst_library::model::HeadingElem;
use typst_library::text::{Font, Lang};
@ -81,6 +81,22 @@ fn convert_pages(gc: &mut GlobalContext, document: &mut Document) -> SourceResul
typst_page.frame.height().to_f32(),
);
if typst_page.bleed != Sides::splat(Abs::zero()) {
settings = settings
.with_bleed_box(Rect::from_xywh(
0.0,
0.0,
typst_page.frame.width().to_f32(),
typst_page.frame.height().to_f32(),
))
.with_trim_box(Rect::from_ltrb(
typst_page.bleed.left.to_f32(),
typst_page.bleed.top.to_f32(),
(typst_page.frame.width() - typst_page.bleed.right).to_f32(),
(typst_page.frame.height() - typst_page.bleed.bottom).to_f32(),
));
}
if let Some(label) = typst_page
.numbering
.as_ref()

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
tests/ref/page-bleed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

View File

@ -348,6 +348,26 @@ A
A
]
--- page-bleed ---
#set page(
bleed: 20pt,
margin: 20pt,
height: 80pt,
width: 80pt,
background: rect(width: 100%, height: 100%, fill: gray),
)
#rect(width: 100%, height: 100%, fill: black)
--- page-bleed-content-bleeding ---
#set page(
bleed: 20pt,
margin: 20pt,
height: 80pt,
width: 80pt,
)
#set align(center + horizon)
#rect(width: 100pt, height: 100pt, fill: black)
--- issue-2631-page-header-ordering ---
#set text(6pt)
#show heading: set text(6pt, weight: "regular")