Aperio Home All Suites
PDF Skills Evaluation

Document Generation, Editing & Form Filling

Ten self-contained PDF tasks that probe how well a model creates, edits, merges, splits, and fills PDF files β€” from simple pdf-lib text pages to AcroForm filling, static form annotation, and multi-step document pipelines. Each task has a copy-paste prompt, a verify command, and clear pass criteria.

These tasks test the pdf skill β€” the model's ability to produce and manipulate PDF files using pdf-lib, qpdf, the project's PDF scripts, and the DOCXβ†’PDF conversion pipeline. Tasks 1–3 cover pdf-lib creation. Tasks 4–5 use qpdf for splitting and rotating. Task 6 covers the DOCXβ†’PDF route. Tasks 7–9 cover extraction, AcroForm filling, and static form annotation. Task 10 is a comprehensive multi-step pipeline. Start at Task 1 and work up.

PDF Technology (Quick Reference)

These tasks use pdf-lib for programmatic creation, qpdf for document manipulation, and the project's own PDF scripts for form handling and annotation. Knowing these conventions helps you evaluate whether the model followed the skill correctly.

ConceptRuleWhy
pdf-libUse for programmatic PDF creation from scratchZero extra deps, already installed
DOCX→PDFPreferred for formatted documentsLibreOffice gives proper typography
qpdfmerge, split, rotate, decryptSystem CLI, powerful document surgery
rgb()pdf-lib colors: rgb(0, 0, 0)Values 0.0–1.0, not 0–255
Standard fontsEmbed via StandardFonts.HelveticaNo external font files needed
Page sizeUS Letter = 612Γ—792 pointsDefault; set explicitly when needed
AcroFormCheck with check_fillable_fields.jsDetect if form is fillable or static
Static formsextract_form_structure.js β†’ annotateFind text position first, then overlay
Validationcheck_bounding_boxes.jsCatch overlapping annotation boxes
Imagesconvert_pdf_to_images.js (needs poppler)Visual QA of PDF output

Setup

You need one terminal with the Aperio server running. Paste each task prompt into the chat. The model will write scripts and use CLI tools to produce .pdf files.

Terminal β€” Aperio server
npm run start:local

Paste each task prompt into the chat. Use a fresh conversation per task when possible. Verify commands use pdf-lib, qpdf, and the project's PDF scripts.

Dependencies by task

Some tasks require system tools not installed by default:

# qpdf β€” needed for Tasks 4, 5, 10
brew install qpdf

# LibreOffice β€” needed for Task 6
brew install --cask libreoffice

# poppler β€” needed for convert_pdf_to_images.js (optional)
brew install poppler

Cleanup between runs

Clean up output files before re-running against a different model:

rm -f trash/test-*.pdf trash/create-*.js trash/fields-*.json

The Tasks

Ordered by complexity: pdf-lib creation first (single page, multi-page, merge), then qpdf manipulation, DOCX→PDF conversion, text extraction, AcroForm filling, static form annotation, and finally a full pipeline. Each task lists dependencies, difficulty, what to look for, the prompt to paste, and the verify command.

Task 1 β€” pdf-lib Creation

Simple Title Page β˜…β˜†β˜†β˜†β˜†

Probes: pdf-lib API, text positioning, font embedding, page setup

Files:

1 script + 1 output  (trash/create-title.js β†’ trash/test-title.pdf)

Deps: pdf-lib (built-in)

Paste thisWrite a Node.js script at trash/create-title.js that creates trash/test-title.pdf using pdf-lib: - US Letter page (612 Γ— 792 points) - Embed Helvetica-Bold font via StandardFonts.HelveticaBold - Draw a title: "Aperio β€” Personal Memory Layer" at x=72, y=650, size=28pt, dark navy color (rgb(0.1, 0.15, 0.27)) - Draw a subtitle: "One brain. Every agent. Nothing forgotten." at x=72, y=600, size=14pt, gray color (rgb(0.39, 0.42, 0.45)) - Draw a horizontal line using drawLine from x=72 to x=540, at y=580, thickness=1.5, purple color (rgb(0.48, 0.23, 0.93)) - Draw a date line: "Generated: July 2026" at x=72, y=540, size=10pt, gray - Use ES module import syntax and top-level await - Save with writeFileSync
What to evaluate
  • Does the script compile and run without errors?
  • Does the output file start with %PDF- magic bytes?
  • Is the PDF exactly 1 page?
  • Are all text elements present at correct positions?
