pycadkit 0.3.3__tar.gz → 0.3.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.
- {pycadkit-0.3.3 → pycadkit-0.3.5}/Cargo.lock +10 -10
- {pycadkit-0.3.3 → pycadkit-0.3.5}/Cargo.toml +7 -7
- {pycadkit-0.3.3 → pycadkit-0.3.5}/PKG-INFO +1 -1
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/decode_v8.rs +3 -2
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/mod.rs +15 -23
- pycadkit-0.3.5/crates/cadkit-dgn/src/writer/named.rs +203 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/raster_template.rs +36 -12
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tables.rs +201 -44
- pycadkit-0.3.5/crates/cadkit-dgn/src/writer/tests/dictionary.rs +604 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/raster_template.rs +117 -1
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests.rs +1 -0
- pycadkit-0.3.5/crates/cadkit-dgn/tests/private_dictionary.rs +173 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/examples/interchange.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/aci.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/bytes.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/error.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/export.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/geom.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/geom_ops.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/model.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/ops.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/options.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/polygon.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/svg.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/text.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-core/src/units.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/examples/dgn_summary.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/examples/raster_structure.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/budget.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/cfb.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/fuzz_tests.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/le.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/level_names.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/map/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/map/v7.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/map/v8.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/element.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/linkage.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/v7.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/native/v8.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/palette.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/text.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/container.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/encode.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/polyline.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/raster.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/raster_builtin.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/application.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/microstation.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/raster.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/regressions.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/tests/text.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/writer/text.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/src/zlib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/common/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/private_corpus.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/robustness.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/v7_corpus.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/v8_oracle.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/v8_public.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dgn/tests/v8_range.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/examples/docsummary.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/examples/dwgdump.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/bits.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/classes.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/codepage.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/lz77_r2004.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/lz77_r2007.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/r13.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/r2004.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/r2007.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/container/reed_solomon.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/crc.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/handles.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/header.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/mapping/entity.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/mapping/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/native/entities.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/native/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/annotation.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/curves.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/entities.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/hatch.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/misc.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/registry.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/object/tables.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/reader.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/src/version.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/tests/corpus.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/tests/oracle.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dwg/tests/robustness.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/examples/dump.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/native/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/native/text.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/ocs.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/common.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/entities.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/hatch.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/records.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/reader/tables.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/writer/curves.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/writer/entities.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/writer/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/src/writer/out.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/ascii_vs_binary.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/common/mod.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/compare_reader_with_ezdxf.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/review_fixes.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/robustness.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/roundtrip.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/synthetic_read.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/synthetic_write.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-dxf/tests/validate_with_ezdxf.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/curves.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/export.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/geometry.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/mapping.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/native.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/tkgm.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/validate.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/src/xml.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/tests/roundtrip.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/tests/support/export_cases.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-gml/tests/support/tkgm_cases.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/Cargo.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/LICENSE-APACHE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/LICENSE-MIT +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/NOTICE +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/README.md +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/src/lib.rs +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/tests/conftest.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/tests/test_cadkit.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/crates/cadkit-py/tests/test_citygml.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/pyproject.toml +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/python/cadkit/__init__.py +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/python/cadkit/__init__.pyi +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/python/cadkit/_cadkit.pyi +0 -0
- {pycadkit-0.3.3 → pycadkit-0.3.5}/python/cadkit/py.typed +0 -0
|
@@ -66,7 +66,7 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
|
66
66
|
|
|
67
67
|
[[package]]
|
|
68
68
|
name = "cadkit"
|
|
69
|
-
version = "0.3.
|
|
69
|
+
version = "0.3.5"
|
|
70
70
|
dependencies = [
|
|
71
71
|
"cadkit-core",
|
|
72
72
|
"cadkit-dgn",
|
|
@@ -78,7 +78,7 @@ dependencies = [
|
|
|
78
78
|
|
|
79
79
|
[[package]]
|
|
80
80
|
name = "cadkit-capi"
|
|
81
|
-
version = "0.3.
|
|
81
|
+
version = "0.3.5"
|
|
82
82
|
dependencies = [
|
|
83
83
|
"cadkit",
|
|
84
84
|
"serde",
|
|
@@ -87,7 +87,7 @@ dependencies = [
|
|
|
87
87
|
|
|
88
88
|
[[package]]
|
|
89
89
|
name = "cadkit-cli"
|
|
90
|
-
version = "0.3.
|
|
90
|
+
version = "0.3.5"
|
|
91
91
|
dependencies = [
|
|
92
92
|
"cadkit",
|
|
93
93
|
"clap",
|
|
@@ -96,7 +96,7 @@ dependencies = [
|
|
|
96
96
|
|
|
97
97
|
[[package]]
|
|
98
98
|
name = "cadkit-core"
|
|
99
|
-
version = "0.3.
|
|
99
|
+
version = "0.3.5"
|
|
100
100
|
dependencies = [
|
|
101
101
|
"serde",
|
|
102
102
|
"serde_json",
|
|
@@ -105,7 +105,7 @@ dependencies = [
|
|
|
105
105
|
|
|
106
106
|
[[package]]
|
|
107
107
|
name = "cadkit-dgn"
|
|
108
|
-
version = "0.3.
|
|
108
|
+
version = "0.3.5"
|
|
109
109
|
dependencies = [
|
|
110
110
|
"cadkit-core",
|
|
111
111
|
"cfb",
|
|
@@ -116,7 +116,7 @@ dependencies = [
|
|
|
116
116
|
|
|
117
117
|
[[package]]
|
|
118
118
|
name = "cadkit-dwg"
|
|
119
|
-
version = "0.3.
|
|
119
|
+
version = "0.3.5"
|
|
120
120
|
dependencies = [
|
|
121
121
|
"cadkit-core",
|
|
122
122
|
"cadkit-dxf",
|
|
@@ -126,7 +126,7 @@ dependencies = [
|
|
|
126
126
|
|
|
127
127
|
[[package]]
|
|
128
128
|
name = "cadkit-dxf"
|
|
129
|
-
version = "0.3.
|
|
129
|
+
version = "0.3.5"
|
|
130
130
|
dependencies = [
|
|
131
131
|
"cadkit-core",
|
|
132
132
|
"encoding_rs",
|
|
@@ -135,7 +135,7 @@ dependencies = [
|
|
|
135
135
|
|
|
136
136
|
[[package]]
|
|
137
137
|
name = "cadkit-gml"
|
|
138
|
-
version = "0.3.
|
|
138
|
+
version = "0.3.5"
|
|
139
139
|
dependencies = [
|
|
140
140
|
"cadkit-core",
|
|
141
141
|
"quick-xml",
|
|
@@ -145,7 +145,7 @@ dependencies = [
|
|
|
145
145
|
|
|
146
146
|
[[package]]
|
|
147
147
|
name = "cadkit-py"
|
|
148
|
-
version = "0.3.
|
|
148
|
+
version = "0.3.5"
|
|
149
149
|
dependencies = [
|
|
150
150
|
"cadkit",
|
|
151
151
|
"pyo3",
|
|
@@ -155,7 +155,7 @@ dependencies = [
|
|
|
155
155
|
|
|
156
156
|
[[package]]
|
|
157
157
|
name = "cadkit-wasm"
|
|
158
|
-
version = "0.3.
|
|
158
|
+
version = "0.3.5"
|
|
159
159
|
dependencies = [
|
|
160
160
|
"cadkit",
|
|
161
161
|
"js-sys",
|
|
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
|
|
4
4
|
exclude = ["fuzz"]
|
|
5
5
|
|
|
6
6
|
[workspace.package]
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.5"
|
|
8
8
|
edition = "2024"
|
|
9
9
|
rust-version = "1.85"
|
|
10
10
|
license = "MIT OR Apache-2.0"
|
|
@@ -14,12 +14,12 @@ keywords = ["cad", "dwg", "dgn", "dxf", "citygml"]
|
|
|
14
14
|
categories = ["parser-implementations", "graphics", "encoding", "science::geo"]
|
|
15
15
|
|
|
16
16
|
[workspace.dependencies]
|
|
17
|
-
cadkit-core = { path = "crates/cadkit-core", version = "0.3.
|
|
18
|
-
cadkit-dgn = { path = "crates/cadkit-dgn", version = "0.3.
|
|
19
|
-
cadkit-dwg = { path = "crates/cadkit-dwg", version = "0.3.
|
|
20
|
-
cadkit-dxf = { path = "crates/cadkit-dxf", version = "0.3.
|
|
21
|
-
cadkit-gml = { path = "crates/cadkit-gml", version = "0.3.
|
|
22
|
-
cadkit = { path = "crates/cadkit", version = "0.3.
|
|
17
|
+
cadkit-core = { path = "crates/cadkit-core", version = "0.3.5" }
|
|
18
|
+
cadkit-dgn = { path = "crates/cadkit-dgn", version = "0.3.5" }
|
|
19
|
+
cadkit-dwg = { path = "crates/cadkit-dwg", version = "0.3.5" }
|
|
20
|
+
cadkit-dxf = { path = "crates/cadkit-dxf", version = "0.3.5" }
|
|
21
|
+
cadkit-gml = { path = "crates/cadkit-gml", version = "0.3.5" }
|
|
22
|
+
cadkit = { path = "crates/cadkit", version = "0.3.5" }
|
|
23
23
|
serde = { version = "1", features = ["derive"] }
|
|
24
24
|
serde_json = "1"
|
|
25
25
|
thiserror = "2"
|
|
@@ -136,9 +136,10 @@ pub fn decode(raw: &RawElement, enc: &'static Encoding, limits: &Limits) -> Elem
|
|
|
136
136
|
35 => d.cell().map(ElementData::SharedCellInstance),
|
|
137
137
|
37 => d.tag(&linkages).map(ElementData::Tag),
|
|
138
138
|
39 => d.tag_set(&linkages).map(ElementData::TagSet),
|
|
139
|
+
// An attachment written with a relative path stores an empty full path.
|
|
139
140
|
90 => Ok(ElementData::RasterReference {
|
|
140
|
-
file_name: linkage::string(&linkages, 3),
|
|
141
|
-
full_path: linkage::string(&linkages, 0x1f),
|
|
141
|
+
file_name: linkage::string(&linkages, 3).filter(|s| !s.is_empty()),
|
|
142
|
+
full_path: linkage::string(&linkages, 0x1f).filter(|s| !s.is_empty()),
|
|
142
143
|
}),
|
|
143
144
|
94 => le::f64s::<16>(d.b, 0x78)
|
|
144
145
|
.map(|matrix| ElementData::RasterFrame {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
mod container;
|
|
4
4
|
mod encode;
|
|
5
|
+
mod named;
|
|
5
6
|
mod polyline;
|
|
6
7
|
mod raster;
|
|
7
8
|
mod raster_builtin;
|
|
@@ -237,14 +238,7 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
237
238
|
}
|
|
238
239
|
let max_id = max_id.max(le::u64_at(&seed_header.data, 0x128).unwrap_or(0));
|
|
239
240
|
let rasters = raster::Preserved::prepare(doc, seed, model, &read, options)?;
|
|
240
|
-
let mut tables = Tables::new(
|
|
241
|
-
native
|
|
242
|
-
.named_pages
|
|
243
|
-
.iter()
|
|
244
|
-
.flat_map(|p| p.elements.iter().cloned()),
|
|
245
|
-
max_id,
|
|
246
|
-
&read,
|
|
247
|
-
)?;
|
|
241
|
+
let mut tables = Tables::new(&native.named_pages, max_id, &read)?;
|
|
248
242
|
tables.observe_model_fonts(
|
|
249
243
|
model
|
|
250
244
|
.graphic_pages
|
|
@@ -283,7 +277,7 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
283
277
|
let named = state.tables.finish(options.timestamp_ms)?;
|
|
284
278
|
let max_id = records
|
|
285
279
|
.iter()
|
|
286
|
-
.chain(named.iter())
|
|
280
|
+
.chain(named.iter().map(|r| &r.bytes))
|
|
287
281
|
.filter_map(|r| le::u64_at(r, 16))
|
|
288
282
|
.max()
|
|
289
283
|
.unwrap_or(max_id)
|
|
@@ -304,7 +298,7 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
304
298
|
.collect();
|
|
305
299
|
let mut replacements = BTreeMap::new();
|
|
306
300
|
for stream in &native.streams {
|
|
307
|
-
|
|
301
|
+
let model_stream = stream
|
|
308
302
|
.path
|
|
309
303
|
.starts_with(&format!("Dgn-Md/{}/Dgn^G/", model.storage))
|
|
310
304
|
|| stream
|
|
@@ -315,9 +309,8 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
315
309
|
.starts_with(&format!("Dgn-Md/{}/Dgn^GA/", model.storage))
|
|
316
310
|
|| stream
|
|
317
311
|
.path
|
|
318
|
-
.starts_with(&format!("Dgn-Md/{}/Dgn^CA/", model.storage))
|
|
319
|
-
|
|
320
|
-
{
|
|
312
|
+
.starts_with(&format!("Dgn-Md/{}/Dgn^CA/", model.storage));
|
|
313
|
+
if model_stream || stream.path.starts_with("Dgn^Nm/") {
|
|
321
314
|
// A page the reader could not decode was neither checked for content nor
|
|
322
315
|
// carried into the named tables; dropping it would lose seed data silently.
|
|
323
316
|
let page = stream.path.rsplit('/').next().is_some_and(|n| {
|
|
@@ -330,7 +323,10 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
330
323
|
format!("seed page {} could not be decoded", stream.path),
|
|
331
324
|
));
|
|
332
325
|
}
|
|
333
|
-
|
|
326
|
+
// Dictionary pages are kept and only rewritten where they change (`named`).
|
|
327
|
+
if model_stream {
|
|
328
|
+
replacements.insert(stream.path.clone(), None);
|
|
329
|
+
}
|
|
334
330
|
}
|
|
335
331
|
}
|
|
336
332
|
let version = graphic_page_version(model);
|
|
@@ -382,15 +378,11 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
382
378
|
// Every control auxiliary page is retained as is; so is its page counter.
|
|
383
379
|
replacements.remove(&format!("Dgn-Md/{}/Dgn^CA/^AH", model.storage));
|
|
384
380
|
}
|
|
385
|
-
|
|
381
|
+
named::write(
|
|
386
382
|
&mut replacements,
|
|
387
|
-
|
|
383
|
+
&native.named_pages,
|
|
384
|
+
&native.named_aux,
|
|
388
385
|
&named,
|
|
389
|
-
native
|
|
390
|
-
.named_pages
|
|
391
|
-
.first()
|
|
392
|
-
.map(|p| p.header.format_version)
|
|
393
|
-
.unwrap_or(3),
|
|
394
386
|
options.limits.max_decompressed_bytes,
|
|
395
387
|
)?;
|
|
396
388
|
let mut mh = header.raw.clone();
|
|
@@ -456,8 +448,8 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
|
|
|
456
448
|
#[cfg(test)]
|
|
457
449
|
mod tests;
|
|
458
450
|
|
|
459
|
-
/// Writes `records` to pages `{prefix}/$1`, `$2`, ... of at most 256 KiB and returns
|
|
460
|
-
/// page number of every record.
|
|
451
|
+
/// Writes graphic `records` to pages `{prefix}/$1`, `$2`, ... of at most 256 KiB and returns
|
|
452
|
+
/// the page number of every record. Dictionary pages keep the seed's layout instead (`named`).
|
|
461
453
|
fn put_pages(
|
|
462
454
|
replacements: &mut BTreeMap<String, Option<Vec<u8>>>,
|
|
463
455
|
prefix: &str,
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
//! Dictionary pages of the output: `Dgn^Nm/$n` (tables, tag sets, cell definitions) and
|
|
2
|
+
//! their XAttribute pages `Dgn^NmA/$n`.
|
|
3
|
+
//!
|
|
4
|
+
//! The seed's page layout is kept: every seed page keeps its number, its records stay in
|
|
5
|
+
//! order, and a page whose records did not change keeps its stream byte for byte (empty
|
|
6
|
+
//! pages included). A changed page is rewritten in place; new tables and tag sets share one
|
|
7
|
+
//! new page after the seed's last page. MicroStation keeps a complex element (a table with
|
|
8
|
+
//! its entries, a cell definition with its components) on one page and does not join one
|
|
9
|
+
//! across a page boundary: a level table split over two pages is not loaded as one table.
|
|
10
|
+
//! Its pages hold as many records as that takes (595,424 inflated bytes observed), so only
|
|
11
|
+
//! the caller's decompression limit bounds a page; nothing is split at a fixed size
|
|
12
|
+
//! (`docs/dgn/FORMAT_NOTES.md`, "Dictionary pages").
|
|
13
|
+
|
|
14
|
+
use super::{compressed, encode::u32_at, raster, tables::NamedRecord};
|
|
15
|
+
use crate::native::v8::{AuxPage, AuxRecord, Page, PageHeader, RawElement};
|
|
16
|
+
use cadkit_core::{Error, Result};
|
|
17
|
+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
|
18
|
+
|
|
19
|
+
/// Puts the dictionary pages that differ from the seed's, and the XAttribute pages whose
|
|
20
|
+
/// records moved with their elements, into `replacements`. `records` are in stored order
|
|
21
|
+
/// (as [`super::tables::Tables::finish`] returns them). The inflated records together must
|
|
22
|
+
/// not exceed `max` bytes.
|
|
23
|
+
pub(super) fn write(
|
|
24
|
+
replacements: &mut BTreeMap<String, Option<Vec<u8>>>,
|
|
25
|
+
seed_pages: &[Page],
|
|
26
|
+
seed_aux: &[AuxPage],
|
|
27
|
+
records: &[NamedRecord],
|
|
28
|
+
max: u64,
|
|
29
|
+
) -> Result<()> {
|
|
30
|
+
let total = records
|
|
31
|
+
.iter()
|
|
32
|
+
.try_fold(0u64, |sum, r| sum.checked_add(r.bytes.len() as u64 + 4))
|
|
33
|
+
.ok_or_else(|| Error::LimitExceeded("DGN dictionary records".into()))?;
|
|
34
|
+
if total > max {
|
|
35
|
+
return Err(Error::LimitExceeded("DGN dictionary records".into()));
|
|
36
|
+
}
|
|
37
|
+
let new_page = seed_pages
|
|
38
|
+
.iter()
|
|
39
|
+
.map(|p| p.number)
|
|
40
|
+
.max()
|
|
41
|
+
.unwrap_or(0)
|
|
42
|
+
.checked_add(1)
|
|
43
|
+
.ok_or_else(|| Error::LimitExceeded("DGN page count".into()))?;
|
|
44
|
+
let mut pages: BTreeMap<u32, Vec<&NamedRecord>> = BTreeMap::new();
|
|
45
|
+
for r in records {
|
|
46
|
+
pages.entry(r.page.unwrap_or(new_page)).or_default().push(r);
|
|
47
|
+
}
|
|
48
|
+
for page in seed_pages {
|
|
49
|
+
let kept = pages.get(&page.number).map(Vec::as_slice).unwrap_or(&[]);
|
|
50
|
+
let same = kept.len() == page.elements.len()
|
|
51
|
+
&& kept
|
|
52
|
+
.iter()
|
|
53
|
+
.zip(&page.elements)
|
|
54
|
+
.all(|(r, e)| r.prefix == e.prefix && r.bytes == e.bytes);
|
|
55
|
+
if !same {
|
|
56
|
+
replacements.insert(
|
|
57
|
+
page.path.clone(),
|
|
58
|
+
Some(encode(kept, page.header.format_version)?),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// As before 0.3.5, new pages take the version of the seed's first page.
|
|
63
|
+
let version = seed_pages
|
|
64
|
+
.first()
|
|
65
|
+
.map(|p| p.header.format_version)
|
|
66
|
+
.unwrap_or(3);
|
|
67
|
+
if let Some(added) = pages.get(&new_page) {
|
|
68
|
+
replacements.insert(
|
|
69
|
+
format!(
|
|
70
|
+
"{}/${new_page}",
|
|
71
|
+
storage(seed_pages.first().map(|p| &p.path), "Dgn^Nm")
|
|
72
|
+
),
|
|
73
|
+
Some(encode(added, version)?),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
move_xattributes(replacements, seed_pages, seed_aux, &pages)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// The storage of a seed page `path` (`Dgn^Nm` of `Dgn^Nm/$1`), or `default`.
|
|
80
|
+
fn storage<'a>(path: Option<&'a String>, default: &'a str) -> &'a str {
|
|
81
|
+
path.and_then(|p| p.rsplit_once('/'))
|
|
82
|
+
.map(|(storage, _)| storage)
|
|
83
|
+
.unwrap_or(default)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// A dictionary page: `{record count, format version, 0, 0}`, then the zlib-compressed
|
|
87
|
+
/// records, each after its 4-byte prefix. An empty page is the header alone. The page
|
|
88
|
+
/// number and population are 0 as on every dictionary page MicroStation wrote (private
|
|
89
|
+
/// corpus: 3,932 of 3,932 pages); cadkit before 0.3.5 wrote the page number and the
|
|
90
|
+
/// record count there.
|
|
91
|
+
fn encode(records: &[&NamedRecord], version: u32) -> Result<Vec<u8>> {
|
|
92
|
+
let mut out = vec![0; 16];
|
|
93
|
+
u32_at(
|
|
94
|
+
&mut out,
|
|
95
|
+
0,
|
|
96
|
+
u32::try_from(records.len())
|
|
97
|
+
.map_err(|_| Error::LimitExceeded("DGN page records".into()))?,
|
|
98
|
+
)?;
|
|
99
|
+
u32_at(&mut out, 4, version)?;
|
|
100
|
+
if !records.is_empty() {
|
|
101
|
+
let mut data = vec![];
|
|
102
|
+
for r in records {
|
|
103
|
+
data.extend(r.prefix.to_le_bytes());
|
|
104
|
+
data.extend_from_slice(&r.bytes);
|
|
105
|
+
}
|
|
106
|
+
out.extend(compressed(&data)?);
|
|
107
|
+
}
|
|
108
|
+
Ok(out)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Keeps every XAttribute on the auxiliary page numbered like the page of its element, as
|
|
112
|
+
/// on every observed file (private corpus: 4,215 of 4,215 `Dgn^NmA` records): records of
|
|
113
|
+
/// moved elements move with them, records of dropped seed elements are dropped, and records
|
|
114
|
+
/// of ids that are not dictionary elements stay where they are. Only pages that change are
|
|
115
|
+
/// rewritten; `Dgn^NmA/^AH` is kept (it never exceeds the highest dictionary page number
|
|
116
|
+
/// in the corpus, and no page is removed).
|
|
117
|
+
fn move_xattributes(
|
|
118
|
+
replacements: &mut BTreeMap<String, Option<Vec<u8>>>,
|
|
119
|
+
seed_pages: &[Page],
|
|
120
|
+
seed_aux: &[AuxPage],
|
|
121
|
+
pages: &BTreeMap<u32, Vec<&NamedRecord>>,
|
|
122
|
+
) -> Result<()> {
|
|
123
|
+
if seed_aux.is_empty() {
|
|
124
|
+
return Ok(());
|
|
125
|
+
}
|
|
126
|
+
let mut location: HashMap<u64, u32> = HashMap::new();
|
|
127
|
+
for (number, records) in pages {
|
|
128
|
+
for r in records {
|
|
129
|
+
if let Some(id) = crate::le::u64_at(&r.bytes, 16) {
|
|
130
|
+
location.entry(id).or_insert(*number);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let seed_ids: HashSet<u64> = seed_pages
|
|
135
|
+
.iter()
|
|
136
|
+
.flat_map(|p| &p.elements)
|
|
137
|
+
.filter_map(RawElement::id)
|
|
138
|
+
.collect();
|
|
139
|
+
let mut placed: BTreeMap<u32, Vec<AuxRecord>> = BTreeMap::new();
|
|
140
|
+
let mut changed = BTreeSet::new();
|
|
141
|
+
for page in seed_aux {
|
|
142
|
+
placed.entry(page.number).or_default();
|
|
143
|
+
for record in &page.records {
|
|
144
|
+
let target = match location.get(&record.element_id) {
|
|
145
|
+
Some(number) => *number,
|
|
146
|
+
None if seed_ids.contains(&record.element_id) => {
|
|
147
|
+
changed.insert(page.number);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
None => page.number,
|
|
151
|
+
};
|
|
152
|
+
if target != page.number {
|
|
153
|
+
changed.insert(page.number);
|
|
154
|
+
changed.insert(target);
|
|
155
|
+
}
|
|
156
|
+
placed.entry(target).or_default().push(record.clone());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for number in changed {
|
|
160
|
+
let seed = seed_aux.iter().find(|p| p.number == number);
|
|
161
|
+
if let Some(page) = seed.filter(|p| !p.complete) {
|
|
162
|
+
// Its undecoded tail would be lost.
|
|
163
|
+
return Err(Error::invalid(
|
|
164
|
+
0,
|
|
165
|
+
format!("seed page {} could not be decoded", page.path),
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
let path = match seed {
|
|
169
|
+
Some(page) => page.path.clone(),
|
|
170
|
+
None => format!(
|
|
171
|
+
"{}/${number}",
|
|
172
|
+
storage(seed_aux.first().map(|p| &p.path), "Dgn^NmA")
|
|
173
|
+
),
|
|
174
|
+
};
|
|
175
|
+
// A new page takes the version of the seed's first one.
|
|
176
|
+
let version = seed
|
|
177
|
+
.or(seed_aux.first())
|
|
178
|
+
.map(|p| p.header.format_version)
|
|
179
|
+
.unwrap_or(2);
|
|
180
|
+
let records = placed.get(&number).map(Vec::as_slice).unwrap_or(&[]);
|
|
181
|
+
replacements.insert(path, Some(aux_page(records, version)?));
|
|
182
|
+
}
|
|
183
|
+
Ok(())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// An XAttribute page with page number and population 0 (private corpus: 1,188 of 1,188
|
|
187
|
+
/// `Dgn^NmA` pages); an empty page is the header alone.
|
|
188
|
+
fn aux_page(records: &[AuxRecord], version: u32) -> Result<Vec<u8>> {
|
|
189
|
+
let header = PageHeader {
|
|
190
|
+
record_count: 0,
|
|
191
|
+
format_version: version,
|
|
192
|
+
page_number: 0,
|
|
193
|
+
population: 0,
|
|
194
|
+
};
|
|
195
|
+
if records.is_empty() {
|
|
196
|
+
let mut out = vec![0; 16];
|
|
197
|
+
u32_at(&mut out, 4, version)?;
|
|
198
|
+
return Ok(out);
|
|
199
|
+
}
|
|
200
|
+
let mut page = raster::aux_page(records, header)?;
|
|
201
|
+
u32_at(&mut page, 12, 0)?;
|
|
202
|
+
Ok(page)
|
|
203
|
+
}
|
|
@@ -168,7 +168,7 @@ impl Template {
|
|
|
168
168
|
let (Some(path), Some(size)) = (path, size_px) else {
|
|
169
169
|
return Err(Error::Unsupported(NEEDS_PATH_AND_SIZE.into()));
|
|
170
170
|
};
|
|
171
|
-
let url =
|
|
171
|
+
let url = full_path(path)?;
|
|
172
172
|
let name = file_name(e, path)?;
|
|
173
173
|
let p = Placement::new(*position, *u_vector, *v_vector, *size, model)?;
|
|
174
174
|
let dpi = self.dpi(e)?;
|
|
@@ -388,9 +388,11 @@ impl XAttribute {
|
|
|
388
388
|
off = start.checked_add(len)?;
|
|
389
389
|
}
|
|
390
390
|
let trailer = payload.get(off..)?.to_vec();
|
|
391
|
-
// Sub-records 3 and 4 must hold exactly one string, which is what gets replaced
|
|
391
|
+
// Sub-records 3 and 4 must hold exactly one string, which is what gets replaced; the
|
|
392
|
+
// full path in 4 is empty in attachments written with a relative path.
|
|
392
393
|
let single = |i: usize| subs.get(i).and_then(|(_, d)| utf16z_text(d));
|
|
393
|
-
|
|
394
|
+
let empty = subs.get(4).is_some_and(|(_, d)| d.as_slice() == [0, 0]);
|
|
395
|
+
if trailer.len() > 16 || single(3).is_none() || (single(4).is_none() && !empty) {
|
|
394
396
|
return None;
|
|
395
397
|
}
|
|
396
398
|
Some(Self {
|
|
@@ -466,10 +468,14 @@ fn identity(b: &mut [u8], id: u64, time: f64) -> Result<()> {
|
|
|
466
468
|
f64_at(b, 0x18, time)
|
|
467
469
|
}
|
|
468
470
|
|
|
469
|
-
/// The stored
|
|
470
|
-
///
|
|
471
|
-
///
|
|
472
|
-
|
|
471
|
+
/// The stored full path (string linkage `0x1F`, XAttribute sub-record 4).
|
|
472
|
+
///
|
|
473
|
+
/// An absolute path (a drive, UNC or rooted path) is stored as a `file://` URL, as every
|
|
474
|
+
/// observed attachment stores it; a path that already is one is kept. A relative path
|
|
475
|
+
/// names a file beside the DGN, so the attachment carries no machine-specific location:
|
|
476
|
+
/// a bare file name stores an empty full path, `.\name` (or `./name`) is stored as given.
|
|
477
|
+
/// Relative paths into other folders are refused.
|
|
478
|
+
fn full_path(path: &str) -> Result<String> {
|
|
473
479
|
let has_scheme = path
|
|
474
480
|
.get(..7)
|
|
475
481
|
.is_some_and(|s| s.eq_ignore_ascii_case("file://"));
|
|
@@ -478,17 +484,35 @@ fn file_url(path: &str) -> Result<String> {
|
|
|
478
484
|
} else {
|
|
479
485
|
path
|
|
480
486
|
};
|
|
487
|
+
if local.contains('\0') {
|
|
488
|
+
return Err(Error::invalid(0, "DGN raster path contains NUL"));
|
|
489
|
+
}
|
|
481
490
|
let bytes = local.as_bytes();
|
|
482
491
|
let drive = matches!(bytes, [d, b':', b'\\' | b'/', ..] if d.is_ascii_alphabetic());
|
|
483
|
-
if
|
|
492
|
+
if drive || local.starts_with('\\') || local.starts_with('/') {
|
|
493
|
+
return Ok(if has_scheme {
|
|
494
|
+
path.to_owned()
|
|
495
|
+
} else {
|
|
496
|
+
format!("file://{path}")
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
let name = local
|
|
500
|
+
.strip_prefix(".\\")
|
|
501
|
+
.or_else(|| local.strip_prefix("./"))
|
|
502
|
+
.unwrap_or(local);
|
|
503
|
+
if has_scheme
|
|
504
|
+
|| name.is_empty()
|
|
505
|
+
|| name.contains(['\\', '/', ':'])
|
|
506
|
+
|| matches!(name, "." | "..")
|
|
507
|
+
{
|
|
484
508
|
return Err(Error::Unsupported(
|
|
485
|
-
"DGN raster path must be absolute".into(),
|
|
509
|
+
"DGN raster path must be absolute or name a file beside the DGN".into(),
|
|
486
510
|
));
|
|
487
511
|
}
|
|
488
|
-
Ok(if
|
|
489
|
-
|
|
512
|
+
Ok(if name.len() == local.len() {
|
|
513
|
+
String::new()
|
|
490
514
|
} else {
|
|
491
|
-
|
|
515
|
+
local.to_owned()
|
|
492
516
|
})
|
|
493
517
|
}
|
|
494
518
|
|