- A4 invoice with a table, totals and page numbers
- Assemble an audit dossier, then split it for the reviewers
- Watermark and password-protect a contract
- Fill and flatten a PDF form
- KPI one-pager with a hand-drawn chart and a thumbnail
- Training certificate in A4 landscape with a seal
- Shrink a scanned dossier before archiving
- Stamp a PDF received in base64 from an API
- Monthly statements: one file, then one PDF per customer
- Archive compliance: check and fix the PDF metadata
A4 invoice with a table, totals and page numbers
A tool shop issues invoice F-2026-118 with three lines. The script draws the header band, the lines table, computes the VAT and the total, right-aligns the total in a box sized with the text width, adds a payment link and the page numbers, saves the file and reads its text back to check it.
#1. New A4 document with its metadata; pdf create "rx_inv"; pdf info set "rx_inv" "title=Invoice F-2026-118;author=Acme Tools;subject=Invoice"; pdf page add "rx_inv" "A4" "portrait"; #2. Header band, customer and lines; pdf draw rect "rx_inv" 1 0 0 595 90 "fill=#1E5AA0"; pdf text add "rx_inv" 1 40 32 "ACME TOOLS" "font=HELVETICA_BOLD;size=22;color=#FFFFFF"; pdf text add "rx_inv" 1 555 36 "INVOICE F-2026-118" "font=HELVETICA_BOLD;size=13;color=#FFFFFF;align=right"; pdf text add "rx_inv" 1 40 110 "Bill to: Garage Hoarau, 12 rue de Paris, 97400 Saint-Denis" "size=10"; json load "rx_pos" (pdf table add "rx_inv" 1 40 135 "{\"columns\":[\"Item\",{\"title\":\"Qty\",\"align\":\"right\",\"width\":50},{\"title\":\"Unit\",\"align\":\"right\",\"width\":70},{\"title\":\"Amount\",\"align\":\"right\",\"width\":80}],\"rows\":[[\"Cordless drill 18V\",\"2\",\"129.00\",\"258.00\"],[\"HSS bits set\",\"5\",\"12.90\",\"64.50\"],[\"Safety glasses\",\"10\",\"4.20\",\"42.00\"]]}" "zebra=#F2F5FA;width=515;header_bg=#1E5AA0"); #3. VAT 20 percent and total in a box sized to the text; -> "[net]" (+ 258 64.5 42); -> "[total]" (concat "Total incl. VAT: " (math decimal_format (* [net] 1.2) "0.00" "." " ") " EUR"); -> "[w]" (pdf text width "rx_inv" [total] "font=HELVETICA_BOLD;size=12"); -> "[y]" (+ (json select "rx_pos" "/y") 15); pdf draw rect "rx_inv" 1 (- 549 [w]) [y] (+ [w] 12) 24 "fill=#FFF4D6;stroke=#E0A800;radius=4"; pdf text add "rx_inv" 1 549 (+ [y] 6) [total] "font=HELVETICA_BOLD;size=12;align=right"; #4. Payment link, footer line and page numbers; pdf text add "rx_inv" 1 40 (+ [y] 50) "Pay online: https://pay.example.com/F-2026-118" "size=10;color=#1E5AA0;underline=true"; pdf link add "rx_inv" 1 40 (+ [y] 50) 260 14 "https://pay.example.com/F-2026-118"; pdf draw line "rx_inv" 1 40 800 555 800 "color=#CCCCCC;line_width=1"; pdf page_number add "rx_inv" "Page {page} / {total}" 555 810 "align=right;size=9"; pdf save "rx_inv" "tmp/rx_invoice.pdf"; pdf close "rx_inv"; #5. Check the file; concat "pages: " (pdf page_count_file "tmp/rx_invoice.pdf") [_n_] (pdf extract_text "tmp/rx_invoice.pdf");
pages: 1 ACME TOOLS INVOICE F-2026-118 Bill to: Garage Hoarau, 12 rue de Paris, 97400 Saint-Denis Item Qty Unit Amount Cordless drill 18V 2 129.00 258.00 HSS bits set 5 12.90 64.50 Safety glasses 10 4.20 42.00 Total incl. VAT: 437.40 EUR Pay online: https://pay.example.com/F-2026-118 Page 1 / 1
- Fill the rows from a database with
sql to json:pdf table addaccepts its output directly - Send it with
pdf to_base64as a mail attachment or a REST payload
Assemble an audit dossier, then split it for the reviewers
An auditor renders three chapters from HTML and merges them with a draft page. The script removes the draft, appends an appendix page, rotates it, adds bookmarks, then splits the dossier into one file per page and extracts the findings into a separate file.
#1. Chapters rendered from HTML; pdf from html "<html><body><h1>1. Scope</h1><p>Purchase-to-pay process, fiscal year 2026.</p></body></html>" "tmp/rx_ch1.pdf"; pdf from html "<html><body><h1>DRAFT NOTES</h1><p>To remove.</p></body></html>" "tmp/rx_draft.pdf"; pdf from html "<html><body><h1>2. Findings</h1><p>3 invoices paid twice, 1 order without PO.</p></body></html>" "tmp/rx_ch2.pdf"; pdf from html "<html><body><h1>Appendix A</h1><p>Sample of 40 invoices.</p></body></html>" "tmp/rx_appx.pdf"; #2. Merge, remove the draft page, append and rotate the appendix; pdf merge "[\"tmp/rx_ch1.pdf\",\"tmp/rx_draft.pdf\",\"tmp/rx_ch2.pdf\"]" "tmp/rx_audit.pdf"; pdf load "rx_aud" "tmp/rx_audit.pdf"; pdf page delete "rx_aud" 2; -> "[p]" (pdf page import "rx_aud" "tmp/rx_appx.pdf" 1); pdf page rotate "rx_aud" [p] 90; #3. Bookmarks for the reader outline; pdf bookmark add "rx_aud" "1. Scope" 1; pdf bookmark add "rx_aud" "2. Findings" 2; pdf bookmark add "rx_aud" "Appendix A" [p]; -> "[n]" (pdf page count "rx_aud"); json load "rx_ps" (pdf page size "rx_aud" [p]); pdf save "rx_aud" "tmp/rx_audit.pdf"; pdf close "rx_aud"; #4. One file per page, and the findings alone; json load "rx_parts" (pdf split "tmp/rx_audit.pdf" "tmp/rx_parts"); pdf extract_pages "tmp/rx_audit.pdf" "tmp/rx_findings.pdf" 2 2; concat "{\"pages\": " [n] ", \"appendix_rotation\": " (json select "rx_ps" "/rotation") ", \"split_files\": " (json count "rx_parts" "/") ", \"findings\": \"" (string replace (string trim (pdf extract_text "tmp/rx_findings.pdf")) [_n_] " | ") "\"}";
{"pages": 3, "appendix_rotation": 90, "split_files": 3, "findings": "2. Findings | 3 invoices paid twice, 1 order without PO."}- Split every N pages with
pdf split <source> <directory> <pagesPerFile> - Use
pdf extract_text <source> <from> <to>to index each chapter in a search engine
Watermark and password-protect a contract
A legal team sends a contract to a client: it must be marked CONFIDENTIAL, open only with a password, and forbid copy and modification. The script watermarks the PDF, protects it with AES-256, re-opens it with the user password to check the encryption, then removes the protection with the owner password.
#1. The contract and its watermark; pdf from html "<html><body><h2>Service agreement 2026-044</h2><p>Between Acme Tools and Garage Hoarau. Duration 24 months, fee 1 200 EUR per month.</p></body></html>" "tmp/rx_contract.pdf"; pdf load "rx_c" "tmp/rx_contract.pdf"; pdf watermark add "rx_c" "CONFIDENTIAL" "color=#C00000;opacity=0.12;size=70;angle=45"; pdf save "rx_c" "tmp/rx_contract.pdf"; pdf close "rx_c"; #2. Protect: user password to open, no copy, no modification; pdf protect "tmp/rx_contract.pdf" "tmp/rx_contract_sec.pdf" "Legal-Owner-26" "Client-7Q4" "print=true;copy=false;modify=false"; #3. Open it as the client would; pdf load "rx_s" "tmp/rx_contract_sec.pdf" "Client-7Q4"; json load "rx_info" (pdf info get "rx_s"); -> "[loaded]" (concat "{\"exist\": " (pdf exist "rx_s") ", \"pdfs\": " (string regexp_replace (pdf show) "\s" "") "}"); pdf close "rx_s"; #4. The owner removes the protection, then shares a read-only copy without password; pdf unprotect "tmp/rx_contract_sec.pdf" "tmp/rx_contract_open.pdf" "Legal-Owner-26"; pdf protect "tmp/rx_contract_open.pdf" "tmp/rx_contract_ro.pdf" "Legal-Owner-26" ""; concat "{\"encrypted\": " (json select "rx_info" "/encrypted") ", \"pages\": " (json select "rx_info" "/pages") ", \"loaded\": " [loaded] ", \"title_after_unprotect\": \"" (string regexp_extract (pdf extract_text "tmp/rx_contract_open.pdf" 1 1) "(Service agreement [0-9-]+)" 1) "\", \"read_only_pages\": " (pdf page_count_file "tmp/rx_contract_ro.pdf") "}";
{"encrypted": true, "pages": 1, "loaded": {"exist": 1, "pdfs": ["rx_s"]}, "title_after_unprotect": "Service agreement 2026-044", "read_only_pages": 1}- Allow only printing and form filling with
pdf protect ... "print=true;fill_in=true;copy=false;modify=false" - Generate a strong password per client with
string generate_random_strand send it by another channel
Fill and flatten a PDF form
A supplier registration arrives as a fillable PDF (AcroForm). For the demo, the script first writes a tiny two-field form; then it lists the fields, fills them, flattens the form so it cannot be edited, saves it, and returns it as base64 ready for an API.
#1. A minimal fillable PDF with two text fields, for the demo; file create "tmp/rx_form.pdf" (concat "%PDF-1.4" [_n_] "1 0 obj <</Type/Catalog/Pages 2 0 R/AcroForm <</Fields[4 0 R 6 0 R]/DA(/Helv 0 Tf 0 g)/DR<</Font<</Helv 5 0 R>>>>>>>> endobj" [_n_] "2 0 obj <</Type/Pages/Kids[3 0 R]/Count 1>> endobj" [_n_] "3 0 obj <</Type/Page/Parent 2 0 R/MediaBox[0 0 595 842]/Annots[4 0 R 6 0 R]>> endobj" [_n_] "4 0 obj <</Type/Annot/Subtype/Widget/FT/Tx/T(company)/Rect[100 700 400 720]/P 3 0 R/DA(/Helv 12 Tf 0 g)>> endobj" [_n_] "6 0 obj <</Type/Annot/Subtype/Widget/FT/Tx/T(siret)/Rect[100 660 400 680]/P 3 0 R/DA(/Helv 12 Tf 0 g)>> endobj" [_n_] "5 0 obj <</Type/Font/Subtype/Type1/BaseFont/Helvetica>> endobj" [_n_] "trailer <</Root 1 0 R>>" [_n_] "%%EOF"); #2. Load it and list the fields; pdf load "rx_form" "tmp/rx_form.pdf"; json load "rx_fields" (pdf form fields "rx_form"); #3. Fill, flatten and save; -> "[filled]" (pdf form fill "rx_form" "{\"company\":\"Garage Hoarau SARL\",\"siret\":\"812 345 678 00021\"}"); pdf form flatten "rx_form"; pdf save "rx_form" "tmp/rx_form_done.pdf"; #4. Base64 for an API, reloaded to check it; -> "[b64]" (pdf to_base64 "rx_form"); pdf load_base64 "rx_copy" [b64]; -> "[copy_fields]" (json load "rx_f2" (pdf form fields "rx_copy")); pdf close_all; concat "{\"fields\": [\"" (json select "rx_fields" "/[0]/name") "\", \"" (json select "rx_fields" "/[1]/name") "\"], \"type\": \"" (json select "rx_fields" "/[0]/type") "\", \"filled\": " [filled] ", \"fields_after_flatten\": " (json count "rx_f2" "/") ", \"base64_starts\": \"" (string left [b64] 8) "\", \"text\": \"" (string replace (string trim (pdf extract_text "tmp/rx_form_done.pdf")) [_n_] " | ") "\"}";
{"fields": ["company", "siret"], "type": "TextField", "filled": 2, "fields_after_flatten": 0, "base64_starts": "JVBERi0x", "text": "Garage Hoarau SARL | 812 345 678 00021"}- For a real form, start at
pdf loadwith the file received; checkboxes take true or false inpdf form fill - Keep the form editable: skip
pdf form flattenand only save
KPI one-pager with a hand-drawn chart and a thumbnail
A sales manager wants a one-page A5 report: a paragraph of comment, a line chart of monthly orders drawn with polygons and circles, and the logo. The script maps the values to page coordinates, draws the chart, saves the PDF, renders a PNG thumbnail and tests the image compression.
#1. A5 landscape page, a TrueType font and the comment; pdf create "rx_kpi"; pdf page add "rx_kpi" "A5" "landscape"; pdf font load "rx_kpi" "mono" "MentDB_Weak_Editor_GPLv3/tools/JetBrainsMono-Regular.ttf"; json load "rx_size" (pdf page size "rx_kpi" 1); json load "rx_fonts" (pdf font show "rx_kpi"); pdf image add "rx_kpi" 1 "images/info.png" 30 25 0 24; pdf text add "rx_kpi" 1 62 30 "Orders per month - H1 2026" "font=HELVETICA_BOLD;size=16"; json load "rx_end" (pdf text paragraph "rx_kpi" 1 30 65 530 "Orders grew by 62 percent between January and May, then slowed down in June after the price change." "font=mono;size=10;align=justify"); #2. Map the values to page coordinates, y grows downwards; json load "rx_v" "[42, 55, 49, 63, 68, 61]"; -> "[pts]" ""; -> "[i]" 0; json parse_array "rx_v" "/" "[v]" { -> "[x]" (+ 60 (* [i] 90)); -> "[y]" (- 380 (* [v] 3.5)); -> "[pts]" (concat [pts] " " [x] "," [y]); pdf draw circle "rx_kpi" 1 [x] [y] 4 "fill=#1E5AA0"; pdf text add "rx_kpi" 1 [x] (- [y] 18) [v] "size=9;align=center"; -> "[i]" (+ [i] 1); }; #3. Axis and curve; pdf draw line "rx_kpi" 1 50 380 530 380 "color=#999999;line_width=1"; pdf draw polygon "rx_kpi" 1 (string trim [pts]) "stroke=#1E5AA0;line_width=2;close=false"; pdf save "rx_kpi" "tmp/rx_kpi.pdf"; pdf close "rx_kpi"; #4. Thumbnail and compression test; pdf to_image "tmp/rx_kpi.pdf" 1 50 "tmp/rx_kpi.png"; pdf compress_image "tmp/rx_kpi.pdf" "tmp/rx_kpi_small.pdf" 72; concat "{\"page_width\": " (math round (json select "rx_size" "/width") 0) ", \"page_height\": " (math round (json select "rx_size" "/height") 0) ", \"points\": \"" (string trim [pts]) "\", \"after_paragraph_y\": " (json select "rx_end" "/y") ", \"fonts\": " (json count "rx_fonts" "/") ", \"thumbnail_png\": " (file exist "tmp/rx_kpi.png") ", \"compressed_is_image_only\": \"" (string trim (pdf extract_text "tmp/rx_kpi_small.pdf")) "\"}";
{"page_width": 595, "page_height": 420, "points": "60,233 150,187.5 240,208.5 330,159.5 420,142 510,166.5", "after_paragraph_y": 90.0, "fonts": 15, "thumbnail_png": 1, "compressed_is_image_only": ""}pdf compress_imageturns every page into an image: use it only for scanned documents, here the text is gone- List the fonts you can use with
pdf font showafterpdf font load
Training certificate in A4 landscape with a seal
Produce the completion certificate of a safety training: an A4 landscape page with a double frame, a round seal and a justified paragraph, then check the page geometry and the metadata before sending it.
#1. A4 landscape page with a double frame; pdf create "rx_p4i_cert"; pdf info set "rx_p4i_cert" "title=Certificate SST-2026-0457;author=Training Center Est;subject=Workplace first aid;keywords=SST,training,certificate"; -> "[p]" (pdf page add "rx_p4i_cert" "A4" "landscape"); pdf draw rect "rx_p4i_cert" [p] 20 20 802 555 "stroke=#1E5AA0;line_width=3"; pdf draw rect "rx_p4i_cert" [p] 30 30 782 535 "stroke=#C9A227;line_width=1;dash=4 2"; #2. Title, trainee and a justified paragraph; pdf text add "rx_p4i_cert" [p] 421 80 "CERTIFICATE OF COMPLETION" "font=TIMES_BOLD;size=30;color=#1E5AA0;align=center"; pdf text add "rx_p4i_cert" [p] 421 150 "Marie Lauret" "font=TIMES_BOLD_ITALIC;size=26;align=center"; json load "rx_p4i_pos" (pdf text paragraph "rx_p4i_cert" [p] 120 210 600 "has successfully completed the Workplace First Aid training, 14 hours, from 13 to 14 October 2026, and passed the practical assessment on cardiac arrest, bleeding and choking. This certificate is valid for 24 months." "font=TIMES_ROMAN;size=14;align=justify;line_height=1.4"); #3. Round gold seal at the bottom right, signature line at the left; pdf draw circle "rx_p4i_cert" [p] 700 470 55 "fill=#C9A227;opacity=0.85"; pdf draw circle "rx_p4i_cert" [p] 700 470 45 "stroke=#FFFFFF;line_width=2"; pdf text add "rx_p4i_cert" [p] 700 462 "SST 2026" "font=HELVETICA_BOLD;size=13;color=#FFFFFF;align=center"; pdf draw line "rx_p4i_cert" [p] 120 500 340 500 "color=#333333;line_width=1"; pdf text add "rx_p4i_cert" [p] 120 508 "J. Hoarau, lead trainer" "font=HELVETICA_OBLIQUE;size=10"; #4. Checks before saving; json load "rx_p4i_size" (pdf page size "rx_p4i_cert" [p]); -> "[fonts]" (string regexp_count (pdf font show "rx_p4i_cert") "TIMES"); pdf save "rx_p4i_cert" "tmp/rx_p4i_certificate.pdf"; pdf close "rx_p4i_cert"; pdf load "rx_p4i_chk" "tmp/rx_p4i_certificate.pdf"; json load "rx_p4i_info" (pdf info get "rx_p4i_chk"); pdf close "rx_p4i_chk"; concat "Page " (math round (json select "rx_p4i_size" "/width") 0) " x " (math round (json select "rx_p4i_size" "/height") 0) " pt, landscape: " (> (json select "rx_p4i_size" "/width") (json select "rx_p4i_size" "/height")) [_n_] "Paragraph ends at y = " (json select "rx_p4i_pos" "/y") " on page " (json select "rx_p4i_pos" "/page") ", Times fonts available: " [fonts] [_n_] "Title: " (json select "rx_p4i_info" "/title") ", author: " (json select "rx_p4i_info" "/author") ", pages: " (json select "rx_p4i_info" "/pages");
Page 842 x 595 pt, landscape: 1 Paragraph ends at y = 268.8 on page 1, Times fonts available: 4 Title: Certificate SST-2026-0457, author: Training Center Est, pages: 1
- Load a TrueType font with
pdf font loadto print accented or non Latin names. - Add a verification URL with
pdf link addunder the seal. - Generate one certificate per trainee from a SQL query with
sql parse.
Shrink a scanned dossier before archiving
A three-page dossier made of scanned images is too heavy for the archive. Rebuild it at a lower resolution with pdf compress_image and report the size gain, keeping the page count.
#1. Simulate the scan: one full-page picture per page; pdf create "rx_p4i_scan"; for (-> "[i]" 1) (<= [i] 3) (++ "[i]") { -> "[p]" (pdf page add "rx_p4i_scan" "A4" "portrait"); pdf image add "rx_p4i_scan" [p] "images/bg1500x500.jpeg" 0 0 595 0; pdf text add "rx_p4i_scan" [p] 40 300 (concat "Scanned page " [i] " / 3") "size=16"; }; pdf save "rx_p4i_scan" "tmp/rx_p4i_scan.pdf"; pdf close "rx_p4i_scan"; #2. Rebuild the dossier at 60 dpi; pdf compress_image "tmp/rx_p4i_scan.pdf" "tmp/rx_p4i_scan_60dpi.pdf" 60; #3. Compare; -> "[before]" (file size "tmp/rx_p4i_scan.pdf"); -> "[after]" (file size "tmp/rx_p4i_scan_60dpi.pdf"); -> "[gain]" (math round (* 100 (- 1 (/ [after] [before]))) 1); concat "Pages: " (pdf page_count_file "tmp/rx_p4i_scan.pdf") " -> " (pdf page_count_file "tmp/rx_p4i_scan_60dpi.pdf") [_n_] "Size: " (math round (/ [before] 1024) 1) " KB -> " (math round (/ [after] 1024) 1) " KB" [_n_] "Gain: " [gain] " %";
Pages: 3 -> 3 Size: 189.9 KB -> 87.1 KB Gain: 54.2 %
- Run it on every file of an archive folder with
file dir_list_regex. - Keep the original only if the gain is under 20 % with an
ifon [gain]. - Note: the output is made of images, run
pdf extract_textbefore compressing if you need the text.
Stamp a PDF received in base64 from an API
A supplier portal sends invoices as base64 strings in JSON. Load the document from base64, stamp it RECEIVED with the date and a reference, and return it in base64 for the ERP, with a short control report.
#1. Simulate the portal payload: a PDF in base64 inside a JSON; pdf from html "<html><body><h2>Invoice SUP-88412</h2><p>Supplier: Ocean Pack Ltd</p><p>Total: 4 812.60 EUR</p></body></html>" "tmp/rx_p4i_sup.pdf"; pdf load "rx_p4i_src" "tmp/rx_p4i_sup.pdf"; json load "rx_p4i_msg" "{}"; json iobject "rx_p4i_msg" / "invoice" "SUP-88412" STR; json iobject "rx_p4i_msg" / "file" (concat "data:application/pdf;base64," (pdf to_base64 "rx_p4i_src")) STR; pdf close "rx_p4i_src"; #2. Load the document from the base64 field; pdf load_base64 "rx_p4i_inv" (json select "rx_p4i_msg" "/file"); #3. Stamp: rotated red text and a reception reference; -> "[ref]" (concat "REC-" (string replace (date sysdate) "-" "") "-0042"); pdf draw rect "rx_p4i_inv" 1 380 40 180 56 "stroke=#C00000;line_width=2;radius=6"; pdf text add "rx_p4i_inv" 1 470 48 "RECEIVED" "font=HELVETICA_BOLD;size=18;color=#C00000;align=center"; pdf text add "rx_p4i_inv" 1 470 72 [ref] "font=COURIER;size=10;color=#C00000;align=center"; #4. Back to base64 for the ERP, write it once to check it; -> "[out]" (pdf to_base64 "rx_p4i_inv"); pdf close "rx_p4i_inv"; file b64_write [out] "tmp/rx_p4i_sup_stamped.pdf"; -> "[text]" (pdf extract_text "tmp/rx_p4i_sup_stamped.pdf"); concat "Invoice " (json select "rx_p4i_msg" "/invoice") ": base64 in " (string length (json select "rx_p4i_msg" "/file")) " chars, out " (string length [out]) " chars" [_n_] "Stamp found: " (string contains [text] "RECEIVED") ", reference found: " (string contains [text] [ref]) ", total line: " (string regexp_extract [text] "(Total: [0-9 .]+ EUR)" 1);
Invoice SUP-88412: base64 in 1708 chars, out 3312 chars Stamp found: 1, reference found: 1, total line: Total: 4 812.60 EUR
- Read the payload with
rest httpsfrom the portal, then answer withrest https_json_post. - Store the base64 in a mail attachment instead of a file.
- Refuse the document when
pdf info getsays it is encrypted.
Monthly statements: one file, then one PDF per customer
Print all the monthly statements of account in one PDF (one page per customer, overdue amounts flagged in red), then cut it into one file per customer named by the account number, ready to be mailed.
#1. The accounts: number, name, invoiced, overdue; json load "rx_p4i_acc" "[[\"C-1001\",\"Garage Hoarau\",1240.50,0],[\"C-1002\",\"Boulangerie Payet\",385.20,385.20],[\"C-1003\",\"Pharmacie du Port\",2910.00,910.00]]"; pdf create "rx_p4i_st"; json parse_array "rx_p4i_acc" "/" "[a]" { json load "rx_p4i_a" [a]; -> "[p]" (pdf page add "rx_p4i_st" "A4" "portrait"); pdf text add "rx_p4i_st" [p] 40 40 (concat "Statement of account, October 2026 - " (json select "rx_p4i_a" "/[0]")) "font=HELVETICA_BOLD;size=14"; pdf text add "rx_p4i_st" [p] 40 70 (json select "rx_p4i_a" "/[1]") "size=12"; pdf text add "rx_p4i_st" [p] 40 100 (concat "Invoiced: " (math decimal_format (json select "rx_p4i_a" "/[2]") "0.00" "." " ") " EUR") "size=11"; #2. Red badge when an amount is overdue; if (> (json select "rx_p4i_a" "/[3]") 0) { pdf draw rect "rx_p4i_st" [p] 380 92 175 26 "fill=#C00000;radius=4"; pdf text add "rx_p4i_st" [p] 467 98 (concat "OVERDUE " (math decimal_format (json select "rx_p4i_a" "/[3]") "0.00" "." " ")) "font=HELVETICA_BOLD;size=11;color=#FFFFFF;align=center"; }; }; pdf page_number add "rx_p4i_st" "Page {page} / {total}" 555 815 "align=right;size=9"; pdf save "rx_p4i_st" "tmp/rx_p4i_statements.pdf"; pdf close "rx_p4i_st"; #3. One file per customer, page i = customer i; file mkdir "tmp/rx_p4i_st_out"; -> "[i]" 0; json parse_array "rx_p4i_acc" "/" "[a]" { json load "rx_p4i_a" [a]; ++ "[i]"; pdf extract_pages "tmp/rx_p4i_statements.pdf" (concat "tmp/rx_p4i_st_out/" (json select "rx_p4i_a" "/[0]") ".pdf") [i] [i]; }; #4. Check the folder and one file; concat "Pages in the batch: " (pdf page_count_file "tmp/rx_p4i_statements.pdf") [_n_] "Files: " (string regexp_replace (file dir_list_regex "tmp/rx_p4i_st_out" "C-.*\.pdf" true false) "\s+" "") [_n_] "C-1003.pdf says: " (string replace (string trim (pdf extract_text "tmp/rx_p4i_st_out/C-1003.pdf")) [_n_] " | ");
Pages in the batch: 3 Files: ["C-1001.pdf","C-1003.pdf","C-1002.pdf"] C-1003.pdf says: Statement of account, October 2026 - C-1003 | Pharmacie du Port | Invoiced: 2910.00 EUR OVERDUE 910.00 | Page 3 / 3
- Mail each file to its customer with
mail sendand the account in a SQL table. - Use
pdf splitwith pagesPerFile when each customer has a fixed number of pages. - Add the payment terms with
pdf text paragraphon each page.
Archive compliance: check and fix the PDF metadata
Before an electronic archive deposit, every PDF must carry a title, an author and a subject. Inspect a batch with pdf info get, fill what is missing from the file name and produce a compliance report.
#1. A batch of documents, one already tagged; pdf from html "<html><body><h3>Purchase order PO-7781</h3></body></html>" "tmp/rx_p4i_arc_PO-7781.pdf"; pdf from html "<html><body><h3>Delivery note DN-5520</h3></body></html>" "tmp/rx_p4i_arc_DN-5520.pdf"; pdf from html "<html><body><h3>Invoice INV-3301</h3></body></html>" "tmp/rx_p4i_arc_INV-3301.pdf"; pdf load "rx_p4i_d" "tmp/rx_p4i_arc_INV-3301.pdf"; pdf info set "rx_p4i_d" "title=INV-3301;author=Accounting;subject=Invoice"; pdf save "rx_p4i_d" "tmp/rx_p4i_arc_INV-3301.pdf"; pdf close "rx_p4i_d"; #2. Inspect each file, fix what is missing; json load "rx_p4i_rep" "{}"; json load "rx_p4i_files" (file dir_list_regex "tmp" "rx_p4i_arc_.*\.pdf" true false); json parse_array "rx_p4i_files" "/" "[f]" { -> "[path]" (concat "tmp/" [f]); -> "[docid]" (string regexp_extract [f] "rx_p4i_arc_(.*)\.pdf" 1); pdf load "rx_p4i_d" [path]; json load "rx_p4i_i" (pdf info get "rx_p4i_d"); -> "[missing]" 0; if (is null or empty (json select "rx_p4i_i" "/title")) {++ "[missing]";}; if (is null or empty (json select "rx_p4i_i" "/author")) {++ "[missing]";}; if (> [missing] 0) { pdf info set "rx_p4i_d" (concat "title=" [docid] ";author=Archive robot;subject=" (string regexp_extract [docid] "^([A-Z]+)-" 1)); pdf save "rx_p4i_d" [path]; json iobject "rx_p4i_rep" / [docid] (concat "fixed, " [missing] " field(s) were missing") STR; } { json iobject "rx_p4i_rep" / [docid] "compliant" STR; }; pdf close "rx_p4i_d"; }; #3. Check one fixed file; pdf load "rx_p4i_d" "tmp/rx_p4i_arc_DN-5520.pdf"; json load "rx_p4i_i" (pdf info get "rx_p4i_d"); json iobject "rx_p4i_rep" / "DN-5520 after fix" (concat (json select "rx_p4i_i" "/title") " / " (json select "rx_p4i_i" "/author") " / " (json select "rx_p4i_i" "/subject")) STR; pdf close "rx_p4i_d"; json doc "rx_p4i_rep";
{
"PO-7781": "fixed, 2 field(s) were missing",
"INV-3301": "compliant",
"DN-5520": "fixed, 2 field(s) were missing",
"DN-5520 after fix": "DN-5520 / Archive robot / DN"
}- Add the PDF version with
json selecton /version and reject the files under 1.4. - Move the compliant files to the deposit folder with
file copy_file. - Log each fix with
log writefor the audit trail.