Verify:
head -c 5 trash/test-title.pdf | grep -q '%PDF-' && echo "Valid PDF header OK" || echo "NOT a PDF"

node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-title.pdf');const d=await PDFDocument.load(b);console.log('Pages:',d.getPageCount());process.exit(d.getPageCount()===1?0:1)})"
Result:
Task 2 β€” Multi-Page pdf-lib

Multi-Page Report β˜…β˜…β˜†β˜†β˜†

Probes: addPage, multi-page document, repeated elements

Files:

1 script + 1 output  (trash/create-report.js β†’ trash/test-report.pdf)

Deps: pdf-lib (built-in)

Paste thisWrite a Node.js script at trash/create-report.js that creates a 3-page PDF at trash/test-report.pdf: Page 1 (Cover): Title "Q2 2026 Performance Report" (28pt, bold), subtitle "Confidential" (12pt, italic, gray), date line Page 2 (Content): Heading "Revenue Analysis" (18pt, bold), 4 paragraphs of text about quarterly revenue trends (use drawText with line wrapping β€” approximate by drawing multiple lines manually). Section heading "Cost Breakdown" (16pt, bold) with bullet-like text lines below. Page 3 (Summary): Heading "Key Takeaways" (18pt, bold), 3 bullet-like text lines, footer "β€” End of Report β€”" centered at bottom. Each page should have: - A thin header line near the top (use drawLine) - Page number footer: "Page X of Y" centered at bottom (x=306, y=30) Use Helvetica font. US Letter size. ES module syntax.
What to evaluate
  • Are there exactly 3 pages?
  • Does each page have a header line and page number?
  • Is the content different on each page?
  • Does the PDF header validate correctly?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-report.pdf');const d=await PDFDocument.load(b);console.log('Pages:',d.getPageCount());process.exit(d.getPageCount()===3?0:1)})"
Result:
Task 3 β€” Merge PDFs

Combine Two PDFs β˜…β˜…β˜†β˜†β˜†

Probes: PDFDocument.copyPages, multi-document merge, page ordering

Files:

1 script + 1 output  (trash/create-merge.js β†’ trash/test-merged.pdf)

Deps: pdf-lib (built-in)

Paste thisWrite a Node.js script at trash/create-merge.js that: 1. Creates two separate PDFs in memory using pdf-lib: - PDF A (1 page): Title "Part 1 β€” Introduction" (24pt), centered. Helvetica font. - PDF B (2 pages): Page 1 heading "Part 2 β€” Analysis" (20pt) with a paragraph of body text. Page 2 heading "Part 2 β€” Conclusion" (20pt) with a paragraph. 2. Merges them into a single PDF: PDF A then PDF B (3 total pages). Use copyPages to copy pages from each source document into a new document. 3. Adds a centered page number "Page X of 3" at the bottom of each merged page. 4. Saves to trash/test-merged.pdf. Use ES module syntax. US Letter size.
What to evaluate
  • Are there exactly 3 pages in the merged output?
  • Are pages in the correct order (Part 1 β†’ Part 2 pages)?
  • Is copyPages used (not regenerating content)?
  • Are page numbers present on each page?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-merged.pdf');const d=await PDFDocument.load(b);console.log('Pages:',d.getPageCount());process.exit(d.getPageCount()===3?0:1)})"
Result:
Task 4 β€” qpdf Split

Split Pages from a PDF β˜…β˜…β˜…β˜†β˜†

Probes: qpdf CLI usage, page range extraction, file management

Files:

Script + output  (first create source β†’ then split β†’ trash/test-split-pages2-3.pdf)

Deps: qpdf (system tool β€” brew install qpdf)

