hive-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +235 -0
  3. package/assets/LICENSE +32 -0
  4. package/assets/THIRD_PARTY_NOTICES.md +72 -0
  5. package/assets/manifest.json +1290 -0
  6. package/assets/skills/authored/code-review/composable/BUNDLE.md +192 -0
  7. package/assets/skills/authored/code-review/composable/INDEX.md +13 -0
  8. package/assets/skills/authored/code-review/composable/VERSION +1 -0
  9. package/assets/skills/authored/code-review/composable/mini/01-review-method.md +34 -0
  10. package/assets/skills/authored/code-review/composable/mini/02-security-review.md +20 -0
  11. package/assets/skills/authored/code-review/composable/mini/03-correctness-bugs.md +19 -0
  12. package/assets/skills/authored/code-review/composable/mini/04-concurrency.md +16 -0
  13. package/assets/skills/authored/code-review/composable/mini/05-error-handling.md +12 -0
  14. package/assets/skills/authored/code-review/composable/mini/06-performance.md +11 -0
  15. package/assets/skills/authored/code-review/composable/mini/07-api-design.md +20 -0
  16. package/assets/skills/authored/code-review/composable/mini/08-missing-tests.md +25 -0
  17. package/assets/skills/authored/code-review/composable/mini/09-refactoring-and-communication.md +32 -0
  18. package/assets/skills/authored/data-analysis/composable/BUNDLE.md +186 -0
  19. package/assets/skills/authored/data-analysis/composable/INDEX.md +13 -0
  20. package/assets/skills/authored/data-analysis/composable/VERSION +1 -0
  21. package/assets/skills/authored/data-analysis/composable/mini/01-data-quality-profiling.md +26 -0
  22. package/assets/skills/authored/data-analysis/composable/mini/02-aggregation-pitfalls.md +21 -0
  23. package/assets/skills/authored/data-analysis/composable/mini/03-time-series.md +19 -0
  24. package/assets/skills/authored/data-analysis/composable/mini/04-segmentation.md +23 -0
  25. package/assets/skills/authored/data-analysis/composable/mini/05-discount-pricing.md +17 -0
  26. package/assets/skills/authored/data-analysis/composable/mini/06-insight-generation.md +25 -0
  27. package/assets/skills/authored/data-analysis/composable/mini/07-executive-writing.md +26 -0
  28. package/assets/skills/authored/data-analysis/composable/mini/08-recommendations.md +20 -0
  29. package/assets/skills/authored/data-analysis/composable/mini/09-reproducibility-assumptions.md +20 -0
  30. package/assets/skills/authored/financial-analysis/composable/BUNDLE.md +1293 -0
  31. package/assets/skills/authored/financial-analysis/composable/INDEX.md +17 -0
  32. package/assets/skills/authored/financial-analysis/composable/VERSION +1 -0
  33. package/assets/skills/authored/financial-analysis/composable/mini/00-core.md +69 -0
  34. package/assets/skills/authored/financial-analysis/composable/mini/01-profitability-efficiency-ratios.md +173 -0
  35. package/assets/skills/authored/financial-analysis/composable/mini/02-liquidity-leverage-ratios.md +120 -0
  36. package/assets/skills/authored/financial-analysis/composable/mini/03-valuation-ratios-interpretation.md +107 -0
  37. package/assets/skills/authored/financial-analysis/composable/mini/04-dcf-projection-wacc.md +122 -0
  38. package/assets/skills/authored/financial-analysis/composable/mini/05-dcf-terminal-value-sensitivity.md +75 -0
  39. package/assets/skills/authored/financial-analysis/composable/mini/06-comparables-precedents.md +78 -0
  40. package/assets/skills/authored/financial-analysis/composable/mini/07-budget-variance-analysis.md +47 -0
  41. package/assets/skills/authored/financial-analysis/composable/mini/08-driver-based-forecasting-scenarios.md +111 -0
  42. package/assets/skills/authored/financial-analysis/composable/mini/09-rolling-forecasts-accuracy.md +114 -0
  43. package/assets/skills/authored/financial-analysis/composable/mini/10-reporting-exec-summary.md +58 -0
  44. package/assets/skills/authored/financial-analysis/composable/mini/11-industry-adaptations.md +130 -0
  45. package/assets/skills/authored/financial-analysis/composable/mini/12-toolkit-scripts-io.md +102 -0
  46. package/assets/skills/authored/python-api/composable/BUNDLE.md +389 -0
  47. package/assets/skills/authored/python-api/composable/INDEX.md +13 -0
  48. package/assets/skills/authored/python-api/composable/VERSION +1 -0
  49. package/assets/skills/authored/python-api/composable/mini/01-project-structure.md +44 -0
  50. package/assets/skills/authored/python-api/composable/mini/02-routing-and-app.md +38 -0
  51. package/assets/skills/authored/python-api/composable/mini/03-pydantic-models.md +45 -0
  52. package/assets/skills/authored/python-api/composable/mini/04-validation-and-errors.md +59 -0
  53. package/assets/skills/authored/python-api/composable/mini/05-dependencies-and-auth.md +60 -0
  54. package/assets/skills/authored/python-api/composable/mini/06-pagination-filtering.md +44 -0
  55. package/assets/skills/authored/python-api/composable/mini/07-async-performance.md +30 -0
  56. package/assets/skills/authored/python-api/composable/mini/08-testing.md +45 -0
  57. package/assets/skills/authored/python-api/composable/mini/09-middleware-observability.md +29 -0
  58. package/assets/skills/authored/tech-writing/composable/BUNDLE.md +220 -0
  59. package/assets/skills/authored/tech-writing/composable/INDEX.md +12 -0
  60. package/assets/skills/authored/tech-writing/composable/VERSION +1 -0
  61. package/assets/skills/authored/tech-writing/composable/mini/01-audience-analysis.md +26 -0
  62. package/assets/skills/authored/tech-writing/composable/mini/02-document-types.md +27 -0
  63. package/assets/skills/authored/tech-writing/composable/mini/03-readme-quickstart-changelog.md +22 -0
  64. package/assets/skills/authored/tech-writing/composable/mini/04-information-architecture.md +21 -0
  65. package/assets/skills/authored/tech-writing/composable/mini/05-style-clarity.md +29 -0
  66. package/assets/skills/authored/tech-writing/composable/mini/06-breaking-changes-migrations.md +33 -0
  67. package/assets/skills/authored/tech-writing/composable/mini/07-troubleshooting-security.md +30 -0
  68. package/assets/skills/authored/tech-writing/composable/mini/08-faq-voice-tone.md +32 -0
  69. package/assets/skills/converted/claude-api/composable/BUNDLE.md +14996 -0
  70. package/assets/skills/converted/claude-api/composable/INDEX.md +98 -0
  71. package/assets/skills/converted/claude-api/composable/VERSION +1 -0
  72. package/assets/skills/converted/claude-api/composable/mini/00-core.md +235 -0
  73. package/assets/skills/converted/claude-api/composable/mini/01-model-api-quick-reference.md +258 -0
  74. package/assets/skills/converted/claude-api/composable/mini/02-common-pitfalls.md +42 -0
  75. package/assets/skills/converted/claude-api/composable/mini/10-models.md +135 -0
  76. package/assets/skills/converted/claude-api/composable/mini/11-platform-availability.md +96 -0
  77. package/assets/skills/converted/claude-api/composable/mini/12-claude-platform-on-aws.md +59 -0
  78. package/assets/skills/converted/claude-api/composable/mini/13-live-sources.md +143 -0
  79. package/assets/skills/converted/claude-api/composable/mini/14-error-codes.md +256 -0
  80. package/assets/skills/converted/claude-api/composable/mini/15-prompt-caching.md +223 -0
  81. package/assets/skills/converted/claude-api/composable/mini/16-token-counting.md +56 -0
  82. package/assets/skills/converted/claude-api/composable/mini/17-tool-use-concepts.md +444 -0
  83. package/assets/skills/converted/claude-api/composable/mini/18-agent-design.md +101 -0
  84. package/assets/skills/converted/claude-api/composable/mini/19-anthropic-cli.md +246 -0
  85. package/assets/skills/converted/claude-api/composable/mini/20-model-migration.md +1301 -0
  86. package/assets/skills/converted/claude-api/composable/mini/30-managed-agents-overview.md +71 -0
  87. package/assets/skills/converted/claude-api/composable/mini/31-managed-agents-core.md +278 -0
  88. package/assets/skills/converted/claude-api/composable/mini/32-managed-agents-environments.md +219 -0
  89. package/assets/skills/converted/claude-api/composable/mini/33-managed-agents-tools.md +366 -0
  90. package/assets/skills/converted/claude-api/composable/mini/34-managed-agents-events.md +252 -0
  91. package/assets/skills/converted/claude-api/composable/mini/35-managed-agents-outcomes.md +106 -0
  92. package/assets/skills/converted/claude-api/composable/mini/36-managed-agents-multiagent.md +101 -0
  93. package/assets/skills/converted/claude-api/composable/mini/37-managed-agents-webhooks.md +123 -0
  94. package/assets/skills/converted/claude-api/composable/mini/38-managed-agents-memory.md +197 -0
  95. package/assets/skills/converted/claude-api/composable/mini/39-managed-agents-scheduled-deployments.md +146 -0
  96. package/assets/skills/converted/claude-api/composable/mini/40-managed-agents-client-patterns.md +211 -0
  97. package/assets/skills/converted/claude-api/composable/mini/41-managed-agents-onboarding.md +82 -0
  98. package/assets/skills/converted/claude-api/composable/mini/42-managed-agents-api-reference.md +441 -0
  99. package/assets/skills/converted/claude-api/composable/mini/43-managed-agents-self-hosted-sandboxes.md +174 -0
  100. package/assets/skills/converted/claude-api/composable/mini/50-python-readme.md +564 -0
  101. package/assets/skills/converted/claude-api/composable/mini/51-python-tool-use.md +590 -0
  102. package/assets/skills/converted/claude-api/composable/mini/52-python-streaming-batches-files.md +549 -0
  103. package/assets/skills/converted/claude-api/composable/mini/53-python-managed-agents.md +335 -0
  104. package/assets/skills/converted/claude-api/composable/mini/55-typescript-readme.md +404 -0
  105. package/assets/skills/converted/claude-api/composable/mini/56-typescript-tool-use.md +548 -0
  106. package/assets/skills/converted/claude-api/composable/mini/57-typescript-streaming-batches-files.md +384 -0
  107. package/assets/skills/converted/claude-api/composable/mini/58-typescript-managed-agents.md +358 -0
  108. package/assets/skills/converted/claude-api/composable/mini/60-csharp-readme.md +361 -0
  109. package/assets/skills/converted/claude-api/composable/mini/61-csharp-tool-use.md +163 -0
  110. package/assets/skills/converted/claude-api/composable/mini/62-csharp-streaming-batches-files.md +64 -0
  111. package/assets/skills/converted/claude-api/composable/mini/65-go-readme.md +185 -0
  112. package/assets/skills/converted/claude-api/composable/mini/66-go-tool-use.md +219 -0
  113. package/assets/skills/converted/claude-api/composable/mini/67-go-streaming-files.md +63 -0
  114. package/assets/skills/converted/claude-api/composable/mini/68-go-managed-agents.md +564 -0
  115. package/assets/skills/converted/claude-api/composable/mini/70-java-readme.md +237 -0
  116. package/assets/skills/converted/claude-api/composable/mini/71-java-tool-use.md +227 -0
  117. package/assets/skills/converted/claude-api/composable/mini/72-java-streaming-files.md +49 -0
  118. package/assets/skills/converted/claude-api/composable/mini/73-java-managed-agents.md +443 -0
  119. package/assets/skills/converted/claude-api/composable/mini/75-php-readme.md +173 -0
  120. package/assets/skills/converted/claude-api/composable/mini/76-php-tool-use.md +252 -0
  121. package/assets/skills/converted/claude-api/composable/mini/77-php-streaming-batches-files.md +53 -0
  122. package/assets/skills/converted/claude-api/composable/mini/78-php-managed-agents.md +443 -0
  123. package/assets/skills/converted/claude-api/composable/mini/80-ruby-readme.md +133 -0
  124. package/assets/skills/converted/claude-api/composable/mini/81-ruby-tool-use-streaming.md +56 -0
  125. package/assets/skills/converted/claude-api/composable/mini/82-ruby-managed-agents.md +394 -0
  126. package/assets/skills/converted/claude-api/composable/mini/85-curl-examples.md +255 -0
  127. package/assets/skills/converted/claude-api/composable/mini/86-curl-managed-agents.md +340 -0
  128. package/assets/skills/converted/claude-api/composable/presets/csharp.md +1151 -0
  129. package/assets/skills/converted/claude-api/composable/presets/curl.md +1154 -0
  130. package/assets/skills/converted/claude-api/composable/presets/go.md +1598 -0
  131. package/assets/skills/converted/claude-api/composable/presets/java.md +1523 -0
  132. package/assets/skills/converted/claude-api/composable/presets/php.md +1488 -0
  133. package/assets/skills/converted/claude-api/composable/presets/python.md +2605 -0
  134. package/assets/skills/converted/claude-api/composable/presets/ruby.md +1146 -0
  135. package/assets/skills/converted/claude-api/composable/presets/shared.md +6478 -0
  136. package/assets/skills/converted/claude-api/composable/presets/typescript.md +2261 -0
  137. package/assets/skills/converted/docx/composable/BUNDLE.md +617 -0
  138. package/assets/skills/converted/docx/composable/INDEX.md +12 -0
  139. package/assets/skills/converted/docx/composable/VERSION +1 -0
  140. package/assets/skills/converted/docx/composable/mini/00-core.md +26 -0
  141. package/assets/skills/converted/docx/composable/mini/01-reading-converting.md +36 -0
  142. package/assets/skills/converted/docx/composable/mini/02-creating-setup-layout.md +85 -0
  143. package/assets/skills/converted/docx/composable/mini/03-creating-elements.md +257 -0
  144. package/assets/skills/converted/docx/composable/mini/04-editing-existing.md +57 -0
  145. package/assets/skills/converted/docx/composable/mini/05-xml-reference.md +128 -0
  146. package/assets/skills/converted/internal-comms/composable/BUNDLE.md +220 -0
  147. package/assets/skills/converted/internal-comms/composable/INDEX.md +11 -0
  148. package/assets/skills/converted/internal-comms/composable/VERSION +1 -0
  149. package/assets/skills/converted/internal-comms/composable/mini/00-core.md +30 -0
  150. package/assets/skills/converted/internal-comms/composable/mini/01-3p-updates.md +49 -0
  151. package/assets/skills/converted/internal-comms/composable/mini/02-company-newsletter.md +67 -0
  152. package/assets/skills/converted/internal-comms/composable/mini/03-faq-answers.md +32 -0
  153. package/assets/skills/converted/internal-comms/composable/mini/04-general-comms.md +18 -0
  154. package/assets/skills/converted/mcp-builder/composable/BUNDLE.md +2460 -0
  155. package/assets/skills/converted/mcp-builder/composable/INDEX.md +23 -0
  156. package/assets/skills/converted/mcp-builder/composable/VERSION +1 -0
  157. package/assets/skills/converted/mcp-builder/composable/mini/00-core.md +43 -0
  158. package/assets/skills/converted/mcp-builder/composable/mini/01-workflow-research-planning.md +54 -0
  159. package/assets/skills/converted/mcp-builder/composable/mini/02-implementation-process.md +78 -0
  160. package/assets/skills/converted/mcp-builder/composable/mini/03-naming-conventions.md +33 -0
  161. package/assets/skills/converted/mcp-builder/composable/mini/04-response-formats.md +21 -0
  162. package/assets/skills/converted/mcp-builder/composable/mini/05-pagination.md +24 -0
  163. package/assets/skills/converted/mcp-builder/composable/mini/06-transport.md +47 -0
  164. package/assets/skills/converted/mcp-builder/composable/mini/07-annotations.md +20 -0
  165. package/assets/skills/converted/mcp-builder/composable/mini/08-security.md +35 -0
  166. package/assets/skills/converted/mcp-builder/composable/mini/09-error-handling.md +32 -0
  167. package/assets/skills/converted/mcp-builder/composable/mini/10-testing-and-documentation.md +21 -0
  168. package/assets/skills/converted/mcp-builder/composable/mini/11-python-implementation.md +432 -0
  169. package/assets/skills/converted/mcp-builder/composable/mini/12-python-advanced-and-checklist.md +233 -0
  170. package/assets/skills/converted/mcp-builder/composable/mini/13-node-implementation.md +717 -0
  171. package/assets/skills/converted/mcp-builder/composable/mini/14-node-advanced-and-checklist.md +200 -0
  172. package/assets/skills/converted/mcp-builder/composable/mini/15-evaluation-design.md +200 -0
  173. package/assets/skills/converted/mcp-builder/composable/mini/16-evaluation-running.md +198 -0
  174. package/assets/skills/converted/mcp-builder/composable/presets/node-server.md +1381 -0
  175. package/assets/skills/converted/mcp-builder/composable/presets/python-server.md +1129 -0
  176. package/assets/skills/converted/pdf/composable/BUNDLE.md +1284 -0
  177. package/assets/skills/converted/pdf/composable/INDEX.md +14 -0
  178. package/assets/skills/converted/pdf/composable/VERSION +1 -0
  179. package/assets/skills/converted/pdf/composable/mini/00-core.md +46 -0
  180. package/assets/skills/converted/pdf/composable/mini/01-creation.md +176 -0
  181. package/assets/skills/converted/pdf/composable/mini/02-extraction.md +178 -0
  182. package/assets/skills/converted/pdf/composable/mini/03-merge-split-manipulate.md +240 -0
  183. package/assets/skills/converted/pdf/composable/mini/04-images-ocr.md +168 -0
  184. package/assets/skills/converted/pdf/composable/mini/05-encryption.md +51 -0
  185. package/assets/skills/converted/pdf/composable/mini/06-forms.md +300 -0
  186. package/assets/skills/converted/pdf/composable/mini/07-libraries-reference.md +89 -0
  187. package/assets/skills/converted/pptx/composable/BUNDLE.md +911 -0
  188. package/assets/skills/converted/pptx/composable/INDEX.md +12 -0
  189. package/assets/skills/converted/pptx/composable/VERSION +1 -0
  190. package/assets/skills/converted/pptx/composable/mini/00-core.md +37 -0
  191. package/assets/skills/converted/pptx/composable/mini/01-reading.md +37 -0
  192. package/assets/skills/converted/pptx/composable/mini/02-creating-from-scratch.md +422 -0
  193. package/assets/skills/converted/pptx/composable/mini/03-editing-templates.md +207 -0
  194. package/assets/skills/converted/pptx/composable/mini/04-design.md +89 -0
  195. package/assets/skills/converted/pptx/composable/mini/05-qa-and-rendering.md +91 -0
  196. package/assets/skills/converted/pptx/composable/presets/creating.md +659 -0
  197. package/assets/skills/converted/pptx/composable/presets/editing.md +485 -0
  198. package/assets/skills/converted/skill-creator/composable/BUNDLE.md +1681 -0
  199. package/assets/skills/converted/skill-creator/composable/INDEX.md +21 -0
  200. package/assets/skills/converted/skill-creator/composable/VERSION +1 -0
  201. package/assets/skills/converted/skill-creator/composable/mini/00-core.md +59 -0
  202. package/assets/skills/converted/skill-creator/composable/mini/01-creating-a-skill.md +27 -0
  203. package/assets/skills/converted/skill-creator/composable/mini/02-skill-writing-guide.md +71 -0
  204. package/assets/skills/converted/skill-creator/composable/mini/03-test-cases.md +21 -0
  205. package/assets/skills/converted/skill-creator/composable/mini/04-running-and-evaluating.md +128 -0
  206. package/assets/skills/converted/skill-creator/composable/mini/05-improving-the-skill.md +30 -0
  207. package/assets/skills/converted/skill-creator/composable/mini/06-blind-comparison.md +5 -0
  208. package/assets/skills/converted/skill-creator/composable/mini/07-description-optimization.md +74 -0
  209. package/assets/skills/converted/skill-creator/composable/mini/08-packaging.md +11 -0
  210. package/assets/skills/converted/skill-creator/composable/mini/09-environment-specific.md +38 -0
  211. package/assets/skills/converted/skill-creator/composable/mini/10-scripts-and-references.md +34 -0
  212. package/assets/skills/converted/skill-creator/composable/mini/11-json-schemas.md +414 -0
  213. package/assets/skills/converted/skill-creator/composable/mini/12-agent-grader.md +225 -0
  214. package/assets/skills/converted/skill-creator/composable/mini/13-agent-comparator.md +204 -0
  215. package/assets/skills/converted/skill-creator/composable/mini/14-agent-analyzer.md +276 -0
  216. package/assets/skills/converted/skill-creator/composable/presets/authoring.md +198 -0
  217. package/assets/skills/converted/skill-creator/composable/presets/evaluating.md +1334 -0
  218. package/assets/skills/converted/skill-creator/composable/presets/optimizing.md +160 -0
  219. package/assets/skills/converted/skill-creator/composable/presets.json +5 -0
  220. package/assets/skills/meta/ccs-skill-creator/composable/BUNDLE.md +465 -0
  221. package/assets/skills/meta/ccs-skill-creator/composable/INDEX.md +8 -0
  222. package/assets/skills/meta/ccs-skill-creator/composable/VERSION +1 -0
  223. package/assets/skills/meta/ccs-skill-creator/composable/mini/00-core.md +46 -0
  224. package/assets/skills/meta/ccs-skill-creator/composable/mini/01-create-new-skill.md +167 -0
  225. package/assets/skills/meta/ccs-skill-creator/composable/mini/02-convert-existing-skill.md +120 -0
  226. package/assets/skills/meta/ccs-skill-creator/composable/mini/03-review-and-maintain.md +112 -0
  227. package/assets/skills/sources/anthropic/PROVENANCE.md +3 -0
  228. package/assets/skills/sources/anthropic/claude-api/LICENSE.txt +202 -0
  229. package/assets/skills/sources/anthropic/docx/LICENSE.txt +30 -0
  230. package/assets/skills/sources/anthropic/internal-comms/LICENSE.txt +202 -0
  231. package/assets/skills/sources/anthropic/mcp-builder/LICENSE.txt +202 -0
  232. package/assets/skills/sources/anthropic/pdf/LICENSE.txt +30 -0
  233. package/assets/skills/sources/anthropic/pptx/LICENSE.txt +30 -0
  234. package/assets/skills/sources/anthropic/skill-creator/LICENSE.txt +202 -0
  235. package/assets/skills/sources/financial-analyst/PROVENANCE.md +1 -0
  236. package/assets/tools/hive.py +728 -0
  237. package/dist/cli.js +7494 -0
  238. package/package.json +40 -0
