fullbleed 2.2.2__tar.gz → 2.2.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 (184) hide show
  1. {fullbleed-2.2.2 → fullbleed-2.2.4}/Cargo.lock +1 -1
  2. {fullbleed-2.2.2 → fullbleed-2.2.4}/Cargo.toml +1 -1
  3. {fullbleed-2.2.2 → fullbleed-2.2.4}/PKG-INFO +33 -4
  4. {fullbleed-2.2.2 → fullbleed-2.2.4}/README.md +32 -3
  5. {fullbleed-2.2.2 → fullbleed-2.2.4}/pyproject.toml +1 -1
  6. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/_abi.py +22 -1
  7. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/core.py +5 -1
  8. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/flate_native.rs +182 -71
  9. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/flowable.rs +136 -3
  10. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/font.rs +39 -0
  11. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html.rs +87 -25
  12. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_dom.rs +664 -3
  13. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/lib.rs +1586 -92
  14. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf.rs +1580 -161
  15. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python/abi_exports.rs +45 -0
  16. {fullbleed-2.2.2 → fullbleed-2.2.4}/COPYRIGHT +0 -0
  17. {fullbleed-2.2.2 → fullbleed-2.2.4}/FONT_LICENSE_AUDIT.json +0 -0
  18. {fullbleed-2.2.2 → fullbleed-2.2.4}/FONT_LICENSE_AUDIT.md +0 -0
  19. {fullbleed-2.2.2 → fullbleed-2.2.4}/LICENSE +0 -0
  20. {fullbleed-2.2.2 → fullbleed-2.2.4}/LICENSING.md +0 -0
  21. {fullbleed-2.2.2 → fullbleed-2.2.4}/THIRD_PARTY_LICENSES.md +0 -0
  22. {fullbleed-2.2.2 → fullbleed-2.2.4}/build.rs +0 -0
  23. {fullbleed-2.2.2 → fullbleed-2.2.4}/build_backend/fullbleed_build_backend.py +0 -0
  24. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/Cargo.lock +0 -0
  25. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/Cargo.toml +0 -0
  26. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/LICENSE +0 -0
  27. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/README.md +0 -0
  28. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/fullbleed.audit_registry.v1.yaml +0 -0
  29. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/section508_html_registry.v1.yaml +0 -0
  30. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/wcag20aa_registry.v1.yaml +0 -0
  31. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/json.rs +0 -0
  32. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/lib.rs +0 -0
  33. {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/sha256.rs +0 -0
  34. {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.a11y.verify.v1.schema.json +0 -0
  35. {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.audit_registry.v1.yaml +0 -0
  36. {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.pmr.v1.schema.json +0 -0
  37. {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/section508_html_registry.v1.yaml +0 -0
  38. {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/wcag20aa_registry.v1.yaml +0 -0
  39. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/__init__.py +0 -0
  40. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/__main__.py +0 -0
  41. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/__init__.py +0 -0
  42. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/engine.py +0 -0
  43. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/runtime.py +0 -0
  44. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/types.py +0 -0
  45. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_prototype.py +0 -0
  46. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_section508.py +0 -0
  47. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_wcag.py +0 -0
  48. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/__init__.py +0 -0
  49. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/accessibility.py +0 -0
  50. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/__init__.py +0 -0
  51. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/_core.py +0 -0
  52. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/agency_letter.py +0 -0
  53. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/court_motion_form.py +0 -0
  54. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/declaration_form.py +0 -0
  55. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/instruction_sheet.py +0 -0
  56. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/investment_portfolio_report.py +0 -0
  57. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/marriage_record.py +0 -0
  58. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/profiles.py +0 -0
  59. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/recorded_plat.py +0 -0
  60. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/redaction_request_form.py +0 -0
  61. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/warranty_deed.py +0 -0
  62. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/primitives.py +0 -0
  63. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/style.py +0 -0
  64. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/__init__.py +0 -0
  65. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/bootstrap.min.css +0 -0
  66. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/Inter-Variable.ttf +0 -0
  67. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSans-Regular.ttf +0 -0
  68. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansMath-Regular.ttf +0 -0
  69. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansSymbols-Regular.ttf +0 -0
  70. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansSymbols2-Regular.ttf +0 -0
  71. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/icons/bootstrap-icons.svg +0 -0
  72. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/__init__.py +0 -0
  73. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/__main__.py +0 -0
  74. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/assets.py +0 -0
  75. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/cache.py +0 -0
  76. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/cli.py +0 -0
  77. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/finalize.py +0 -0
  78. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold.py +0 -0
  79. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/__init__.py +0 -0
  80. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/.gitignore +0 -0
  81. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/COMPLIANCE.md +0 -0
  82. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/SCAFFOLDING.md +0 -0
  83. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/__init__.py +0 -0
  84. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/body.py +0 -0
  85. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/fb_ui.py +0 -0
  86. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/footer.py +0 -0
  87. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/header.py +0 -0
  88. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/primitives.py +0 -0
  89. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/body.css +0 -0
  90. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/footer.css +0 -0
  91. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/header.css +0 -0
  92. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/primitives.css +0 -0
  93. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/report.py +0 -0
  94. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/styles/report.css +0 -0
  95. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/styles/tokens.css +0 -0
  96. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/README.md +0 -0
  97. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitignore +0 -0
  98. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitkeep +0 -0
  99. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/report.py +0 -0
  100. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/styles/report.css +0 -0
  101. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/vendor/fonts/Inter-Variable.ttf +0 -0
  102. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitignore +0 -0
  103. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitkeep +0 -0
  104. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/report.py +0 -0
  105. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.css +0 -0
  106. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.html +0 -0
  107. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/COMPLIANCE.md +0 -0
  108. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/README.md +0 -0
  109. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/SCAFFOLDING.md +0 -0
  110. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/brand-mark.svg +0 -0
  111. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/reference-pattern.svg +0 -0
  112. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/__init__.py +0 -0
  113. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/appendix.py +0 -0
  114. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/architecture.py +0 -0
  115. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/cover.py +0 -0
  116. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/data_pages.py +0 -0
  117. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/fb_ui.py +0 -0
  118. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/primitives.py +0 -0
  119. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/appendix.css +0 -0
  120. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/architecture.css +0 -0
  121. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/cover.css +0 -0
  122. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/data.css +0 -0
  123. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/primitives.css +0 -0
  124. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/visuals.css +0 -0
  125. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/visuals.py +0 -0
  126. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/data/reference.json +0 -0
  127. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/output/.gitignore +0 -0
  128. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/report.py +0 -0
  129. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/report.css +0 -0
  130. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/tokens.css +0 -0
  131. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitignore +0 -0
  132. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitkeep +0 -0
  133. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/report.py +0 -0
  134. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.css +0 -0
  135. {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.html +0 -0
  136. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/assets.rs +0 -0
  137. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/base64.rs +0 -0
  138. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/canvas.rs +0 -0
  139. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/css_native.rs +0 -0
  140. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/css_queries.rs +0 -0
  141. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/debug.rs +0 -0
  142. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/doc_context.rs +0 -0
  143. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/doc_template.rs +0 -0
  144. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/error.rs +0 -0
  145. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/finalize.rs +0 -0
  146. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/font_subset.rs +0 -0
  147. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/frame.rs +0 -0
  148. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/glyph_report.rs +0 -0
  149. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities.rs +0 -0
  150. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_0.rs +0 -0
  151. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_1.rs +0 -0
  152. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_2.rs +0 -0
  153. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_3.rs +0 -0
  154. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_4.rs +0 -0
  155. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_5.rs +0 -0
  156. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/image_native.rs +0 -0
  157. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/jit.rs +0 -0
  158. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/jpeg_native.rs +0 -0
  159. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/math.rs +0 -0
  160. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/metrics.rs +0 -0
  161. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/native_shape.rs +0 -0
  162. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/page_data.rs +0 -0
  163. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/page_template.rs +0 -0
  164. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/parallel.rs +0 -0
  165. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_encodings.rs +0 -0
  166. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_native.rs +0 -0
  167. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_raster.rs +0 -0
  168. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdfinspect.rs +0 -0
  169. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/perf.rs +0 -0
  170. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/plan.rs +0 -0
  171. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python.rs +0 -0
  172. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python_abi.rs +0 -0
  173. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/raster.rs +0 -0
  174. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/raster_native.rs +0 -0
  175. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt.rs +0 -0
  176. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt_cff.rs +0 -0
  177. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt_outline.rs +0 -0
  178. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/spill.rs +0 -0
  179. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/style.rs +0 -0
  180. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/svg.rs +0 -0
  181. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/text_shape.rs +0 -0
  182. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/types.rs +0 -0
  183. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/unicode_data.rs +0 -0
  184. {fullbleed-2.2.2 → fullbleed-2.2.4}/src/xml.rs +0 -0
@@ -4,7 +4,7 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "fullbleed"
7
- version = "2.2.2"
7
+ version = "2.2.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.2.2"
3
+ version = "2.2.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.2.2
3
+ Version: 2.2.4
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Operating System :: MacOS
@@ -136,6 +136,24 @@ size-changing records render 200x faster. See
136
136
  [`docs/performance-architecture.md`](docs/performance-architecture.md) for the packed vector IR,
137
137
  typed-binding, virtual-linker, and shader roadmap.
138
138
 
139
+ Fullbleed 2.2.4 adds a third compiler lane for
140
+ size-changing records: `CompiledDocument.render_pdf_reflow_bindings(...)`. It parses and recovers
141
+ the template DOM once and compiles encountered structural flow variants into guarded fixed-point
142
+ display programs. Matching records bind directly into those programs; workers execute pre-shaped
143
+ text/TJ paint slots, cached static PDF page segments, page-local Deflate, and one ordered linker.
144
+ Explicit trusted structural slots use `data-fb-bind-html="slot"`. A value that no compiled variant
145
+ can safely place runs ordinary fixed-point layout once to add another variant.
146
+
147
+ On the independent 1,000-distinct-record case study, the final ABI3 wheel's 29-sample hot
148
+ direct-file run had a 168.839 ms median: **5,923 records/s and 10,365 pages/s**, with a 12,510
149
+ pages/s best. It produced
150
+ the exact 1,750-page 500/300/150/50 reflow distribution and verified all 24,900 markers. The
151
+ throughput-tuned 6,870,320-byte PDF was deterministic across all samples. With the compact
152
+ compression setting, compiled output was also byte-for-byte identical to ordinary rendering:
153
+ 5,298,961 bytes and SHA-256 `bb3c441313a08fb00d3bd15f23a567981f3bbbd550908e3a9fe7a07ca5d7f138`.
154
+ This is a hot compiled-variant result, not a claim for previously unseen structure; the first
155
+ compile-on-demand batch measured 407.521 ms (4,294 pages/s).
156
+
139
157
  ## Install
140
158
 
141
159
  New to Python or setting up on a fresh machine? Start with `docs/install-non-technical.md`.
@@ -147,7 +165,7 @@ python -m pip install fullbleed
147
165
  From a local wheel:
148
166
 
149
167
  ```bash
150
- python -m pip install C:\path\to\fullbleed-2.2.2-cp310-abi3-win_amd64.whl
168
+ python -m pip install C:\path\to\fullbleed-2.2.4-cp310-abi3-win_amd64.whl
151
169
  ```
152
170
 
153
171
  From a source checkout with Rust installed, no Python build package is needed:
@@ -198,7 +216,9 @@ vendors `inter` into `vendor/fonts/Inter-Variable.ttf`, writes license notices
198
216
  and seeds `assets.lock.json` with pinned hashes.
199
217
  The scaffolded `report.py` also runs a component mount smoke validation before
200
218
  main render and writes `output/component_mount_validation.json` (fails fast on
201
- missing glyphs, placement overflow, or CSS miss signals parsed from debug logs).
219
+ missing glyphs, actual pagination overflow, or CSS miss signals). On current engines,
220
+ render-time pagination is authoritative for overflow; conservative JIT placement bounds
221
+ remain the compatibility fallback for older engines.
202
222
  Scaffolded components now include `components/primitives.py` with reusable
203
223
  layout/content helpers (`Stack`, `Row`, `Text`, table/list helpers, key/value rows, etc.).
204
224
  Each scaffolded project also includes `SCAFFOLDING.md`, which should be your
@@ -1005,13 +1025,22 @@ Module exports:
1005
1025
  | `render_pdf_batch(copies, deterministic_hash=None)` | `bytes` |
1006
1026
  | `render_pdf_bindings(bindings, deterministic_hash=None)` | `bytes` |
1007
1027
  | `render_pdf_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
1028
+ | `render_pdf_reflow_bindings(bindings, deterministic_hash=None)` | `bytes` |
1029
+ | `render_pdf_reflow_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
1008
1030
 
1009
1031
  The compiled lane runs parsing/layout once. `render_pdf_batch` virtualizes identical untagged page
1010
- content. The binding methods instead accept columnar `dict[str, list[str]]` data and emit distinct
1032
+ content. The fixed binding methods accept columnar `dict[str, list[str]]` data and emit distinct
1011
1033
  fixed-geometry text overlays per record while sharing static paint. They require exact slot keys,
1012
1034
  equal non-zero column lengths, page-local WinAnsi-compatible text, and an untagged profile; bound
1013
1035
  values do not trigger reflow.
1014
1036
 
1037
+ The reflow binding methods use the same exact-column contract but execute the compiled parsed-DOM
1038
+ program. `{{slot}}` values are literal text and may reshape, wrap, resize blocks, and change page
1039
+ counts. Empty elements can opt into trusted generated markup with
1040
+ `data-fb-bind-html="slot"`; those values replace the element's children. One DOM is reused per
1041
+ native worker and record output is linked in input order through a bounded queue. See
1042
+ [`docs/python-api.md`](docs/python-api.md) for the trust boundary and current limitations.
1043
+
1015
1044
  When `deterministic_hash` is set, engine writes PDF SHA-256 to the provided file path.
1016
1045
 
1017
1046
  `AssetBundle` methods:
@@ -99,6 +99,24 @@ size-changing records render 200x faster. See
99
99
  [`docs/performance-architecture.md`](docs/performance-architecture.md) for the packed vector IR,
100
100
  typed-binding, virtual-linker, and shader roadmap.
101
101
 
102
+ Fullbleed 2.2.4 adds a third compiler lane for
103
+ size-changing records: `CompiledDocument.render_pdf_reflow_bindings(...)`. It parses and recovers
104
+ the template DOM once and compiles encountered structural flow variants into guarded fixed-point
105
+ display programs. Matching records bind directly into those programs; workers execute pre-shaped
106
+ text/TJ paint slots, cached static PDF page segments, page-local Deflate, and one ordered linker.
107
+ Explicit trusted structural slots use `data-fb-bind-html="slot"`. A value that no compiled variant
108
+ can safely place runs ordinary fixed-point layout once to add another variant.
109
+
110
+ On the independent 1,000-distinct-record case study, the final ABI3 wheel's 29-sample hot
111
+ direct-file run had a 168.839 ms median: **5,923 records/s and 10,365 pages/s**, with a 12,510
112
+ pages/s best. It produced
113
+ the exact 1,750-page 500/300/150/50 reflow distribution and verified all 24,900 markers. The
114
+ throughput-tuned 6,870,320-byte PDF was deterministic across all samples. With the compact
115
+ compression setting, compiled output was also byte-for-byte identical to ordinary rendering:
116
+ 5,298,961 bytes and SHA-256 `bb3c441313a08fb00d3bd15f23a567981f3bbbd550908e3a9fe7a07ca5d7f138`.
117
+ This is a hot compiled-variant result, not a claim for previously unseen structure; the first
118
+ compile-on-demand batch measured 407.521 ms (4,294 pages/s).
119
+
102
120
  ## Install
103
121
 
104
122
  New to Python or setting up on a fresh machine? Start with `docs/install-non-technical.md`.
@@ -110,7 +128,7 @@ python -m pip install fullbleed
110
128
  From a local wheel:
111
129
 
112
130
  ```bash
113
- python -m pip install C:\path\to\fullbleed-2.2.2-cp310-abi3-win_amd64.whl
131
+ python -m pip install C:\path\to\fullbleed-2.2.4-cp310-abi3-win_amd64.whl
114
132
  ```
115
133
 
116
134
  From a source checkout with Rust installed, no Python build package is needed:
@@ -161,7 +179,9 @@ vendors `inter` into `vendor/fonts/Inter-Variable.ttf`, writes license notices
161
179
  and seeds `assets.lock.json` with pinned hashes.
162
180
  The scaffolded `report.py` also runs a component mount smoke validation before
163
181
  main render and writes `output/component_mount_validation.json` (fails fast on
164
- missing glyphs, placement overflow, or CSS miss signals parsed from debug logs).
182
+ missing glyphs, actual pagination overflow, or CSS miss signals). On current engines,
183
+ render-time pagination is authoritative for overflow; conservative JIT placement bounds
184
+ remain the compatibility fallback for older engines.
165
185
  Scaffolded components now include `components/primitives.py` with reusable
166
186
  layout/content helpers (`Stack`, `Row`, `Text`, table/list helpers, key/value rows, etc.).
167
187
  Each scaffolded project also includes `SCAFFOLDING.md`, which should be your
@@ -968,13 +988,22 @@ Module exports:
968
988
  | `render_pdf_batch(copies, deterministic_hash=None)` | `bytes` |
969
989
  | `render_pdf_bindings(bindings, deterministic_hash=None)` | `bytes` |
970
990
  | `render_pdf_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
991
+ | `render_pdf_reflow_bindings(bindings, deterministic_hash=None)` | `bytes` |
992
+ | `render_pdf_reflow_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
971
993
 
972
994
  The compiled lane runs parsing/layout once. `render_pdf_batch` virtualizes identical untagged page
973
- content. The binding methods instead accept columnar `dict[str, list[str]]` data and emit distinct
995
+ content. The fixed binding methods accept columnar `dict[str, list[str]]` data and emit distinct
974
996
  fixed-geometry text overlays per record while sharing static paint. They require exact slot keys,
975
997
  equal non-zero column lengths, page-local WinAnsi-compatible text, and an untagged profile; bound
976
998
  values do not trigger reflow.
977
999
 
1000
+ The reflow binding methods use the same exact-column contract but execute the compiled parsed-DOM
1001
+ program. `{{slot}}` values are literal text and may reshape, wrap, resize blocks, and change page
1002
+ counts. Empty elements can opt into trusted generated markup with
1003
+ `data-fb-bind-html="slot"`; those values replace the element's children. One DOM is reused per
1004
+ native worker and record output is linked in input order through a bounded queue. See
1005
+ [`docs/python-api.md`](docs/python-api.md) for the trust boundary and current limitations.
1006
+
978
1007
  When `deterministic_hash` is set, engine writes PDF SHA-256 to the provided file path.
979
1008
 
980
1009
  `AssetBundle` methods:
@@ -5,7 +5,7 @@ backend-path = ["build_backend"]
5
5
 
6
6
  [project]
7
7
  name = "fullbleed"
8
- version = "2.2.2"
8
+ version = "2.2.4"
9
9
  description = "Deterministic static HTML/CSS-to-PDF renderer built in Rust"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -161,7 +161,7 @@ class WatermarkSpec(metaclass=_NativeFacadeType):
161
161
 
162
162
 
163
163
  class CompiledDocument(metaclass=_NativeFacadeType):
164
- """Immutable fixed-point display document produced by ``PdfEngine.compile_pdf``."""
164
+ """Compiled fixed-paint and content-reflow programs from ``PdfEngine.compile_pdf``."""
165
165
 
166
166
  __slots__ = ("_handle",)
167
167
 
@@ -219,6 +219,27 @@ class CompiledDocument(metaclass=_NativeFacadeType):
219
219
  deterministic_hash,
220
220
  )
221
221
 
222
+ def render_pdf_reflow_bindings(self, /, bindings, deterministic_hash=None):
223
+ """Render columnar text bindings with content-driven layout and pagination."""
224
+ return _call(
225
+ self._handle,
226
+ "CompiledDocument.render_pdf_reflow_bindings",
227
+ bindings,
228
+ deterministic_hash,
229
+ )
230
+
231
+ def render_pdf_reflow_bindings_to_file(
232
+ self, /, bindings, path, deterministic_hash=None
233
+ ):
234
+ """Stream a compiled content-reflow batch directly to ``path``."""
235
+ return _call(
236
+ self._handle,
237
+ "CompiledDocument.render_pdf_reflow_bindings_to_file",
238
+ bindings,
239
+ path,
240
+ deterministic_hash,
241
+ )
242
+
222
243
 
223
244
  class PdfEngine(metaclass=_NativeFacadeType):
224
245
  __slots__ = ("_handle",)
@@ -885,7 +885,11 @@ def validate_component_mount(
885
885
  asset_unresolved_count = int(asset_summary.get("unresolved_count") or 0)
886
886
  asset_unsupported_count = int(asset_summary.get("unsupported_count") or 0)
887
887
  asset_reference_count = int(asset_summary.get("image_reference_count") or 0)
888
- if pagination_overflow_count > overflow_count:
888
+ # The native pagination trace describes layout overflow directly. Legacy
889
+ # JIT placement bounds are a fallback for older engines and can be wider
890
+ # than the painted glyph outlines because glyph runs use conservative
891
+ # diagnostic padding.
892
+ if pagination_trace_available:
889
893
  overflow_count = pagination_overflow_count
890
894
  overflow_samples = pagination_overflow_samples
891
895
  if pagination_trace_available:
@@ -1,11 +1,17 @@
1
+ use std::cell::RefCell;
2
+ use std::sync::OnceLock;
3
+
1
4
  const ADLER_BASE: u32 = 65_521;
2
5
  const DEFAULT_ADLER_CHUNK: usize = 1 << 20;
6
+ const ADLER_NMAX: usize = 5_552;
3
7
 
4
8
  const LZ77_CHUNK_BYTES: usize = 128 * 1024;
5
9
  const MIN_MATCH: usize = 3;
6
10
  const MAX_MATCH: usize = 258;
7
11
  const MAX_DISTANCE: usize = 32 * 1024;
8
12
  const MAX_CHAIN_STEPS: usize = 64;
13
+ const DEFAULT_COMPILED_FLOW_CHAIN_STEPS: usize = 4;
14
+ const COMPILED_FLOW_THROUGHPUT_MIN_BYTES: usize = 4 * 1024;
9
15
  const HASH_BITS: usize = 15;
10
16
  const HASH_SIZE: usize = 1 << HASH_BITS;
11
17
 
@@ -41,19 +47,19 @@ impl AdlerPartial {
41
47
  }
42
48
 
43
49
  fn for_bytes(data: &[u8]) -> Self {
44
- let mut a: u32 = 1;
45
- let mut b: u32 = 0;
46
- for &byte in data {
47
- a += byte as u32;
48
- if a >= ADLER_BASE {
49
- a -= ADLER_BASE;
50
+ let mut a = 1_u64;
51
+ let mut b = 0_u64;
52
+ for chunk in data.chunks(ADLER_NMAX) {
53
+ for &byte in chunk {
54
+ a += u64::from(byte);
55
+ b += a;
50
56
  }
51
- b += a;
52
- b %= ADLER_BASE;
57
+ a %= u64::from(ADLER_BASE);
58
+ b %= u64::from(ADLER_BASE);
53
59
  }
54
60
  Self {
55
- a,
56
- b,
61
+ a: a as u32,
62
+ b: b as u32,
57
63
  len: data.len(),
58
64
  }
59
65
  }
@@ -166,87 +172,155 @@ fn hash3(data: &[u8], i: usize) -> usize {
166
172
 
167
173
  fn match_len(data: &[u8], a: usize, b: usize, max_len: usize) -> usize {
168
174
  let mut l = 0usize;
175
+ while l + 8 <= max_len {
176
+ let left = u64::from_le_bytes(
177
+ data[a + l..a + l + 8]
178
+ .try_into()
179
+ .expect("eight-byte match window"),
180
+ );
181
+ let right = u64::from_le_bytes(
182
+ data[b + l..b + l + 8]
183
+ .try_into()
184
+ .expect("eight-byte match window"),
185
+ );
186
+ let difference = left ^ right;
187
+ if difference != 0 {
188
+ return l + (difference.trailing_zeros() as usize >> 3);
189
+ }
190
+ l += 8;
191
+ }
169
192
  while l < max_len && data[a + l] == data[b + l] {
170
193
  l += 1;
171
194
  }
172
195
  l
173
196
  }
174
197
 
175
- fn plan_lz77_chunk(data: &[u8]) -> ChunkPlan {
176
- let n = data.len();
177
- if n == 0 {
178
- return ChunkPlan { tokens: Vec::new() };
198
+ struct Lz77Workspace {
199
+ head: Vec<i32>,
200
+ head_epoch: Vec<u32>,
201
+ prev: Vec<i32>,
202
+ epoch: u32,
203
+ }
204
+
205
+ impl Lz77Workspace {
206
+ fn new() -> Self {
207
+ Self {
208
+ head: vec![-1; HASH_SIZE],
209
+ head_epoch: vec![0; HASH_SIZE],
210
+ prev: Vec::new(),
211
+ epoch: 0,
212
+ }
179
213
  }
180
214
 
181
- let mut head = vec![-1_i32; HASH_SIZE];
182
- let mut prev = vec![-1_i32; n];
183
- let mut tokens = Vec::with_capacity(n / 2);
215
+ fn begin_page(&mut self, len: usize) -> u32 {
216
+ self.epoch = self.epoch.wrapping_add(1);
217
+ if self.epoch == 0 {
218
+ self.head_epoch.fill(0);
219
+ self.epoch = 1;
220
+ }
221
+ if self.prev.len() < len {
222
+ self.prev.resize(len, -1);
223
+ }
224
+ self.epoch
225
+ }
184
226
 
185
- let mut i = 0usize;
186
- while i < n {
187
- if i + MIN_MATCH > n {
188
- tokens.push(Token::Literal(data[i]));
189
- i += 1;
190
- continue;
227
+ fn plan(&mut self, data: &[u8], max_chain_steps: usize) -> ChunkPlan {
228
+ let n = data.len();
229
+ if n == 0 {
230
+ return ChunkPlan { tokens: Vec::new() };
191
231
  }
232
+ let epoch = self.begin_page(n);
233
+ let mut tokens = Vec::with_capacity(n / 2);
234
+
235
+ let mut i = 0usize;
236
+ while i < n {
237
+ if i + MIN_MATCH > n {
238
+ tokens.push(Token::Literal(data[i]));
239
+ i += 1;
240
+ continue;
241
+ }
192
242
 
193
- let h = hash3(data, i);
194
- let mut cand = head[h];
195
- prev[i] = cand;
196
- head[h] = i as i32;
243
+ let h = hash3(data, i);
244
+ let mut cand = if self.head_epoch[h] == epoch {
245
+ self.head[h]
246
+ } else {
247
+ -1
248
+ };
249
+ self.prev[i] = cand;
250
+ self.head[h] = i as i32;
251
+ self.head_epoch[h] = epoch;
252
+
253
+ let mut best_len = 0usize;
254
+ let mut best_dist = 0usize;
255
+ let mut steps = 0usize;
256
+
257
+ while cand >= 0 && steps < max_chain_steps {
258
+ let c = cand as usize;
259
+ let dist = i - c;
260
+ if dist > MAX_DISTANCE {
261
+ break;
262
+ }
197
263
 
198
- let mut best_len = 0usize;
199
- let mut best_dist = 0usize;
200
- let mut steps = 0usize;
264
+ if data[c] == data[i] && data[c + 1] == data[i + 1] && data[c + 2] == data[i + 2] {
265
+ let max_len = MAX_MATCH.min(n - i);
266
+ let len = match_len(data, c, i, max_len);
267
+ if len >= MIN_MATCH && (len > best_len || (len == best_len && dist < best_dist))
268
+ {
269
+ best_len = len;
270
+ best_dist = dist;
271
+ if best_len == MAX_MATCH {
272
+ break;
273
+ }
274
+ }
275
+ }
201
276
 
202
- while cand >= 0 && steps < MAX_CHAIN_STEPS {
203
- let c = cand as usize;
204
- let dist = i - c;
205
- if dist > MAX_DISTANCE {
206
- break;
277
+ cand = self.prev[c];
278
+ steps += 1;
207
279
  }
208
280
 
209
- if data[c] == data[i] && data[c + 1] == data[i + 1] && data[c + 2] == data[i + 2] {
210
- let max_len = MAX_MATCH.min(n - i);
211
- let len = match_len(data, c, i, max_len);
212
- if len >= MIN_MATCH && (len > best_len || (len == best_len && dist < best_dist)) {
213
- best_len = len;
214
- best_dist = dist;
215
- if best_len == MAX_MATCH {
216
- break;
281
+ if best_len >= MIN_MATCH {
282
+ tokens.push(Token::Match {
283
+ len: best_len as u16,
284
+ dist: best_dist as u16,
285
+ });
286
+
287
+ let end = (i + best_len).min(n);
288
+ let mut j = i + 1;
289
+ while j < end {
290
+ if j + MIN_MATCH <= n {
291
+ let hj = hash3(data, j);
292
+ self.prev[j] = if self.head_epoch[hj] == epoch {
293
+ self.head[hj]
294
+ } else {
295
+ -1
296
+ };
297
+ self.head[hj] = j as i32;
298
+ self.head_epoch[hj] = epoch;
217
299
  }
300
+ j += 1;
218
301
  }
219
- }
220
302
 
221
- cand = prev[c];
222
- steps += 1;
223
- }
224
-
225
- if best_len >= MIN_MATCH {
226
- tokens.push(Token::Match {
227
- len: best_len as u16,
228
- dist: best_dist as u16,
229
- });
230
-
231
- let end = (i + best_len).min(n);
232
- let mut j = i + 1;
233
- while j < end {
234
- if j + MIN_MATCH <= n {
235
- let hj = hash3(data, j);
236
- prev[j] = head[hj];
237
- head[hj] = j as i32;
238
- }
239
- j += 1;
303
+ i += best_len;
304
+ } else {
305
+ tokens.push(Token::Literal(data[i]));
306
+ i += 1;
240
307
  }
241
-
242
- i += best_len;
243
- } else {
244
- tokens.push(Token::Literal(data[i]));
245
- i += 1;
246
308
  }
309
+
310
+ ChunkPlan { tokens }
247
311
  }
312
+ }
248
313
 
249
- ChunkPlan { tokens }
314
+ thread_local! {
315
+ static LZ77_WORKSPACE: RefCell<Lz77Workspace> = RefCell::new(Lz77Workspace::new());
316
+ }
317
+
318
+ fn plan_lz77_chunk_with_chain(data: &[u8], max_chain_steps: usize) -> ChunkPlan {
319
+ LZ77_WORKSPACE.with(|workspace| {
320
+ workspace
321
+ .borrow_mut()
322
+ .plan(data, max_chain_steps.clamp(1, MAX_CHAIN_STEPS))
323
+ })
250
324
  }
251
325
 
252
326
  fn reverse_bits(mut value: u16, len: u8) -> u16 {
@@ -348,10 +422,32 @@ fn estimate_deflate_capacity(input_len: usize) -> usize {
348
422
  }
349
423
 
350
424
  pub(crate) fn zlib_deflate_parallel(data: &[u8]) -> Vec<u8> {
425
+ zlib_deflate_with_chain(data, MAX_CHAIN_STEPS)
426
+ }
427
+
428
+ pub(crate) fn zlib_deflate_compiled_flow(data: &[u8]) -> Vec<u8> {
429
+ static COMPILED_FLOW_CHAIN_STEPS: OnceLock<usize> = OnceLock::new();
430
+ let steps = *COMPILED_FLOW_CHAIN_STEPS.get_or_init(|| {
431
+ std::env::var("FULLBLEED_COMPILED_FLOW_DEFLATE_CHAIN")
432
+ .ok()
433
+ .and_then(|value| value.parse::<usize>().ok())
434
+ .unwrap_or(DEFAULT_COMPILED_FLOW_CHAIN_STEPS)
435
+ .clamp(1, MAX_CHAIN_STEPS)
436
+ });
437
+ let steps = if data.len() < COMPILED_FLOW_THROUGHPUT_MIN_BYTES {
438
+ MAX_CHAIN_STEPS
439
+ } else {
440
+ steps
441
+ };
442
+ zlib_deflate_with_chain(data, steps)
443
+ }
444
+
445
+ fn zlib_deflate_with_chain(data: &[u8], max_chain_steps: usize) -> Vec<u8> {
351
446
  let ranges = chunk_ranges(data.len(), LZ77_CHUNK_BYTES);
352
447
 
353
- let plans =
354
- crate::parallel::map_ordered(&ranges, |(start, end)| plan_lz77_chunk(&data[*start..*end]));
448
+ let plans = crate::parallel::map_ordered(&ranges, |(start, end)| {
449
+ plan_lz77_chunk_with_chain(&data[*start..*end], max_chain_steps)
450
+ });
355
451
 
356
452
  let adler = adler32_parallel(data, DEFAULT_ADLER_CHUNK);
357
453
 
@@ -812,6 +908,21 @@ mod tests {
812
908
  );
813
909
  }
814
910
 
911
+ #[test]
912
+ fn zlib_compiled_flow_chain_levels_roundtrip_deterministically() {
913
+ let src = b"BT /F1 10 Tf 72 720 Td [(REFLOW-000001)] TJ ET\n".repeat(2_000);
914
+ for steps in [1, 4, 8, 16, 32, 64] {
915
+ let a = zlib_deflate_with_chain(&src, steps);
916
+ let b = zlib_deflate_with_chain(&src, steps);
917
+ assert_eq!(a, b, "deterministic at {steps} chain steps");
918
+ assert_eq!(
919
+ zlib_inflate(&a, src.len()).expect("native inflate"),
920
+ src,
921
+ "roundtrip at {steps} chain steps",
922
+ );
923
+ }
924
+ }
925
+
815
926
  #[test]
816
927
  fn zlib_lz77_roundtrip_empty() {
817
928
  let src: Vec<u8> = Vec::new();