Paste thisCreate a 4-page PDF, then split out pages 2–3 into a new file. Step 1 β€” Create source.pdf Write a pdf-lib script trash/create-source.js that creates trash/test-source.pdf with 4 pages. Each page should have a large page number visible (e.g., "Page 1", "Page 2", etc.) drawn with drawText at center. Step 2 β€” Split Use qpdf to split pages 2 and 3 into a separate file: qpdf trash/test-source.pdf --pages . 2-3 -- trash/test-split-pages2-3.pdf Step 3 β€” Verify Check that the output has exactly 2 pages (formerly pages 2 and 3) using pdf-lib: node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-split-pages2-3.pdf');const d=await PDFDocument.load(b);console.log('Pages:',d.getPageCount());process.exit(d.getPageCount()===2?0:1)})"
What to evaluate
  • Does qpdf produce a valid 2-page PDF?
  • Is the source PDF free of corruption?
  • Does the model use qpdf --pages correctly?
Verify:
head -c 5 trash/test-split-pages2-3.pdf | grep -q '%PDF-' && echo "Valid PDF"

node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-split-pages2-3.pdf');const d=await PDFDocument.load(b);console.log('Split pages:',d.getPageCount());process.exit(d.getPageCount()===2?0:1)})"
Result:
Task 5 β€” qpdf Rotate

Rotate a Page β˜…β˜…β˜…β˜†β˜†

Probes: qpdf page rotation, multi-page PDF with mixed orientations

Files:

Script + output  (create β†’ rotate β†’ trash/test-rotated.pdf)

Deps: qpdf (system tool β€” brew install qpdf)

Paste thisCreate a 2-page PDF, then rotate the second page 90 degrees clockwise. Step 1 β€” Create source Write a pdf-lib script trash/create-rotate-source.js that creates trash/test-rotate-source.pdf. Page 1: "Normal Page" (portrait orientation). Page 2: "This text should rotate" β€” also drawn in portrait but will be rotated with qpdf. Step 2 β€” Rotate page 2 qpdf trash/test-rotate-source.pdf --rotate=+90:2 -- trash/test-rotated.pdf Step 3 β€” Verify Check that the output has 2 pages and that the rotation was applied. The verify command loads the PDF and checks that page 2 has a non-zero rotation.
What to evaluate
  • Are both pages present after rotation?
  • Does page 2 have a 90Β° rotation applied?
  • Is the --rotate flag syntax correct?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-rotated.pdf');const d=await PDFDocument.load(b);const p2=d.getPage(1);const rot=p2.getRotation().angle;console.log('Pages:',d.getPageCount(),'Rotation pg2:',rot);process.exit(d.getPageCount()===2&&(rot===90||rot===270)?0:1)})"
Result:
Task 6 β€” DOCX to PDF Conversion

Formatted Document via LibreOffice β˜…β˜…β˜…β˜†β˜†

Probes: generate_docx tool, soffice.py conversion pipeline, file path handling

Files:

DOCX β†’ PDF  (via generate_docx + soffice.py)

Deps: LibreOffice (brew install --cask libreoffice)

Paste thisDemonstrate the formatted DOCX→PDF pipeline: Step 1 — Create a Word document Use the generate_docx MCP tool to create trash/test-source.docx — a structured report with: - Title: "Aperio Technical Report — Q3 2026" (Heading 1) - Section 1 "Architecture Overview" (Heading 2) — 2 paragraphs of text - Section 2 "Performance Benchmarks" (Heading 2) — a table with 3 columns (Metric, Result, Status) and 4 data rows - Arial font, professional styling Step 2 — Convert to PDF Use the LibreOffice conversion script. Note the absolute path of the docx from the Saved at: output: python3 skills/docx/scripts/office/soffice.py --headless --convert-to pdf /absolute/path/to/trash/test-source.docx --outdir /absolute/path/to/trash The output will be trash/test-source.pdf. Step 3 — Verify Check the resulting PDF is valid and has multiple pages using pdf-lib.
What to evaluate
  • Does the conversion produce a valid PDF?
  • Is the PDF content from the source DOCX preserved?
  • Does the PDF have multiple pages (text + table)?
  • Is soffice.py called with correct args?
Verify:
head -c 5 trash/test-source.pdf | grep -q '%PDF-' && echo "Valid PDF"

