<?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>Forem: Iurii Rogulia</title>
    <description>The latest articles on Forem by Iurii Rogulia (@iurii_rogulia).</description>
    <link>https://forem.com/iurii_rogulia</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3561015%2Fd1b53175-2e87-4fa8-9c54-90f6b713141b.jpg</url>
      <title>Forem: Iurii Rogulia</title>
      <link>https://forem.com/iurii_rogulia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iurii_rogulia"/>
    <language>en</language>
    <item>
      <title>5 Signs a PDF Document Has Been Tampered With</title>
      <dc:creator>Iurii Rogulia</dc:creator>
      <pubDate>Thu, 09 Apr 2026 11:38:43 +0000</pubDate>
      <link>https://forem.com/iurii_rogulia/5-signs-a-pdf-document-has-been-tampered-with-54bk</link>
      <guid>https://forem.com/iurii_rogulia/5-signs-a-pdf-document-has-been-tampered-with-54bk</guid>
      <description>&lt;p&gt;PDF documents are everywhere in business — invoices, contracts, certificates, reports. We trust them because they look official and professional. But that trust can be misplaced. Document tampering is a growing problem, with criminals modifying PDFs to commit fraud, alter contracts, or forge credentials.&lt;/p&gt;

&lt;p&gt;The challenge: PDF tampering can be nearly invisible. A modified PDF can look identical to the original, making it difficult to detect changes without knowing what to look for.&lt;/p&gt;

&lt;p&gt;This article outlines five clear warning signs that a PDF has been tampered with. Recognizing these signs helps you protect yourself and your organization from document fraud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Document Tampering Is a Growing Concern
&lt;/h2&gt;

&lt;p&gt;Document fraud affects businesses and individuals worldwide. According to research from &lt;a href="https://smallpdf.com/blog/spotting-fakes-types-document-fraud" rel="noopener noreferrer"&gt;Smallpdf&lt;/a&gt;, document fraud has increased significantly with the rise of digital document workflows. PDFs are particularly vulnerable because they are easy to modify and hard to verify.&lt;/p&gt;

&lt;p&gt;The consequences of document tampering can be severe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial losses from fraudulent invoices&lt;/li&gt;
&lt;li&gt;Legal disputes over modified contracts&lt;/li&gt;
&lt;li&gt;Identity theft through forged certificates&lt;/li&gt;
&lt;li&gt;Reputation damage from document fraud incidents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As &lt;a href="https://www.helpnetsecurity.com/2025/07/07/detect-pdf-tampering-forgery/" rel="noopener noreferrer"&gt;Help Net Security reports&lt;/a&gt;, new techniques for detecting PDF tampering are emerging, but awareness of warning signs remains the first line of defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign #1: Mismatched Creation and Modification Dates
&lt;/h2&gt;

&lt;p&gt;The simplest sign of PDF tampering is when creation and modification dates do not match — or when they do not make sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Look For
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In PDF Properties:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creation date: When the PDF was first created&lt;/li&gt;
&lt;li&gt;Modification date: When the PDF was last modified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Red flags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modification date is significantly later than creation date&lt;/li&gt;
&lt;li&gt;Old document (years old) with recent modification date&lt;/li&gt;
&lt;li&gt;Modification date matches suspicious activity timeline&lt;/li&gt;
&lt;li&gt;Dates that do not align with document history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Check
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the PDF in Adobe Acrobat Reader&lt;/li&gt;
&lt;li&gt;Right-click → Properties (or File → Properties)&lt;/li&gt;
&lt;li&gt;Check the “Created” and “Modified” dates in the Description tab&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Legitimate documents typically have matching dates or modifications that make sense (like adding a signature). When a document claims to be old but was modified recently, it suggests tampering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: A contract dated 2020 that was modified in 2025 should raise suspicion, especially if no legitimate reason exists for the modification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Some PDF creation tools set both dates to the same value even after editing. Additionally, legitimate actions like re-saving or adding signatures will change the modification date. This sign should be considered alongside other indicators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign #2: Suspicious Producer/Creator Applications in Metadata
&lt;/h2&gt;

&lt;p&gt;PDF metadata reveals which applications created and last processed a document. Suspicious applications can indicate tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Metadata Fields
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Creator&lt;/strong&gt;: Application that originally created the PDF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Producer&lt;/strong&gt;: Software that last processed the PDF&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to Look For
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Suspicious patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Legal document created in photo editing software&lt;/li&gt;
&lt;li&gt;Invoice created in a graphics design tool&lt;/li&gt;
&lt;li&gt;Document claiming to be from Word but producer is a PDF editor&lt;/li&gt;
&lt;li&gt;Multiple different producers (suggesting multiple edits)&lt;/li&gt;
&lt;li&gt;Unknown or suspicious application names&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Check
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open PDF Properties (File → Properties)&lt;/li&gt;
&lt;li&gt;Go to the Description tab&lt;/li&gt;
&lt;li&gt;Review the “Application” and “PDF Producer” fields&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Real-World Examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoice fraud&lt;/strong&gt;: Invoice created in “Adobe Acrobat Pro” when the vendor normally uses accounting software&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certificate forgery&lt;/strong&gt;: Certificate created in “Photoshop” instead of an official certificate generation system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract tampering&lt;/strong&gt;: Contract producer changed from “Microsoft Word” to “Foxit PhantomPDF” between versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As &lt;a href="https://www.herondata.io/blog/fraud-document-detection" rel="noopener noreferrer"&gt;Heron Data explains&lt;/a&gt;, metadata analysis can reveal editing history even when visual inspection shows no changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Legitimate documents are created using appropriate software. When metadata shows unexpected applications, it suggests the document was edited or created fraudulently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign #3: Visual Inconsistencies (Fonts, Spacing, Alignment)
&lt;/h2&gt;

