Skip to content

PDF

mash.pdf · Files & Storage

mash.pdf reads the text out of a PDF, blacks out sensitive content for good, splits a long PDF into several, and builds new PDFs. Use text when you need to look at what a document says. Use redact before you share something. Use the split methods for a scanned batch where each document starts with a recognisable header or footer.

text reads the text layer. It returns the text with a form feed character (\f) after each page. A PDF that is only a picture of text, such as a scan, has no text layer, so text comes back empty. Captur’s OCR (mash.ai.ocr) is the way in for those.

redact removes content, it doesn’t cover it. Redacted text is deleted from the page and can’t be copied out afterwards. Give it phrases, areas or both. Phrases match exactly and ignore case, against the text layer only. An area is a box on a page, burnt out whatever lies underneath, including the pixels of a scanned image. It returns a dictionary:

KeyMeaning
filethe redacted copy, held in temporary storage
redactedhow many places were blanked
foundthe phrases that matched
missingthe phrases that didn’t
scanned_pagespages with no text layer, which phrases can’t search

redact writes nothing to a share. You save file yourself with a folder’s write, so the run’s permissions apply.

Areas count from the page’s top-left. An area is {page, rect: [x0, y0, x1, y1]}. Pages start at 1. The units are points (1/72 inch) unless you say unit: "mm" or give a dpi for pixel positions.

Splitting uses a custom field. splitOnHeader and splitOnFooter cut a PDF where a field’s text zone changes (or, with discard, drop the pages that don’t match). They give a list of parts, each with its own file.

Count a PDF’s pages and search it:

set pdf to mash.files.get("Contracts", "Inbox/agreement.pdf")
set text to mash.pdf.text(pdf)
set pages to mash.str(text).split("\f")
printline "{{count(pages) - 1}} pages"
if mash.str(text).containsAny(["termination", "Termination"])
printline "Mentions termination"
end

Redact names and save a copy:

set pdf to mash.files.get("Contracts", "Inbox/agreement.pdf")
set result to mash.pdf.redact(pdf, ["Jane Smith", "07700 900123"])
if count(result["scanned_pages"]) is more than 0
fail("Some pages are scans and can't be searched. A person needs to look.")
end
set out to mash.files.get("Contracts", "Redacted")
out.write(result["file"].name, result["file"])
printline "Blanked {{result['redacted']}} places"

Black out a box on page 1, such as a signature:

set result to mash.pdf.redact(pdf, [], [
{page: 1, rect: [50, 700, 250, 760]}
])
  • A scan isn’t searched. Check scanned_pages and decide what to do. A copy that looks redacted but wasn’t is worse than no copy.
  • Phrases of one character are ignored, and a call takes at most 200 phrases and 500 areas.
  • redact always returns a copy, even when nothing matched, so a script that shares “the redacted version” never falls back to the original by mistake. Check redacted to see whether anything was blanked.
  • The page count is one less than the pieces. text ends each page with a form feed, so splitting on it leaves an empty piece at the end.
  • Only PDFs, and not huge ones. A file that isn’t a PDF, or is over the size limit, is an error.

Files and folders, and mash.files for the same split calls.

NameTypeDescription
tmpManager
pdf.builder(orientation?: mixed = "P", unit?: mixed = "mm", size?: mixed = "A4")

Create a new PDF builder (mPDF)

pdf.redact(file: File, phrases: array, areas?: array = [])

Redact phrases and/or areas from a PDF: removed from the page, not covered. areas: [{page, rect: [x0, y0, x1, y1], unit?: “pt”|“mm”, dpi?}], from the page’s top-left. Returns { file, redacted, found, missing, scanned_pages }; write file where you want it.

Same engine as Grace’s redaction (PdfRedactor: mupdf text search, then mutool run redact.js). Matching is exact and case-insensitive against the PDF’s TEXT LAYER, so a scanned page cannot be searched here: its page numbers come back in scanned_pages and the script must decide (refuse, or route to a person) rather than treat it as clean.

An area is burned whatever lies under it: text, vector drawing, and the PIXELS of any image it overlaps (mupdf rewrites the image with that region blanked, so extracting the image afterwards does not recover it). That is the way to scrub a scanned page, a photo or a signature. Coordinates are on the page as displayed (rotation applied), measured from its top-left corner: PDF points by default (1/72 in), unit: "mm", or dpi: 300 for pixel positions on a page rendered at that resolution, which is what OCR word boxes are.

Nothing is written to storage. The redacted copy is a TmpFile and the script saves it through the ordinary write path, which carries the run’s permissions; this method only reads a file the script already holds.

pdf.splitOnFooter(file: File, zone: mixed, onChange?: mixed = false, discard?: mixed = false) → array

Split a PDF on footer zone matches.

pdf.splitOnHeader(file: File, zone: mixed, onChange?: mixed = false, discard?: mixed = false) → array

Split a PDF on header zone matches.

pdf.text(file: File) → string

The PDF’s text layer, pages separated by a form feed (“\f”). The same text pdf.redact() searches, so a phrase copied out of it will match.