node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-source.pdf');const d=await PDFDocument.load(b);console.log('Pages:',d.getPageCount());process.exit(d.getPageCount()>=1?0:1)})"
Result:
Task 7 β€” PDF Text Extraction

Read Text from a PDF β˜…β˜…β˜†β˜†β˜†

Probes: pdfjs-dist usage, text content extraction, page iteration

Files:

1 script + 1 output  (create β†’ then read back with pdfjs-dist)

Deps: pdfjs-dist (built-in)

Paste thisCreate a PDF with known content, then write a script to extract and verify the text. Step 1 β€” Create Write a pdf-lib script trash/create-text-source.js that creates trash/test-text-source.pdf (2 pages) with these exact strings embedded: - Page 1: "Aperio Memory Layer" (title), "Context is everything." (subtitle) - Page 2: "Architecture Overview", "SQLite and Postgres backends" Use Helvetica. US Letter. ES module syntax. Step 2 β€” Read & verify Write a script trash/read-pdf.js that uses pdfjs-dist (import from "pdfjs-dist/legacy/build/pdf.mjs") to load trash/test-text-source.pdf, extract text from all pages, and print it to console. The script should count pages and confirm specific strings exist in the extracted text. Run with: node trash/read-pdf.js
What to evaluate
  • Does the read script load the PDF successfully?
  • Does extracted text contain the expected strings?
  • Are all 2 pages processed?
  • Is the correct pdfjs-dist import path used?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-text-source.pdf');const d=await PDFDocument.load(b);console.log('Source pages:',d.getPageCount());process.exit(d.getPageCount()===2?0:1)})"

node trash/read-pdf.js 2>&1 | head -20
Result:
Task 8 β€” AcroForm Filling

Create & Fill a Fillable PDF Form β˜…β˜…β˜…β˜…β˜†

Probes: pdf-lib form creation, AcroForm fields, field extraction, field filling

Files:

Scripts + JSON + output  (form β†’ extract β†’ fill)

Deps: pdf-lib (built-in)

Paste thisCreate a fillable PDF form, inspect it, and fill it. Step 1 β€” Create a fillable form Write a pdf-lib script trash/create-form.js that creates trash/test-form.pdf with 3 AcroForm text fields using doc.createForm(): - Field "name" at position (x=100, y=650, w=300, h=30) β€” label: "Full Name:" - Field "email" at position (x=100, y=580, w=300, h=30) β€” label: "Email Address:" - Field "company" at position (x=100, y=510, w=300, h=30) β€” label: "Company:" - Title at top: "Registration Form" (24pt, bold) - Each field should have a border and be easily fillable Step 2 β€” Extract field info Run: node skills/pdf/scripts/check_fillable_fields.js trash/test-form.pdf Then: node skills/pdf/scripts/extract_form_field_info.js trash/test-form.pdf trash/fields-info.json Read the fields-info.json to confirm the 3 fields. Step 3 β€” Fill the form Create trash/fields-values.json by adding "value" keys to each field entry from step 2. Values: - name: "Dr. Sarah Chen" - email: "sarah.chen@novacloud.io" - company: "NovaCloud Systems" Run: node skills/pdf/scripts/fill_fillable_fields.js trash/test-form.pdf trash/fields-values.json trash/test-filled.pdf Step 4 β€” Verify Confirm the output PDF has filled fields using pdf-lib's doc.getForm().
What to evaluate
  • Does the source PDF have 3 AcroForm fields?
  • Does the extraction script detect the fields correctly?
  • Does the filling script produce a valid output PDF?
  • Are the filled values readable in the output?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-filled.pdf');const d=await PDFDocument.load(b);const f=d.getForm();const fields=f.getFields();console.log('Filled fields:',fields.length);fields.forEach(f=>console.log(' ',f.getName(),f.getText()));process.exit(fields.length>=3?0:1)})"
Result:
Task 9 β€” Static Form Annotation

Fill a Non-Fillable Form by Overlaying Text β˜…β˜…β˜…β˜…β˜…

Probes: extract_form_structure.js, fill_pdf_form_with_annotations.js, bounding boxes