@@ -0,0 +1,1284 @@
1
+ <!-- GENERATED by tools/hive.py compile. Do not hand-edit. -->
2
+ # pdf: Compiled Skill Bundle (v1.0.0)
3
+
4
+ All composable mini-skills for `pdf` concatenated in index order, frontmatter stripped. Regenerate with `tools/hive.py compile <skill-dir>`. Do not edit by hand.
5
+
6
+ ---
7
+ <!-- module: 00-core.md -->
8
+
9
+ # Core: PDF Processing
10
+
11
+ Always-loaded foundation for doing anything with PDF files using Python libraries and command-line tools. Load the focused minis (see INDEX) for creation, extraction, merging/splitting, images/OCR, encryption, form-filling, and library/performance guidance.
12
+
13
+ ## Overview
14
+
15
+ This skill covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see the extraction, merge/split, images-ocr, and libraries-reference minis. If you need to fill out a PDF form, read the forms mini and follow its instructions.
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from pypdf import PdfReader, PdfWriter
21
+
22
+ # Read a PDF
23
+ reader = PdfReader("document.pdf")
24
+ print(f"Pages: {len(reader.pages)}")
25
+
26
+ # Extract text
27
+ text = ""
28
+ for page in reader.pages:
29
+ text += page.extract_text()
30
+ ```
31
+
32
+ ## Quick Reference
33
+
34
+ | Task | Best Tool | Command/Code |
35
+ |------|-----------|--------------|
36
+ | Merge PDFs | pypdf | `writer.add_page(page)` |
37
+ | Split PDFs | pypdf | One page per file |
38
+ | Extract text | pdfplumber | `page.extract_text()` |
39
+ | Extract tables | pdfplumber | `page.extract_tables()` |
40
+ | Create PDFs | reportlab | Canvas or Platypus |
41
+ | Command line merge | qpdf | `qpdf --empty --pages ...` |
42
+ | OCR scanned PDFs | pytesseract | Convert to image first |
43
+ | Fill PDF forms | pdf-lib or pypdf (see forms mini) | See forms mini |
44
+
45
+ ## Vendored scripts
46
+
47
+ The form-filling helper scripts referenced by the forms mini (`check_fillable_fields.py`, `extract_form_field_info.py`, `convert_pdf_to_images.py`, `fill_fillable_fields.py`, `extract_form_structure.py`, `check_bounding_boxes.py`, `fill_pdf_form_with_annotations.py`) are vendored in the source skill at `skills/sources/anthropic/pdf/scripts/`. Command lines that say `scripts/...` refer to that vendored directory.
48
+
49
+ ## Next Steps
50
+
51
+ - For advanced pypdfium2 usage, see the images-ocr and libraries-reference minis.
52
+ - For JavaScript libraries (pdf-lib, pdfjs-dist), see the creation, extraction, merge-split, and libraries-reference minis.
53
+ - If you need to fill out a PDF form, follow the instructions in the forms mini.
54
+ - For troubleshooting guides, see the libraries-reference mini.
55
+
56
+ ---
57
+ <!-- module: 01-creation.md -->
58
+
59
+ # Creating PDFs
60
+
61
+ Creating new PDF documents with reportlab (Python) and pdf-lib (JavaScript).
62
+
63
+ ## reportlab - Create PDFs
64
+
65
+ ### Basic PDF Creation
66
+ ```python
67
+ from reportlab.lib.pagesizes import letter
68
+ from reportlab.pdfgen import canvas
69
+
70
+ c = canvas.Canvas("hello.pdf", pagesize=letter)
71
+ width, height = letter
72
+
73
+ # Add text
74
+ c.drawString(100, height - 100, "Hello World!")
75
+ c.drawString(100, height - 120, "This is a PDF created with reportlab")
76
+
77
+ # Add a line
78
+ c.line(100, height - 140, 400, height - 140)
79
+
80
+ # Save
81
+ c.save()
82
+ ```
83
+
84
+ ### Create PDF with Multiple Pages
85
+ ```python
86
+ from reportlab.lib.pagesizes import letter
87
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
88
+ from reportlab.lib.styles import getSampleStyleSheet
89
+
90
+ doc = SimpleDocTemplate("report.pdf", pagesize=letter)
91
+ styles = getSampleStyleSheet()
92
+ story = []
93
+
94
+ # Add content
95
+ title = Paragraph("Report Title", styles['Title'])
96
+ story.append(title)
97
+ story.append(Spacer(1, 12))
98
+
99
+ body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
100
+ story.append(body)
101
+ story.append(PageBreak())
102
+
103
+ # Page 2
104
+ story.append(Paragraph("Page 2", styles['Heading1']))
105
+ story.append(Paragraph("Content for page 2", styles['Normal']))
106
+
107
+ # Build PDF
108
+ doc.build(story)
109
+ ```
110
+
111
+ ### Subscripts and Superscripts
112
+
113
+ **IMPORTANT**: Never use Unicode subscript/superscript characters (₀₁₂₃₄₅₆₇₈₉, ⁰¹²³⁴⁵⁶⁷⁸⁹) in ReportLab PDFs. The built-in fonts do not include these glyphs, causing them to render as solid black boxes.
114
+
115
+ Instead, use ReportLab's XML markup tags in Paragraph objects:
116
+ ```python
117
+ from reportlab.platypus import Paragraph
118
+ from reportlab.lib.styles import getSampleStyleSheet
119
+
120
+ styles = getSampleStyleSheet()
121
+
122
+ # Subscripts: use <sub> tag
123
+ chemical = Paragraph("H<sub>2</sub>O", styles['Normal'])
124
+
125
+ # Superscripts: use <super> tag
126
+ squared = Paragraph("x<super>2</super> + y<super>2</super>", styles['Normal'])
127
+ ```
128
+
129
+ For canvas-drawn text (not Paragraph objects), manually adjust font the size and position rather than using Unicode subscripts/superscripts.
130
+
131
+ ### reportlab Advanced Features
132
+
133
+ #### Create Professional Reports with Tables
134
+ ```python
135
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
136
+ from reportlab.lib.styles import getSampleStyleSheet
137
+ from reportlab.lib import colors
138
+
139
+ # Sample data
140
+ data = [
141
+ ['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
142
+ ['Widgets', '120', '135', '142', '158'],
143
+ ['Gadgets', '85', '92', '98', '105']
144
+ ]
145
+
146
+ # Create PDF with table
147
+ doc = SimpleDocTemplate("report.pdf")
148
+ elements = []
149
+
150
+ # Add title
151
+ styles = getSampleStyleSheet()
152
+ title = Paragraph("Quarterly Sales Report", styles['Title'])
153
+ elements.append(title)
154
+
155
+ # Add table with advanced styling
156
+ table = Table(data)
157
+ table.setStyle(TableStyle([
158
+ ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
159
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
160
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
161
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
162
+ ('FONTSIZE', (0, 0), (-1, 0), 14),
163
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
164
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
165
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
166
+ ]))
167
+ elements.append(table)
168
+
169
+ doc.build(elements)
170
+ ```
171
+
172
+ ## pdf-lib (MIT License) - Create Complex PDFs from Scratch
173
+
174
+ pdf-lib is a powerful JavaScript library for creating and modifying PDF documents in any JavaScript environment.
175
+
176
+ ```javascript
177
+ import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
178
+ import fs from 'fs';
179
+
180
+ async function createPDF() {
181
+ const pdfDoc = await PDFDocument.create();
182
+
183
+ // Add fonts
184
+ const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
185
+ const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
186
+
187
+ // Add page
188
+ const page = pdfDoc.addPage([595, 842]); // A4 size
189
+ const { width, height } = page.getSize();
190
+
191
+ // Add text with styling
192
+ page.drawText('Invoice #12345', {
193
+ x: 50,
194
+ y: height - 50,
195
+ size: 18,
196
+ font: helveticaBold,
197
+ color: rgb(0.2, 0.2, 0.8)
198
+ });
199
+
200
+ // Add rectangle (header background)
201
+ page.drawRectangle({
202
+ x: 40,
203
+ y: height - 100,
204
+ width: width - 80,
205
+ height: 30,
206
+ color: rgb(0.9, 0.9, 0.9)
207
+ });
208
+
209
+ // Add table-like content
210
+ const items = [
211
+ ['Item', 'Qty', 'Price', 'Total'],
212
+ ['Widget', '2', '$50', '$100'],
213
+ ['Gadget', '1', '$75', '$75']
214
+ ];
215
+
216
+ let yPos = height - 150;
217
+ items.forEach(row => {
218
+ let xPos = 50;
219
+ row.forEach(cell => {
220
+ page.drawText(cell, {
221
+ x: xPos,
222
+ y: yPos,
223
+ size: 12,
224
+ font: helveticaFont
225
+ });
226
+ xPos += 120;
227
+ });
228
+ yPos -= 25;
229
+ });
230
+
231
+ const pdfBytes = await pdfDoc.save();
232
+ fs.writeFileSync('created.pdf', pdfBytes);
233
+ }
234
+ ```
235
+
236
+ ---
237
+ <!-- module: 02-extraction.md -->
238
+
239
+ # Extracting Text, Tables, and Metadata
240
+
241
+ Extracting text, tables, coordinates, metadata, and annotations from PDFs (pdfplumber, pdftotext, pypdf, pdfjs-dist).
242
+
243
+ ## pdfplumber - Text and Table Extraction
244
+
245
+ ### Extract Text with Layout
246
+ ```python
247
+ import pdfplumber
248
+
249
+ with pdfplumber.open("document.pdf") as pdf:
250
+ for page in pdf.pages:
251
+ text = page.extract_text()
252
+ print(text)
253
+ ```
254
+
255
+ ### Extract Tables
256
+ ```python
257
+ with pdfplumber.open("document.pdf") as pdf:
258
+ for i, page in enumerate(pdf.pages):
259
+ tables = page.extract_tables()
260
+ for j, table in enumerate(tables):
261
+ print(f"Table {j+1} on page {i+1}:")
262
+ for row in table:
263
+ print(row)
264
+ ```
265
+
266
+ ### Advanced Table Extraction
267
+ ```python
268
+ import pandas as pd
269
+
270
+ with pdfplumber.open("document.pdf") as pdf:
271
+ all_tables = []
272
+ for page in pdf.pages:
273
+ tables = page.extract_tables()
274
+ for table in tables:
275
+ if table: # Check if table is not empty
276
+ df = pd.DataFrame(table[1:], columns=table[0])
277
+ all_tables.append(df)
278
+
279
+ # Combine all tables
280
+ if all_tables:
281
+ combined_df = pd.concat(all_tables, ignore_index=True)
282
+ combined_df.to_excel("extracted_tables.xlsx", index=False)
283
+ ```
284
+
285
+ ## pypdf - Extract Metadata
286
+ ```python
287
+ reader = PdfReader("document.pdf")
288
+ meta = reader.metadata
289
+ print(f"Title: {meta.title}")
290
+ print(f"Author: {meta.author}")
291
+ print(f"Subject: {meta.subject}")
292
+ print(f"Creator: {meta.creator}")
293
+ ```
294
+
295
+ ## pdftotext (poppler-utils)
296
+ ```bash
297
+ # Extract text
298
+ pdftotext input.pdf output.txt
299
+
300
+ # Extract text preserving layout
301
+ pdftotext -layout input.pdf output.txt
302
+
303
+ # Extract specific pages
304
+ pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
305
+ ```
306
+
307
+ ## Advanced: pdfplumber
308
+
309
+ ### Extract Text with Precise Coordinates
310
+ ```python
311
+ import pdfplumber
312
+
313
+ with pdfplumber.open("document.pdf") as pdf:
314
+ page = pdf.pages[0]
315
+
316
+ # Extract all text with coordinates
317
+ chars = page.chars
318
+ for char in chars[:10]: # First 10 characters
319
+ print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
320
+
321
+ # Extract text by bounding box (left, top, right, bottom)
322
+ bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
323
+ ```
324
+
325
+ ### Advanced Table Extraction with Custom Settings
326
+ ```python
327
+ import pdfplumber
328
+ import pandas as pd
329
+
330
+ with pdfplumber.open("complex_table.pdf") as pdf:
331
+ page = pdf.pages[0]
332
+
333
+ # Extract tables with custom settings for complex layouts
334
+ table_settings = {
335
+ "vertical_strategy": "lines",
336
+ "horizontal_strategy": "lines",
337
+ "snap_tolerance": 3,
338
+ "intersection_tolerance": 15
339
+ }
340
+ tables = page.extract_tables(table_settings)
341
+
342
+ # Visual debugging for table extraction
343
+ img = page.to_image(resolution=150)
344
+ img.save("debug_layout.png")
345
+ ```
346
+
347
+ ## Advanced: poppler-utils
348
+
349
+ ### Extract Text with Bounding Box Coordinates
350
+ ```bash
351
+ # Extract text with bounding box coordinates (essential for structured data)
352
+ pdftotext -bbox-layout document.pdf output.xml
353
+
354
+ # The XML output contains precise coordinates for each text element
355
+ ```
356
+
357
+ ## pdfjs-dist (Apache License)
358
+
359
+ PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser.
360
+
361
+ ### Extract Text with Coordinates
362
+ ```javascript
363
+ import * as pdfjsLib from 'pdfjs-dist';
364
+
365
+ async function extractText() {
366
+ const loadingTask = pdfjsLib.getDocument('document.pdf');
367
+ const pdf = await loadingTask.promise;
368
+
369
+ let fullText = '';
370
+
371
+ // Extract text from all pages
372
+ for (let i = 1; i <= pdf.numPages; i++) {
373
+ const page = await pdf.getPage(i);
374
+ const textContent = await page.getTextContent();
375
+
376
+ const pageText = textContent.items
377
+ .map(item => item.str)
378
+ .join(' ');
379
+
380
+ fullText += `\n--- Page ${i} ---\n${pageText}`;
381
+
382
+ // Get text with coordinates for advanced processing
383
+ const textWithCoords = textContent.items.map(item => ({
384
+ text: item.str,
385
+ x: item.transform[4],
386
+ y: item.transform[5],
387
+ width: item.width,
388
+ height: item.height
389
+ }));
390
+ }
391
+
392
+ console.log(fullText);
393
+ return fullText;
394
+ }
395
+ ```
396
+
397
+ ### Extract Annotations and Forms
398
+ ```javascript
399
+ import * as pdfjsLib from 'pdfjs-dist';
400
+
401
+ async function extractAnnotations() {
402
+ const loadingTask = pdfjsLib.getDocument('annotated.pdf');
403
+ const pdf = await loadingTask.promise;
404
+
405
+ for (let i = 1; i <= pdf.numPages; i++) {
406
+ const page = await pdf.getPage(i);
407
+ const annotations = await page.getAnnotations();
408
+
409
+ annotations.forEach(annotation => {
410
+ console.log(`Annotation type: ${annotation.subtype}`);
411
+ console.log(`Content: ${annotation.contents}`);
412
+ console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`);
413
+ });
414
+ }
415
+ }
416
+ ```
417
+
418
+ ---
419
+ <!-- module: 03-merge-split-manipulate.md -->
420
+
421
+ # Merging, Splitting, Rotating, and Manipulating Pages
422
+
423
+ Combining, splitting, rotating, watermarking, cropping, optimizing, and batch-processing PDFs (pypdf, qpdf, pdftk, pdf-lib).
424
+
425
+ ## pypdf - Basic Operations
426
+
427
+ ### Merge PDFs
428
+ ```python
429
+ from pypdf import PdfWriter, PdfReader
430
+
431
+ writer = PdfWriter()
432
+ for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
433
+ reader = PdfReader(pdf_file)
434
+ for page in reader.pages:
435
+ writer.add_page(page)
436
+
437
+ with open("merged.pdf", "wb") as output:
438
+ writer.write(output)
439
+ ```
440
+
441
+ ### Split PDF
442
+ ```python
443
+ reader = PdfReader("input.pdf")
444
+ for i, page in enumerate(reader.pages):
445
+ writer = PdfWriter()
446
+ writer.add_page(page)
447
+ with open(f"page_{i+1}.pdf", "wb") as output:
448
+ writer.write(output)
449
+ ```
450
+
451
+ ### Rotate Pages
452
+ ```python
453
+ reader = PdfReader("input.pdf")
454
+ writer = PdfWriter()
455
+
456
+ page = reader.pages[0]
457
+ page.rotate(90) # Rotate 90 degrees clockwise
458
+ writer.add_page(page)
459
+
460
+ with open("rotated.pdf", "wb") as output:
461
+ writer.write(output)
462
+ ```
463
+
464
+ ## qpdf
465
+ ```bash
466
+ # Merge PDFs
467
+ qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
468
+
469
+ # Split pages
470
+ qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
471
+ qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
472
+
473
+ # Rotate pages
474
+ qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
475
+ ```
476
+
477
+ ## pdftk (if available)
478
+ ```bash
479
+ # Merge
480
+ pdftk file1.pdf file2.pdf cat output merged.pdf
481
+
482
+ # Split
483
+ pdftk input.pdf burst
484
+
485
+ # Rotate
486
+ pdftk input.pdf rotate 1east output rotated.pdf
487
+ ```
488
+
489
+ ## Add Watermark
490
+ ```python
491
+ from pypdf import PdfReader, PdfWriter
492
+
493
+ # Create watermark (or load existing)
494
+ watermark = PdfReader("watermark.pdf").pages[0]
495
+
496
+ # Apply to all pages
497
+ reader = PdfReader("document.pdf")
498
+ writer = PdfWriter()
499
+
500
+ for page in reader.pages:
501
+ page.merge_page(watermark)
502
+ writer.add_page(page)
503
+
504
+ with open("watermarked.pdf", "wb") as output:
505
+ writer.write(output)
506
+ ```
507
+
508
+ ## pdf-lib (MIT License)
509
+
510
+ ### Load and Manipulate Existing PDF
511
+ ```javascript
512
+ import { PDFDocument } from 'pdf-lib';
513
+ import fs from 'fs';
514
+
515
+ async function manipulatePDF() {
516
+ // Load existing PDF
517
+ const existingPdfBytes = fs.readFileSync('input.pdf');
518
+ const pdfDoc = await PDFDocument.load(existingPdfBytes);
519
+
520
+ // Get page count
521
+ const pageCount = pdfDoc.getPageCount();
522
+ console.log(`Document has ${pageCount} pages`);
523
+
524
+ // Add new page
525
+ const newPage = pdfDoc.addPage([600, 400]);
526
+ newPage.drawText('Added by pdf-lib', {
527
+ x: 100,
528
+ y: 300,
529
+ size: 16
530
+ });
531
+
532
+ // Save modified PDF
533
+ const pdfBytes = await pdfDoc.save();
534
+ fs.writeFileSync('modified.pdf', pdfBytes);
535
+ }
536
+ ```
537
+
538
+ ### Advanced Merge and Split Operations
539
+ ```javascript
540
+ import { PDFDocument } from 'pdf-lib';
541
+ import fs from 'fs';
542
+
543
+ async function mergePDFs() {
544
+ // Create new document
545
+ const mergedPdf = await PDFDocument.create();
546
+
547
+ // Load source PDFs
548
+ const pdf1Bytes = fs.readFileSync('doc1.pdf');
549
+ const pdf2Bytes = fs.readFileSync('doc2.pdf');
550
+
551
+ const pdf1 = await PDFDocument.load(pdf1Bytes);
552
+ const pdf2 = await PDFDocument.load(pdf2Bytes);
553
+
554
+ // Copy pages from first PDF
555
+ const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
556
+ pdf1Pages.forEach(page => mergedPdf.addPage(page));
557
+
558
+ // Copy specific pages from second PDF (pages 0, 2, 4)
559
+ const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
560
+ pdf2Pages.forEach(page => mergedPdf.addPage(page));
561
+
562
+ const mergedPdfBytes = await mergedPdf.save();
563
+ fs.writeFileSync('merged.pdf', mergedPdfBytes);
564
+ }
565
+ ```
566
+
567
+ ## qpdf Advanced Features
568
+
569
+ ### Complex Page Manipulation
570
+ ```bash
571
+ # Split PDF into groups of pages
572
+ qpdf --split-pages=3 input.pdf output_group_%02d.pdf
573
+
574
+ # Extract specific pages with complex ranges
575
+ qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
576
+
577
+ # Merge specific pages from multiple PDFs
578
+ qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
579
+ ```
580
+
581
+ ### PDF Optimization and Repair
582
+ ```bash
583
+ # Optimize PDF for web (linearize for streaming)
584
+ qpdf --linearize input.pdf optimized.pdf
585
+
586
+ # Remove unused objects and compress
587
+ qpdf --optimize-level=all input.pdf compressed.pdf
588
+
589
+ # Attempt to repair corrupted PDF structure
590
+ qpdf --check input.pdf
591
+ qpdf --fix-qdf damaged.pdf repaired.pdf
592
+
593
+ # Show detailed PDF structure for debugging
594
+ qpdf --show-all-pages input.pdf > structure.txt
595
+ ```
596
+
597
+ ## Advanced PDF Cropping
598
+ ```python
599
+ from pypdf import PdfWriter, PdfReader
600
+
601
+ reader = PdfReader("input.pdf")
602
+ writer = PdfWriter()
603
+
604
+ # Crop page (left, bottom, right, top in points)
605
+ page = reader.pages[0]
606
+ page.mediabox.left = 50
607
+ page.mediabox.bottom = 50
608
+ page.mediabox.right = 550
609
+ page.mediabox.top = 750
610
+
611
+ writer.add_page(page)
612
+ with open("cropped.pdf", "wb") as output:
613
+ writer.write(output)
614
+ ```
615
+
616
+ ## Batch PDF Processing with Error Handling
617
+ ```python
618
+ import os
619
+ import glob
620
+ from pypdf import PdfReader, PdfWriter
621
+ import logging
622
+
623
+ logging.basicConfig(level=logging.INFO)
624
+ logger = logging.getLogger(__name__)
625
+
626
+ def batch_process_pdfs(input_dir, operation='merge'):
627
+ pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
628
+
629
+ if operation == 'merge':
630
+ writer = PdfWriter()
631
+ for pdf_file in pdf_files:
632
+ try:
633
+ reader = PdfReader(pdf_file)
634
+ for page in reader.pages:
635
+ writer.add_page(page)
636
+ logger.info(f"Processed: {pdf_file}")
637
+ except Exception as e:
638
+ logger.error(f"Failed to process {pdf_file}: {e}")
639
+ continue
640
+
641
+ with open("batch_merged.pdf", "wb") as output:
642
+ writer.write(output)
643
+
644
+ elif operation == 'extract_text':
645
+ for pdf_file in pdf_files:
646
+ try:
647
+ reader = PdfReader(pdf_file)
648
+ text = ""
649
+ for page in reader.pages:
650
+ text += page.extract_text()
651
+
652
+ output_file = pdf_file.replace('.pdf', '.txt')
653
+ with open(output_file, 'w', encoding='utf-8') as f:
654
+ f.write(text)
655
+ logger.info(f"Extracted text from: {pdf_file}")
656
+
657
+ except Exception as e:
658
+ logger.error(f"Failed to extract text from {pdf_file}: {e}")
659
+ continue
660
+ ```
661
+
662
+ ---
663
+ <!-- module: 04-images-ocr.md -->
664
+
665
+ # Images, Rendering, and OCR
666
+
667
+ Extracting images, rendering pages to images, extracting figures, and OCR on scanned PDFs (pdfimages, pypdfium2, pdftoppm, pdfjs-dist, pytesseract).
668
+
669
+ ## Extract Text from Scanned PDFs (OCR)
670
+ ```python
671
+ # Requires: pip install pytesseract pdf2image
672
+ import pytesseract
673
+ from pdf2image import convert_from_path
674
+
675
+ # Convert PDF to images
676
+ images = convert_from_path('scanned.pdf')
677
+
678
+ # OCR each page
679
+ text = ""
680
+ for i, image in enumerate(images):
681
+ text += f"Page {i+1}:\n"
682
+ text += pytesseract.image_to_string(image)
683
+ text += "\n\n"
684
+
685
+ print(text)
686
+ ```
687
+
688
+ ## Extract Images
689
+ ```bash
690
+ # Using pdfimages (poppler-utils)
691
+ pdfimages -j input.pdf output_prefix
692
+
693
+ # This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
694
+ ```
695
+
696
+ ## pypdfium2 Library (Apache/BSD License)
697
+
698
+ ### Overview
699
+ pypdfium2 is a Python binding for PDFium (Chromium's PDF library). It's excellent for fast PDF rendering, image generation, and serves as a PyMuPDF replacement.
700
+
701
+ ### Render PDF to Images
702
+ ```python
703
+ import pypdfium2 as pdfium
704
+ from PIL import Image
705
+
706
+ # Load PDF
707
+ pdf = pdfium.PdfDocument("document.pdf")
708
+
709
+ # Render page to image
710
+ page = pdf[0] # First page
711
+ bitmap = page.render(
712
+ scale=2.0, # Higher resolution
713
+ rotation=0 # No rotation
714
+ )
715
+
716
+ # Convert to PIL Image
717
+ img = bitmap.to_pil()
718
+ img.save("page_1.png", "PNG")
719
+
720
+ # Process multiple pages
721
+ for i, page in enumerate(pdf):
722
+ bitmap = page.render(scale=1.5)
723
+ img = bitmap.to_pil()
724
+ img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
725
+ ```
726
+
727
+ ### Extract Text with pypdfium2
728
+ ```python
729
+ import pypdfium2 as pdfium
730
+
731
+ pdf = pdfium.PdfDocument("document.pdf")
732
+ for i, page in enumerate(pdf):
733
+ text = page.get_text()
734
+ print(f"Page {i+1} text length: {len(text)} chars")
735
+ ```
736
+
737
+ ## pdfjs-dist (Apache License) - Rendering
738
+
739
+ PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser.
740
+
741
+ ### Basic PDF Loading and Rendering
742
+ ```javascript
743
+ import * as pdfjsLib from 'pdfjs-dist';
744
+
745
+ // Configure worker (important for performance)
746
+ pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
747
+
748
+ async function renderPDF() {
749
+ // Load PDF
750
+ const loadingTask = pdfjsLib.getDocument('document.pdf');
751
+ const pdf = await loadingTask.promise;
752
+
753
+ console.log(`Loaded PDF with ${pdf.numPages} pages`);
754
+
755
+ // Get first page
756
+ const page = await pdf.getPage(1);
757
+ const viewport = page.getViewport({ scale: 1.5 });
758
+
759
+ // Render to canvas
760
+ const canvas = document.createElement('canvas');
761
+ const context = canvas.getContext('2d');
762
+ canvas.height = viewport.height;
763
+ canvas.width = viewport.width;
764
+
765
+ const renderContext = {
766
+ canvasContext: context,
767
+ viewport: viewport
768
+ };
769
+
770
+ await page.render(renderContext).promise;
771
+ document.body.appendChild(canvas);
772
+ }
773
+ ```
774
+
775
+ ## Advanced Image Conversion (poppler-utils)
776
+ ```bash
777
+ # Convert to PNG images with specific resolution
778
+ pdftoppm -png -r 300 document.pdf output_prefix
779
+
780
+ # Convert specific page range with high resolution
781
+ pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
782
+
783
+ # Convert to JPEG with quality setting
784
+ pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
785
+ ```
786
+
787
+ ## Extract Embedded Images (poppler-utils)
788
+ ```bash
789
+ # Extract all embedded images with metadata
790
+ pdfimages -j -p document.pdf page_images
791
+
792
+ # List image info without extracting
793
+ pdfimages -list document.pdf
794
+
795
+ # Extract images in their original format
796
+ pdfimages -all document.pdf images/img
797
+ ```
798
+
799
+ ## Extract Figures/Images from PDF
800
+
801
+ ### Method 1: Using pdfimages (fastest)
802
+ ```bash
803
+ # Extract all images with original quality
804
+ pdfimages -all document.pdf images/img
805
+ ```
806
+
807
+ ### Method 2: Using pypdfium2 + Image Processing
808
+ ```python
809
+ import pypdfium2 as pdfium
810
+ from PIL import Image
811
+ import numpy as np
812
+
813
+ def extract_figures(pdf_path, output_dir):
814
+ pdf = pdfium.PdfDocument(pdf_path)
815
+
816
+ for page_num, page in enumerate(pdf):
817
+ # Render high-resolution page
818
+ bitmap = page.render(scale=3.0)
819
+ img = bitmap.to_pil()
820
+
821
+ # Convert to numpy for processing
822
+ img_array = np.array(img)
823
+
824
+ # Simple figure detection (non-white regions)
825
+ mask = np.any(img_array != [255, 255, 255], axis=2)
826
+
827
+ # Find contours and extract bounding boxes
828
+ # (This is simplified - real implementation would need more sophisticated detection)
829
+
830
+ # Save detected figures
831
+ # ... implementation depends on specific needs
832
+ ```
833
+
834
+ ---
835
+ <!-- module: 05-encryption.md -->
836
+
837
+ # Encryption, Passwords, and Decryption
838
+
839
+ Adding and removing password protection and setting permissions (pypdf, qpdf).
840
+
841
+ ## Password Protection (pypdf)
842
+ ```python
843
+ from pypdf import PdfReader, PdfWriter
844
+
845
+ reader = PdfReader("input.pdf")
846
+ writer = PdfWriter()
847
+
848
+ for page in reader.pages:
849
+ writer.add_page(page)
850
+
851
+ # Add password
852
+ writer.encrypt("userpassword", "ownerpassword")
853
+
854
+ with open("encrypted.pdf", "wb") as output:
855
+ writer.write(output)
856
+ ```
857
+
858
+ ## Remove Password (qpdf)
859
+ ```bash
860
+ # Remove password
861
+ qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
862
+ ```
863
+
864
+ ## Advanced Encryption (qpdf)
865
+ ```bash
866
+ # Add password protection with specific permissions
867
+ qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
868
+
869
+ # Check encryption status
870
+ qpdf --show-encryption encrypted.pdf
871
+
872
+ # Remove password protection (requires password)
873
+ qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
874
+ ```
875
+
876
+ ## Handling Encrypted PDFs (pypdf)
877
+ ```python
878
+ # Handle password-protected PDFs
879
+ from pypdf import PdfReader
880
+
881
+ try:
882
+ reader = PdfReader("encrypted.pdf")
883
+ if reader.is_encrypted:
884
+ reader.decrypt("password")
885
+ except Exception as e:
886
+ print(f"Failed to decrypt: {e}")
887
+ ```
888
+
889
+ ---
890
+ <!-- module: 06-forms.md -->
891
+
892
+ # Filling PDF Forms
893
+
894
+ Filling both fillable (AcroForm) and non-fillable (flat/scanned) PDF forms.
895
+
896
+ > **Vendored scripts:** every `python scripts/...` command below refers to the helper scripts vendored in the source skill at `skills/sources/anthropic/pdf/scripts/` (`check_fillable_fields.py`, `extract_form_field_info.py`, `convert_pdf_to_images.py`, `fill_fillable_fields.py`, `extract_form_structure.py`, `check_bounding_boxes.py`, `fill_pdf_form_with_annotations.py`). "This file's directory" means the source skill directory.
897
+
898
+ **CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
899
+
900
+ If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
901
+ `python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
902
+
903
+ # Fillable fields
904
+ If the PDF has fillable form fields:
905
+ - Run this script from this file's directory: `python scripts/extract_form_field_info.py <input.pdf> <field_info.json>`. It will create a JSON file with a list of fields in this format:
906
+ ```
907
+ [
908
+ {
909
+ "field_id": (unique ID for the field),
910
+ "page": (page number, 1-based),
911
+ "rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page),
912
+ "type": ("text", "checkbox", "radio_group", or "choice"),
913
+ },
914
+ // Checkboxes have "checked_value" and "unchecked_value" properties:
915
+ {
916
+ "field_id": (unique ID for the field),
917
+ "page": (page number, 1-based),
918
+ "type": "checkbox",
919
+ "checked_value": (Set the field to this value to check the checkbox),
920
+ "unchecked_value": (Set the field to this value to uncheck the checkbox),
921
+ },
922
+ // Radio groups have a "radio_options" list with the possible choices.
923
+ {
924
+ "field_id": (unique ID for the field),
925
+ "page": (page number, 1-based),
926
+ "type": "radio_group",
927
+ "radio_options": [
928
+ {
929
+ "value": (set the field to this value to select this radio option),
930
+ "rect": (bounding box for the radio button for this option)
931
+ },
932
+ // Other radio options
933
+ ]
934
+ },
935
+ // Multiple choice fields have a "choice_options" list with the possible choices:
936
+ {
937
+ "field_id": (unique ID for the field),
938
+ "page": (page number, 1-based),
939
+ "type": "choice",
940
+ "choice_options": [
941
+ {
942
+ "value": (set the field to this value to select this option),
943
+ "text": (display text of the option)
944
+ },
945
+ // Other choice options
946
+ ],
947
+ }
948
+ ]
949
+ ```
950
+ - Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory):
951
+ `python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
952
+ Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates).
953
+ - Create a `field_values.json` file in this format with the values to be entered for each field:
954
+ ```
955
+ [
956
+ {
957
+ "field_id": "last_name", // Must match the field_id from `extract_form_field_info.py`
958
+ "description": "The user's last name",
959
+ "page": 1, // Must match the "page" value in field_info.json
960
+ "value": "Simpson"
961
+ },
962
+ {
963
+ "field_id": "Checkbox12",
964
+ "description": "Checkbox to be checked if the user is 18 or over",
965
+ "page": 1,
966
+ "value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options".
967
+ },
968
+ // more fields
969
+ ]
970
+ ```
971
+ - Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
972
+ `python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
973
+ This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
974
+
975
+ # Non-fillable fields
976
+ If the PDF doesn't have fillable form fields, you'll add text annotations. First try to extract coordinates from the PDF structure (more accurate), then fall back to visual estimation if needed.
977
+
978
+ ## Step 1: Try Structure Extraction First
979
+
980
+ Run this script to extract text labels, lines, and checkboxes with their exact PDF coordinates:
981
+ `python scripts/extract_form_structure.py <input.pdf> form_structure.json`
982
+
983
+ This creates a JSON file containing:
984
+ - **labels**: Every text element with exact coordinates (x0, top, x1, bottom in PDF points)
985
+ - **lines**: Horizontal lines that define row boundaries
986
+ - **checkboxes**: Small square rectangles that are checkboxes (with center coordinates)
987
+ - **row_boundaries**: Row top/bottom positions calculated from horizontal lines
988
+
989
+ **Check the results**: If `form_structure.json` has meaningful labels (text elements that correspond to form fields), use **Approach A: Structure-Based Coordinates**. If the PDF is scanned/image-based and has few or no labels, use **Approach B: Visual Estimation**.
990
+
991
+ ---
992
+
993
+ ## Approach A: Structure-Based Coordinates (Preferred)
994
+
995
+ Use this when `extract_form_structure.py` found text labels in the PDF.
996
+
997
+ ### A.1: Analyze the Structure
998
+
999
+ Read form_structure.json and identify:
1000
+
1001
+ 1. **Label groups**: Adjacent text elements that form a single label (e.g., "Last" + "Name")
1002
+ 2. **Row structure**: Labels with similar `top` values are in the same row
1003
+ 3. **Field columns**: Entry areas start after label ends (x0 = label.x1 + gap)
1004
+ 4. **Checkboxes**: Use the checkbox coordinates directly from the structure
1005
+
1006
+ **Coordinate system**: PDF coordinates where y=0 is at TOP of page, y increases downward.
1007
+
1008
+ ### A.2: Check for Missing Elements
1009
+
1010
+ The structure extraction may not detect all form elements. Common cases:
1011
+ - **Circular checkboxes**: Only square rectangles are detected as checkboxes
1012
+ - **Complex graphics**: Decorative elements or non-standard form controls
1013
+ - **Faded or light-colored elements**: May not be extracted
1014
+
1015
+ If you see form fields in the PDF images that aren't in form_structure.json, you'll need to use **visual analysis** for those specific fields (see "Hybrid Approach" below).
1016
+
1017
+ ### A.3: Create fields.json with PDF Coordinates
1018
+
1019
+ For each field, calculate entry coordinates from the extracted structure:
1020
+
1021
+ **Text fields:**
1022
+ - entry x0 = label x1 + 5 (small gap after label)
1023
+ - entry x1 = next label's x0, or row boundary
1024
+ - entry top = same as label top
1025
+ - entry bottom = row boundary line below, or label bottom + row_height
1026
+
1027
+ **Checkboxes:**
1028
+ - Use the checkbox rectangle coordinates directly from form_structure.json
1029
+ - entry_bounding_box = [checkbox.x0, checkbox.top, checkbox.x1, checkbox.bottom]
1030
+
1031
+ Create fields.json using `pdf_width` and `pdf_height` (signals PDF coordinates):
1032
+ ```json
1033
+ {
1034
+ "pages": [
1035
+ {"page_number": 1, "pdf_width": 612, "pdf_height": 792}
1036
+ ],
1037
+ "form_fields": [
1038
+ {
1039
+ "page_number": 1,
1040
+ "description": "Last name entry field",
1041
+ "field_label": "Last Name",
1042
+ "label_bounding_box": [43, 63, 87, 73],
1043
+ "entry_bounding_box": [92, 63, 260, 79],
1044
+ "entry_text": {"text": "Smith", "font_size": 10}
1045
+ },
1046
+ {
1047
+ "page_number": 1,
1048
+ "description": "US Citizen Yes checkbox",
1049
+ "field_label": "Yes",
1050
+ "label_bounding_box": [260, 200, 280, 210],
1051
+ "entry_bounding_box": [285, 197, 292, 205],
1052
+ "entry_text": {"text": "X"}
1053
+ }
1054
+ ]
1055
+ }
1056
+ ```
1057
+
1058
+ **Important**: Use `pdf_width`/`pdf_height` and coordinates directly from form_structure.json.
1059
+
1060
+ ### A.4: Validate Bounding Boxes
1061
+
1062
+ Before filling, check your bounding boxes for errors:
1063
+ `python scripts/check_bounding_boxes.py fields.json`
1064
+
1065
+ This checks for intersecting bounding boxes and entry boxes that are too small for the font size. Fix any reported errors before filling.
1066
+
1067
+ ---
1068
+
1069
+ ## Approach B: Visual Estimation (Fallback)
1070
+
1071
+ Use this when the PDF is scanned/image-based and structure extraction found no usable text labels (e.g., all text shows as "(cid:X)" patterns).
1072
+
1073
+ ### B.1: Convert PDF to Images
1074
+
1075
+ `python scripts/convert_pdf_to_images.py <input.pdf> <images_dir/>`
1076
+
1077
+ ### B.2: Initial Field Identification
1078
+
1079
+ Examine each page image to identify form sections and get **rough estimates** of field locations:
1080
+ - Form field labels and their approximate positions
1081
+ - Entry areas (lines, boxes, or blank spaces for text input)
1082
+ - Checkboxes and their approximate locations
1083
+
1084
+ For each field, note approximate pixel coordinates (they don't need to be precise yet).
1085
+
1086
+ ### B.3: Zoom Refinement (CRITICAL for accuracy)
1087
+
1088
+ For each field, crop a region around the estimated position to refine coordinates precisely.
1089
+
1090
+ **Create a zoomed crop using ImageMagick:**
1091
+ ```bash
1092
+ magick <page_image> -crop <width>x<height>+<x>+<y> +repage <crop_output.png>
1093
+ ```
1094
+
1095
+ Where:
1096
+ - `<x>, <y>` = top-left corner of crop region (use your rough estimate minus padding)
1097
+ - `<width>, <height>` = size of crop region (field area plus ~50px padding on each side)
1098
+
1099
+ **Example:** To refine a "Name" field estimated around (100, 150):
1100
+ ```bash
1101
+ magick images_dir/page_1.png -crop 300x80+50+120 +repage crops/name_field.png
1102
+ ```
1103
+
1104
+ (Note: if the `magick` command isn't available, try `convert` with the same arguments).
1105
+
1106
+ **Examine the cropped image** to determine precise coordinates:
1107
+ 1. Identify the exact pixel where the entry area begins (after the label)
1108
+ 2. Identify where the entry area ends (before next field or edge)
1109
+ 3. Identify the top and bottom of the entry line/box
1110
+
1111
+ **Convert crop coordinates back to full image coordinates:**
1112
+ - full_x = crop_x + crop_offset_x
1113
+ - full_y = crop_y + crop_offset_y
1114
+
1115
+ Example: If the crop started at (50, 120) and the entry box starts at (52, 18) within the crop:
1116
+ - entry_x0 = 52 + 50 = 102
1117
+ - entry_top = 18 + 120 = 138
1118
+
1119
+ **Repeat for each field**, grouping nearby fields into single crops when possible.
1120
+
1121
+ ### B.4: Create fields.json with Refined Coordinates
1122
+
1123
+ Create fields.json using `image_width` and `image_height` (signals image coordinates):
1124
+ ```json
1125
+ {
1126
+ "pages": [
1127
+ {"page_number": 1, "image_width": 1700, "image_height": 2200}
1128
+ ],
1129
+ "form_fields": [
1130
+ {
1131
+ "page_number": 1,
1132
+ "description": "Last name entry field",
1133
+ "field_label": "Last Name",
1134
+ "label_bounding_box": [120, 175, 242, 198],
1135
+ "entry_bounding_box": [255, 175, 720, 218],
1136
+ "entry_text": {"text": "Smith", "font_size": 10}
1137
+ }
1138
+ ]
1139
+ }
1140
+ ```
1141
+
1142
+ **Important**: Use `image_width`/`image_height` and the refined pixel coordinates from the zoom analysis.
1143
+
1144
+ ### B.5: Validate Bounding Boxes
1145
+
1146
+ Before filling, check your bounding boxes for errors:
1147
+ `python scripts/check_bounding_boxes.py fields.json`
1148
+
1149
+ This checks for intersecting bounding boxes and entry boxes that are too small for the font size. Fix any reported errors before filling.
1150
+
1151
+ ---
1152
+
1153
+ ## Hybrid Approach: Structure + Visual
1154
+
1155
+ Use this when structure extraction works for most fields but misses some elements (e.g., circular checkboxes, unusual form controls).
1156
+
1157
+ 1. **Use Approach A** for fields that were detected in form_structure.json
1158
+ 2. **Convert PDF to images** for visual analysis of missing fields
1159
+ 3. **Use zoom refinement** (from Approach B) for the missing fields
1160
+ 4. **Combine coordinates**: For fields from structure extraction, use `pdf_width`/`pdf_height`. For visually-estimated fields, you must convert image coordinates to PDF coordinates:
1161
+ - pdf_x = image_x * (pdf_width / image_width)
1162
+ - pdf_y = image_y * (pdf_height / image_height)
1163
+ 5. **Use a single coordinate system** in fields.json - convert all to PDF coordinates with `pdf_width`/`pdf_height`
1164
+
1165
+ ---
1166
+
1167
+ ## Step 2: Validate Before Filling
1168
+
1169
+ **Always validate bounding boxes before filling:**
1170
+ `python scripts/check_bounding_boxes.py fields.json`
1171
+
1172
+ This checks for:
1173
+ - Intersecting bounding boxes (which would cause overlapping text)
1174
+ - Entry boxes that are too small for the specified font size
1175
+
1176
+ Fix any reported errors in fields.json before proceeding.
1177
+
1178
+ ## Step 3: Fill the Form
1179
+
1180
+ The fill script auto-detects the coordinate system and handles conversion:
1181
+ `python scripts/fill_pdf_form_with_annotations.py <input.pdf> fields.json <output.pdf>`
1182
+
1183
+ ## Step 4: Verify Output
1184
+
1185
+ Convert the filled PDF to images and verify text placement:
1186
+ `python scripts/convert_pdf_to_images.py <output.pdf> <verify_images/>`
1187
+
1188
+ If text is mispositioned:
1189
+ - **Approach A**: Check that you're using PDF coordinates from form_structure.json with `pdf_width`/`pdf_height`
1190
+ - **Approach B**: Check that image dimensions match and coordinates are accurate pixels
1191
+ - **Hybrid**: Ensure coordinate conversions are correct for visually-estimated fields
1192
+
1193
+ ---
1194
+ <!-- module: 07-libraries-reference.md -->
1195
+
1196
+ # Library Guidance, Performance, Troubleshooting, and Licenses
1197
+
1198
+ Cross-cutting reference: performance optimization, troubleshooting recipes, and per-library license information. For library-specific code, see the creation, extraction, merge-split, and images-ocr minis.
1199
+
1200
+ ## Performance Optimization Tips
1201
+
1202
+ ### 1. For Large PDFs
1203
+ - Use streaming approaches instead of loading entire PDF in memory
1204
+ - Use `qpdf --split-pages` for splitting large files
1205
+ - Process pages individually with pypdfium2
1206
+
1207
+ ### 2. For Text Extraction
1208
+ - `pdftotext -bbox-layout` is fastest for plain text extraction
1209
+ - Use pdfplumber for structured data and tables
1210
+ - Avoid `pypdf.extract_text()` for very large documents
1211
+
1212
+ ### 3. For Image Extraction
1213
+ - `pdfimages` is much faster than rendering pages
1214
+ - Use low resolution for previews, high resolution for final output
1215
+
1216
+ ### 4. For Form Filling
1217
+ - pdf-lib maintains form structure better than most alternatives
1218
+ - Pre-validate form fields before processing
1219
+
1220
+ ### 5. Memory Management
1221
+ ```python
1222
+ # Process PDFs in chunks
1223
+ def process_large_pdf(pdf_path, chunk_size=10):
1224
+ reader = PdfReader(pdf_path)
1225
+ total_pages = len(reader.pages)
1226
+
1227
+ for start_idx in range(0, total_pages, chunk_size):
1228
+ end_idx = min(start_idx + chunk_size, total_pages)
1229
+ writer = PdfWriter()
1230
+
1231
+ for i in range(start_idx, end_idx):
1232
+ writer.add_page(reader.pages[i])
1233
+
1234
+ # Process chunk
1235
+ with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
1236
+ writer.write(output)
1237
+ ```
1238
+
1239
+ ## Troubleshooting Common Issues
1240
+
1241
+ ### Encrypted PDFs
1242
+ ```python
1243
+ # Handle password-protected PDFs
1244
+ from pypdf import PdfReader
1245
+
1246
+ try:
1247
+ reader = PdfReader("encrypted.pdf")
1248
+ if reader.is_encrypted:
1249
+ reader.decrypt("password")
1250
+ except Exception as e:
1251
+ print(f"Failed to decrypt: {e}")
1252
+ ```
1253
+
1254
+ ### Corrupted PDFs
1255
+ ```bash
1256
+ # Use qpdf to repair
1257
+ qpdf --check corrupted.pdf
1258
+ qpdf --replace-input corrupted.pdf
1259
+ ```
1260
+
1261
+ ### Text Extraction Issues
1262
+ ```python
1263
+ # Fallback to OCR for scanned PDFs
1264
+ import pytesseract
1265
+ from pdf2image import convert_from_path
1266
+
1267
+ def extract_text_with_ocr(pdf_path):
1268
+ images = convert_from_path(pdf_path)
1269
+ text = ""
1270
+ for i, image in enumerate(images):
1271
+ text += pytesseract.image_to_string(image)
1272
+ return text
1273
+ ```
1274
+
1275
+ ## License Information
1276
+
1277
+ - **pypdf**: BSD License
1278
+ - **pdfplumber**: MIT License
1279
+ - **pypdfium2**: Apache/BSD License
1280
+ - **reportlab**: BSD License
1281
+ - **poppler-utils**: GPL-2 License
1282
+ - **qpdf**: Apache License
1283
+ - **pdf-lib**: MIT License
1284
+ - **pdfjs-dist**: Apache License