&lt;p&gt;Sometimes, the best way to detect tampering is simply to look carefully. Visual inconsistencies can reveal editing, especially when text or images were added to an existing PDF.&lt;/p&gt;

&lt;h3&gt;
  
  
  Font Inconsistencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different fonts in what should be uniform text&lt;/li&gt;
&lt;li&gt;Font sizes that do not match the document style&lt;/li&gt;
&lt;li&gt;Fonts that appear pixelated or low-quality&lt;/li&gt;
&lt;li&gt;Text that looks pasted in rather than original&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;: When someone edits a PDF by adding text, matching the original font perfectly is difficult. Differences can indicate tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spacing and Alignment Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text that does not align with margins&lt;/li&gt;
&lt;li&gt;Uneven line spacing&lt;/li&gt;
&lt;li&gt;Text that overlaps or appears misaligned&lt;/li&gt;
&lt;li&gt;Inconsistent spacing between elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;: Original documents have consistent formatting. Editing often disrupts this consistency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Quality Problems
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-resolution images that do not match the document quality&lt;/li&gt;
&lt;li&gt;Images that appear stretched or distorted&lt;/li&gt;
&lt;li&gt;Watermarks or logos that look different&lt;/li&gt;
&lt;li&gt;Images with different color profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;: Adding or modifying images in PDFs can create quality mismatches that reveal tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color and Style Inconsistencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Colors that do not match the document theme&lt;/li&gt;
&lt;li&gt;Text colors different from surrounding content&lt;/li&gt;
&lt;li&gt;Inconsistent styling (bold, italic, underline)&lt;/li&gt;
&lt;li&gt;Different border styles or line weights&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visual inspection requires careful attention but can catch tampering that automated tools might miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign #4: Invalid or Missing Digital Signatures
&lt;/h2&gt;

&lt;p&gt;Digital signatures provide cryptographic proof that a document has not been modified since signing. Invalid signatures are a strong indicator of tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Digital Signatures Work
&lt;/h3&gt;

