<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Xin Zhang</title>
    <description>The latest articles on DEV Community by Xin Zhang (@zx_5e60fcc977a7cbc).</description>
    <link>https://dev.to/zx_5e60fcc977a7cbc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3988157%2F777629a8-7310-4806-8e7d-059b6f989944.png</url>
      <title>DEV Community: Xin Zhang</title>
      <link>https://dev.to/zx_5e60fcc977a7cbc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zx_5e60fcc977a7cbc"/>
    <language>en</language>
    <item>
      <title>pdf-pagenum: Fix Messy macOS Preview Page Numbers in PDFs from the CLI</title>
      <dc:creator>Xin Zhang</dc:creator>
      <pubDate>Wed, 17 Jun 2026 03:17:54 +0000</pubDate>
      <link>https://dev.to/zx_5e60fcc977a7cbc/pdf-pagenum-fix-messy-macos-preview-page-numbers-in-pdfs-from-the-cli-2emh</link>
      <guid>https://dev.to/zx_5e60fcc977a7cbc/pdf-pagenum-fix-messy-macos-preview-page-numbers-in-pdfs-from-the-cli-2emh</guid>
      <description>&lt;p&gt;A pip-installable CLI tool that auto-centers off-center page number annotations created by macOS Preview, or batch-adds new ones — with smart content avoidance and landscape support.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you've ever used macOS Preview to add page numbers to a PDF (via the text annotation tool), you know the pain: numbers land wherever you drop them, never centered, and manually positioning dozens or hundreds of them is soul-crushing. Especially when the PDF has mixed portrait and landscape pages.&lt;/p&gt;

&lt;p&gt;I ran into this preparing a thesis — 200+ pages of final manuscript, page numbers visibly off-center on every single page. Editing each one by hand wasn't an option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;pdf-pagenum&lt;/strong&gt; is a single CLI command that reads a folder of PDFs and centers every page number annotation to the bottom of its page. It works by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detecting FreeText annotations that look like page numbers&lt;/li&gt;
&lt;li&gt;Measuring body content boundaries on each page&lt;/li&gt;
&lt;li&gt;Repositioning the annotation to a clean, centered position below the content — with proper margins&lt;/li&gt;
&lt;li&gt;Preserving the original page dimensions (no resizing, ever)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your PDF has pages with no annotations at all, it can &lt;strong&gt;generate new page numbers&lt;/strong&gt; from scratch in the correct position.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdf-pagenum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. PyMuPDF and natsort come along as dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fix Mode (default)
&lt;/h3&gt;

&lt;p&gt;Reposition existing page number annotations so they're centered at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdf-pagenum ./scans/ ./output/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the mode you'll use 90% of the time — it takes whatever rough page numbers Preview gave you and snaps them to the mathematically correct center.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Mode
&lt;/h3&gt;

&lt;p&gt;Generate brand-new page numbers on pages that lack them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Number all pages starting from 1&lt;/span&gt;
pdf-pagenum ./scans/ ./output/ &lt;span class="nt"&gt;--add&lt;/span&gt; all

&lt;span class="c"&gt;# Number pages 3 through 7 only&lt;/span&gt;
pdf-pagenum ./scans/ ./output/ &lt;span class="nt"&gt;--add&lt;/span&gt; 3-7

&lt;span class="c"&gt;# Number specific pages, starting count from 10&lt;/span&gt;
pdf-pagenum ./scans/ ./output/ &lt;span class="nt"&gt;--add&lt;/span&gt; 1,3,5-7 &lt;span class="nt"&gt;--start&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ranges and comma-separated lists can be mixed freely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Offset
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--start N&lt;/code&gt; flag works in both modes — it shifts the logical page number without touching the files. Handy when you're processing chapter PDFs that don't start at page 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;The pipeline runs per-PDF, processing pages independently for speed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Detection&lt;/strong&gt;: For each page, PyMuPDF extracts all text blocks and images to find the bounding box of actual body content. This tells us how low the content goes — and where the page number should sit below it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Annotation Matching&lt;/strong&gt;: FreeText annotations are tested against patterns like &lt;code&gt;\d+&lt;/code&gt; to identify candidates for repositioning. Non-page-number annotations are left untouched.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Font Measurement&lt;/strong&gt;: Rather than guessing digit widths, the tool renders each page number string with the actual font to get exact bounding dimensions. This guarantees true horizontal centering regardless of digit composition.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Placement&lt;/strong&gt;: The annotation is repositioned at &lt;code&gt;(page_center - text_width/2, below_content + margin)&lt;/code&gt;, with a white background fill so it's legible over images or dark areas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Save&lt;/strong&gt;: PDFs are written with compression and garbage collection enabled, keeping output sizes small.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design Decisions Worth Mentioning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A4 dimensions are never changed.&lt;/strong&gt; The tool refuses to resize pages. If content is too tight and a page number overlaps body text, it accepts the overlap rather than distorting the page. This is by design — changing page geometry breaks downstream workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Landscape pages are handled correctly.&lt;/strong&gt; Content boundaries are measured against the actual page orientation, so a landscape spread's "bottom" is correctly identified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural sorting of input files.&lt;/strong&gt; File names like &lt;code&gt;chapter10.pdf&lt;/code&gt; sort after &lt;code&gt;chapter9.pdf&lt;/code&gt;, not after &lt;code&gt;chapter1.pdf&lt;/code&gt;, thanks to natsort. This matters when you're numbering pages sequentially across files and want them in human order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thesis / dissertation submission&lt;/strong&gt;: Universities often require centered page numbers. If you annotated them hastily in Preview, this fixes them in seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project proposals and bid documents&lt;/strong&gt;: Combine multiple PDF scans, then number them end-to-end with &lt;code&gt;--add all --start 1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conference proceedings&lt;/strong&gt;: Normalize page number positions across papers from different authors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;E-book and print prep&lt;/strong&gt;: Ensure page numbers are consistently placed before sending to a printer or conversion tool.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Not Just Use [Other Tool]?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preview alone&lt;/strong&gt; has no "center annotation" or "batch page number" feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adobe Acrobat&lt;/strong&gt; can do this but costs $20+/month and is overkill for one task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual LaTeX / Word layouts&lt;/strong&gt; require the source document — pdf-pagenum works on the final PDF, even if it's a scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;MIT licensed. PRs welcome — if you have a feature request (custom fonts for add mode? different placement positions?), open an issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pdf-pagenum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/zhangxin0611/pdf-pagenum" rel="noopener noreferrer"&gt;github.com/zhangxin0611/pdf-pagenum&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
