fullbleed 2.2.3__tar.gz → 2.2.5__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.3 → fullbleed-2.2.5}/Cargo.lock +1 -1
- {fullbleed-2.2.3 → fullbleed-2.2.5}/Cargo.toml +1 -1
- {fullbleed-2.2.3 → fullbleed-2.2.5}/PKG-INFO +38 -4
- {fullbleed-2.2.3 → fullbleed-2.2.5}/README.md +37 -3
- {fullbleed-2.2.3 → fullbleed-2.2.5}/pyproject.toml +1 -1
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/_abi.py +57 -3
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/cli.py +10 -2
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/flate_native.rs +205 -71
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/flowable.rs +148 -3
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/font.rs +39 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html.rs +87 -25
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_dom.rs +664 -3
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/lib.rs +1756 -90
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/pdf.rs +1603 -161
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/python/abi_exports.rs +75 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/python.rs +5 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/style.rs +19 -1
- {fullbleed-2.2.3 → fullbleed-2.2.5}/COPYRIGHT +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/FONT_LICENSE_AUDIT.json +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/FONT_LICENSE_AUDIT.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/LICENSE +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/LICENSING.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/THIRD_PARTY_LICENSES.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/build.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/build_backend/fullbleed_build_backend.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/Cargo.lock +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/Cargo.toml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/LICENSE +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/README.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/specs/fullbleed.audit_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/specs/section508_html_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/specs/wcag20aa_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/src/json.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/src/lib.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/crates/fullbleed_audit_contract/src/sha256.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/docs/specs/fullbleed.a11y.verify.v1.schema.json +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/docs/specs/fullbleed.audit_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/docs/specs/fullbleed.pmr.v1.schema.json +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/docs/specs/section508_html_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/docs/specs/wcag20aa_registry.v1.yaml +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/__main__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/accessibility/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/accessibility/engine.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/accessibility/runtime.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/accessibility/types.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/audit_prototype.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/audit_section508.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/audit_wcag.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/accessibility.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/_core.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/agency_letter.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/court_motion_form.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/declaration_form.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/instruction_sheet.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/investment_portfolio_report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/marriage_record.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/profiles.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/recorded_plat.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/redaction_request_form.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/cav/warranty_deed.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/core.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/primitives.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed/ui/style.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/bootstrap.min.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/fonts/Inter-Variable.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/fonts/NotoSans-Regular.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/fonts/NotoSansMath-Regular.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/fonts/NotoSansSymbols-Regular.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/fonts/NotoSansSymbols2-Regular.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_assets/icons/bootstrap-icons.svg +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/__main__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/assets.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/cache.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/finalize.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/.gitignore +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/COMPLIANCE.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/SCAFFOLDING.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/body.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/fb_ui.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/footer.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/header.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/primitives.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/styles/body.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/styles/footer.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/styles/header.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/components/styles/primitives.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/styles/report.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/init/styles/tokens.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/README.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitignore +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/output/.gitkeep +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/styles/report.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/accessible/vendor/fonts/Inter-Variable.ttf +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitignore +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/invoice/output/.gitkeep +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/invoice/report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/invoice/templates/invoice.html +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/COMPLIANCE.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/README.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/SCAFFOLDING.md +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/assets/brand-mark.svg +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/assets/reference-pattern.svg +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/__init__.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/appendix.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/architecture.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/cover.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/data_pages.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/fb_ui.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/primitives.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/appendix.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/architecture.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/cover.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/data.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/primitives.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/styles/visuals.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/components/visuals.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/data/reference.json +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/output/.gitignore +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/styles/report.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/reference/styles/tokens.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitignore +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/statement/output/.gitkeep +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/statement/report.py +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.css +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/python/fullbleed_cli/scaffold_templates/new/statement/templates/statement.html +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/assets.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/base64.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/canvas.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/css_native.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/css_queries.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/debug.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/doc_context.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/doc_template.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/error.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/finalize.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/font_subset.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/frame.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/glyph_report.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_0.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_1.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_2.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_3.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_4.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/html_entities_5.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/image_native.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/jit.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/jpeg_native.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/math.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/metrics.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/native_shape.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/page_data.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/page_template.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/parallel.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/pdf_encodings.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/pdf_native.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/pdf_raster.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/pdfinspect.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/perf.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/plan.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/python_abi.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/raster.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/raster_native.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/sfnt.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/sfnt_cff.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/sfnt_outline.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/spill.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/svg.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/text_shape.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/types.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/src/unicode_data.rs +0 -0
- {fullbleed-2.2.3 → fullbleed-2.2.5}/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.5
|
|
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,26 @@ 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.5 hardens the third compiler lane introduced in 2.2.4 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 29-sample hot direct-file median was
|
|
148
|
+
216.160 ms: **4,626 records/s and 8,096 pages/s**. The 168.995 ms best sample reached **5,917
|
|
149
|
+
records/s and 10,355 pages/s**. 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
|
+
variant-discovery render measured 0.509 s (3,437 pages/s), and the fully cold job including setup,
|
|
156
|
+
bindings, compilation, discovery, and write measured 0.588 s (2,975 pages/s). The independently
|
|
157
|
+
observed hot-median speedup over the ordinary path was 43.8x.
|
|
158
|
+
|
|
139
159
|
## Install
|
|
140
160
|
|
|
141
161
|
New to Python or setting up on a fresh machine? Start with `docs/install-non-technical.md`.
|
|
@@ -147,7 +167,7 @@ python -m pip install fullbleed
|
|
|
147
167
|
From a local wheel:
|
|
148
168
|
|
|
149
169
|
```bash
|
|
150
|
-
python -m pip install C:\path\to\fullbleed-2.2.
|
|
170
|
+
python -m pip install C:\path\to\fullbleed-2.2.5-cp310-abi3-win_amd64.whl
|
|
151
171
|
```
|
|
152
172
|
|
|
153
173
|
From a source checkout with Rust installed, no Python build package is needed:
|
|
@@ -600,7 +620,8 @@ Machine discovery:
|
|
|
600
620
|
fullbleed capabilities --json
|
|
601
621
|
```
|
|
602
622
|
|
|
603
|
-
Inspect the `svg` object in `fullbleed.capabilities.v1` for SVG support metadata.
|
|
623
|
+
Inspect the `svg` object in `fullbleed.capabilities.v1` for SVG support metadata. The `engine`
|
|
624
|
+
object also reports compiled-document/reflow availability and supported per-call compression modes.
|
|
604
625
|
It reports the compiled `svg_raster` build feature plus a feature matrix for
|
|
605
626
|
native-vector SVG, raster-fallback-required SVG, and unsupported/known-loss SVG
|
|
606
627
|
features.
|
|
@@ -959,6 +980,7 @@ Module exports:
|
|
|
959
980
|
|
|
960
981
|
- `PdfEngine`
|
|
961
982
|
- `CompiledDocument`
|
|
983
|
+
- `CompiledFlowCompression`
|
|
962
984
|
- `AssetBundle`
|
|
963
985
|
- `Asset`
|
|
964
986
|
- `AssetKind`
|
|
@@ -1007,13 +1029,25 @@ Module exports:
|
|
|
1007
1029
|
| `render_pdf_batch(copies, deterministic_hash=None)` | `bytes` |
|
|
1008
1030
|
| `render_pdf_bindings(bindings, deterministic_hash=None)` | `bytes` |
|
|
1009
1031
|
| `render_pdf_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
|
|
1032
|
+
| `render_pdf_reflow_bindings(bindings, deterministic_hash=None, *, compression="throughput")` | `bytes` |
|
|
1033
|
+
| `render_pdf_reflow_bindings_to_file(bindings, path, deterministic_hash=None, *, compression="throughput")` | `int` |
|
|
1010
1034
|
|
|
1011
1035
|
The compiled lane runs parsing/layout once. `render_pdf_batch` virtualizes identical untagged page
|
|
1012
|
-
content. The binding methods
|
|
1036
|
+
content. The fixed binding methods accept columnar `dict[str, list[str]]` data and emit distinct
|
|
1013
1037
|
fixed-geometry text overlays per record while sharing static paint. They require exact slot keys,
|
|
1014
1038
|
equal non-zero column lengths, page-local WinAnsi-compatible text, and an untagged profile; bound
|
|
1015
1039
|
values do not trigger reflow.
|
|
1016
1040
|
|
|
1041
|
+
The reflow binding methods use the same exact-column contract but execute the compiled parsed-DOM
|
|
1042
|
+
program. `{{slot}}` values are literal text and may reshape, wrap, resize blocks, and change page
|
|
1043
|
+
counts. Empty elements can opt into trusted generated markup with
|
|
1044
|
+
`data-fb-bind-html="slot"`; those values replace the element's children. One DOM is reused per
|
|
1045
|
+
native worker and record output is linked in input order through a bounded queue. Structural slot
|
|
1046
|
+
values are parsed as trusted markup: sanitize untrusted input or use scalar `{{slot}}` bindings,
|
|
1047
|
+
which remain literal text. Choose `CompiledFlowCompression.Throughput` (default) or
|
|
1048
|
+
`CompiledFlowCompression.Compact` per render; modes can be mixed safely in one process. See
|
|
1049
|
+
[`docs/python-api.md`](docs/python-api.md) for the trust boundary and current limitations.
|
|
1050
|
+
|
|
1017
1051
|
When `deterministic_hash` is set, engine writes PDF SHA-256 to the provided file path.
|
|
1018
1052
|
|
|
1019
1053
|
`AssetBundle` methods:
|
|
@@ -99,6 +99,26 @@ 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.5 hardens the third compiler lane introduced in 2.2.4 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 29-sample hot direct-file median was
|
|
111
|
+
216.160 ms: **4,626 records/s and 8,096 pages/s**. The 168.995 ms best sample reached **5,917
|
|
112
|
+
records/s and 10,355 pages/s**. 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
|
+
variant-discovery render measured 0.509 s (3,437 pages/s), and the fully cold job including setup,
|
|
119
|
+
bindings, compilation, discovery, and write measured 0.588 s (2,975 pages/s). The independently
|
|
120
|
+
observed hot-median speedup over the ordinary path was 43.8x.
|
|
121
|
+
|
|
102
122
|
## Install
|
|
103
123
|
|
|
104
124
|
New to Python or setting up on a fresh machine? Start with `docs/install-non-technical.md`.
|
|
@@ -110,7 +130,7 @@ python -m pip install fullbleed
|
|
|
110
130
|
From a local wheel:
|
|
111
131
|
|
|
112
132
|
```bash
|
|
113
|
-
python -m pip install C:\path\to\fullbleed-2.2.
|
|
133
|
+
python -m pip install C:\path\to\fullbleed-2.2.5-cp310-abi3-win_amd64.whl
|
|
114
134
|
```
|
|
115
135
|
|
|
116
136
|
From a source checkout with Rust installed, no Python build package is needed:
|
|
@@ -563,7 +583,8 @@ Machine discovery:
|
|
|
563
583
|
fullbleed capabilities --json
|
|
564
584
|
```
|
|
565
585
|
|
|
566
|
-
Inspect the `svg` object in `fullbleed.capabilities.v1` for SVG support metadata.
|
|
586
|
+
Inspect the `svg` object in `fullbleed.capabilities.v1` for SVG support metadata. The `engine`
|
|
587
|
+
object also reports compiled-document/reflow availability and supported per-call compression modes.
|
|
567
588
|
It reports the compiled `svg_raster` build feature plus a feature matrix for
|
|
568
589
|
native-vector SVG, raster-fallback-required SVG, and unsupported/known-loss SVG
|
|
569
590
|
features.
|
|
@@ -922,6 +943,7 @@ Module exports:
|
|
|
922
943
|
|
|
923
944
|
- `PdfEngine`
|
|
924
945
|
- `CompiledDocument`
|
|
946
|
+
- `CompiledFlowCompression`
|
|
925
947
|
- `AssetBundle`
|
|
926
948
|
- `Asset`
|
|
927
949
|
- `AssetKind`
|
|
@@ -970,13 +992,25 @@ Module exports:
|
|
|
970
992
|
| `render_pdf_batch(copies, deterministic_hash=None)` | `bytes` |
|
|
971
993
|
| `render_pdf_bindings(bindings, deterministic_hash=None)` | `bytes` |
|
|
972
994
|
| `render_pdf_bindings_to_file(bindings, path, deterministic_hash=None)` | `int` |
|
|
995
|
+
| `render_pdf_reflow_bindings(bindings, deterministic_hash=None, *, compression="throughput")` | `bytes` |
|
|
996
|
+
| `render_pdf_reflow_bindings_to_file(bindings, path, deterministic_hash=None, *, compression="throughput")` | `int` |
|
|
973
997
|
|
|
974
998
|
The compiled lane runs parsing/layout once. `render_pdf_batch` virtualizes identical untagged page
|
|
975
|
-
content. The binding methods
|
|
999
|
+
content. The fixed binding methods accept columnar `dict[str, list[str]]` data and emit distinct
|
|
976
1000
|
fixed-geometry text overlays per record while sharing static paint. They require exact slot keys,
|
|
977
1001
|
equal non-zero column lengths, page-local WinAnsi-compatible text, and an untagged profile; bound
|
|
978
1002
|
values do not trigger reflow.
|
|
979
1003
|
|
|
1004
|
+
The reflow binding methods use the same exact-column contract but execute the compiled parsed-DOM
|
|
1005
|
+
program. `{{slot}}` values are literal text and may reshape, wrap, resize blocks, and change page
|
|
1006
|
+
counts. Empty elements can opt into trusted generated markup with
|
|
1007
|
+
`data-fb-bind-html="slot"`; those values replace the element's children. One DOM is reused per
|
|
1008
|
+
native worker and record output is linked in input order through a bounded queue. Structural slot
|
|
1009
|
+
values are parsed as trusted markup: sanitize untrusted input or use scalar `{{slot}}` bindings,
|
|
1010
|
+
which remain literal text. Choose `CompiledFlowCompression.Throughput` (default) or
|
|
1011
|
+
`CompiledFlowCompression.Compact` per render; modes can be mixed safely in one process. See
|
|
1012
|
+
[`docs/python-api.md`](docs/python-api.md) for the trust boundary and current limitations.
|
|
1013
|
+
|
|
980
1014
|
When `deterministic_hash` is set, engine writes PDF SHA-256 to the provided file path.
|
|
981
1015
|
|
|
982
1016
|
`AssetBundle` methods:
|
|
@@ -54,6 +54,16 @@ class AssetKind(metaclass=_NativeFacadeType):
|
|
|
54
54
|
raise TypeError("No constructor defined for AssetKind")
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
class CompiledFlowCompression(metaclass=_NativeFacadeType):
|
|
58
|
+
"""Per-call size/speed policy for compiled content-reflow PDF streams."""
|
|
59
|
+
|
|
60
|
+
Throughput = "throughput"
|
|
61
|
+
Compact = "compact"
|
|
62
|
+
|
|
63
|
+
def __new__(cls, /, *args, **kwargs):
|
|
64
|
+
raise TypeError("No constructor defined for CompiledFlowCompression")
|
|
65
|
+
|
|
66
|
+
|
|
57
67
|
class Asset(metaclass=_NativeFacadeType):
|
|
58
68
|
__slots__ = ("_handle",)
|
|
59
69
|
|
|
@@ -161,7 +171,7 @@ class WatermarkSpec(metaclass=_NativeFacadeType):
|
|
|
161
171
|
|
|
162
172
|
|
|
163
173
|
class CompiledDocument(metaclass=_NativeFacadeType):
|
|
164
|
-
"""
|
|
174
|
+
"""Compiled fixed-paint and content-reflow programs from ``PdfEngine.compile_pdf``."""
|
|
165
175
|
|
|
166
176
|
__slots__ = ("_handle",)
|
|
167
177
|
|
|
@@ -219,6 +229,44 @@ class CompiledDocument(metaclass=_NativeFacadeType):
|
|
|
219
229
|
deterministic_hash,
|
|
220
230
|
)
|
|
221
231
|
|
|
232
|
+
def render_pdf_reflow_bindings(
|
|
233
|
+
self,
|
|
234
|
+
/,
|
|
235
|
+
bindings,
|
|
236
|
+
deterministic_hash=None,
|
|
237
|
+
*,
|
|
238
|
+
compression=CompiledFlowCompression.Throughput,
|
|
239
|
+
):
|
|
240
|
+
"""Render columnar text bindings with content-driven layout and pagination."""
|
|
241
|
+
_require_string(compression, "compression")
|
|
242
|
+
return _call(
|
|
243
|
+
self._handle,
|
|
244
|
+
"CompiledDocument.render_pdf_reflow_bindings",
|
|
245
|
+
bindings,
|
|
246
|
+
deterministic_hash,
|
|
247
|
+
compression,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def render_pdf_reflow_bindings_to_file(
|
|
251
|
+
self,
|
|
252
|
+
/,
|
|
253
|
+
bindings,
|
|
254
|
+
path,
|
|
255
|
+
deterministic_hash=None,
|
|
256
|
+
*,
|
|
257
|
+
compression=CompiledFlowCompression.Throughput,
|
|
258
|
+
):
|
|
259
|
+
"""Stream a compiled content-reflow batch directly to ``path``."""
|
|
260
|
+
_require_string(compression, "compression")
|
|
261
|
+
return _call(
|
|
262
|
+
self._handle,
|
|
263
|
+
"CompiledDocument.render_pdf_reflow_bindings_to_file",
|
|
264
|
+
bindings,
|
|
265
|
+
path,
|
|
266
|
+
deterministic_hash,
|
|
267
|
+
compression,
|
|
268
|
+
)
|
|
269
|
+
|
|
222
270
|
|
|
223
271
|
class PdfEngine(metaclass=_NativeFacadeType):
|
|
224
272
|
__slots__ = ("_handle",)
|
|
@@ -558,7 +606,9 @@ class PdfEngine(metaclass=_NativeFacadeType):
|
|
|
558
606
|
)
|
|
559
607
|
|
|
560
608
|
def render_pdf(self, /, html, css, deterministic_hash=None):
|
|
561
|
-
return _call(
|
|
609
|
+
return _call(
|
|
610
|
+
self._handle, "PdfEngine.render_pdf", html, css, deterministic_hash
|
|
611
|
+
)
|
|
562
612
|
|
|
563
613
|
def compile_pdf(self, /, html, css):
|
|
564
614
|
handle = _call(self._handle, "PdfEngine.compile_pdf", html, css)
|
|
@@ -726,7 +776,9 @@ class PdfEngine(metaclass=_NativeFacadeType):
|
|
|
726
776
|
deterministic_hash,
|
|
727
777
|
)
|
|
728
778
|
|
|
729
|
-
def render_pdf_batch_to_file(
|
|
779
|
+
def render_pdf_batch_to_file(
|
|
780
|
+
self, /, html_list, css, path, deterministic_hash=None
|
|
781
|
+
):
|
|
730
782
|
return _call(
|
|
731
783
|
self._handle,
|
|
732
784
|
"PdfEngine.render_pdf_batch_to_file",
|
|
@@ -876,6 +928,7 @@ def audit_sparse_page_visual_pair(source_png_path, render_png_path):
|
|
|
876
928
|
__all__ = [
|
|
877
929
|
"PdfEngine",
|
|
878
930
|
"CompiledDocument",
|
|
931
|
+
"CompiledFlowCompression",
|
|
879
932
|
"AssetKind",
|
|
880
933
|
"Asset",
|
|
881
934
|
"AssetBundle",
|
|
@@ -904,6 +957,7 @@ __all__ = [
|
|
|
904
957
|
_CLASS_NAMES = {
|
|
905
958
|
"PdfEngine",
|
|
906
959
|
"CompiledDocument",
|
|
960
|
+
"CompiledFlowCompression",
|
|
907
961
|
"AssetKind",
|
|
908
962
|
"Asset",
|
|
909
963
|
"AssetBundle",
|
|
@@ -2793,6 +2793,8 @@ def cmd_capabilities(args):
|
|
|
2793
2793
|
build_features = {}
|
|
2794
2794
|
svg_raster_available = bool(build_features.get("svg_raster", False))
|
|
2795
2795
|
pdf_engine = getattr(fullbleed, "PdfEngine", None)
|
|
2796
|
+
compiled_document = getattr(fullbleed, "CompiledDocument", None)
|
|
2797
|
+
compression_modes = list(build_features.get("compiled_flow_compression_modes", []))
|
|
2796
2798
|
|
|
2797
2799
|
commands = [
|
|
2798
2800
|
"render",
|
|
@@ -2828,6 +2830,14 @@ def cmd_capabilities(args):
|
|
|
2828
2830
|
"--repro-check",
|
|
2829
2831
|
],
|
|
2830
2832
|
"engine": {
|
|
2833
|
+
"compiled_document": bool(pdf_engine and hasattr(pdf_engine, "compile_pdf")),
|
|
2834
|
+
"compiled_reflow_bindings": bool(
|
|
2835
|
+
build_features.get("compiled_reflow", False)
|
|
2836
|
+
and pdf_engine
|
|
2837
|
+
and compiled_document
|
|
2838
|
+
and hasattr(compiled_document, "render_pdf_reflow_bindings")
|
|
2839
|
+
),
|
|
2840
|
+
"compiled_flow_compression_modes": compression_modes,
|
|
2831
2841
|
"batch_render": bool(pdf_engine and hasattr(pdf_engine, "render_pdf_batch")),
|
|
2832
2842
|
"batch_render_parallel": bool(
|
|
2833
2843
|
pdf_engine and hasattr(pdf_engine, "render_pdf_batch_parallel")
|
|
@@ -3677,5 +3687,3 @@ if __name__ == "__main__":
|
|
|
3677
3687
|
raise SystemExit(main())
|
|
3678
3688
|
|
|
3679
3689
|
|
|
3680
|
-
|
|
3681
|
-
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
use std::cell::RefCell;
|
|
1
2
|
const ADLER_BASE: u32 = 65_521;
|
|
2
3
|
const DEFAULT_ADLER_CHUNK: usize = 1 << 20;
|
|
4
|
+
const ADLER_NMAX: usize = 5_552;
|
|
3
5
|
|
|
4
6
|
const LZ77_CHUNK_BYTES: usize = 128 * 1024;
|
|
5
7
|
const MIN_MATCH: usize = 3;
|
|
6
8
|
const MAX_MATCH: usize = 258;
|
|
7
9
|
const MAX_DISTANCE: usize = 32 * 1024;
|
|
8
10
|
const MAX_CHAIN_STEPS: usize = 64;
|
|
11
|
+
const DEFAULT_COMPILED_FLOW_CHAIN_STEPS: usize = 4;
|
|
12
|
+
const COMPILED_FLOW_THROUGHPUT_MIN_BYTES: usize = 4 * 1024;
|
|
9
13
|
const HASH_BITS: usize = 15;
|
|
10
14
|
const HASH_SIZE: usize = 1 << HASH_BITS;
|
|
11
15
|
|
|
@@ -41,19 +45,19 @@ impl AdlerPartial {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
fn for_bytes(data: &[u8]) -> Self {
|
|
44
|
-
let mut a
|
|
45
|
-
let mut b
|
|
46
|
-
for
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
let mut a = 1_u64;
|
|
49
|
+
let mut b = 0_u64;
|
|
50
|
+
for chunk in data.chunks(ADLER_NMAX) {
|
|
51
|
+
for &byte in chunk {
|
|
52
|
+
a += u64::from(byte);
|
|
53
|
+
b += a;
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
b %= ADLER_BASE;
|
|
55
|
+
a %= u64::from(ADLER_BASE);
|
|
56
|
+
b %= u64::from(ADLER_BASE);
|
|
53
57
|
}
|
|
54
58
|
Self {
|
|
55
|
-
a,
|
|
56
|
-
b,
|
|
59
|
+
a: a as u32,
|
|
60
|
+
b: b as u32,
|
|
57
61
|
len: data.len(),
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -166,87 +170,155 @@ fn hash3(data: &[u8], i: usize) -> usize {
|
|
|
166
170
|
|
|
167
171
|
fn match_len(data: &[u8], a: usize, b: usize, max_len: usize) -> usize {
|
|
168
172
|
let mut l = 0usize;
|
|
173
|
+
while l + 8 <= max_len {
|
|
174
|
+
let left = u64::from_le_bytes(
|
|
175
|
+
data[a + l..a + l + 8]
|
|
176
|
+
.try_into()
|
|
177
|
+
.expect("eight-byte match window"),
|
|
178
|
+
);
|
|
179
|
+
let right = u64::from_le_bytes(
|
|
180
|
+
data[b + l..b + l + 8]
|
|
181
|
+
.try_into()
|
|
182
|
+
.expect("eight-byte match window"),
|
|
183
|
+
);
|
|
184
|
+
let difference = left ^ right;
|
|
185
|
+
if difference != 0 {
|
|
186
|
+
return l + (difference.trailing_zeros() as usize >> 3);
|
|
187
|
+
}
|
|
188
|
+
l += 8;
|
|
189
|
+
}
|
|
169
190
|
while l < max_len && data[a + l] == data[b + l] {
|
|
170
191
|
l += 1;
|
|
171
192
|
}
|
|
172
193
|
l
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
struct Lz77Workspace {
|
|
197
|
+
head: Vec<i32>,
|
|
198
|
+
head_epoch: Vec<u32>,
|
|
199
|
+
prev: Vec<i32>,
|
|
200
|
+
epoch: u32,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
impl Lz77Workspace {
|
|
204
|
+
fn new() -> Self {
|
|
205
|
+
Self {
|
|
206
|
+
head: vec![-1; HASH_SIZE],
|
|
207
|
+
head_epoch: vec![0; HASH_SIZE],
|
|
208
|
+
prev: Vec::new(),
|
|
209
|
+
epoch: 0,
|
|
210
|
+
}
|
|
179
211
|
}
|
|
180
212
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
213
|
+
fn begin_page(&mut self, len: usize) -> u32 {
|
|
214
|
+
self.epoch = self.epoch.wrapping_add(1);
|
|
215
|
+
if self.epoch == 0 {
|
|
216
|
+
self.head_epoch.fill(0);
|
|
217
|
+
self.epoch = 1;
|
|
218
|
+
}
|
|
219
|
+
if self.prev.len() < len {
|
|
220
|
+
self.prev.resize(len, -1);
|
|
221
|
+
}
|
|
222
|
+
self.epoch
|
|
223
|
+
}
|
|
184
224
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if
|
|
188
|
-
tokens
|
|
189
|
-
i += 1;
|
|
190
|
-
continue;
|
|
225
|
+
fn plan(&mut self, data: &[u8], max_chain_steps: usize) -> ChunkPlan {
|
|
226
|
+
let n = data.len();
|
|
227
|
+
if n == 0 {
|
|
228
|
+
return ChunkPlan { tokens: Vec::new() };
|
|
191
229
|
}
|
|
230
|
+
let epoch = self.begin_page(n);
|
|
231
|
+
let mut tokens = Vec::with_capacity(n / 2);
|
|
232
|
+
|
|
233
|
+
let mut i = 0usize;
|
|
234
|
+
while i < n {
|
|
235
|
+
if i + MIN_MATCH > n {
|
|
236
|
+
tokens.push(Token::Literal(data[i]));
|
|
237
|
+
i += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
192
240
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
241
|
+
let h = hash3(data, i);
|
|
242
|
+
let mut cand = if self.head_epoch[h] == epoch {
|
|
243
|
+
self.head[h]
|
|
244
|
+
} else {
|
|
245
|
+
-1
|
|
246
|
+
};
|
|
247
|
+
self.prev[i] = cand;
|
|
248
|
+
self.head[h] = i as i32;
|
|
249
|
+
self.head_epoch[h] = epoch;
|
|
250
|
+
|
|
251
|
+
let mut best_len = 0usize;
|
|
252
|
+
let mut best_dist = 0usize;
|
|
253
|
+
let mut steps = 0usize;
|
|
254
|
+
|
|
255
|
+
while cand >= 0 && steps < max_chain_steps {
|
|
256
|
+
let c = cand as usize;
|
|
257
|
+
let dist = i - c;
|
|
258
|
+
if dist > MAX_DISTANCE {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
197
261
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
262
|
+
if data[c] == data[i] && data[c + 1] == data[i + 1] && data[c + 2] == data[i + 2] {
|
|
263
|
+
let max_len = MAX_MATCH.min(n - i);
|
|
264
|
+
let len = match_len(data, c, i, max_len);
|
|
265
|
+
if len >= MIN_MATCH && (len > best_len || (len == best_len && dist < best_dist))
|
|
266
|
+
{
|
|
267
|
+
best_len = len;
|
|
268
|
+
best_dist = dist;
|
|
269
|
+
if best_len == MAX_MATCH {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
201
274
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let dist = i - c;
|
|
205
|
-
if dist > MAX_DISTANCE {
|
|
206
|
-
break;
|
|
275
|
+
cand = self.prev[c];
|
|
276
|
+
steps += 1;
|
|
207
277
|
}
|
|
208
278
|
|
|
209
|
-
if
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
if best_len >= MIN_MATCH {
|
|
280
|
+
tokens.push(Token::Match {
|
|
281
|
+
len: best_len as u16,
|
|
282
|
+
dist: best_dist as u16,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
let end = (i + best_len).min(n);
|
|
286
|
+
let mut j = i + 1;
|
|
287
|
+
while j < end {
|
|
288
|
+
if j + MIN_MATCH <= n {
|
|
289
|
+
let hj = hash3(data, j);
|
|
290
|
+
self.prev[j] = if self.head_epoch[hj] == epoch {
|
|
291
|
+
self.head[hj]
|
|
292
|
+
} else {
|
|
293
|
+
-1
|
|
294
|
+
};
|
|
295
|
+
self.head[hj] = j as i32;
|
|
296
|
+
self.head_epoch[hj] = epoch;
|
|
217
297
|
}
|
|
298
|
+
j += 1;
|
|
218
299
|
}
|
|
219
|
-
}
|
|
220
300
|
|
|
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;
|
|
301
|
+
i += best_len;
|
|
302
|
+
} else {
|
|
303
|
+
tokens.push(Token::Literal(data[i]));
|
|
304
|
+
i += 1;
|
|
240
305
|
}
|
|
241
|
-
|
|
242
|
-
i += best_len;
|
|
243
|
-
} else {
|
|
244
|
-
tokens.push(Token::Literal(data[i]));
|
|
245
|
-
i += 1;
|
|
246
306
|
}
|
|
307
|
+
|
|
308
|
+
ChunkPlan { tokens }
|
|
247
309
|
}
|
|
310
|
+
}
|
|
248
311
|
|
|
249
|
-
|
|
312
|
+
thread_local! {
|
|
313
|
+
static LZ77_WORKSPACE: RefCell<Lz77Workspace> = RefCell::new(Lz77Workspace::new());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn plan_lz77_chunk_with_chain(data: &[u8], max_chain_steps: usize) -> ChunkPlan {
|
|
317
|
+
LZ77_WORKSPACE.with(|workspace| {
|
|
318
|
+
workspace
|
|
319
|
+
.borrow_mut()
|
|
320
|
+
.plan(data, max_chain_steps.clamp(1, MAX_CHAIN_STEPS))
|
|
321
|
+
})
|
|
250
322
|
}
|
|
251
323
|
|
|
252
324
|
fn reverse_bits(mut value: u16, len: u8) -> u16 {
|
|
@@ -348,10 +420,31 @@ fn estimate_deflate_capacity(input_len: usize) -> usize {
|
|
|
348
420
|
}
|
|
349
421
|
|
|
350
422
|
pub(crate) fn zlib_deflate_parallel(data: &[u8]) -> Vec<u8> {
|
|
423
|
+
zlib_deflate_with_chain(data, MAX_CHAIN_STEPS)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
pub(crate) fn zlib_deflate_compiled_flow(
|
|
427
|
+
data: &[u8],
|
|
428
|
+
compression: crate::pdf::CompiledFlowCompression,
|
|
429
|
+
) -> Vec<u8> {
|
|
430
|
+
let steps = match compression {
|
|
431
|
+
crate::pdf::CompiledFlowCompression::Throughput => DEFAULT_COMPILED_FLOW_CHAIN_STEPS,
|
|
432
|
+
crate::pdf::CompiledFlowCompression::Compact => MAX_CHAIN_STEPS,
|
|
433
|
+
};
|
|
434
|
+
let steps = if data.len() < COMPILED_FLOW_THROUGHPUT_MIN_BYTES {
|
|
435
|
+
MAX_CHAIN_STEPS
|
|
436
|
+
} else {
|
|
437
|
+
steps
|
|
438
|
+
};
|
|
439
|
+
zlib_deflate_with_chain(data, steps)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
fn zlib_deflate_with_chain(data: &[u8], max_chain_steps: usize) -> Vec<u8> {
|
|
351
443
|
let ranges = chunk_ranges(data.len(), LZ77_CHUNK_BYTES);
|
|
352
444
|
|
|
353
|
-
let plans =
|
|
354
|
-
|
|
445
|
+
let plans = crate::parallel::map_ordered(&ranges, |(start, end)| {
|
|
446
|
+
plan_lz77_chunk_with_chain(&data[*start..*end], max_chain_steps)
|
|
447
|
+
});
|
|
355
448
|
|
|
356
449
|
let adler = adler32_parallel(data, DEFAULT_ADLER_CHUNK);
|
|
357
450
|
|
|
@@ -812,6 +905,47 @@ mod tests {
|
|
|
812
905
|
);
|
|
813
906
|
}
|
|
814
907
|
|
|
908
|
+
#[test]
|
|
909
|
+
fn zlib_compiled_flow_chain_levels_roundtrip_deterministically() {
|
|
910
|
+
let src = b"BT /F1 10 Tf 72 720 Td [(REFLOW-000001)] TJ ET\n".repeat(2_000);
|
|
911
|
+
for steps in [1, 4, 8, 16, 32, 64] {
|
|
912
|
+
let a = zlib_deflate_with_chain(&src, steps);
|
|
913
|
+
let b = zlib_deflate_with_chain(&src, steps);
|
|
914
|
+
assert_eq!(a, b, "deterministic at {steps} chain steps");
|
|
915
|
+
assert_eq!(
|
|
916
|
+
zlib_inflate(&a, src.len()).expect("native inflate"),
|
|
917
|
+
src,
|
|
918
|
+
"roundtrip at {steps} chain steps",
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
#[test]
|
|
924
|
+
fn compiled_flow_compression_modes_are_per_call_and_deterministic() {
|
|
925
|
+
use crate::pdf::CompiledFlowCompression::{Compact, Throughput};
|
|
926
|
+
|
|
927
|
+
let src = (0..8_000)
|
|
928
|
+
.map(|index| format!("BT /F1 10 Tf 72 720 Td [(RECORD-{index:05})] TJ ET\n"))
|
|
929
|
+
.collect::<String>()
|
|
930
|
+
.into_bytes();
|
|
931
|
+
let throughput = zlib_deflate_compiled_flow(&src, Throughput);
|
|
932
|
+
let compact = zlib_deflate_compiled_flow(&src, Compact);
|
|
933
|
+
assert_eq!(throughput, zlib_deflate_compiled_flow(&src, Throughput));
|
|
934
|
+
assert_eq!(compact, zlib_deflate_compiled_flow(&src, Compact));
|
|
935
|
+
assert_eq!(
|
|
936
|
+
zlib_inflate(&throughput, src.len()).expect("throughput roundtrip"),
|
|
937
|
+
src
|
|
938
|
+
);
|
|
939
|
+
assert_eq!(
|
|
940
|
+
zlib_inflate(&compact, src.len()).expect("compact roundtrip"),
|
|
941
|
+
src
|
|
942
|
+
);
|
|
943
|
+
assert!(
|
|
944
|
+
compact.len() <= throughput.len(),
|
|
945
|
+
"compact search should not produce a larger representative stream"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
815
949
|
#[test]
|
|
816
950
|
fn zlib_lz77_roundtrip_empty() {
|
|
817
951
|
let src: Vec<u8> = Vec::new();
|