Files:

Script pipeline  (create static form β†’ extract structure β†’ annotate β†’ fill)

Deps: pdf-lib (built-in), project scripts

Paste thisCreate a static (non-fillable) form PDF, analyze its structure, and overlay text at the correct positions. Step 1 β€” Create static form Write a pdf-lib script trash/create-static-form.js that creates trash/test-static-form.pdf. This should look like a form but NOT have AcroForm fields: - Title: "Employee Onboarding Form" (20pt, bold) - Label "Employee Name:" at (x=72, y=650) followed by a horizontal line (underscore area) at (x=220, y=648, w=300) for the user to write on - Label "Department:" at (x=72, y=600) with underscore line - Label "Start Date:" at (x=72, y=550) with underscore line - A bordered box section "HR Use Only" at the bottom Step 2 β€” Analyze structure Run: node skills/pdf/scripts/check_fillable_fields.js trash/test-static-form.pdf β€” it should report no fillable fields. Run: node skills/pdf/scripts/extract_form_structure.js trash/test-static-form.pdf trash/structure.json β€” this finds label positions, lines, and checkbox rectangles. Step 3 β€” Create field values JSON Create trash/annotate-fields.json with entries for each field. Use PDF coordinates (in points, origin at bottom-left). Each entry needs: {"entry_bounding_box": [x1, y1, x2, y2], "entry_text": "value"} Step 4 β€” Annotate and fill Run node skills/pdf/scripts/check_bounding_boxes.js trash/annotate-fields.json to validate coordinates. Then: node skills/pdf/scripts/fill_pdf_form_with_annotations.js trash/test-static-form.pdf trash/annotate-fields.json trash/test-annotated.pdf Step 5 β€” Verify Check the output PDF is valid and has correct page count.
What to evaluate
  • Does check_fillable_fields.js correctly report no AcroForm fields?
  • Does extract_form_structure.js find the labels and lines?
  • Does check_bounding_boxes.js validate the coordinates?
  • Does the annotated PDF have overlaid text at the expected positions?
Verify:
head -c 5 trash/test-annotated.pdf | grep -q '%PDF-' && echo "Valid PDF"

node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-annotated.pdf');const d=await PDFDocument.load(b);console.log('Annotated pages:',d.getPageCount());process.exit(d.getPageCount()===1?0:1)})"
Result:
Task 10 β€” Comprehensive Pipeline

Create β†’ Split β†’ Rotate β†’ Merge β†’ Extract β˜…β˜…β˜…β˜…β˜…

Probes: end-to-end PDF workflow, multiple tools, output verification at each step

Files:

Multi-step pipeline  (create β†’ split β†’ rotate β†’ merge β†’ extract)

Deps: pdf-lib, qpdf, pdfjs-dist (all available)

Paste thisExecute a full document pipeline with multiple tools. Do the following steps in order: Step 1 β€” Create 3 source PDFs Write a pdf-lib script that creates 3 separate PDFs in trash/: - test-a.pdf: Title "Section A β€” Overview" (1 page) - test-b.pdf: Title "Section B β€” Technical Details" (2 pages: Technical Details, Architecture) - test-c.pdf: Title "Section C β€” Appendices" (2 pages: Appendix 1, Appendix 2) Step 2 β€” Split test-c Use qpdf to split test-c.pdf pages 1 (Appendix 1) into trash/test-appendix1.pdf: qpdf trash/test-c.pdf --pages . 1 -- trash/test-appendix1.pdf Step 3 β€” Rotate test-b page 2 Rotate the Architecture page 90Β° clockwise: qpdf trash/test-b.pdf --rotate=+90:2 -- trash/test-b-rotated.pdf Step 4 β€” Merge all parts Use qpdf to merge: test-a.pdf + test-b-rotated.pdf + test-appendix1.pdf into trash/test-final.pdf: qpdf --empty --pages trash/test-a.pdf trash/test-b-rotated.pdf trash/test-appendix1.pdf -- trash/test-final.pdf Step 5 β€” Verify with pdf-lib Check the merged output has 4 total pages (1 + 2 + 1) and page 2 (from test-b) has the rotation applied.
What to evaluate
  • Are all intermediate files created without corruption?
  • Does the final merge have 4 pages?
  • Is the rotated page still in the correct position?
  • Are all qpdf commands using correct syntax?
