fullbleed 2.5.2__tar.gz → 2.5.4__tar.gz

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 (234) hide show
  1. {fullbleed-2.5.2 → fullbleed-2.5.4}/Cargo.lock +1 -1
  2. {fullbleed-2.5.2 → fullbleed-2.5.4}/Cargo.toml +1 -1
  3. {fullbleed-2.5.2 → fullbleed-2.5.4}/PKG-INFO +4 -2
  4. {fullbleed-2.5.2 → fullbleed-2.5.4}/README.md +3 -1
  5. fullbleed-2.5.4/THIRD_PARTY_LICENSES.md +119 -0
  6. {fullbleed-2.5.2 → fullbleed-2.5.4}/build_backend/fullbleed_build_backend.py +2 -0
  7. {fullbleed-2.5.2 → fullbleed-2.5.4}/fullbleed-agent-contract.json +25 -1
  8. {fullbleed-2.5.2 → fullbleed-2.5.4}/llms.txt +1 -1
  9. {fullbleed-2.5.2 → fullbleed-2.5.4}/pyproject.toml +1 -1
  10. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/cli.py +76 -22
  11. fullbleed-2.5.4/src/base14_metrics.rs +53 -0
  12. fullbleed-2.5.4/src/base14_metrics_data.rs +8745 -0
  13. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/lib.rs +1 -0
  14. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_raster.rs +317 -4
  15. fullbleed-2.5.4/tools/data/base14/Courier-Bold.afm +684 -0
  16. fullbleed-2.5.4/tools/data/base14/Courier-BoldOblique.afm +684 -0
  17. fullbleed-2.5.4/tools/data/base14/Courier-Oblique.afm +684 -0
  18. fullbleed-2.5.4/tools/data/base14/Courier.afm +684 -0
  19. fullbleed-2.5.4/tools/data/base14/Helvetica-Bold.afm +5654 -0
  20. fullbleed-2.5.4/tools/data/base14/Helvetica-BoldOblique.afm +5654 -0
  21. fullbleed-2.5.4/tools/data/base14/Helvetica-Oblique.afm +6102 -0
  22. fullbleed-2.5.4/tools/data/base14/Helvetica.afm +6102 -0
  23. fullbleed-2.5.4/tools/data/base14/MustRead.html +19 -0
  24. fullbleed-2.5.4/tools/data/base14/Symbol.afm +426 -0
  25. fullbleed-2.5.4/tools/data/base14/Times-Bold.afm +5176 -0
  26. fullbleed-2.5.4/tools/data/base14/Times-BoldItalic.afm +4768 -0
  27. fullbleed-2.5.4/tools/data/base14/Times-Italic.afm +5334 -0
  28. fullbleed-2.5.4/tools/data/base14/Times-Roman.afm +4838 -0
  29. fullbleed-2.5.4/tools/data/base14/ZapfDingbats.afm +450 -0
  30. fullbleed-2.5.4/tools/data/base14/agl-LICENSE.md +26 -0
  31. fullbleed-2.5.4/tools/data/base14/agl-glyphlist.txt +4325 -0
  32. fullbleed-2.5.4/tools/data/base14/agl-zapfdingbats.txt +245 -0
  33. fullbleed-2.5.4/tools/data/base14/sources.json +97 -0
  34. fullbleed-2.5.4/tools/generate_base14_metrics.py +113 -0
  35. fullbleed-2.5.2/THIRD_PARTY_LICENSES.md +0 -49
  36. {fullbleed-2.5.2 → fullbleed-2.5.4}/COPYRIGHT +0 -0
  37. {fullbleed-2.5.2 → fullbleed-2.5.4}/FONT_LICENSE_AUDIT.json +0 -0
  38. {fullbleed-2.5.2 → fullbleed-2.5.4}/FONT_LICENSE_AUDIT.md +0 -0
  39. {fullbleed-2.5.2 → fullbleed-2.5.4}/LICENSE +0 -0
  40. {fullbleed-2.5.2 → fullbleed-2.5.4}/LICENSING.md +0 -0
  41. {fullbleed-2.5.2 → fullbleed-2.5.4}/build.rs +0 -0
  42. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/Cargo.lock +0 -0
  43. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/Cargo.toml +0 -0
  44. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/LICENSE +0 -0
  45. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/README.md +0 -0
  46. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/specs/fullbleed.audit_registry.v1.yaml +0 -0
  47. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/specs/section508_html_registry.v1.yaml +0 -0
  48. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/specs/wcag20aa_registry.v1.yaml +0 -0
  49. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/src/json.rs +0 -0
  50. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/src/lib.rs +0 -0
  51. {fullbleed-2.5.2 → fullbleed-2.5.4}/crates/fullbleed_audit_contract/src/sha256.rs +0 -0
  52. {fullbleed-2.5.2 → fullbleed-2.5.4}/docs/specs/fullbleed.a11y.verify.v1.schema.json +0 -0
  53. {fullbleed-2.5.2 → fullbleed-2.5.4}/docs/specs/fullbleed.audit_registry.v1.yaml +0 -0
  54. {fullbleed-2.5.2 → fullbleed-2.5.4}/docs/specs/fullbleed.pmr.v1.schema.json +0 -0
  55. {fullbleed-2.5.2 → fullbleed-2.5.4}/docs/specs/section508_html_registry.v1.yaml +0 -0
  56. {fullbleed-2.5.2 → fullbleed-2.5.4}/docs/specs/wcag20aa_registry.v1.yaml +0 -0
  57. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/__init__.py +0 -0
  58. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/__main__.py +0 -0
  59. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/_abi.py +0 -0
  60. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/accessibility/__init__.py +0 -0
  61. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/accessibility/engine.py +0 -0
  62. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/accessibility/runtime.py +0 -0
  63. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/accessibility/types.py +0 -0
  64. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/audit_prototype.py +0 -0
  65. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/audit_section508.py +0 -0
  66. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/audit_wcag.py +0 -0
  67. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/__init__.py +0 -0
  68. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/accessibility.py +0 -0
  69. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/__init__.py +0 -0
  70. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/_core.py +0 -0
  71. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/agency_letter.py +0 -0
  72. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/court_motion_form.py +0 -0
  73. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/declaration_form.py +0 -0
  74. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/instruction_sheet.py +0 -0
  75. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/investment_portfolio_report.py +0 -0
  76. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/marriage_record.py +0 -0
  77. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/profiles.py +0 -0
  78. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/recorded_plat.py +0 -0
  79. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/redaction_request_form.py +0 -0
  80. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/cav/warranty_deed.py +0 -0
  81. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/core.py +0 -0
  82. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/primitives.py +0 -0
  83. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed/ui/style.py +0 -0
  84. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/__init__.py +0 -0
  85. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/bootstrap.min.css +0 -0
  86. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/fonts/Inter-Variable.ttf +0 -0
  87. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/fonts/NotoSans-Regular.ttf +0 -0
  88. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/fonts/NotoSansMath-Regular.ttf +0 -0
  89. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/fonts/NotoSansSymbols-Regular.ttf +0 -0
  90. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/fonts/NotoSansSymbols2-Regular.ttf +0 -0
  91. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_assets/icons/bootstrap-icons.svg +0 -0
  92. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/__init__.py +0 -0
  93. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/__main__.py +0 -0
  94. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/agent.py +0 -0
  95. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/agent_acceptance.py +0 -0
  96. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/agent_contract.py +0 -0
  97. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/assets.py +0 -0
  98. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/cache.py +0 -0
  99. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/finalize.py +0 -0
  100. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/mcp.py +0 -0
  101. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold.py +0 -0
  102. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/__init__.py +0 -0
  103. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/.gitignore +0 -0
  104. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/AGENTS.md +0 -0
  105. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/COMPLIANCE.md +0 -0
  106. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/SCAFFOLDING.md +0 -0
  107. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/__init__.py +0 -0
  108. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/body.py +0 -0
  109. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/fb_ui.py +0 -0
  110. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/footer.py +0 -0
  111. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/header.py +0 -0
  112. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/primitives.py +0 -0
  113. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/body.css +0 -0
  114. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/footer.css +0 -0
  115. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/header.css +0 -0
  116. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/primitives.css +0 -0
  117. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/report.py +0 -0
  118. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/styles/report.css +0 -0
  119. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/init/styles/tokens.css +0 -0
  120. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/README.md +0 -0
  121. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitignore +0 -0
  122. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitkeep +0 -0
  123. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/report.py +0 -0
  124. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/styles/report.css +0 -0
  125. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/accessible/vendor/fonts/Inter-Variable.ttf +0 -0
  126. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitignore +0 -0
  127. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitkeep +0 -0
  128. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/invoice/report.py +0 -0
  129. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.css +0 -0
  130. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.html +0 -0
  131. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/COMPLIANCE.md +0 -0
  132. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/README.md +0 -0
  133. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/SCAFFOLDING.md +0 -0
  134. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/brand-mark.svg +0 -0
  135. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/reference-pattern.svg +0 -0
  136. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/__init__.py +0 -0
  137. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/appendix.py +0 -0
  138. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/architecture.py +0 -0
  139. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/cover.py +0 -0
  140. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/data_pages.py +0 -0
  141. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/fb_ui.py +0 -0
  142. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/primitives.py +0 -0
  143. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/appendix.css +0 -0
  144. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/architecture.css +0 -0
  145. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/cover.css +0 -0
  146. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/data.css +0 -0
  147. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/primitives.css +0 -0
  148. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/visuals.css +0 -0
  149. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/visuals.py +0 -0
  150. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/data/reference.json +0 -0
  151. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/output/.gitignore +0 -0
  152. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/report.py +0 -0
  153. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/report.css +0 -0
  154. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/tokens.css +0 -0
  155. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitignore +0 -0
  156. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitkeep +0 -0
  157. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/statement/report.py +0 -0
  158. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.css +0 -0
  159. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.html +0 -0
  160. {fullbleed-2.5.2 → fullbleed-2.5.4}/python/fullbleed_cli/watch.py +0 -0
  161. {fullbleed-2.5.2 → fullbleed-2.5.4}/skills/fullbleed/SKILL.md +0 -0
  162. {fullbleed-2.5.2 → fullbleed-2.5.4}/skills/fullbleed/agents/openai.yaml +0 -0
  163. {fullbleed-2.5.2 → fullbleed-2.5.4}/skills/fullbleed/references/compliance.md +0 -0
  164. {fullbleed-2.5.2 → fullbleed-2.5.4}/skills/fullbleed/references/selection.md +0 -0
  165. {fullbleed-2.5.2 → fullbleed-2.5.4}/skills/fullbleed/references/workflows.md +0 -0
  166. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/assets.rs +0 -0
  167. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/authoring.rs +0 -0
  168. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/base64.rs +0 -0
  169. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/canvas.rs +0 -0
  170. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/chart.rs +0 -0
  171. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/chart_document.rs +0 -0
  172. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/css_native.rs +0 -0
  173. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/css_queries.rs +0 -0
  174. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/debug.rs +0 -0
  175. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/doc_context.rs +0 -0
  176. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/doc_template.rs +0 -0
  177. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/document_metadata.rs +0 -0
  178. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/error.rs +0 -0
  179. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/finalize.rs +0 -0
  180. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/flate_native.rs +0 -0
  181. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/flowable.rs +0 -0
  182. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/font.rs +0 -0
  183. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/font_subset.rs +0 -0
  184. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/frame.rs +0 -0
  185. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/glyph_report.rs +0 -0
  186. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html.rs +0 -0
  187. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_dom.rs +0 -0
  188. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities.rs +0 -0
  189. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_0.rs +0 -0
  190. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_1.rs +0 -0
  191. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_2.rs +0 -0
  192. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_3.rs +0 -0
  193. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_4.rs +0 -0
  194. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/html_entities_5.rs +0 -0
  195. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/icc.rs +0 -0
  196. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/image_native.rs +0 -0
  197. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/jit.rs +0 -0
  198. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/jpeg_native.rs +0 -0
  199. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/math.rs +0 -0
  200. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/metrics.rs +0 -0
  201. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/native_shape.rs +0 -0
  202. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/page_data.rs +0 -0
  203. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/page_template.rs +0 -0
  204. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/parallel.rs +0 -0
  205. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf.rs +0 -0
  206. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_encodings.rs +0 -0
  207. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_identity.rs +0 -0
  208. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_native.rs +0 -0
  209. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_print_contract.rs +0 -0
  210. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_profile.rs +0 -0
  211. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_table.rs +0 -0
  212. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_vt.rs +0 -0
  213. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdf_vt_inspect.rs +0 -0
  214. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/pdfinspect.rs +0 -0
  215. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/perf.rs +0 -0
  216. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/plan.rs +0 -0
  217. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/python/abi_exports.rs +0 -0
  218. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/python/pdf_vt.rs +0 -0
  219. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/python.rs +0 -0
  220. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/python_abi.rs +0 -0
  221. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/raster.rs +0 -0
  222. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/raster_native.rs +0 -0
  223. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/sfnt.rs +0 -0
  224. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/sfnt_cff.rs +0 -0
  225. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/sfnt_outline.rs +0 -0
  226. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/spill.rs +0 -0
  227. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/style.rs +0 -0
  228. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/svg.rs +0 -0
  229. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/table_header_tests.rs +0 -0
  230. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/table_semantics.rs +0 -0
  231. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/text_shape.rs +0 -0
  232. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/types.rs +0 -0
  233. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/unicode_data.rs +0 -0
  234. {fullbleed-2.5.2 → fullbleed-2.5.4}/src/xml.rs +0 -0
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "fullbleed"
7
- version = "2.5.2"
7
+ version = "2.5.4"
8
8
  dependencies = [
9
9
  "fullbleed_audit_contract",
10
10
  "subsetter",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fullbleed"
3
- version = "2.5.2"
3
+ version = "2.5.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.85"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fullbleed
3
- Version: 2.5.2
3
+ Version: 2.5.4
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Operating System :: MacOS
@@ -91,7 +91,9 @@ Open `invoice.pdf`. The [quickstart](https://docs.fullbleed.dev/getting-started/
91
91
  | A native Rust application | [Rust quickstart and runnable examples](https://docs.fullbleed.dev/getting-started/rust/) |
92
92
  | An invoice from JSON | [Complete, checked example](examples/agent_workflows) |
93
93
  | Automatic rebuilds while editing HTML/CSS (2.5.0+) | [Watch-mode invoice example](examples/render_watch) |
94
+ | Catch PDF changes in pull requests | [Standalone project with a reviewed baseline and CI workflow](examples/pdf_regression) |
94
95
  | A PDF download in FastAPI, Flask, or Django | [Runnable web app examples](examples/web_frameworks) |
96
+ | A styled PDF report from a pandas DataFrame | [Paginated report with CSV data and verification](examples/pandas_report) |
95
97
  | A styled invoice from CSV | [Acme invoice project](examples/acme_invoice) |
96
98
  | A report that flows across pages | [Business report example](examples/agent_workflows) |
97
99
  | Tagged output with accessibility checks | [Accessibility workflow](docs/ui-accessibility.md) |
@@ -266,7 +268,7 @@ python -m pip install fullbleed
266
268
  From a local wheel:
267
269
 
268
270
  ```bash
269
- python -m pip install C:\path\to\fullbleed-2.5.2-cp310-abi3-win_amd64.whl
271
+ python -m pip install C:\path\to\fullbleed-2.5.4-cp310-abi3-win_amd64.whl
270
272
  ```
271
273
 
272
274
  From a source checkout with Rust installed, no Python build package is needed:
@@ -48,7 +48,9 @@ Open `invoice.pdf`. The [quickstart](https://docs.fullbleed.dev/getting-started/
48
48
  | A native Rust application | [Rust quickstart and runnable examples](https://docs.fullbleed.dev/getting-started/rust/) |
49
49
  | An invoice from JSON | [Complete, checked example](examples/agent_workflows) |
50
50
  | Automatic rebuilds while editing HTML/CSS (2.5.0+) | [Watch-mode invoice example](examples/render_watch) |
51
+ | Catch PDF changes in pull requests | [Standalone project with a reviewed baseline and CI workflow](examples/pdf_regression) |
51
52
  | A PDF download in FastAPI, Flask, or Django | [Runnable web app examples](examples/web_frameworks) |
53
+ | A styled PDF report from a pandas DataFrame | [Paginated report with CSV data and verification](examples/pandas_report) |
52
54
  | A styled invoice from CSV | [Acme invoice project](examples/acme_invoice) |
53
55
  | A report that flows across pages | [Business report example](examples/agent_workflows) |
54
56
  | Tagged output with accessibility checks | [Accessibility workflow](docs/ui-accessibility.md) |
@@ -223,7 +225,7 @@ python -m pip install fullbleed
223
225
  From a local wheel:
224
226
 
225
227
  ```bash
226
- python -m pip install C:\path\to\fullbleed-2.5.2-cp310-abi3-win_amd64.whl
228
+ python -m pip install C:\path\to\fullbleed-2.5.4-cp310-abi3-win_amd64.whl
227
229
  ```
228
230
 
229
231
  From a source checkout with Rust installed, no Python build package is needed:
@@ -0,0 +1,119 @@
1
+ <!-- SPDX-License-Identifier: MIT -->
2
+ # Third-Party Licenses
3
+
4
+ Schema: `fullbleed.third_party_licenses.v1`
5
+ Last updated: 2026-10-02
6
+ Scope: third-party artifacts directly redistributed by this repository and wheel package.
7
+
8
+ ## Bundled Artifacts
9
+
10
+ | Component | Bundled Path | License | Upstream | License Text |
11
+ | --- | --- | --- | --- | --- |
12
+ | Bootstrap CSS `v5.0.0` | `python/fullbleed_assets/bootstrap.min.css` | `MIT` | `https://getbootstrap.com/` | `https://github.com/twbs/bootstrap/blob/v5.0.0/LICENSE` |
13
+ | Bootstrap Icons SVG Sprite `v1.11.3` | `python/fullbleed_assets/icons/bootstrap-icons.svg` | `MIT` | `https://icons.getbootstrap.com/` | `https://raw.githubusercontent.com/twbs/icons/v1.11.3/LICENSE` |
14
+ | Inter Variable | `python/fullbleed_assets/fonts/Inter-Variable.ttf` | `OFL-1.1` | `https://fonts.google.com/specimen/Inter` | `https://raw.githubusercontent.com/google/fonts/main/ofl/inter/OFL.txt` |
15
+ | Noto Sans Regular | `python/fullbleed_assets/fonts/NotoSans-Regular.ttf` | `OFL-1.1` | `https://fonts.google.com/noto` | `https://raw.githubusercontent.com/google/fonts/main/ofl/notosans/OFL.txt` |
16
+ | Noto Sans Math Regular | `python/fullbleed_assets/fonts/NotoSansMath-Regular.ttf` | `OFL-1.1` | `https://fonts.google.com/noto` | `https://raw.githubusercontent.com/google/fonts/main/ofl/notosansmath/OFL.txt` |
17
+ | Noto Sans Symbols Regular | `python/fullbleed_assets/fonts/NotoSansSymbols-Regular.ttf` | `OFL-1.1` | `https://fonts.google.com/noto` | `https://raw.githubusercontent.com/google/fonts/main/ofl/notosanssymbols/OFL.txt` |
18
+ | Noto Sans Symbols2 Regular | `python/fullbleed_assets/fonts/NotoSansSymbols2-Regular.ttf` | `OFL-1.1` | `https://fonts.google.com/noto` | `https://raw.githubusercontent.com/google/fonts/main/ofl/notosanssymbols2/OFL.txt` |
19
+ | Adobe Core 14 metrics | `src/base14_metrics_data.rs`; originals in `tools/data/base14/*.afm` | Adobe AFM permission below | Pinned Apache PDFBox mirror | `tools/data/base14/MustRead.html` |
20
+ | Adobe Glyph List | `src/base14_metrics_data.rs`; originals in `tools/data/base14/agl-*.txt` | BSD-3-Clause | `https://github.com/adobe-type-tools/agl-aglfn` | `tools/data/base14/agl-LICENSE.md` and copyright notices below |
21
+
22
+ ## Remote-Installable Asset Registry
23
+
24
+ `fullbleed assets install <name>` supports additional remote fonts.
25
+
26
+ - Current audit artifacts:
27
+ - `FONT_LICENSE_AUDIT.md`
28
+ - `FONT_LICENSE_AUDIT.json`
29
+ - Audit summary (2026-07-28):
30
+ - `39` fonts checked
31
+ - `39` passed
32
+ - Allowed license set: `OFL-1.1`, `Apache-2.0`, `UFL-1.0`, `MIT`
33
+ - Includes barcode families: `libre-barcode-128`, `libre-barcode-128-text`, `libre-barcode-39`, `libre-barcode-39-text`, `libre-barcode-39-extended`, `libre-barcode-ean13-text`
34
+
35
+ ## Compliance Policy Semantics
36
+
37
+ Machine/readable policy identifier: `fullbleed.cli_compliance.v1`
38
+
39
+ - `LIC_MISSING_NOTICE`: bundled third-party artifact missing an entry in this document.
40
+ - `LIC_DISALLOWED`: artifact license is outside allowlist.
41
+ - `LIC_UNKNOWN`: artifact license cannot be determined.
42
+ - `LIC_AUDIT_STALE`: remote asset audit artifacts are missing or stale.
43
+ - `LIC_ASSET_UNMAPPED`: asset exists in distribution but has no license mapping.
44
+
45
+ ## Notes
46
+
47
+ - This file focuses on directly redistributed static assets (CSS/fonts/font metrics) and remote asset registry policy.
48
+ - USPS IMB fonts are currently treated as manual user-supplied assets pending explicit redistribution policy sign-off.
49
+ - For project license terms, see `LICENSE`.
50
+ - For licensing questions, email `info@fullbleed.dev` or visit `fullbleed.dev`.
51
+
52
+
53
+ ## Adobe Core 14 metrics
54
+
55
+ The original AFM files and accompanying `MustRead.html` are retained unchanged in
56
+ `tools/data/base14`. Their source commits, SHA-256 hashes, and byte lengths are
57
+ recorded in `sources.json`. The generated Rust tables are modified derivatives:
58
+ Fullbleed extracts and reorganizes advances, glyph names, and character mappings
59
+ for native PDF preview. They contain no font programs or glyph outlines.
60
+
61
+ Original copyright and trademark notices:
62
+
63
+ Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.
64
+
65
+ Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.ITC Zapf Dingbats is a registered trademark of International Typeface Corporation.
66
+
67
+ Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
68
+
69
+ Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
70
+
71
+ Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
72
+
73
+ Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
74
+
75
+ Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
76
+
77
+ Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
78
+
79
+ Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
80
+
81
+ Original redistribution permission (unchanged):
82
+
83
+ This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files.
84
+
85
+ ## Adobe Glyph List
86
+
87
+ Copyright 2002-2019 Adobe (http://www.adobe.com/).
88
+
89
+ The generated character mappings derive from the Adobe Glyph List and its Zapf
90
+ Dingbats list. Original source files and license text are retained unchanged in
91
+ `tools/data/base14`. The redistribution terms below also apply to the derived
92
+ mappings in source and binary distributions.
93
+
94
+ Redistribution and use in source and binary forms, with or without
95
+ modification, are permitted provided that the following conditions are
96
+ met:
97
+
98
+ Redistributions of source code must retain the above copyright notice,
99
+ this list of conditions and the following disclaimer.
100
+
101
+ Redistributions in binary form must reproduce the above copyright
102
+ notice, this list of conditions and the following disclaimer in the
103
+ documentation and/or other materials provided with the distribution.
104
+
105
+ Neither the name of Adobe nor the names of its contributors may be
106
+ used to endorse or promote products derived from this software without
107
+ specific prior written permission.
108
+
109
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
110
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
111
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
112
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
113
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
114
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
115
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
116
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
117
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
118
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
119
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -941,6 +941,7 @@ def _sdist_files() -> list[Path]:
941
941
  "Cargo.lock",
942
942
  "build.rs",
943
943
  "pyproject.toml",
944
+ "tools/generate_base14_metrics.py",
944
945
  str(project.get("readme", "README.md")),
945
946
  *project.get("license-files", []),
946
947
  )
@@ -948,6 +949,7 @@ def _sdist_files() -> list[Path]:
948
949
  roots = [
949
950
  _ROOT / "src",
950
951
  _ROOT / "build_backend",
952
+ _ROOT / "tools" / "data" / "base14",
951
953
  _ROOT / "crates" / "fullbleed_audit_contract" / "src",
952
954
  _ROOT / "crates" / "fullbleed_audit_contract" / "specs",
953
955
  ]
@@ -961,6 +961,7 @@
961
961
  "new:statement": "fullbleed.new_template.v1",
962
962
  "plan": "fullbleed.plan_result.v1",
963
963
  "render": "fullbleed.render_result.v1",
964
+ "repro-record": "fullbleed.repro_record.v1",
964
965
  "run": "fullbleed.run_result.v1",
965
966
  "verify": "fullbleed.verify_result.v1"
966
967
  },
@@ -4206,7 +4207,7 @@
4206
4207
  "license": "MIT",
4207
4208
  "name": "Fullbleed PDF Engine",
4208
4209
  "python_package": "fullbleed",
4209
- "version": "2.5.2"
4210
+ "version": "2.5.4"
4210
4211
  },
4211
4212
  "profiles": {
4212
4213
  "pdf": {
@@ -5281,22 +5282,45 @@
5281
5282
  },
5282
5283
  "fullbleed.repro_record.v1": {
5283
5284
  "properties": {
5285
+ "assets_lock": {
5286
+ "properties": {
5287
+ "path": {
5288
+ "type": "string"
5289
+ },
5290
+ "sha256": {
5291
+ "pattern": "^[0-9a-f]{64}$",
5292
+ "type": "string"
5293
+ }
5294
+ },
5295
+ "required": [
5296
+ "sha256"
5297
+ ],
5298
+ "type": [
5299
+ "object",
5300
+ "null"
5301
+ ]
5302
+ },
5284
5303
  "cli_version": {
5285
5304
  "type": "string"
5286
5305
  },
5287
5306
  "input_fingerprint_sha256": {
5307
+ "pattern": "^[0-9a-f]{64}$",
5288
5308
  "type": "string"
5289
5309
  },
5290
5310
  "input_manifest_sha256": {
5311
+ "pattern": "^[0-9a-f]{64}$",
5291
5312
  "type": "string"
5292
5313
  },
5293
5314
  "output_bytes_written": {
5315
+ "minimum": 0,
5294
5316
  "type": "integer"
5295
5317
  },
5296
5318
  "output_pdf_sha256": {
5319
+ "pattern": "^[0-9a-f]{64}$",
5297
5320
  "type": "string"
5298
5321
  },
5299
5322
  "schema": {
5323
+ "const": "fullbleed.repro_record.v1",
5300
5324
  "type": "string"
5301
5325
  }
5302
5326
  },
@@ -1,6 +1,6 @@
1
1
  # Fullbleed PDF Engine
2
2
 
3
- > Fullbleed 2.5.2 is a deterministic, self-contained document-generation runtime for Python and Rust. It turns structured data plus static HTML/CSS into validated PDF documents without a browser.
3
+ > Fullbleed 2.5.4 is a deterministic, self-contained document-generation runtime for Python and Rust. It turns structured data plus static HTML/CSS into validated PDF documents without a browser.
4
4
 
5
5
  The installed runtime is authoritative. Do not infer its API from this file or from remembered release knowledge. Start with:
6
6
 
@@ -5,7 +5,7 @@ backend-path = ["build_backend"]
5
5
 
6
6
  [project]
7
7
  name = "fullbleed"
8
- version = "2.5.2"
8
+ version = "2.5.4"
9
9
  description = "Deterministic Python PDF engine for reports, invoices, accessible documents, print-ready output, and compiled VDP"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -108,6 +108,7 @@ COMPLIANCE_POLICY = {
108
108
  }
109
109
 
110
110
  SCHEMA_REGISTRY = {
111
+ "repro-record": "fullbleed.repro_record.v1",
111
112
  "render": "fullbleed.render_result.v1",
112
113
  "verify": "fullbleed.verify_result.v1",
113
114
  "plan": "fullbleed.plan_result.v1",
@@ -543,12 +544,20 @@ SCHEMA_DEFS = {
543
544
  "type": "object",
544
545
  "required": ["schema", "input_fingerprint_sha256", "output_pdf_sha256"],
545
546
  "properties": {
546
- "schema": {"type": "string"},
547
+ "schema": {"type": "string", "const": "fullbleed.repro_record.v1"},
547
548
  "cli_version": {"type": "string"},
548
- "input_manifest_sha256": {"type": "string"},
549
- "input_fingerprint_sha256": {"type": "string"},
550
- "output_pdf_sha256": {"type": "string"},
551
- "output_bytes_written": {"type": "integer"},
549
+ "input_manifest_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
550
+ "input_fingerprint_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
551
+ "output_pdf_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
552
+ "output_bytes_written": {"type": "integer", "minimum": 0},
553
+ "assets_lock": {
554
+ "type": ["object", "null"],
555
+ "required": ["sha256"],
556
+ "properties": {
557
+ "path": {"type": "string"},
558
+ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
559
+ },
560
+ },
552
561
  },
553
562
  },
554
563
  }
@@ -2119,6 +2128,9 @@ def _enrich_repro_failures(failures):
2119
2128
  "REPRO_RECORD_INVALID": [
2120
2129
  "Regenerate the record from a trusted clean render; do not edit digest fields manually."
2121
2130
  ],
2131
+ "REPRO_RECORD_CONFLICT": [
2132
+ "Use different files for --repro-record and --repro-check so checking cannot replace the baseline."
2133
+ ],
2122
2134
  "REPRO_INPUT_DRIFT": [
2123
2135
  "Compare the input manifest, vendored assets, fonts, and assets.lock.json with the recorded run."
2124
2136
  ],
@@ -2143,6 +2155,36 @@ def _enrich_repro_failures(failures):
2143
2155
  )
2144
2156
 
2145
2157
 
2158
+ def _repro_record_error(value, definition=None, location="record"):
2159
+ """Validate the record using the same shape and digest rules exposed by --schema."""
2160
+ if definition is None:
2161
+ definition = SCHEMA_DEFS["fullbleed.repro_record.v1"]
2162
+ expected_types = definition["type"]
2163
+ if isinstance(expected_types, str):
2164
+ expected_types = [expected_types]
2165
+ types = {"object": dict, "string": str, "integer": int, "null": type(None)}
2166
+ if not any(type(value) is types[name] for name in expected_types):
2167
+ return f"{location} must have type {' or '.join(expected_types)}"
2168
+ if value is None:
2169
+ return None
2170
+ if "const" in definition and value != definition["const"]:
2171
+ return f"{location} must be {definition['const']}"
2172
+ if "pattern" in definition and re.fullmatch(definition["pattern"], value) is None:
2173
+ return f"{location} must be a lowercase SHA-256 digest (64 hexadecimal characters)"
2174
+ if "minimum" in definition and value < definition["minimum"]:
2175
+ return f"{location} must be at least {definition['minimum']}"
2176
+ if isinstance(value, dict):
2177
+ for key in definition.get("required", []):
2178
+ if key not in value:
2179
+ return f"{location}.{key} is required"
2180
+ for key, child in definition.get("properties", {}).items():
2181
+ if key in value:
2182
+ error = _repro_record_error(value[key], child, f"{location}.{key}")
2183
+ if error:
2184
+ return error
2185
+ return None
2186
+
2187
+
2146
2188
  def _run_repro_record_or_check(args, manifest, html, css, output_hash, bytes_written):
2147
2189
  """Write/check reproducibility records and return associated failures."""
2148
2190
  repro_record_path = getattr(args, "repro_record", None)
@@ -2153,11 +2195,16 @@ def _run_repro_record_or_check(args, manifest, html, css, output_hash, bytes_wri
2153
2195
  current_record = _build_repro_record(args, manifest, html, css, output_hash, bytes_written)
2154
2196
  failures = []
2155
2197
 
2156
- if repro_record_path:
2157
- Path(repro_record_path).write_text(
2158
- _json_dumps(current_record, indent=2),
2159
- encoding="utf-8",
2160
- )
2198
+ if repro_record_path and repro_check_path:
2199
+ record_file, check_file = Path(repro_record_path), Path(repro_check_path)
2200
+ same_file = record_file.resolve() == check_file.resolve()
2201
+ if not same_file and record_file.exists() and check_file.exists():
2202
+ same_file = record_file.samefile(check_file)
2203
+ if same_file:
2204
+ return current_record, [{
2205
+ "code": "REPRO_RECORD_CONFLICT",
2206
+ "message": "The record output and checked baseline refer to the same file",
2207
+ }]
2161
2208
 
2162
2209
  if repro_check_path:
2163
2210
  check_file = Path(repro_check_path)
@@ -2171,46 +2218,53 @@ def _run_repro_record_or_check(args, manifest, html, css, output_hash, bytes_wri
2171
2218
  else:
2172
2219
  try:
2173
2220
  expected = json.loads(check_file.read_text(encoding="utf-8"))
2174
- except json.JSONDecodeError as exc:
2221
+ except (ValueError, OSError) as exc:
2175
2222
  failures.append(
2176
2223
  {
2177
2224
  "code": "REPRO_RECORD_INVALID",
2178
- "message": f"Failed to parse repro record JSON: {exc}",
2225
+ "message": f"Failed to read repro record JSON: {exc}",
2179
2226
  }
2180
2227
  )
2181
- expected = {}
2182
- expected_input = expected.get("input_fingerprint_sha256")
2183
- expected_output = expected.get("output_pdf_sha256")
2184
- if expected_input and expected_input != current_record["input_fingerprint_sha256"]:
2228
+ expected = None
2229
+ else:
2230
+ error = _repro_record_error(expected)
2231
+ if error:
2232
+ failures.append({"code": "REPRO_RECORD_INVALID", "message": error})
2233
+ expected = None
2234
+ if expected is not None and expected["input_fingerprint_sha256"] != current_record["input_fingerprint_sha256"]:
2185
2235
  failures.append(
2186
2236
  {
2187
2237
  "code": "REPRO_INPUT_DRIFT",
2188
2238
  "message": "Input fingerprint drift detected compared to repro record",
2189
- "expected": expected_input,
2239
+ "expected": expected["input_fingerprint_sha256"],
2190
2240
  "observed": current_record["input_fingerprint_sha256"],
2191
2241
  }
2192
2242
  )
2193
- if expected_output and expected_output != current_record["output_pdf_sha256"]:
2243
+ if expected is not None and expected["output_pdf_sha256"] != current_record["output_pdf_sha256"]:
2194
2244
  failures.append(
2195
2245
  {
2196
2246
  "code": "REPRO_HASH_MISMATCH",
2197
2247
  "message": "Output PDF hash mismatch against repro record",
2198
- "expected": expected_output,
2248
+ "expected": expected["output_pdf_sha256"],
2199
2249
  "observed": current_record["output_pdf_sha256"],
2200
2250
  }
2201
2251
  )
2202
- expected_lock = (expected.get("assets_lock") or {}).get("sha256")
2252
+ expected_lock = ((expected or {}).get("assets_lock") or {}).get("sha256")
2203
2253
  observed_lock = (current_record.get("assets_lock") or {}).get("sha256")
2204
- if expected_lock and observed_lock and expected_lock != observed_lock:
2254
+ if expected is not None and "assets_lock" in expected and expected_lock != observed_lock:
2205
2255
  failures.append(
2206
2256
  {
2207
2257
  "code": "REPRO_LOCK_MISMATCH",
2208
- "message": "assets.lock.json hash mismatch against repro record",
2258
+ "message": "assets.lock.json content or presence differs from the repro record",
2209
2259
  "expected": expected_lock,
2210
2260
  "observed": observed_lock,
2211
2261
  }
2212
2262
  )
2213
2263
 
2264
+ if repro_record_path:
2265
+ Path(repro_record_path).write_text(
2266
+ _json_dumps(current_record, indent=2), encoding="utf-8"
2267
+ )
2214
2268
  return current_record, failures
2215
2269
 
2216
2270
 
@@ -0,0 +1,53 @@
1
+ //! Standard 14 font metrics for interpreting existing PDF text positions.
2
+ //!
3
+ //! This module supplies advances and character mappings, not font programs.
4
+ //! It does not change authoring measurements or the emitted PDF.
5
+
6
+ #[path = "base14_metrics_data.rs"]
7
+ mod data;
8
+
9
+ pub(crate) struct GlyphMetric {
10
+ name: &'static str,
11
+ pub(crate) text: &'static str,
12
+ pub(crate) width: u16,
13
+ }
14
+
15
+ #[derive(Clone, Copy)]
16
+ pub(crate) struct Base14Font {
17
+ glyphs: &'static [GlyphMetric],
18
+ unicode: &'static [(u32, u16)],
19
+ builtin: &'static [u16; 256],
20
+ }
21
+
22
+ impl Base14Font {
23
+ pub(crate) fn glyph_by_name(self, name: &[u8]) -> Option<&'static GlyphMetric> {
24
+ let index = self
25
+ .glyphs
26
+ .binary_search_by(|glyph| glyph.name.as_bytes().cmp(name))
27
+ .ok()?;
28
+ self.glyphs.get(index)
29
+ }
30
+
31
+ pub(crate) fn glyph_by_unicode(self, ch: char) -> Option<&'static GlyphMetric> {
32
+ // These WinAnsi aliases use the ordinary space and hyphen glyphs.
33
+ let ch = match ch {
34
+ '\u{00a0}' => ' ',
35
+ '\u{00ad}' => '-',
36
+ other => other,
37
+ };
38
+ let index = self
39
+ .unicode
40
+ .binary_search_by_key(&(ch as u32), |pair| pair.0)
41
+ .ok()?;
42
+ self.glyphs.get(usize::from(self.unicode[index].1))
43
+ }
44
+
45
+ pub(crate) fn builtin_glyph(self, code: u8) -> Option<&'static GlyphMetric> {
46
+ let index = self.builtin[usize::from(code)].checked_sub(1)?;
47
+ self.glyphs.get(usize::from(index))
48
+ }
49
+ }
50
+
51
+ pub(crate) fn font(name: &str) -> Option<Base14Font> {
52
+ data::font(name)
53
+ }