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.
- {fullbleed-2.2.2 → fullbleed-2.2.4}/Cargo.lock +1 -1
- {fullbleed-2.2.2 → fullbleed-2.2.4}/Cargo.toml +1 -1
- {fullbleed-2.2.2 → fullbleed-2.2.4}/PKG-INFO +33 -4
- {fullbleed-2.2.2 → fullbleed-2.2.4}/README.md +32 -3
- {fullbleed-2.2.2 → fullbleed-2.2.4}/pyproject.toml +1 -1
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/_abi.py +22 -1
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/core.py +5 -1
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/flate_native.rs +182 -71
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/flowable.rs +136 -3
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/font.rs +39 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html.rs +87 -25
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_dom.rs +664 -3
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/lib.rs +1586 -92
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf.rs +1580 -161
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python/abi_exports.rs +45 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/COPYRIGHT +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/FONT_LICENSE_AUDIT.json +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/FONT_LICENSE_AUDIT.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/LICENSE +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/LICENSING.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/THIRD_PARTY_LICENSES.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/build.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/build_backend/fullbleed_build_backend.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/Cargo.lock +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/Cargo.toml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/LICENSE +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/README.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/fullbleed.audit_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/section508_html_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/specs/wcag20aa_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/json.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/lib.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/crates/fullbleed_audit_contract/src/sha256.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.a11y.verify.v1.schema.json +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.audit_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/fullbleed.pmr.v1.schema.json +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/section508_html_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/docs/specs/wcag20aa_registry.v1.yaml +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/__main__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/engine.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/runtime.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/accessibility/types.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_prototype.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_section508.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/audit_wcag.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/accessibility.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/_core.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/agency_letter.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/court_motion_form.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/declaration_form.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/instruction_sheet.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/investment_portfolio_report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/marriage_record.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/profiles.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/recorded_plat.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/redaction_request_form.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/cav/warranty_deed.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/primitives.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed/ui/style.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/bootstrap.min.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/Inter-Variable.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSans-Regular.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansMath-Regular.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansSymbols-Regular.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/fonts/NotoSansSymbols2-Regular.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_assets/icons/bootstrap-icons.svg +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/__main__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/assets.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/cache.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/cli.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/finalize.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/.gitignore +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/COMPLIANCE.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/SCAFFOLDING.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/body.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/fb_ui.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/footer.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/header.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/primitives.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/body.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/footer.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/header.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/components/styles/primitives.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/styles/report.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/init/styles/tokens.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/README.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitignore +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitkeep +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/styles/report.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/accessible/vendor/fonts/Inter-Variable.ttf +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitignore +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitkeep +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.html +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/COMPLIANCE.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/README.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/SCAFFOLDING.md +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/brand-mark.svg +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/assets/reference-pattern.svg +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/__init__.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/appendix.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/architecture.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/cover.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/data_pages.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/fb_ui.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/primitives.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/appendix.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/architecture.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/cover.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/data.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/primitives.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/visuals.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/components/visuals.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/data/reference.json +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/output/.gitignore +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/report.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/reference/styles/tokens.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitignore +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitkeep +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/report.py +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.css +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.html +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/assets.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/base64.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/canvas.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/css_native.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/css_queries.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/debug.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/doc_context.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/doc_template.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/error.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/finalize.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/font_subset.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/frame.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/glyph_report.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_0.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_1.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_2.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_3.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_4.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/html_entities_5.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/image_native.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/jit.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/jpeg_native.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/math.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/metrics.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/native_shape.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/page_data.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/page_template.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/parallel.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_encodings.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_native.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdf_raster.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/pdfinspect.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/perf.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/plan.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/python_abi.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/raster.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/raster_native.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt_cff.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/sfnt_outline.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/spill.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/style.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/svg.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/text_shape.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/types.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/unicode_data.rs +0 -0
- {fullbleed-2.2.2 → fullbleed-2.2.4}/src/xml.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fullbleed
|
|
3
|
-
Version: 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.
|
|
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,
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
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:
|
|
@@ -161,7 +161,7 @@ class WatermarkSpec(metaclass=_NativeFacadeType):
|
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
class CompiledDocument(metaclass=_NativeFacadeType):
|
|
164
|
-
"""
|
|
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
|
-
|
|
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
|
|
45
|
-
let mut b
|
|
46
|
-
for
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
if
|
|
188
|
-
tokens
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|