Compare commits

...

4 Commits

Author SHA1 Message Date
Gabriel Araújo
2b58876817
Merge 263cd4d1d96437b07da39ac37553f44cd184a522 into d1deb80bb8b4d5fad348c23a3e079e4854aa57e4 2025-07-06 12:44:13 +00:00
Gabriel Araújo
263cd4d1d9 Improve documentation for page bleed 2025-07-06 09:44:10 -03:00
Gabriel Araújo
9ae94b2f64 rename bleed_start with content_origin 2025-07-06 09:44:10 -03:00
Gabriel Araújo
6094516a63 Add bleed support to page layout 2025-07-06 09:44:10 -03:00
7 changed files with 101 additions and 10 deletions

View File

@ -15,6 +15,7 @@ pub fn finalize(
LayoutedPage { LayoutedPage {
inner, inner,
mut margin, mut margin,
bleed,
binding, binding,
two_sided, two_sided,
header, header,
@ -34,33 +35,36 @@ pub fn finalize(
} }
// Create a frame for the full page. // 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. // Add tags.
for tag in tags.drain(..) { for tag in tags.drain(..) {
frame.push(Point::zero(), FrameItem::Tag(tag)); frame.push(Point::zero(), FrameItem::Tag(tag));
} }
let content_origin = Point::new(bleed.left, bleed.top);
// Add the "before" marginals. The order in which we push things here is // Add the "before" marginals. The order in which we push things here is
// important as it affects the relative ordering of introspectable elements // important as it affects the relative ordering of introspectable elements
// and thus how counters resolve. // and thus how counters resolve.
if let Some(background) = background { if let Some(background) = background {
frame.push_frame(Point::zero(), background); frame.push_frame(content_origin, background);
} }
if let Some(header) = header { if let Some(header) = header {
frame.push_frame(Point::with_x(margin.left), header); frame.push_frame(content_origin + Point::with_x(margin.left), header);
} }
// Add the inner contents. // Add the inner contents.
frame.push_frame(Point::new(margin.left, margin.top), inner); frame.push_frame(content_origin + Point::new(margin.left, margin.top), inner);
// Add the "after" marginals. // Add the "after" marginals.
if let Some(footer) = footer { if let Some(footer) = footer {
let y = frame.height() - footer.height(); let y = frame.height() - footer.height() - bleed.bottom;
frame.push_frame(Point::new(margin.left, y), footer); frame.push_frame(Point::new(margin.left + bleed.left, y), footer);
} }
if let Some(foreground) = foreground { if let Some(foreground) = foreground {
frame.push_frame(Point::zero(), foreground); frame.push_frame(content_origin + Point::zero(), foreground);
} }
// Apply counter updates from within the page to the manual page counter. // Apply counter updates from within the page to the manual page counter.
@ -70,5 +74,5 @@ pub fn finalize(
let number = counter.logical(); let number = counter.logical();
counter.step(); 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 struct LayoutedPage {
pub inner: Frame, pub inner: Frame,
pub margin: Sides<Abs>, pub margin: Sides<Abs>,
pub bleed: Sides<Abs>,
pub binding: Binding, pub binding: Binding,
pub two_sided: bool, pub two_sided: bool,
pub header: Option<Frame>, pub header: Option<Frame>,
@ -123,6 +124,12 @@ fn layout_page_run_impl(
.resolve(styles) .resolve(styles)
.relative_to(size); .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 fill = PageElem::fill_in(styles);
let foreground = PageElem::foreground_in(styles); let foreground = PageElem::foreground_in(styles);
let background = PageElem::background_in(styles); let background = PageElem::background_in(styles);
@ -215,6 +222,7 @@ fn layout_page_run_impl(
background: layout_marginal(background, full_size, mid)?, background: layout_marginal(background, full_size, mid)?,
foreground: layout_marginal(foreground, full_size, mid)?, foreground: layout_marginal(foreground, full_size, mid)?,
margin, margin,
bleed,
binding, binding,
two_sided, two_sided,
}); });

View File

@ -148,6 +148,47 @@ pub struct PageElem {
#[ghost] #[ghost]
pub margin: Margin, pub margin: Margin,
/// The page's bleed margin.
///
/// The bleed is the area of content that extends beyond the final trimmed
/// size of the page. It ensures that no unprinted edges appear in the final
/// product, even if minor trimming misalignments occur.
///
/// Accepted values:
///
/// - `{auto}`: Sets the bleed to `0mm` on all sides.
/// - A single length: Applies the same bleed to all sides.
/// - A dictionary: Allows setting bleed values individually. The dictionary
/// may include the following keys, listed in order of precedence:
/// - `top`: Bleed at the top of the page.
/// - `right`: Bleed at the right side.
/// - `bottom`: Bleed at the bottom.
/// - `left`: Bleed at the left side.
/// - `inside`: Bleed on the inner side of the page (next to
/// [binding]($page.binding)).
/// - `outside`: Bleed on the outer side of the page (opposite the
/// [binding]($page.binding)).
/// - `x`: Horizontal bleed (applies to both left/right or inside/outside).
/// - `y`: Vertical bleed (applies to both top and bottom).
/// - `rest`: Default bleed for any sides not explicitly set.
///
/// Note: The keys `left` and `right` are mutually exclusive with `inside` and
/// `outside`.
///
/// On PDF output, if the bleed is non-zero, a `TrimBox` and a `BleedBox` are
/// defined 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. /// On which side the pages will be bound.
/// ///
/// - `{auto}`: Equivalent to `left` if the [text direction]($text.dir) /// - `{auto}`: Equivalent to `left` if the [text direction]($text.dir)
@ -467,6 +508,8 @@ pub struct PagedDocument {
pub struct Page { pub struct Page {
/// The frame that defines the page. /// The frame that defines the page.
pub frame: Frame, pub frame: Frame,
/// The bleed amount to be added on each side of the page.
pub bleed: Sides<Abs>,
/// How the page is filled. /// How the page is filled.
/// ///
/// - When `None`, the background is transparent. /// - 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::destination::{NamedDestination, XyzDestination};
use krilla::embed::EmbedError; use krilla::embed::EmbedError;
use krilla::error::KrillaError; use krilla::error::KrillaError;
use krilla::geom::PathBuilder; use krilla::geom::{PathBuilder, Rect};
use krilla::page::{PageLabel, PageSettings}; use krilla::page::{PageLabel, PageSettings};
use krilla::surface::Surface; use krilla::surface::Surface;
use krilla::{Document, SerializeSettings}; 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::foundations::{NativeElement, Repr};
use typst_library::introspection::Location; use typst_library::introspection::Location;
use typst_library::layout::{ 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::model::HeadingElem;
use typst_library::text::{Font, Lang}; 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(), 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 if let Some(label) = typst_page
.numbering .numbering
.as_ref() .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 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 --- --- issue-2631-page-header-ordering ---
#set text(6pt) #set text(6pt)
#show heading: set text(6pt, weight: "regular") #show heading: set text(6pt, weight: "regular")