&lt;p&gt;A digital signature creates a unique cryptographic hash of document content. If the document is modified after signing, the hash changes and the signature becomes invalid.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Look For
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Signature status indicators:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valid&lt;/strong&gt;: Document has not been modified since signing (good sign)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalid&lt;/strong&gt;: Document was modified after signing (red flag)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown&lt;/strong&gt;: Signature certificate cannot be verified (investigate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing&lt;/strong&gt;: Document lacks a signature when one is expected (suspicious)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Check
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the PDF in Adobe Acrobat Reader&lt;/li&gt;
&lt;li&gt;Look for the signature panel or signature field&lt;/li&gt;
&lt;li&gt;Click on the signature to view its status&lt;/li&gt;
&lt;li&gt;Review signature details and validity&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;A valid digital signature proves the document has not been tampered with since signing. An invalid signature means the document was modified after signing — a clear sign of tampering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: A valid signature does not guarantee the document was never edited — it only proves it has not been modified since signing. If someone edited the document before signing it, the signature will still be valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Example
&lt;/h3&gt;

&lt;p&gt;A contract was digitally signed by both parties. Later, one party modified payment terms and re-saved the document. The digital signature became invalid, alerting the other party to the tampering.&lt;/p&gt;

&lt;p&gt;Digital signature verification is one of the most reliable methods for detecting PDF tampering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign #5: Multiple Revision Layers or Incremental Updates
&lt;/h2&gt;

&lt;p&gt;PDFs can be modified using incremental updates, which add changes without rewriting the entire file. Multiple revision layers can indicate tampering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Incremental Updates
&lt;/h3&gt;

&lt;p&gt;When a PDF is modified, some editors use incremental updates that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new content without removing old content&lt;/li&gt;
&lt;li&gt;Create multiple versions within the same file&lt;/li&gt;
&lt;li&gt;Leave traces of editing history&lt;/li&gt;
&lt;li&gt;Can be detected through technical analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to Look For
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technical indicators:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple cross-reference tables&lt;/li&gt;
&lt;li&gt;Incremental update markers&lt;/li&gt;
&lt;li&gt;Revision history in file structure&lt;/li&gt;
&lt;li&gt;Multiple producer entries in metadata&lt;/li&gt;
&lt;li&gt;File size inconsistencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Check
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Manual inspection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check PDF properties for multiple producer entries&lt;/li&gt;
&lt;li&gt;Review file size (unusually large files may contain multiple revisions)&lt;/li&gt;
&lt;li&gt;Look for revision history in document properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Automated tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use PDF analysis tools to detect incremental updates&lt;/li&gt;
&lt;li&gt;Check cross-reference table integrity&lt;/li&gt;
&lt;li&gt;Analyze file structure for revision layers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Legitimate documents typically have a single revision layer. Multiple layers suggest the document was edited multiple times, which could indicate tampering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: An invoice with three incremental updates when it should have been created once suggests multiple editing sessions, potentially for fraudulent purposes.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://www.docuclipper.com/blog/how-to-detect-fraudulent-documents/" rel="noopener noreferrer"&gt;DocuClipper notes&lt;/a&gt;, incremental update analysis requires technical knowledge but provides strong evidence of tampering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: What These Signs Mean (Fraud vs Legitimate Edits)
&lt;/h2&gt;

&lt;p&gt;Not all PDF modifications indicate fraud. Understanding the difference helps you avoid false alarms while catching real threats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legitimate Modifications
&lt;/h3&gt;

&lt;p&gt;These actions commonly modify PDFs and are usually harmless:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Modification Type&lt;/th&gt;
&lt;th&gt;Why It Happens&lt;/th&gt;
&lt;th&gt;Should You Be Concerned?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Re-saving&lt;/td&gt;
&lt;td&gt;Software auto-save, format conversion&lt;/td&gt;
&lt;td&gt;No — normal workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signature added&lt;/td&gt;
&lt;td&gt;Standard signing process&lt;/td&gt;
&lt;td&gt;No — expected behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form fields filled&lt;/td&gt;
&lt;td&gt;Completing PDF forms&lt;/td&gt;
&lt;td&gt;No — intended use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document merged&lt;/td&gt;
&lt;td&gt;Combining multiple PDFs&lt;/td&gt;
&lt;td&gt;Possibly — verify source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format conversion&lt;/td&gt;
&lt;td&gt;Converting from Word/Excel&lt;/td&gt;
&lt;td&gt;No — normal creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimization&lt;/td&gt;
&lt;td&gt;File size reduction&lt;/td&gt;
&lt;td&gt;No — performance improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Suspicious Modifications
&lt;/h3&gt;

&lt;p&gt;These patterns warrant investigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modification after signature&lt;/strong&gt;: Document changed after being digitally signed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected creator&lt;/strong&gt;: Document created in unusual software&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent modification of old document&lt;/strong&gt;: Old document suddenly modified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata inconsistencies&lt;/strong&gt;: Conflicting information in metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual anomalies&lt;/strong&gt;: Obvious editing artifacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple revisions&lt;/strong&gt;: Unusual number of incremental updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Context Matters
&lt;/h3&gt;

&lt;p&gt;The same modification can be legitimate or suspicious depending on context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contract modified after signing&lt;/strong&gt;: Suspicious — should not happen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoice re-saved for formatting&lt;/strong&gt;: Normal — common workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certificate created in photo editor&lt;/strong&gt;: Suspicious — should use official tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report converted from Word&lt;/strong&gt;: Normal — standard creation process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As research from &lt;a href="https://arxiv.org/abs/2507.00827" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt; shows, context-aware analysis improves tampering detection accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automated Solution: Let Technology Do the Work
&lt;/h2&gt;

&lt;p&gt;While manual inspection can catch obvious tampering, sophisticated edits require automated analysis. PDF verification tools like &lt;strong&gt;HTPBE&lt;/strong&gt; examine multiple technical indicators simultaneously to detect modifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Automated Tools Are Superior
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Analysis completes in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy&lt;/strong&gt;: Detects modifications that manual methods miss&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive&lt;/strong&gt;: Checks all five signs plus additional indicators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objective&lt;/strong&gt;: No human error or bias&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessible&lt;/strong&gt;: No technical knowledge required&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What HTPBE Checks
&lt;/h3&gt;

&lt;p&gt;When you upload a PDF to &lt;a href="https://htpbe.tech" rel="noopener noreferrer"&gt;HTPBE&lt;/a&gt;, the algorithm analyzes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document properties and dates&lt;/li&gt;
&lt;li&gt;Metadata analysis (creator, producer)&lt;/li&gt;
&lt;li&gt;Digital signature verification&lt;/li&gt;
&lt;li&gt;Incremental update detection&lt;/li&gt;
&lt;li&gt;Cross-reference table integrity&lt;/li&gt;
&lt;li&gt;Structural anomalies
The result is a clear verdict with specific findings — no technical knowledge required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use Automated Verification
&lt;/h3&gt;

&lt;p&gt;Use automated tools for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical documents (contracts, invoices, certificates)&lt;/li&gt;
&lt;li&gt;Documents from unknown sources&lt;/li&gt;
&lt;li&gt;Documents that seem suspicious&lt;/li&gt;
&lt;li&gt;Regular verification workflows&lt;/li&gt;
&lt;li&gt;Compliance and audit requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Detecting PDF tampering requires awareness of warning signs and careful inspection. The five signs outlined above — mismatched dates, suspicious metadata, visual inconsistencies, invalid signatures, and multiple revisions — provide a framework for identifying tampered documents.&lt;/p&gt;

&lt;p&gt;Remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not all modifications are fraud&lt;/strong&gt;: Many are part of normal workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context matters&lt;/strong&gt;: Consider the document type and expected modifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple signs increase suspicion&lt;/strong&gt;: One sign might be normal; multiple signs warrant investigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated tools help&lt;/strong&gt;: Use verification tools for critical documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By recognizing these signs, you can protect yourself and your organization from document fraud. When in doubt, verify through automated tools or independent channels.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>security</category>
      <category>fraud</category>
    </item>
    <item>
      <title>React Masonry Layout: Why the Popular Reorder Trick Fails</title>
      <dc:creator>Iurii Rogulia</dc:creator>
      <pubDate>Thu, 12 Mar 2026 15:37:15 +0000</pubDate>
      <link>https://forem.com/iurii_rogulia/react-masonry-layout-why-the-popular-reorder-trick-fails-5f9l</link>
      <guid>https://forem.com/iurii_rogulia/react-masonry-layout-why-the-popular-reorder-trick-fails-5f9l</guid>
      <description>&lt;p&gt;A masonry grid looks simple: variable-height cards, left-to-right reading order, columns that fill naturally. You reach for &lt;code&gt;column-count&lt;/code&gt; in CSS because that's what the tutorials recommend. Everything looks correct on your three uniform-height test cards. Then you add real content — cards with different amounts of text, images, tags — and the order breaks. Card 4 appears before card 2. Card 7 jumps to the top of column 1. The layout is shuffled.&lt;/p&gt;

&lt;p&gt;I hit this on my portfolio's blog and projects pages. I found the popular fix, implemented it, and watched it fail in production. Then I built an approach that actually works. Here's what the popular fix gets wrong and what to do instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;column-count&lt;/code&gt; Breaks Ordering
&lt;/h2&gt;

&lt;p&gt;CSS &lt;code&gt;column-count&lt;/code&gt; is a newspaper-layout primitive. You give it a container and a number of columns, and the browser fills those columns top-to-bottom — not left-to-right. The first column fills completely before the second starts.&lt;/p&gt;

&lt;p&gt;That's the correct behavior for newspaper text. It's the wrong behavior for a card grid where reading order should go left-to-right across the top row.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;Items&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;6&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nt"&gt;column-count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;

&lt;span class="nt"&gt;What&lt;/span&gt; &lt;span class="nt"&gt;CSS&lt;/span&gt; &lt;span class="nt"&gt;does&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="nt"&gt;What&lt;/span&gt; &lt;span class="nt"&gt;you&lt;/span&gt; &lt;span class="nt"&gt;want&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="err"&gt;┌────┬────┬────┐&lt;/span&gt;         &lt;span class="err"&gt;┌────┬────┬────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;5&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;6&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;5&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;6&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└────┴────┴────┘&lt;/span&gt;         &lt;span class="err"&gt;└────┴────┴────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standard fix that circulates on Medium and in Stack Overflow answers is to reorder the array in JavaScript before rendering it into the &lt;code&gt;column-count&lt;/code&gt; container — placing items in column-reading order so that CSS's top-to-bottom fill produces the correct left-to-right result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Popular Reorder Algorithm — And Why It Fails
&lt;/h2&gt;

&lt;p&gt;Jesse Korzan published &lt;a href="https://medium.com/hackernoon/masonry-layout-technique-react-demo-of-100-css-control-of-the-view-e4190fa4296" rel="noopener noreferrer"&gt;a clean write-up of this technique on Medium&lt;/a&gt; with a live demo. The idea is elegant: instead of passing &lt;code&gt;[1, 2, 3, 4, 5, 6]&lt;/code&gt; to the CSS column container, reorder it to &lt;code&gt;[1, 4, 2, 5, 3, 6]&lt;/code&gt; so that CSS fills column 1 with items 1 and 4, column 2 with items 2 and 5, column 3 with items 3 and 6 — producing the correct left-to-right reading order.&lt;/p&gt;

&lt;p&gt;The solution is correct under its own assumption. The problem is the assumption itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ This approach breaks with variable-height cards&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reorder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Broken in production — see explanation below&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;columnCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;columnGap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.5rem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;reorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;breakInside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;avoid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostCard&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The algorithm assumes CSS will put exactly &lt;code&gt;Math.ceil(N / cols)&lt;/code&gt; items into each column. That assumption is only valid when every card has the same height.&lt;/p&gt;

&lt;p&gt;When cards have different heights — which is always true for real content — CSS distributes items by pixel height, not by count. A tall card in column 1 means that column fills up with fewer items. Column 2 then starts earlier, takes more items, and fills faster. By the time you reach the last column, the distribution is completely different from what the reorder algorithm predicted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6 cards, heights: [300px, 100px, 150px, 200px, 100px, 250px]
Column target height: ~333px each

Algorithm expects:         CSS actually produces:
Col 1: items 1, 4          Col 1: item 1 (300px) → full
Col 2: items 2, 5          Col 2: items 2, 3, 5 → 350px
Col 3: items 3, 6          Col 3: items 4, 6 → 450px

Result: items appear in wrong visual positions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reorder step produces the right order for equal-height cards. For real cards, you've just shuffled the content in a way that makes the visual order less predictable, not more.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  The reorder trick is only correct when all cards have identical heights. With real content —&lt;br&gt;
  varying text lengths, images, tag counts — the algorithm makes ordering worse, not better.&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The Correct Approach: Replace CSS Columns With JS Columns
&lt;/h2&gt;

&lt;p&gt;The fix is to stop using &lt;code&gt;column-count&lt;/code&gt; entirely. Instead of one CSS container that CSS distributes into columns, create N separate &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements — one per column — and distribute items into them in JavaScript using round-robin assignment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;item 0 → col 0
item 1 → col 1
item 2 → col 2
item 3 → col 0  ← back to col 0
item 4 → col 1
item 5 → col 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
  The key insight: when JS controls which column each item goes into, CSS has no say in the&lt;br&gt;
  distribution. Card heights don't matter. The reading order is always exactly what you specified.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;With round-robin assignment, row 1 always contains items &lt;code&gt;[0, 1, 2]&lt;/code&gt;, row 2 always contains &lt;code&gt;[3, 4, 5]&lt;/code&gt; — regardless of how tall each card renders. CSS still controls the visual layout (flex row, gap, column width), but the distribution decision is made entirely in JS before render.&lt;/p&gt;

&lt;p&gt;The trade-off: you lose CSS's ability to balance column heights. With &lt;code&gt;column-count&lt;/code&gt;, CSS naturally puts more items in a column if the previous items were short. With the JS approach, each column gets exactly &lt;code&gt;Math.ceil(N / cols)&lt;/code&gt; items — columns with shorter cards will have more whitespace at the bottom. For most portfolio and blog grid use cases, consistent reading order matters more than height balancing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full MasonryGrid Component
&lt;/h2&gt;

&lt;p&gt;Here is the complete TypeScript component, 60 lines including the breakpoint hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useLayoutEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// useLayoutEffect on client, useEffect on server (Next.js SSR compatible)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useIsomorphicLayoutEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;useLayoutEffect&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Breakpoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;md&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Breakpoints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Breakpoints&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCols&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;useIsomorphicLayoutEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setCols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt; &lt;span class="c1"&gt;// eslint-disable-line react-hooks/exhaustive-deps&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MasonryGridProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;breakpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Breakpoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MasonryGrid&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gap-6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;breakpoints&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;MasonryGridProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;breakpoints&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Round-robin distribution: item i goes to column (i % cols)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;childArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="nx"&gt;childArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colIdx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colIdx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex-1 flex flex-col &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage on the blog page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MasonryGrid&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"gap-6"&lt;/span&gt; &lt;span class="na"&gt;breakpoints&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContentCard&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MasonryGrid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few implementation notes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;useIsomorphicLayoutEffect&lt;/code&gt;&lt;/strong&gt; — Next.js renders on the server where &lt;code&gt;window&lt;/code&gt; does not exist. &lt;code&gt;useLayoutEffect&lt;/code&gt; throws a warning in SSR context. The isomorphic pattern falls back to &lt;code&gt;useEffect&lt;/code&gt; server-side, which is a no-op, and uses &lt;code&gt;useLayoutEffect&lt;/code&gt; client-side so the column count is applied before the first paint — preventing a flash of single-column layout on page load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;bp.default&lt;/code&gt; as SSR value&lt;/strong&gt; — &lt;code&gt;useState(bp.default)&lt;/code&gt; means the server always renders the smallest column count. On hydration, &lt;code&gt;useIsomorphicLayoutEffect&lt;/code&gt; fires synchronously and updates to the correct column count for the viewport. This produces the correct layout with zero layout shift for most visitors, since &lt;code&gt;default: 1&lt;/code&gt; renders a valid single-column layout that gets upgraded immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;flex-1&lt;/code&gt; on column divs&lt;/strong&gt; — columns share available width equally. This is simpler than computing &lt;code&gt;width: calc(100% / cols)&lt;/code&gt; and works correctly with arbitrary gap sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap as a Tailwind class string&lt;/strong&gt; — &lt;code&gt;gap="gap-6"&lt;/code&gt; lets the caller use any Tailwind spacing token. The same gap applies both between columns (on the flex row) and between items within a column (on the flex column). Passing separate horizontal and vertical gap props is straightforward if you need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on Native CSS Masonry
&lt;/h2&gt;

&lt;p&gt;CSS is getting native masonry layout via the &lt;code&gt;display: masonry&lt;/code&gt; / &lt;code&gt;grid-template-rows: masonry&lt;/code&gt; spec (currently renamed to &lt;strong&gt;CSS Grid Lanes&lt;/strong&gt;). Safari 26.4 shipped stable support. Chrome and Firefox have it behind flags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* ❌ Not production-ready — 0.02% global browser support (early 2026) */&lt;/span&gt;

&lt;span class="c"&gt;/* Older spec name — still referenced widely */&lt;/span&gt;
&lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;masonry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;masonry-template-tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Newer name (CSS Grid Lanes) — same situation */&lt;/span&gt;
&lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid-lanes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see both names in articles and browser release notes — &lt;code&gt;display: masonry&lt;/code&gt; was the original proposal, &lt;code&gt;display: grid-lanes&lt;/code&gt; is the current direction after the CSS Working Group renamed the spec in 2024. They are the same feature at different stages. Neither is production-ready: only Safari 26.4 ships stable support, Chrome and Firefox are behind flags. Global coverage in stable browsers is approximately 0.02% as of early 2026. Until this lands across all engines, the JS approach in this post is what you ship.&lt;/p&gt;




&lt;p&gt;Reading order in a masonry grid is one of those problems that looks solved until you add real content. The popular reorder algorithm assumes equal heights — a constraint you can't guarantee in any real application. Separate flex columns with round-robin JS distribution is the correct fix: reading order is always exact, the implementation is transparent, and it works in Next.js without any client-side workarounds beyond the isomorphic effect hook.&lt;/p&gt;

&lt;p&gt;I build portfolio sites, SaaS frontends, and e-commerce UIs where layout correctness matters. If you need a senior developer who handles the full stack — from grid algorithms to checkout flows — &lt;a href="https://iurii.rogulia.fi/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://iurii.rogulia.fi//blog/css-svg-animation-keyframe-sync-no-library" rel="noopener noreferrer"&gt;How I Synced 6 CSS Keyframe Animations Without a Single Library&lt;/a&gt; — more CSS-from-first-principles work&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://iurii.rogulia.fi//blog/nextjs-dynamic-og-images" rel="noopener noreferrer"&gt;Dynamic OG Images in Next.js: Why opengraph-image.tsx Hangs Turbopack&lt;/a&gt; — another case where the documented approach breaks in production&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/hackernoon/masonry-layout-technique-react-demo-of-100-css-control-of-the-view-e4190fa4296" rel="noopener noreferrer"&gt;Jesse Korzan — Masonry Layout Technique (Medium)&lt;/a&gt; — the original reorder technique, works perfectly with uniform card heights&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/css-grid-3/" rel="noopener noreferrer"&gt;CSS Grid Level 3&lt;/a&gt; — Masonry Layout (W3C draft spec for native masonry)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/column-count" rel="noopener noreferrer"&gt;MDN: column-count&lt;/a&gt; — official reference for the CSS primitive&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>masonry</category>
      <category>css</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Binary PDF Modification Detection: How It Works and Where It Fails</title>
      <dc:creator>Iurii Rogulia</dc:creator>
      <pubDate>Tue, 24 Feb 2026 15:01:22 +0000</pubDate>
      <link>https://forem.com/iurii_rogulia/binary-pdf-modification-detection-how-it-works-and-where-it-fails-ph9</link>
      <guid>https://forem.com/iurii_rogulia/binary-pdf-modification-detection-how-it-works-and-where-it-fails-ph9</guid>
      <description>&lt;p&gt;The question sounds simple: has this PDF been edited after it was originally created? The answer, it turns out, requires understanding how PDF files store their history at the byte level.&lt;/p&gt;

&lt;p&gt;This article explains the technical approach behind binary PDF modification detection — what signals we look for, why they are reliable, and critically, where the method breaks down. If you want a quick answer without the technical detail, &lt;a href="https://htpbe.tech" rel="noopener noreferrer"&gt;HTPBE.tech&lt;/a&gt; is an online tool that tells you whether a PDF has been edited (yes/no) in seconds. If you want to understand how that answer is produced, read on.&lt;/p&gt;




&lt;h2&gt;
  
  
  How a PDF File Is Structured
&lt;/h2&gt;

&lt;p&gt;Before discussing detection, you need to understand the PDF file format at a structural level.&lt;/p&gt;

&lt;p&gt;A PDF file consists of four components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Header&lt;/strong&gt; — declares the PDF version (&lt;code&gt;%PDF-1.7&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body&lt;/strong&gt; — a collection of numbered objects (pages, fonts, images, metadata)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-reference table (xref)&lt;/strong&gt; — an index mapping object numbers to byte offsets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trailer&lt;/strong&gt; — points to the xref table and the document root object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A minimal trailer looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trailer
&amp;lt;&amp;lt;
  /Size 42
  /Root 1 0 R
  /Info 2 0 R
&amp;gt;&amp;gt;
startxref
9876
%%EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/Info&lt;/code&gt; entry points to the document information dictionary — this is where metadata lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Metadata Dictionary
&lt;/h2&gt;

&lt;p&gt;The PDF Information Dictionary (&lt;code&gt;/Info&lt;/code&gt;) contains fields that describe the document’s origin and history:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/CreationDate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the document was originally created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ModDate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the document was last modified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Creator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Application that created the source document (e.g., Microsoft Word)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Producer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PDF library or printer that generated the PDF (e.g., macOS Quartz PDFContext)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Author&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Document author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/Title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Document title&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dates are encoded in a specific format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D:20231015143022+03'00'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means: 2023-10-15 at 14:30:22, UTC+3. The timezone offset is significant — we will come back to it.&lt;/p&gt;

&lt;p&gt;The Creator/Producer distinction matters enormously. When someone writes a document in Microsoft Word and exports it to PDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Creator = Microsoft Word&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Producer = Microsoft Word&lt;/code&gt; (or a Word-internal PDF engine)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When someone then opens that PDF in Adobe Acrobat and edits it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Creator&lt;/code&gt; stays as &lt;code&gt;Microsoft Word&lt;/code&gt; (preserved from original)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Producer&lt;/code&gt; changes to &lt;code&gt;Adobe Acrobat 23.0&lt;/code&gt; (the tool that wrote the new version)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mismatch is a strong signal of modification.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Incremental Save Mechanism
&lt;/h2&gt;

&lt;p&gt;This is the core of PDF modification detection, and it is what makes PDFs fundamentally different from most other file formats.&lt;/p&gt;

&lt;p&gt;When you edit a PDF and save it, most PDF editors do &lt;strong&gt;not&lt;/strong&gt; rewrite the entire file. Instead, they &lt;strong&gt;append&lt;/strong&gt; changes to the end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The original file body is left intact&lt;/li&gt;
&lt;li&gt;New or modified objects are appended after &lt;code&gt;%%EOF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A new cross-reference table is appended, containing entries for the changed objects&lt;/li&gt;
&lt;li&gt;A new trailer is appended with a &lt;code&gt;/Prev&lt;/code&gt; field pointing to the byte offset of the previous xref table
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[original body]
%%EOF
[new objects]
xref
[new xref entries]
trailer
&amp;lt;&amp;lt;
  /Size 55
  /Root 1 0 R
  /Prev 9876          ← points to original xref
&amp;gt;&amp;gt;
startxref
14523
%%EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This incremental update mechanism exists for good reasons: it enables fast partial saves, supports undo history, and is required for certain digital signature workflows. But it also leaves a clear paper trail.&lt;/p&gt;

&lt;p&gt;A PDF with multiple &lt;code&gt;%%EOF&lt;/code&gt; markers, multiple xref sections, or a trailer with &lt;code&gt;/Prev&lt;/code&gt; has been saved more than once. This does not automatically mean fraud — legitimate workflows involve multiple saves. But it is a definitive indicator that the file was modified after initial creation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Automated Detection Actually Examines
&lt;/h2&gt;

&lt;p&gt;A detection tool like HTPBE examines several independent signals and combines them into a confidence score.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Timestamp Consistency
&lt;/h3&gt;

&lt;p&gt;The simplest check: is &lt;code&gt;ModDate&lt;/code&gt; later than &lt;code&gt;CreationDate&lt;/code&gt;? If yes, the file was modified. But there are subtleties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ModDate == CreationDate&lt;/code&gt; does not mean the file was never edited. Some editors reset &lt;code&gt;ModDate&lt;/code&gt; to match &lt;code&gt;CreationDate&lt;/code&gt; to hide modifications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ModDate&lt;/code&gt; earlier than &lt;code&gt;CreationDate&lt;/code&gt; is impossible under normal conditions and indicates tampering with metadata.&lt;/li&gt;
&lt;li&gt;Timezone changes between &lt;code&gt;CreationDate&lt;/code&gt; and &lt;code&gt;ModDate&lt;/code&gt; (e.g., creation in &lt;code&gt;+03'00'&lt;/code&gt; but modification in &lt;code&gt;Z&lt;/code&gt;) suggest the file was modified on a different machine or with a different tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Cross-Reference Table Analysis
&lt;/h3&gt;

&lt;p&gt;Counting xref sections reveals the number of save operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 xref section&lt;/strong&gt;: the file was saved exactly once — typical for freshly exported PDFs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2+ xref sections&lt;/strong&gt;: the file was modified at least once after initial creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3+ xref sections&lt;/strong&gt;: multiple rounds of editing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The content of the xref updates also matters. If the updated objects include the &lt;code&gt;/Info&lt;/code&gt; dictionary (metadata), it suggests metadata was modified after the fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Creator/Producer Mismatch Analysis
&lt;/h3&gt;

&lt;p&gt;We maintain a knowledge base of known PDF creation tool combinations. Some mismatches are routine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Creator: Word&lt;/code&gt; + &lt;code&gt;Producer: Adobe PDF Printer&lt;/code&gt; — normal, Word printed to PDF&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Creator: Google Docs&lt;/code&gt; + &lt;code&gt;Producer: Skia/PDF&lt;/code&gt; — normal Google Docs export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some mismatches are suspicious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Creator: Microsoft Excel&lt;/code&gt; + &lt;code&gt;Producer: iLovePDF&lt;/code&gt; — the file was processed by an online editor&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Creator: LibreOffice&lt;/code&gt; + &lt;code&gt;Producer: Adobe Acrobat&lt;/code&gt; — the file was opened and re-saved in Acrobat&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Creator:&lt;/code&gt; (empty) + &lt;code&gt;Producer: FPDF&lt;/code&gt; — programmatically generated, may be a reconstructed document&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool also looks for editors known to be used for document fraud: certain online PDF editors, specific versions of commercial tools with known history-stripping features.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Digital Signature Integrity
&lt;/h3&gt;

&lt;p&gt;PDF supports cryptographic digital signatures (&lt;code&gt;/Sig&lt;/code&gt; objects). When a signed PDF is modified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The signature covers specific byte ranges of the file&lt;/li&gt;
&lt;li&gt;If content outside those byte ranges is modified, the signature becomes invalid&lt;/li&gt;
&lt;li&gt;If the signature object itself is removed and the document re-saved, the absence of a previously-present signature is detectable via the revision history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Object Stream Anomalies
&lt;/h3&gt;

&lt;p&gt;PDF 1.5+ supports compressed object streams. Legitimate PDF generators produce consistent, well-formed object streams. Anomalies we check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Objects with inconsistent generation numbers&lt;/li&gt;
&lt;li&gt;Orphaned objects not reachable from the document root&lt;/li&gt;
&lt;li&gt;Objects that exist in the xref table but whose byte offsets point to different object types (indicates partial overwrite attempts)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Putting It Together: The Confidence Score
&lt;/h2&gt;

&lt;p&gt;No single signal is conclusive. A document with &lt;code&gt;ModDate &amp;gt; CreationDate&lt;/code&gt; might be completely legitimate. A document with matching timestamps might have been forged with a tool that resets them.&lt;/p&gt;

&lt;p&gt;Detection tools combine multiple signals into a weighted confidence score:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multiple xref sections&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Hard to fake without specialized knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creator/Producer mismatch with known editor&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Common in legitimate workflows too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp anomaly (impossible date)&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Never legitimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp anomaly (reset to match)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Detectable but requires suspicion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known fraud-associated producer&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Context-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signature removed&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Strong indicator of post-signing modification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A score above a threshold produces a “Modified” verdict. Below the threshold: “Original”. The threshold is calibrated to minimize false positives on legitimate documents.&lt;/p&gt;




&lt;h2&gt;
  
  
  False Positives: When Unmodified PDFs Look Modified
&lt;/h2&gt;

&lt;p&gt;This is where the method has real limitations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legitimate workflows that trigger modification signals:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PDF/A conversion&lt;/strong&gt; — archival format conversion modifies the file structure. &lt;code&gt;ModDate&lt;/code&gt; will differ from &lt;code&gt;CreationDate&lt;/code&gt; even if content is unchanged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Digital signing&lt;/strong&gt; — adding a digital signature is a modification. A signed-after-creation document will show multiple xref sections even if the content was never altered.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimization&lt;/strong&gt; — tools like Ghostscript, qpdf, or Adobe Acrobat’s “Reduce File Size” rewrite the entire file, resetting the xref structure but potentially preserving old timestamps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Printer workflows&lt;/strong&gt; — some enterprise print systems re-process PDFs (adding watermarks, headers, or compliance stamps) as a routine step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Email clients&lt;/strong&gt; — some email clients modify PDF attachments (adding metadata, stripping features) during send or receive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merging or splitting&lt;/strong&gt; — combining PDFs with tools like PDFtk or iLovePDF creates a new document with a new Producer and potentially inconsistent metadata from the source files.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In all these cases, the document content may be perfectly authentic, but the structural signals indicate modification. This is why results should always be interpreted in context.&lt;/p&gt;




&lt;h2&gt;
  
  
  False Negatives: When Modified PDFs Look Original
&lt;/h2&gt;

&lt;p&gt;More dangerous is the opposite case: a forged document that passes detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Techniques that evade detection:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full reconstruction&lt;/strong&gt; — printing to PDF (or using &lt;code&gt;print → save as PDF&lt;/code&gt;) creates a completely new file with a fresh xref, consistent timestamps, and a single clean structure. All modification traces are lost. The resulting PDF looks freshly created, even if it was forged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata scrubbing&lt;/strong&gt; — tools like &lt;code&gt;exiftool&lt;/code&gt; or custom scripts can overwrite all metadata fields, including timestamps and creator information, before the file is distributed. A sophisticated forger resets &lt;code&gt;CreationDate = ModDate&lt;/code&gt; and sets &lt;code&gt;Producer&lt;/code&gt; to match the expected original tool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Re-export from source format&lt;/strong&gt; — if the forger has access to the source document (a Word file, for example), they can modify the source and re-export to PDF. The resulting PDF has no modification traces because it was never “modified” — it was created fresh from a modified source.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PDF reconstruction tools&lt;/strong&gt; — specialized tools can parse a modified PDF and reconstruct it as a clean single-revision file, stripping all incremental save history.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fundamental limitation: &lt;strong&gt;binary detection works on file structure, not content&lt;/strong&gt;. We can detect that a file’s structure shows signs of modification, but we cannot verify whether the visible text or numbers are correct. A forger skilled enough to reconstruct the PDF properly can evade structural detection entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means in Practice
&lt;/h2&gt;

&lt;p&gt;Binary PDF modification detection is a fast, automated first-line check. It catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Naive modifications made with standard PDF editors (the vast majority of document fraud cases)&lt;/li&gt;
&lt;li&gt;Metadata inconsistencies that reveal post-creation editing&lt;/li&gt;
&lt;li&gt;Structural anomalies that indicate multiple save operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sophisticated forgeries where the PDF was fully reconstructed&lt;/li&gt;
&lt;li&gt;Source-level modifications (edit the Word doc, re-export)&lt;/li&gt;
&lt;li&gt;Cases where the file was “legitimately” processed by a PDF workflow tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For high-stakes verification (legal documents, financial instruments, regulated industries), automated detection should be combined with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human review of the document content for semantic consistency&lt;/li&gt;
&lt;li&gt;Verification of the document’s origin through the issuing party&lt;/li&gt;
&lt;li&gt;Cryptographic verification if the original was digitally signed&lt;/li&gt;
&lt;li&gt;Chain-of-custody controls that prevent the question from arising&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Implementation Notes
&lt;/h2&gt;

&lt;p&gt;For developers building similar tools, the key libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pdf-lib&lt;/strong&gt; (JavaScript/Node.js) — parse PDF structure, read xref tables, access object streams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyMuPDF / fitz&lt;/strong&gt; (Python) — comprehensive PDF analysis, supports most PDF versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pdfminer.six&lt;/strong&gt; (Python) — lower-level access to PDF internals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iTextSharp&lt;/strong&gt; (.NET) — commercial-grade PDF analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core algorithm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the xref table(s) — count revisions&lt;/li&gt;
&lt;li&gt;Extract &lt;code&gt;/Info&lt;/code&gt; dictionary — compare timestamps, analyze Creator/Producer&lt;/li&gt;
&lt;li&gt;Walk the xref chain via &lt;code&gt;/Prev&lt;/code&gt; pointers — identify which objects changed between revisions&lt;/li&gt;
&lt;li&gt;Check for &lt;code&gt;/Sig&lt;/code&gt; objects — verify digital signature presence and coverage&lt;/li&gt;
&lt;li&gt;Score each signal — weight by reliability and combine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implementation challenge is handling malformed PDFs. Real-world documents frequently deviate from the PDF specification. A robust tool must handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing or corrupted xref tables (PDF readers use recovery mode)&lt;/li&gt;
&lt;li&gt;Linearized PDFs (a different structure used for web-optimized files)&lt;/li&gt;
&lt;li&gt;Encrypted PDFs (metadata may be accessible even if content is not)&lt;/li&gt;
&lt;li&gt;PDF 2.0 documents with new structural features&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Binary PDF modification detection is a reliable heuristic, not a cryptographic proof. It answers the question “does this file’s structure suggest modification?” with high accuracy for everyday document fraud. Against a skilled adversary who understands PDF internals, structural detection alone is insufficient.&lt;/p&gt;

&lt;p&gt;The honest answer to “has this PDF been edited?” is: we can tell you what the file’s structure reveals. For most fraud cases — edited invoices, tampered bank statements, modified contracts — that is enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htpbe.tech" rel="noopener noreferrer"&gt;HTPBE.tech&lt;/a&gt; implements the detection approach described in this article. The web tool is free, requires no registration, and processes PDFs in seconds. The API is available for integration into document verification workflows.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>security</category>
      <category>forensic</category>
      <category>fraud</category>
    </item>
  </channel>
</rss>