Verify:
node -e "import('pdf-lib').then(async({PDFDocument})=>{const b=require('fs').readFileSync('trash/test-final.pdf');const d=await PDFDocument.load(b);const p2=d.getPage(1);const rot=p2.getRotation().angle;console.log('Final pages:',d.getPageCount(),'Page2 rotation:',rot);process.exit(d.getPageCount()===4&&(rot===90||rot===270)?0:1)})"
Result:

Per‑Task Scoring Rubric

Apply these five signals to each task after running the verify command. They measure not just "does the file exist" but also "did the model use the right tool for the job and verify its own work".

Signal0 = fail1 = pass
Output is valid PDFcorrupt / missing / wrong formatstarts with %PDF-, loads in pdf-lib
Correct tool for the jobwrote Python/reportlab when pdf-lib is available, or script when qpdf is simplerpdf-lib for creation, qpdf for manipulation, project scripts for forms
Correct page count and contentwrong pages, missing contentexpected pages, expected strings present
Follows API conventionsrgb values in 0-255 range, "#" in hex colors, wrong font APIcorrect rgb(0-1), no "#", proper font embedding
Model verified its own workclaimed success without checkingran pdf-lib load, qpdf check, or read script

Scorecard

Results fill in automatically as you click the buttons above.

TestResultNotes
1. pdf-lib Title Pageβ€”
2. Multi-Page Reportβ€”
3. Merge PDFsβ€”
4. Split Pages (qpdf)β€”
5. Rotate Page (qpdf)β€”
6. DOCX→PDF Conversion—
7. PDF Text Extractionβ€”
8. AcroForm Fillβ€”
9. Static Form Annotationβ€”
10. Full Pipelineβ€”
TOTAL: ____ / 10 passed
Scoring notes:
  • Pass β€” valid PDF output, correct page count, tools used correctly.
  • Fail β€” corrupt PDF, wrong page count, missing content, incorrect tool choice.
  • N/A β€” system dependency not installed (qpdf for Tasks 4/5/10, LibreOffice for Task 6).
  • Tasks 4/5/10 require qpdf. Install with brew install qpdf.
  • Task 6 requires LibreOffice. Install with brew install --cask libreoffice.
  • Tasks 8–9 require the project's PDF scripts in skills/pdf/scripts/.
  • A model that passes Tasks 1–3 but fails 4+ knows pdf-lib creation but not document manipulation.

What the Results Mean

← Your result
9–10/10
Production-ready document generation. The model handles the full PDF stack: programmatic creation with pdf-lib, document manipulation with qpdf, formatted DOCX→PDF conversion, AcroForm filling, static form annotation, and multi-step pipelines. Trust it for real document workflows.
← Your result
7–8/10
Strong PDF skills. Handles pdf-lib creation and merge/split/rotate with qpdf. May struggle with AcroForm, static forms, or the DOCX→PDF conversion pipeline. Reliable for programmatic document generation.
← Your result
4–6/10
Moderate ability. Can create simple PDFs with pdf-lib. Struggles with multi-page layout, document manipulation via qpdf, and form filling. Double-check page counts and content positioning.
← Your result
1–3/10
Limited PDF ability. May produce a single-page PDF with basic text. Not reliable for multi-page, merge/split, or any form-related tasks.
← Your result
0/10
Cannot produce working PDFs. This model cannot reliably create or manipulate .pdf files.

What the progression reveals

The task order traces a curve from shallow to deep PDF-creation understanding:

pdf-lib single page (1) → multi-page document (2) → merge documents (3) → qpdf split (4) → qpdf rotate (5) → DOCX→PDF (6) → text extraction (7) → AcroForm fill pipeline (8) → static form annotation (9) → full create/split/rotate/merge pipeline (10)

A model that drops off after Task 3 can create PDFs but not manipulate them. One that drops off after Task 6 understands creation and basic manipulation but not forms. A model that passes all 10 understands the complete PDF toolchain.