--- name: donesigned-prepare description: Prepare a PDF so it's ready to sign on DoneSigned (donesigned.com). Fill in the values the user gives you, and mark where they sign, initial and date, using standard PDF form fields with DoneSigned's names. The user then opens the file on donesigned.com, checks it, and signs it themselves. --- # Prepare a PDF for DoneSigned You are the agent. The user wants a PDF filled in and ready to sign on [DoneSigned](https://donesigned.com), a free e-signature site that works entirely in the browser: the document never leaves the user's device. Your job is to make a new copy of the PDF where: 1. the values the user gave you are already filled in, and 2. the spots to sign, initial and date are marked, so DoneSigned shows "Sign here" exactly there. You **prepare** the document. The user **signs** it, on donesigned.com, after reading it, agreeing to sign electronically and confirming their email. Never try to sign for them. ## How DoneSigned reads a prepared PDF Everything is a standard PDF form field (AcroForm), so the file still works in any PDF viewer. | To get | Add | | ------------------------------ | ------------------------------------------------------ | | A value filled in | A text field with that value (any name) | | A box ticked | A check box, checked (any name) | | A spot to sign | A text field named `donesigned:signature` | | A spot to initial | A text field named `donesigned:initials` | | The date of signing | A text field named `donesigned:date`, left empty | | A written-out date of signing | One field per blank: `donesigned:date-day`, `-month`, `-year` or `-yy` | Written-out dates are phrases like "this \_\_\_ day of \_\_\_\_\_\_, 20\_\_" or "\_\_\_ day of \_\_\_\_\_\_, \_\_\_\_ A.D.". Put one field on each blank: - `donesigned:date-day` becomes "19th"; - `donesigned:date-month` becomes "October"; - `donesigned:date-year` becomes "2026"; - `donesigned:date-yy` becomes "26", for a blank after a printed "20". Add a suffix when a document has several such phrases (`donesigned:date-day-p5`, `donesigned:date-month-p5`, …). - DoneSigned matches the start of the name, so `donesigned:signature-2` or `donesigned:initials-page3` work too. Field names must be unique in a PDF; use suffixes when you need several separate fields. - Initials on every page: either one `donesigned:initials` field with a box on each page (one field can have many boxes, called widgets), or one field per page with suffixes. Both work. - Real signature fields (`/FT /Sig`) work too: an empty one becomes a spot to sign, and one with a `donesigned:` name follows the table above. - Leave signature, initials and date fields **empty**. DoneSigned fills dates with the day of signing; when it signs, it draws the signature and initials in their boxes and removes those marker fields. - When DoneSigned sees these names, it tells the user the PDF was filled in by their agent and asks them to check it. They can still change everything before signing. ## Conversation flow 1. **Get the PDF and the values.** Ask the user for the PDF and for anything you need to fill in that they haven't told you. Only use values the user gave you or that come from documents they pointed you to. If you're unsure about a value, ask; don't guess. 2. **Look at the PDF.** Check whether it already has form fields. If it does, fill the empty ones you have values for (keep their names), leave fields that already have a value alone unless the user asks, and only add what's missing. Find where each value goes from the page text: labels like "Name:", "Date", "Signature", lines and underscores. A PDF can hold several documents (an agreement, then a certificate): ask which ones to fill. Some pages are images with no text at all (scans, certificates): render them and find the lines in the pixels (see "Pages without text" below). 3. **Fill and mark.** Add or fill the fields as described above, the way someone would write on the document: - text and dates: about 14 to 20 points tall, the bottom of the box on the line; - signature: about 20 to 36 points tall, as wide as the line, with the line about three quarters of the way down the box (DoneSigned writes on a baseline at 72% of the height, with loops above and tails below). So for a line at y, a box of height h runs from y − 0.72h to y + 0.28h; - initials: the same, about 20 to 24 points tall and 50 to 70 wide. Keep boxes off the document's own text: if a line has text just above it, use a shorter box. 4. **Check your work.** Read the fields back, and if you can, render the pages to an image and look at them: every value in the right place, a signature spot for each signature line, nothing covering the document's own text. 5. **Hand it back.** Save it as a new file next to the original (for example `lease-prepared.pdf`) and tell the user: - what you filled in, and where they sign or initial; - to open it at https://donesigned.com (drop the file on the page; it's opened in their browser, not uploaded), check it, and sign. ### Things you must NOT do - Do NOT sign for the user: no signature images, no drawn or typed signatures, no fonts that look like handwriting in the signature or initials fields. Leave those fields empty. - Do NOT upload the document anywhere. DoneSigned never needs it uploaded, and neither do you; work on the user's local file. - Do NOT change the document's own text, or fill in anything the other party is supposed to fill. - Do NOT overwrite the original file, flatten the form, or encrypt the PDF. - Do NOT invent values (addresses, dates, amounts, ID numbers). Ask. ## Pages without text `page.get_text("words")` returns nothing on a page that's an image. Render it at 72 dpi, so one pixel is one point, and look for horizontal runs of dark pixels: those are the lines to write on. ```python pix = doc[8].get_pixmap(dpi=72) # 1 px = 1 pt, y from the top dark = lambda x, y: sum(pix.pixel(x, y)) < 600 # thin lines render gray for y in range(pix.height): x = 0 while x < pix.width: if dark(x, y): x0 = x while x < pix.width and dark(x, y): x += 1 if x - x0 >= 25: # shorter runs are lettering or borders print(f"line y={y} x={x0}..{x}") x += 1 ``` Then look at the rendering to tell which line is which (the label under or next to it), and put the fields on them as usual. ## Recipes Coordinates are in PDF points (1/72 inch). Find positions from the page text first; the numbers below are only examples. ### JavaScript (pdf-lib) `npm install pdf-lib`. pdf-lib measures from the **bottom-left** of the page. ```js import fs from 'fs'; import { PDFDocument } from 'pdf-lib'; const doc = await PDFDocument.load(fs.readFileSync('lease.pdf')); const form = doc.getForm(); // creates the form if the PDF has none const [page1, page2] = doc.getPages(); // A value const tenant = form.createTextField('tenant_name'); tenant.setText('Jane Smith'); tenant.addToPage(page1, { x: 72, y: 534, width: 268, height: 20, borderWidth: 0 }); // A ticked box const pets = form.createCheckBox('no_pets'); pets.addToPage(page1, { x: 72, y: 494, width: 16, height: 16 }); pets.check(); // Where to sign (the line is at y=380: the box runs from 380 − 0.28 × 36), and the date (left empty) form.createTextField('donesigned:signature').addToPage(page1, { x: 72, y: 370, width: 268, height: 36, borderWidth: 0 }); form.createTextField('donesigned:date').addToPage(page1, { x: 380, y: 382, width: 160, height: 20, borderWidth: 0 }); // Initials on every page: one field, one box per page const initials = form.createTextField('donesigned:initials'); for (const page of [page1, page2]) initials.addToPage(page, { x: 480, y: 60, width: 60, height: 24, borderWidth: 0 }); fs.writeFileSync('lease-prepared.pdf', await doc.save()); ``` If the PDF already has a form, fill its fields instead: `form.getTextField('Tenant').setText('Jane Smith')`, `form.getCheckBox('Pets').check()`. List them with `form.getFields().map((f) => f.getName())`. ### Python (PyMuPDF) `pip install pymupdf`. PyMuPDF measures from the **top-left** of the page, and `page.get_text("words")` gives each word with its box, which is the easiest way to find labels and lines. ```python import fitz # PyMuPDF doc = fitz.open("lease.pdf") def text_field(page, name, rect, value=""): w = fitz.Widget() w.field_type = fitz.PDF_WIDGET_TYPE_TEXT w.field_name = name w.rect = fitz.Rect(rect) # (x0, y0, x1, y1), y from the top w.field_value = value w.text_fontsize = 0 # auto size w.border_width = 0 page.add_widget(w) def check_box(page, name, rect, checked=True): w = fitz.Widget() w.field_type = fitz.PDF_WIDGET_TYPE_CHECKBOX w.field_name = name w.rect = fitz.Rect(rect) w.field_value = checked page.add_widget(w) # Where the labels are: (x0, y0, x1, y1, word, ...) for word in doc[0].get_text("words"): print(word[:5]) p1 = doc[0] text_field(p1, "tenant_name", (72, 238, 340, 258), "Jane Smith") check_box(p1, "no_pets", (72, 282, 88, 298)) text_field(p1, "donesigned:signature", (72, 386, 340, 422)) # line at y=412 text_field(p1, "donesigned:date", (380, 390, 540, 410)) for i, page in enumerate(doc, start=1): # initials on every page text_field(page, f"donesigned:initials-{i}", (480, 708, 540, 732)) # "this ___ day of ________, 20___": one field per blank, filled when signing text_field(p1, "donesigned:date-day", (300, 434, 330, 448)) text_field(p1, "donesigned:date-month", (360, 434, 450, 448)) text_field(p1, "donesigned:date-yy", (468, 434, 486, 448)) doc.save("lease-prepared.pdf") ``` Existing fields: `for w in page.widgets(): print(w.field_name, w.field_type_string)`, then set `w.field_value` and call `w.update()`.