ScreenshotNeo

BlogHTML to image & PDF

How to Remove Extra Blank Space in UIPrintPageRenderer HTML-to-PDF Output

Diagnose oversized margins and extra blank pages in UIKit HTML-to-PDF output by checking renderer geometry, formatter limits, and print CSS.

By the ScreenshotNeo team1 October 20267 min read

Start by identifying which symptom you have. A large blank margin inside otherwise correct pages is a layout-geometry problem. An entirely empty extra page is usually an overflow or page-break problem. They can share a cause, but the fixes are not interchangeable.

In UIKit, inspect the page renderer and formatter together: paperRect, printableRect, formatter contentInsets, maximumContentWidth, maximumContentHeight, and any custom headerHeight or footerHeight. Only after those values are known should you change HTML or print CSS.

1. Classify the blank space

  • Large margin on every page: compare the paper rectangle with the printable rectangle, then check formatter insets and renderer header/footer space.
  • Whitespace around one element: inspect that element’s dimensions, margins, forced breaks, and oversized descendants.
  • One completely blank page: look for content that barely overflows the available height, an explicit page break, or a mismatch between the configured page size and the renderer geometry.

Capture a diagnostic PDF at one fixed paper size and orientation. Change one input at a time so you can identify the setting that changes the output.

2. Understand UIKit’s page geometry

paperRect is the physical page rectangle. printableRect is the area UIKit allows printing inside that page. UIKit can choose default paper and printable geometry based on the output type unless your app overrides it, so do not assume that an A4 or Letter value in your own code is the geometry actually used.

A formatter lays HTML into the printable area after its own constraints are applied. CSS margin: 0 does not remove UIKit formatter insets or renderer-reserved header and footer areas.

// Swift diagnostic helper
func logPrintGeometry(renderer: UIPrintPageRenderer, formatter: UIPrintFormatter) {
    print("paperRect:", renderer.paperRect)
    print("printableRect:", renderer.printableRect)
    print("headerHeight:", renderer.headerHeight)
    print("footerHeight:", renderer.footerHeight)
    print("contentInsets:", formatter.contentInsets)
    print("maximumContentWidth:", formatter.maximumContentWidth)
    print("maximumContentHeight:", formatter.maximumContentHeight)
}

Run this immediately before creating the PDF. If the printable rectangle is smaller than expected, fix that configuration before editing HTML.

3. Use the built-in formatter for ordinary HTML

Apple’s Drawing and Printing Guide recommends a built-in markup formatter when text and HTML should reflow automatically and you do not need custom repeating headers or footers. Use UIPrintPageRenderer when you need custom drawing, mixed content, or explicit page-layout control; attach a formatter when it should lay out the HTML.

import UIKit

func makeRenderer(html: String) -> UIPrintPageRenderer {
    let formatter = UIMarkupTextPrintFormatter(markupText: html)
    formatter.contentInsets = UIEdgeInsets(top: 36, left: 36, bottom: 36, right: 36)
    formatter.maximumContentWidth = 0
    formatter.maximumContentHeight = 0

    let renderer = UIPrintPageRenderer()
    renderer.addPrintFormatter(formatter, startingAtPageAt: 0)
    return renderer
}

func pdfData(html: String, paperSize: CGSize) -> Data {
    let renderer = makeRenderer(html: html)
    let pageRect = CGRect(origin: .zero, size: paperSize)
    renderer.setValue(pageRect, forKey: "paperRect")
    renderer.setValue(pageRect, forKey: "printableRect")

    let data = NSMutableData()
    UIGraphicsBeginPDFContextToData(data, pageRect, nil)
    renderer.prepare(forDrawingPages: NSMakeRange(0, renderer.numberOfPages))
    for page in 0..<renderer.numberOfPages {
        UIGraphicsBeginPDFPageWithInfo(pageRect, nil)
        renderer.drawPage(at: page, in: UIGraphicsGetPDFContextBounds())
    }
    UIGraphicsEndPDFContext()
    return data as Data
}

The contentInsets values are UIKit points. Apple’s archived example uses 72 points on each side to illustrate one-inch margins; treat that as an example, not a universal correction.

4. Objective-C version

- (UIPrintPageRenderer *)rendererForHTML:(NSString *)html {
    UIMarkupTextPrintFormatter *formatter =
        [[UIMarkupTextPrintFormatter alloc] initWithMarkupText:html];
    formatter.contentInsets = UIEdgeInsetsMake(36.0, 36.0, 36.0, 36.0);
    formatter.maximumContentWidth = 0;
    formatter.maximumContentHeight = 0;

    UIPrintPageRenderer *renderer = [[UIPrintPageRenderer alloc] init];
    [renderer addPrintFormatter:formatter startingAtPageAtIndex:0];
    return renderer;
}

- (NSData *)pdfForHTML:(NSString *)html pageSize:(CGSize)size {
    UIPrintPageRenderer *renderer = [self rendererForHTML:html];
    CGRect pageRect = CGRectMake(0, 0, size.width, size.height);
    [renderer setValue:[NSValue valueWithCGRect:pageRect] forKey:@"paperRect"];
    [renderer setValue:[NSValue valueWithCGRect:pageRect] forKey:@"printableRect"];

    NSMutableData *data = [NSMutableData data];
    UIGraphicsBeginPDFContextToData(data, pageRect, nil);
    [renderer prepareForDrawingPages:NSMakeRange(0, renderer.numberOfPages)];
    for (NSInteger page = 0; page < renderer.numberOfPages; page++) {
        UIGraphicsBeginPDFPageWithInfo(pageRect, nil);
        [renderer drawPageAtIndex:page inRect:UIGraphicsGetPDFContextBounds()];
    }
    UIGraphicsEndPDFContext();
    return data;
}

