If changing an export from A4 to Letter moves the last lines onto another page, reducing the font size may hide the symptom without explaining it. The same can happen when a local export and a server-side export use different fonts or print settings.
Start by checking four things: the actual PDF page dimensions, the space left by margins, font-dependent wrapping, and rules that move blocks between pages. This guide gives an order for those checks and a small way to request both paper sizes. It does not assume that every HTML renderer implements the same print CSS.
1. Measure the page before changing the template
A4 is 210 × 297 mm; US Letter is 8.5 × 11 inches. In PDF points, with 72 points per inch:
| Paper | Approximate dimensions | Content area with 36 pt margins on all sides |
|---|---|---|
| A4 | 595.28 × 841.89 pt | 523.28 × 769.89 pt |
| Letter | 612 × 792 pt | 540 × 720 pt |
Letter is wider but shorter. At those margins it has about 49.89 pt less vertical space, while the extra width can reduce line wrapping. Those effects pull in different directions, so paper dimensions alone cannot predict the final page count.
Figure: paper geometry, not an export comparison. The 36 pt margin is an example setting to verify in your renderer.
Inspect the generated file's dimensions in a PDF tool, or with pdfinfo output.pdf. A UI label saying “Letter” is not enough if the output file still uses another size. Keep one source of truth for paper dimensions; avoid a CSS A4 size competing with a Letter setting in the export service.
2. Check margins and fonts as separate causes
For margins, check what the renderer actually applies: CSS @page, export-service parameters, or another layout setting. The CSS paged-media draft defines a page-box model, but implementation support varies. An API accepting HTML does not imply support for every print property.
For fonts, confirm that the server can load the same font files as the local environment. A fallback font can change glyph widths and line breaks. Compare one paragraph with a long name or description, rather than only a short heading. If wrapping changes throughout the document, investigate fonts and content width before touching page-break rules.
Also check whether images load at the expected dimensions. A missing image and a stretched image can change layout in different ways. Use permitted resources accessible to the renderer, and avoid relying on a browser session's private cookies.
3. Request both sizes using exactly the same input
ComPDF's PDF generation API accepts HTML and explicit page width and height in points. That is useful for testing a server-side generation path: hold the input constant and change only the requested dimensions.
Use an existing template, or generate this small text fixture as paper-layout.html:
from pathlib import Path
style = """@page { margin: 36pt; }
body { margin: 0; font: 12pt sans-serif; }
h1 { font-size: 18pt; line-height: 24pt; margin: 0 0 12pt; }
p { margin: 0; line-height: 24pt; }"""
rows = ''.join(f'<p>Item {i:02d} - inspect equipment and record the result.</p>'
for i in range(1, 30))
html = (f'<!doctype html><html><head><meta charset="utf-8"><style>{style}</style>'
f'</head><body><h1>Service schedule - layout fixture</h1>{rows}'
'<p>END-OF-SCHEDULE</p></body></html>')
Path('paper-layout.html').write_text(html, encoding='utf-8')
Install requests, configure COMPDF_API_KEY in your server-side environment, and save the next script as generate_paper_sizes.py beside that HTML file. The key belongs on the server, outside source control.
python3 -m pip install requests
python3 generate_paper_sizes.py
"""Generate two files for inspection. Requires requests and COMPDF_API_KEY."""
import os
from pathlib import Path
import requests
html = Path('paper-layout.html').read_text(encoding='utf-8')
for name, width, height in [('a4', 595.28, 841.89), ('letter', 612, 792)]:
response = requests.post(
'https://api-server.compdf.com/server/v2/process/pdf/generate',
headers={'x-api-key': os.environ['COMPDF_API_KEY']},
data={'generationType': 'html', 'html': html,
'pageWidth': width, 'pageHeight': height}, timeout=120)
response.raise_for_status()
result = response.json()
data = result.get('data')
if isinstance(data, list) and len(data) == 1:
data = data[0]
if str(result.get('code')) != '200' or not isinstance(data, dict):
raise RuntimeError(f'{name}: API did not return a successful result')
if data.get('status') != 'success':
raise RuntimeError(f'{name}: processing did not complete successfully')
output = requests.get(data['downloadUrl'], timeout=60)
output.raise_for_status()
if not output.content.startswith(b'%PDF-'):
raise ValueError(f'{name}: download is not a PDF')
Path(f'{name}.pdf').write_bytes(output.content)
print(f'Saved {name}.pdf - inspect its dimensions, pages and last line')
For the earlier fixture check, the downloaded files measured 595 × 841 pt (approximately A4) and 612 × 792 pt (Letter). Both contained all 29 items and the end marker on one page. That check did not reproduce an extra Letter page. The rendered output also did not show the requested 36 pt page inset, so it does not establish @page margin support for this API path. Confirm the relevant layout controls before selecting it for a print-sensitive template.
The request example demonstrates explicit paper dimensions. It does not establish browser-equivalent CSS behavior or automatic pagination correction. A failed API request or download is a separate issue; obtain two valid files before comparing layout.
4. Investigate block movement after the geometry is clear
| What you see | What to inspect |
|---|---|
| Only the last lines move | Content height, top/bottom margins, paragraph spacing |
| Lines wrap differently throughout | Fonts, content width, cell padding |
| A whole section moves | Forced breaks, break-inside, and fixed-height containers |
| A blank page appears at the end | Trailing break rules and oversized empty blocks |
| A footer covers text | Whether footer space was reserved or positioned over content |
Change one cause at a time. Check the last line before and after the edit. For tables, verify whether the target engine repeats headers and honors row-break controls; a row taller than a page cannot stay intact on that page. Long notes may need a continuation layout rather than a smaller font.
Use your own template to make the decision
Replace the fixture with a real, authorized document template. Check a short case, a case near a page boundary, and a genuinely long case. Include long names, production fonts, a table crossing a page, and any footer or images you depend on.
Record input and template versions, requested dimensions, actual dimensions, margins, and output observations. Different sizes may legitimately need different page counts. Required content should survive in a usable order with no clipping or overlap. Text extraction can help find a missing end marker, but it cannot prove that text is visually readable.
When evaluating a PDF generation integration, use this small regression set to check the layout features your application needs. That gives you a concrete basis for choosing settings or an integration path before changing typography across every document.

Top comments (0)