Page resizing and bleed
This page covers two jobs that change the paper a page sits on. Resizing changes the size of pages that already have content, for example to turn an A4 document into A5 or to put a batch of mixed sizes onto one paper size. Bleed prepares a page for a commercial press: the artwork runs past the edge of the page onto a larger sheet, which is then cut to size along crop marks.
Both are in the core PdfPinata package. PageSize, PageOrientation and PageResizeOptions are
in the PdfPinata namespace.
Resize every page of a document
ResizePages changes the size of every page and scales the content to fit:
// One call. The drawing on both pages is scaled to 70.6%, the link rectangle shrinks with
// the words underneath it, and the destination it points at moves to where those words
// ended up. Setting page.Size on a page that has been drawn on now throws and names
// Resize, because writing a new media box would crop the page and leave the link behind.
document.ResizePages(PageSize.A5);
The content is scaled, not cropped. Everything that refers to a position on a page moves with it:
- the rectangles of links and other annotations on the page;
- every destination that points at the page, wherever it is held: links on other pages, bookmarks, named destinations and the action that runs when the document opens.
A destination keeps its zoom level. A link that showed a page at 100% still does after a resize.
ResizePages also takes a size in points as an XSize. To resize one page, call page.Resize
with the same arguments. To resize every page, use ResizePages: finding the destinations that
point at a page means reading the whole document, and ResizePages does that once rather than once
for each page.
Choose how the content fits
The optional PageResizeOptions decides how the old page is mapped onto the new one:
| Property | Default | What it does |
|---|---|---|
Fit | PageFitMode.Fit | Fit scales until the whole page fits. Fill scales until the new page is covered and crops the overflow. Stretch scales each direction on its own and distorts the content. None does not scale and crops what does not fit. |
Alignment | PageAlignment.MiddleCenter | Where the content sits when there is space left over, and which part is kept when there is not. |
Margin | 0 | An empty border on all four sides of the new page. |
AutoRotate | false | Turns the content a quarter when the old and new pages are of opposite shape, landscape against portrait, instead of shrinking it. |
ScaleAnnotations | true | Moves the annotations with the content. |
ScaleDestinations | true | Moves the destinations that point at the page. |
To put a batch of mixed pages onto A4 with a 10 mm border:
using PdfPinata;
using PdfPinata.Drawing;
document.ResizePages(PageSize.A4, PageOrientation.Portrait, new PageResizeOptions
{
Margin = XUnit.FromMillimeter(10),
AutoRotate = true,
});
PageResizeOptions.Crop keeps the content at its size and crops it to the new page, anchored at
the top left.
Why Size, Width and Height refuse
Setting page.Size, page.Width or page.Height on a page that already has content throws
InvalidOperationException, and the message names Resize. Those setters only write a new page
rectangle. On a page with content that crops the page from its bottom-left corner, which keeps the
foot of the page and throws away the heading. Set the size before you draw, or call Resize after.
What cannot be resized
ResizePages checks the whole document before it changes anything, and refuses:
- a document read from an encrypted file. A document you created and set a password on is fine.
- a signed document. The signature would no longer match the document.
- a tagged document, one with a structure tree for screen readers and other assistive technology. Resizing moves each page's content into a wrapper that the structure tree cannot point into. The page would look correct and print correctly, but a screen reader would find nothing on it, and no visual check would show the damage. So the library refuses instead.
- a document not open for modification, or a page with an
XGraphicsstill open on it.
PinataLayout tags its output by default. To resize a document you render with PinataLayout, set
TagContent = false on the PdfDocumentRenderer before you call RenderDocument. Better still, set
the page size you want in the document's page setup, so that PinataLayout lays the text out for that
size in the first place. See Documents, sections and styles.
The five page boxes
A PDF page has up to five rectangles, each a PdfRectangle property of PdfPage:
| Box | Means |
|---|---|
MediaBox | The whole sheet. Every page has one. |
CropBox | The part a PDF reader shows. When a page has none, it is the media box. |
BleedBox | How far artwork may run past the finished page. |
TrimBox | The finished page, where the sheet is cut. |
ArtBox | The meaningful content of the page. |
You rarely set these by hand. The next section sets all five for you.
Add bleed and crop marks
Set TrimMargins on a page to give it bleed. The sheet written to the file grows by the margin on
each edge, but the page you draw on does not:
// Three millimetres is what a printer asks for and what page-layout applications
// default to: enough that a cut landing slightly off the mark still falls on ink.
var bleed = XUnit.FromMillimeter(3);
var page = document.AddPage();
page.Size = PageSize.A5;
// The one line that makes this a bleeding page. The sheet written to the file grows by
// the margin on each edge, but the page a caller draws on does not: the origin moves to
// the corner of the *trimmed* page, so every coordinate below is measured from where the
// paper will be cut. Nothing in this demo would have to change if the margin were
// removed - it would simply lose its bleed.
page.TrimMargins.All = bleed;
The drawing origin stays at the top-left corner of the trimmed page. Code that draws inside the
page is the same as on a page without bleed, and page.Width and page.Height still report the
trimmed size. To make artwork run off the edge, draw at negative coordinates, or past the width and
height:
// Negative coordinates are the whole technique. The rectangle starts one bleed above and
// one bleed left of the origin and is two bleeds wider than the page, so it covers the
// sheet on the top, left and right and stops short of the bottom.
var bled = new XRect(-over, -over, width + 2 * over, height * 0.62 + over);
Outside the bleed there is room for printer's marks, set by MarkMargins:
// Outside the bleed there is a further margin, five millimetres unless it is changed,
// which is the room the press needs around the artwork. The eight crop marks are drawn
// into it when the document is saved. Setting it to zero takes both away.
var marks = page.MarkMargins.Left;
MarkMargins is 5 mm on each edge unless you change it. When you save, the library draws eight crop
marks there to show the trimmer where to cut. Set MarkMargins to zero to leave out both the room
and the marks. To give every new page of a document the same bleed, set
document.Settings.TrimMargins.
When the document is saved, a page with trim margins gets all five boxes. MediaBox and CropBox
are the whole sheet, BleedBox is the sheet inside the mark margins, and TrimBox and ArtBox are
the trimmed page. A page without trim margins gets none of this.
Things to know
- Resizing scales. It does not reflow. Text keeps its line breaks and becomes smaller or larger. To lay text out again for a new size, render it again at that size.
- Form fields with a fixed font size do not scale with the page, so their text looks proportionally larger after a shrink. Fields with automatic font size are fine. Border widths do not scale either.
- Resizing twice does not stack. A second resize replaces the first, so A4 to A5 and back to A4 returns the page exactly to where it started.
- Text extraction finds nothing on a resized page. The extractor reads the page's own content and does not look inside the wrapper that resizing creates. Extract text before you resize. See Text extraction.
- Reading a box property can create the box. On a page that has no crop box, reading
page.CropBoxadds an empty one to the page. The same is true ofBleedBox,TrimBoxandArtBox. To test whether a page has one, usepage.Elements.ContainsKey("/CropBox"). - Draw a trimmed page in points. An
XGraphicson a page with trim margins must use the defaultXGraphicsUnit.Point, or the drawing origin is placed wrongly. - Assigning
TrimMarginsorMarkMarginscopies the four values. Changing the object you assigned from afterwards does not change the page.
See it in action
The PageResize demo shrinks a two-page A4 document with a link to A5. The Bleed demo draws a photograph off three edges of an A5 page and lists the boxes the saved page carries.
The full PageResize demo
var document = new PdfDocument();
var font = new XFont("Liberation Sans", 14);
var first = document.AddPage();
first.Size = PageSize.A4;
var second = document.AddPage();
second.Size = PageSize.A4;
using (var gfx = XGraphics.FromPdfPage(first))
gfx.DrawString("Go to chapter two", font, XBrushes.Blue, new XPoint(60, 100));
using (var gfx = XGraphics.FromPdfPage(second))
gfx.DrawString("Chapter two", font, XBrushes.Black, new XPoint(60, 100));
// A link on page one, pointing a third of the way down page two.
first.AddDocumentLink(
new PdfRectangle(new XPoint(60, first.Height - 115), new XPoint(220, first.Height - 95)),
destinationPage: 2,
destinationTop: second.Height - 90);
// One call. The drawing on both pages is scaled to 70.6%, the link rectangle shrinks with
// the words underneath it, and the destination it points at moves to where those words
// ended up. Setting page.Size on a page that has been drawn on now throws and names
// Resize, because writing a new media box would crop the page and leave the link behind.
document.ResizePages(PageSize.A5);
The full Bleed demo
var document = new PdfDocument();
// Three millimetres is what a printer asks for and what page-layout applications
// default to: enough that a cut landing slightly off the mark still falls on ink.
var bleed = XUnit.FromMillimeter(3);
var page = document.AddPage();
page.Size = PageSize.A5;
// The one line that makes this a bleeding page. The sheet written to the file grows by
// the margin on each edge, but the page a caller draws on does not: the origin moves to
// the corner of the *trimmed* page, so every coordinate below is measured from where the
// paper will be cut. Nothing in this demo would have to change if the margin were
// removed - it would simply lose its bleed.
page.TrimMargins.All = bleed;
// Outside the bleed there is a further margin, five millimetres unless it is changed,
// which is the room the press needs around the artwork. The eight crop marks are drawn
// into it when the document is saved. Setting it to zero takes both away.
var marks = page.MarkMargins.Left;
// Points, because that is the only unit TrimMargins supports. XGraphics asserts it.
var gfx = XGraphics.FromPdfPage(page);
// Still A5. The extra sheet is not part of the page, and reading Width here rather than
// hard-coding 420 is what keeps the layout right if the page size is changed.
var width = page.Width.Point;
var height = page.Height.Point;
var over = bleed.Point;
using var photograph = XImage.FromStream(
() => Assets.Open(Assets.ImagePrefix + "pdf-pinata.jpg"));
// ---- The photograph, off three edges ------------------------------------------------
//
// Negative coordinates are the whole technique. The rectangle starts one bleed above and
// one bleed left of the origin and is two bleeds wider than the page, so it covers the
// sheet on the top, left and right and stops short of the bottom.
var bled = new XRect(-over, -over, width + 2 * over, height * 0.62 + over);
// Filled rather than fitted, so the photograph covers every point of that rectangle
// instead of leaving paper showing at two edges. The scale is the larger of the two
// ratios, and the part of the image that survives is given as a source rectangle in the
// image's own pixels - the same arithmetic the Images demo works through.
var cover = Math.Max(bled.Width / photograph.PointWidth, bled.Height / photograph.PointHeight);
var sourceWidth = bled.Width / cover * photograph.PixelWidth / photograph.PointWidth;
var sourceHeight = bled.Height / cover * photograph.PixelHeight / photograph.PointHeight;
gfx.DrawImage(photograph, bled,
new XRect(
(photograph.PixelWidth - sourceWidth) / 2,
(photograph.PixelHeight - sourceHeight) / 2,
sourceWidth,
sourceHeight),
XGraphicsUnit.Point);
// ---- The words, safely inside the trim ----------------------------------------------
var title = new XFont("Liberation Sans", 34, XFontStyle.Bold);
var body = new XFont("Liberation Sans", 10);
var note = new XFont("Liberation Sans", 7);
var textTop = bled.Bottom + 28;
gfx.DrawString("Bleed", title, XBrushes.Black, new XPoint(40, textTop));
string[] paragraph =
{
"The photograph above runs off the top, left and right of this page. It was drawn",
"from (-3mm, -3mm) onto a sheet larger than the page on every edge, and the",
"guillotine cuts along the dashed rule below - through the middle of the ink, so",
"that a cut a fraction off the mark still lands on the picture rather than on white",
"paper. The crop marks at the corners of the sheet are where the trimmer lines the",
"cut up, and the library drew them without being asked."
};
var y = textTop + 26;
foreach (var line in paragraph)
{
gfx.DrawString(line, body, XBrushes.Black, new XPoint(40, y));
y += 14;
}
// ---- The trim boundary, drawn so the bleed can be seen on screen --------------------
//
// This rule is part of the demonstration and not part of the artwork. On the printed
// sheet the cut is where it is whether or not anything is drawn there; on screen the
// bleed is invisible without it, because a reader shows the whole sheet and nothing
// marks which part of it survives.
var cut = new XPen(XColors.Crimson, 0.5) { DashStyle = XDashStyle.Dash };
gfx.DrawRectangle(cut, new XRect(0, 0, width, height));
// ---- What the file will say ---------------------------------------------------------
//
// Worked out here rather than read back from the saved page, because the boxes are
// written during the save and this demo hands the document to its caller unsaved.
//
// The three areas nest, outermost first: the sheet that goes through the press, the
// bleed the artwork may run to, and the trim where it is cut. The room between the
// bleed and the sheet edge is the mark allowance, and is where the crop marks are.
var room = marks.Point;
var inset = room + over;
var sheetWidth = width + 2 * inset;
var sheetHeight = height + 2 * inset;
(string Box, string Value)[] boxes =
{
("MediaBox", $"[0 0 {sheetWidth:0.###} {sheetHeight:0.###}] the sheet"),
("CropBox", $"[0 0 {sheetWidth:0.###} {sheetHeight:0.###}] what a reader shows"),
("BleedBox", $"[{room:0.###} {room:0.###} {sheetWidth - room:0.###} {sheetHeight - room:0.###}] how far the ink may run"),
("TrimBox", $"[{inset:0.###} {inset:0.###} {sheetWidth - inset:0.###} {sheetHeight - inset:0.###}] where it is cut"),
("ArtBox", $"[{inset:0.###} {inset:0.###} {sheetWidth - inset:0.###} {sheetHeight - inset:0.###}] the meaningful content")
};
y += 18;
foreach (var row in boxes)
{
gfx.DrawString(row.Box, note, XBrushes.DimGray, new XPoint(40, y));
gfx.DrawString(row.Value, note, XBrushes.DimGray, new XPoint(92, y));
y += 10;
}
gfx.DrawString("the dashed rule is the trim edge - it is drawn by this demo, and would "
+ "not be on a page going to press",
note, XBrushes.Crimson, new XPoint(40, y + 12));