5. Check custom headers and footers

If you subclass UIPrintPageRenderer, headerHeight and footerHeight reserve space on every page. Your drawHeaderForPageAtIndex:inRect: and drawFooterForPageAtIndex:inRect: implementations must draw inside the rectangles UIKit supplies. A header height that is larger than the pixels you actually draw creates a visible blank band. A footer height that consumes the remaining content area can push the final line onto a new page.

final class HeaderRenderer: UIPrintPageRenderer {
    override var headerHeight: CGFloat { 24 }
    override var footerHeight: CGFloat { 0 }

    override func drawHeaderForPage(at pageIndex: Int, in headerRect: CGRect) {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 9),
            .foregroundColor: UIColor.darkGray
        ]
        ("Report" as NSString).draw(in: headerRect, withAttributes: attributes)
    }
}

6. Inspect HTML and print CSS after UIKit geometry

Once the rectangles and formatter limits are correct, inspect the document:

  • Remove unintended page-break-before, page-break-after, and break-before rules.
  • Check that a fixed-height container, image, table, or code block is not taller than the available content area.
  • Use print-specific rules such as @media print for margins and visibility.
  • Check body, heading, list, and table margins. Collapsing margins can make whitespace appear to belong to the wrong element.
  • Check images for intrinsic dimensions, missing dimensions, and large transparent regions.
  • Look for absolutely positioned content that extends below the normal flow.
<style>
@media print {
  @page { margin: 0; }
  html, body { margin: 0; padding: 0; }
  h1, h2, p { margin-top: 0; }
  .avoid-break { break-inside: avoid; page-break-inside: avoid; }
}
</style>

CSS behavior can depend on the renderer. A CSS fix reported for another HTML-to-PDF engine does not prove that the same declaration fixes UIPrintPageRenderer.

7. Diagnosing an entirely extra blank page

  1. Render the same HTML with formatter insets set to zero and with headers and footers disabled. If the blank page disappears, restore each value separately.
  2. Log renderer.numberOfPages and compare it with visible content.
  3. Temporarily remove trailing block elements, bottom margins, forced breaks, and large images.
  4. Confirm that the page width and height use the intended orientation and units.
  5. Test a minimal HTML document. Add sections back until the extra page returns.

A historical Stack Overflow report describes changing an A4 rectangle from 595 × 842 points to 595.2 × 841.8 points and increasing printable margins. Another commenter reported that this did not work for them. Treat that anecdote as a diagnostic lead only, not a validated general fix.

8. Common errors and fixes

Symptom Likely cause Fix
Blank strip on every page Formatter insets or printable rectangle are larger than expected Log both rectangles and contentInsets; set the intended values explicitly.
Only the last page is blank Content height barely overflows, or a forced break follows the final block Remove the break, reduce trailing margins, and test images/tables individually.
Header area is empty headerHeight is nonzero but drawing is absent or offset Set it to zero or draw inside the supplied header rectangle.
CSS margin changes do nothing UIKit geometry still constrains the formatter Fix paper, printable, and formatter geometry first.
Different devices produce different pages Output type selects different default paper or printable areas Set and log explicit geometry for each export path.
Text is clipped after reducing margins Content exceeds the printable width or height Restore a safe inset, check fixed dimensions, and avoid shrinking until overflow is understood.

9. Reliability, performance, and cost considerations

Keep paper size, orientation, insets, and header/footer heights in one configuration object so every export path uses the same geometry. Generate a small set of regression PDFs containing a long paragraph, a table, an image, and a forced break. Compare page count and visible bounds after SDK changes. Do not infer correctness from a single short document.

There is no source-backed universal margin, page-count limit, performance benchmark, or success rate for this issue. Measure your own documents, especially when they contain remote images, web fonts, or complex tables.

10. Or skip the browser setup

If your goal is a clean screenshot or PDF of a URL rather than UIKit-specific layout control, ScreenshotNeo provides a single HTTP request. Its capture options include PDF paper size, margins, landscape mode, page ranges, waiting for a selector or network idle, custom CSS and JavaScript, and full-page lazy-image loading.

See the ScreenshotNeo API documentation for the complete parameter list.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

FAQ

Should I set both paperRect and printableRect to the same value?

Only when that is the geometry you intend. In production, model the printable area explicitly and verify it against your output requirements.

Does setting CSS margins to zero remove all PDF margins?

No. Formatter insets, printableRect, and header/footer reservations still apply.

When should I use UIMarkupTextPrintFormatter alone?

Use it for ordinary HTML that should reflow automatically without custom repeating headers, footers, or drawing.

Is the fractional A4 size a guaranteed fix?

No. It is one historical report and another commenter said it did not solve their case.

How do I know whether the problem is UIKit or my HTML?

Render a minimal document with the same geometry, then add your content incrementally. If the minimal document has the blank space, inspect UIKit values first.