pycadkit 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. {pycadkit-0.3.0 → pycadkit-0.3.1}/Cargo.lock +10 -10
  2. {pycadkit-0.3.0 → pycadkit-0.3.1}/Cargo.toml +7 -7
  3. {pycadkit-0.3.0 → pycadkit-0.3.1}/PKG-INFO +1 -1
  4. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/lib.rs +1 -1
  5. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/map/mod.rs +107 -39
  6. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/encode.rs +7 -2
  7. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/mod.rs +109 -6
  8. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/raster.rs +72 -37
  9. pycadkit-0.3.1/crates/cadkit-dgn/src/writer/raster_template.rs +571 -0
  10. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests/raster.rs +1 -1
  11. pycadkit-0.3.1/crates/cadkit-dgn/src/writer/tests/raster_template.rs +649 -0
  12. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests.rs +1 -0
  13. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/common/mod.rs +25 -0
  14. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/private_corpus.rs +161 -0
  15. {pycadkit-0.3.0 → pycadkit-0.3.1}/LICENSE-APACHE +0 -0
  16. {pycadkit-0.3.0 → pycadkit-0.3.1}/LICENSE-MIT +0 -0
  17. {pycadkit-0.3.0 → pycadkit-0.3.1}/NOTICE +0 -0
  18. {pycadkit-0.3.0 → pycadkit-0.3.1}/README.md +0 -0
  19. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/Cargo.toml +0 -0
  20. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/LICENSE-APACHE +0 -0
  21. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/LICENSE-MIT +0 -0
  22. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/NOTICE +0 -0
  23. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/README.md +0 -0
  24. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/examples/interchange.rs +0 -0
  25. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit/src/lib.rs +0 -0
  26. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/Cargo.toml +0 -0
  27. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/LICENSE-APACHE +0 -0
  28. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/LICENSE-MIT +0 -0
  29. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/NOTICE +0 -0
  30. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/README.md +0 -0
  31. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/aci.rs +0 -0
  32. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/bytes.rs +0 -0
  33. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/error.rs +0 -0
  34. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/export.rs +0 -0
  35. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/geom.rs +0 -0
  36. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/geom_ops.rs +0 -0
  37. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/lib.rs +0 -0
  38. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/model.rs +0 -0
  39. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/ops.rs +0 -0
  40. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/options.rs +0 -0
  41. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/polygon.rs +0 -0
  42. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/svg.rs +0 -0
  43. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/text.rs +0 -0
  44. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-core/src/units.rs +0 -0
  45. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/Cargo.toml +0 -0
  46. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/LICENSE-APACHE +0 -0
  47. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/LICENSE-MIT +0 -0
  48. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/NOTICE +0 -0
  49. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/README.md +0 -0
  50. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/examples/dgn_summary.rs +0 -0
  51. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/examples/raster_structure.rs +0 -0
  52. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/budget.rs +0 -0
  53. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/cfb.rs +0 -0
  54. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/fuzz_tests.rs +0 -0
  55. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/le.rs +0 -0
  56. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/level_names.rs +0 -0
  57. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/map/v7.rs +0 -0
  58. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/map/v8.rs +0 -0
  59. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/decode_v8.rs +0 -0
  60. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/element.rs +0 -0
  61. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/linkage.rs +0 -0
  62. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/mod.rs +0 -0
  63. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/v7.rs +0 -0
  64. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/native/v8.rs +0 -0
  65. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/palette.rs +0 -0
  66. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/text.rs +0 -0
  67. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/container.rs +0 -0
  68. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/polyline.rs +0 -0
  69. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tables.rs +0 -0
  70. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests/application.rs +0 -0
  71. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests/microstation.rs +0 -0
  72. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests/regressions.rs +0 -0
  73. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/tests/text.rs +0 -0
  74. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/writer/text.rs +0 -0
  75. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/src/zlib.rs +0 -0
  76. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/robustness.rs +0 -0
  77. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/v7_corpus.rs +0 -0
  78. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/v8_oracle.rs +0 -0
  79. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/v8_public.rs +0 -0
  80. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dgn/tests/v8_range.rs +0 -0
  81. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/Cargo.toml +0 -0
  82. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/LICENSE-APACHE +0 -0
  83. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/LICENSE-MIT +0 -0
  84. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/NOTICE +0 -0
  85. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/README.md +0 -0
  86. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/examples/docsummary.rs +0 -0
  87. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/examples/dwgdump.rs +0 -0
  88. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/bits.rs +0 -0
  89. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/classes.rs +0 -0
  90. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/codepage.rs +0 -0
  91. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/lz77_r2004.rs +0 -0
  92. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/lz77_r2007.rs +0 -0
  93. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/mod.rs +0 -0
  94. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/r13.rs +0 -0
  95. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/r2004.rs +0 -0
  96. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/r2007.rs +0 -0
  97. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/container/reed_solomon.rs +0 -0
  98. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/crc.rs +0 -0
  99. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/handles.rs +0 -0
  100. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/header.rs +0 -0
  101. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/lib.rs +0 -0
  102. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/mapping/entity.rs +0 -0
  103. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/mapping/mod.rs +0 -0
  104. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/native/entities.rs +0 -0
  105. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/native/mod.rs +0 -0
  106. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/annotation.rs +0 -0
  107. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/curves.rs +0 -0
  108. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/entities.rs +0 -0
  109. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/hatch.rs +0 -0
  110. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/misc.rs +0 -0
  111. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/mod.rs +0 -0
  112. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/registry.rs +0 -0
  113. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/object/tables.rs +0 -0
  114. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/reader.rs +0 -0
  115. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/src/version.rs +0 -0
  116. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/tests/corpus.rs +0 -0
  117. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/tests/oracle.rs +0 -0
  118. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dwg/tests/robustness.rs +0 -0
  119. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/Cargo.toml +0 -0
  120. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/LICENSE-APACHE +0 -0
  121. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/LICENSE-MIT +0 -0
  122. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/NOTICE +0 -0
  123. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/README.md +0 -0
  124. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/examples/dump.rs +0 -0
  125. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/lib.rs +0 -0
  126. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/native/mod.rs +0 -0
  127. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/native/text.rs +0 -0
  128. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/ocs.rs +0 -0
  129. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/common.rs +0 -0
  130. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/entities.rs +0 -0
  131. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/hatch.rs +0 -0
  132. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/mod.rs +0 -0
  133. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/records.rs +0 -0
  134. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/reader/tables.rs +0 -0
  135. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/writer/curves.rs +0 -0
  136. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/writer/entities.rs +0 -0
  137. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/writer/mod.rs +0 -0
  138. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/src/writer/out.rs +0 -0
  139. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/ascii_vs_binary.rs +0 -0
  140. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/common/mod.rs +0 -0
  141. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/compare_reader_with_ezdxf.py +0 -0
  142. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/review_fixes.rs +0 -0
  143. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/robustness.rs +0 -0
  144. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/roundtrip.rs +0 -0
  145. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/synthetic_read.rs +0 -0
  146. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/synthetic_write.rs +0 -0
  147. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-dxf/tests/validate_with_ezdxf.py +0 -0
  148. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/Cargo.toml +0 -0
  149. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/LICENSE-APACHE +0 -0
  150. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/LICENSE-MIT +0 -0
  151. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/NOTICE +0 -0
  152. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/README.md +0 -0
  153. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/curves.rs +0 -0
  154. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/export.rs +0 -0
  155. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/geometry.rs +0 -0
  156. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/lib.rs +0 -0
  157. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/mapping.rs +0 -0
  158. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/native.rs +0 -0
  159. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/tkgm.rs +0 -0
  160. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/validate.rs +0 -0
  161. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/src/xml.rs +0 -0
  162. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/tests/roundtrip.rs +0 -0
  163. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/tests/support/export_cases.rs +0 -0
  164. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-gml/tests/support/tkgm_cases.rs +0 -0
  165. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/Cargo.toml +0 -0
  166. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/LICENSE-APACHE +0 -0
  167. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/LICENSE-MIT +0 -0
  168. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/NOTICE +0 -0
  169. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/README.md +0 -0
  170. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/src/lib.rs +0 -0
  171. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/tests/conftest.py +0 -0
  172. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/tests/test_cadkit.py +0 -0
  173. {pycadkit-0.3.0 → pycadkit-0.3.1}/crates/cadkit-py/tests/test_citygml.py +0 -0
  174. {pycadkit-0.3.0 → pycadkit-0.3.1}/pyproject.toml +0 -0
  175. {pycadkit-0.3.0 → pycadkit-0.3.1}/python/cadkit/__init__.py +0 -0
  176. {pycadkit-0.3.0 → pycadkit-0.3.1}/python/cadkit/__init__.pyi +0 -0
  177. {pycadkit-0.3.0 → pycadkit-0.3.1}/python/cadkit/_cadkit.pyi +0 -0
  178. {pycadkit-0.3.0 → pycadkit-0.3.1}/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.0"
69
+ version = "0.3.1"
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.0"
81
+ version = "0.3.1"
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.0"
90
+ version = "0.3.1"
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.0"
99
+ version = "0.3.1"
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.0"
108
+ version = "0.3.1"
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.0"
119
+ version = "0.3.1"
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.0"
129
+ version = "0.3.1"
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.0"
138
+ version = "0.3.1"
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.0"
148
+ version = "0.3.1"
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.0"
158
+ version = "0.3.1"
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.0"
7
+ version = "0.3.1"
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.0" }
18
- cadkit-dgn = { path = "crates/cadkit-dgn", version = "0.3.0" }
19
- cadkit-dwg = { path = "crates/cadkit-dwg", version = "0.3.0" }
20
- cadkit-dxf = { path = "crates/cadkit-dxf", version = "0.3.0" }
21
- cadkit-gml = { path = "crates/cadkit-gml", version = "0.3.0" }
22
- cadkit = { path = "crates/cadkit", version = "0.3.0" }
17
+ cadkit-core = { path = "crates/cadkit-core", version = "0.3.1" }
18
+ cadkit-dgn = { path = "crates/cadkit-dgn", version = "0.3.1" }
19
+ cadkit-dwg = { path = "crates/cadkit-dwg", version = "0.3.1" }
20
+ cadkit-dxf = { path = "crates/cadkit-dxf", version = "0.3.1" }
21
+ cadkit-gml = { path = "crates/cadkit-gml", version = "0.3.1" }
22
+ cadkit = { path = "crates/cadkit", version = "0.3.1" }
23
23
  serde = { version = "1", features = ["derive"] }
24
24
  serde_json = "1"
25
25
  thiserror = "2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycadkit
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -10,7 +10,7 @@
10
10
  pub mod cfb;
11
11
  pub mod native;
12
12
  pub mod writer;
13
- pub use writer::{WriteOptions, repack_v8, write_v8};
13
+ pub use writer::{WriteOptions, repack_v8, seed_has_raster_template, write_v8};
14
14
  pub mod palette;
15
15
 
16
16
  mod budget;
@@ -671,7 +671,7 @@ impl Mapper {
671
671
  e.props.insert("dgn.point_string".into(), Value::Bool(true));
672
672
  }
673
673
  ElementData::RasterFrame { matrix, .. } => {
674
- // Pixel-to-UOR matrix as stored; the image rectangle comes from the range.
674
+ // Pixel-to-UOR matrix as stored; the range only checks the placement.
675
675
  if let Some([lo, hi]) = item.el.header.range {
676
676
  let (a, b) = (xf.pt(lo), xf.pt(hi));
677
677
  e.props.insert(
@@ -683,6 +683,19 @@ impl Mapper {
683
683
  "dgn.raster_matrix".into(),
684
684
  Value::List(matrix.iter().map(|v| Value::Float(*v)).collect()),
685
685
  );
686
+ // `0x118` / `0x120`: horizontal and vertical resolution in dots per inch.
687
+ let dpi = crate::le::f64_at(item.raw, 0x118);
688
+ if let Some(x) = dpi {
689
+ e.props.insert("dgn.raster_dpi".into(), Value::Float(x));
690
+ }
691
+ if let Some(y) = crate::le::f64_at(item.raw, 0x120).filter(|y| Some(*y) != dpi) {
692
+ e.props.insert("dgn.raster_dpi_y".into(), Value::Float(y));
693
+ }
694
+ if let Some((rotation, skew)) = frame_angles(matrix) {
695
+ e.props
696
+ .insert("dgn.raster_rotation".into(), Value::Float(rotation));
697
+ e.props.insert("dgn.raster_skew".into(), Value::Float(skew));
698
+ }
686
699
  let id = item.el.header.id.unwrap_or(0);
687
700
  if let Some((name, full)) = self.raster_paths.get(&id) {
688
701
  if let Some(n) = name {
@@ -873,34 +886,33 @@ impl Mapper {
873
886
  fn image(&mut self, item: &Item<'_>, xf: &Xf) -> EntityKind {
874
887
  let id = item.el.header.id.unwrap_or(0);
875
888
  let range = item.el.header.range;
876
- let (matrix_placement, axes) = match &item.el.data {
877
- ElementData::RasterFrame { matrix, corner } => {
878
- let [m00, m01, _, _, m10, m11, _, _, m20, m21, ..] = *matrix;
879
- (
880
- frame_placement(matrix, *corner),
881
- Some((Vec3::new(m00, m10, m20), Vec3::new(m01, m11, m21))),
882
- )
883
- }
884
- _ => (None, None),
889
+ let (matrix_placement, pixels, axes) = match &item.el.data {
890
+ ElementData::RasterFrame { matrix, corner } => (
891
+ frame_placement(matrix, *corner),
892
+ frame_pixels(matrix, *corner),
893
+ Some(frame_axes(matrix)),
894
+ ),
895
+ _ => (None, None, None),
885
896
  };
886
- // The element range is MicroStation's own bounding box: a matrix placement that does
887
- // not fit inside it is rejected in favour of a placement derived from the range.
888
- let placement = match (matrix_placement, range) {
897
+ // The element range is MicroStation's own bounding box. It only checks the matrix
898
+ // placement: one that does not fit inside it is replaced by a placement derived from
899
+ // the range, and the pixel size is then not trusted either.
900
+ let (placement, size_px) = match (matrix_placement, range) {
889
901
  (Some(p), Some(r)) if !within_range(&p, &r, RASTER_RANGE_TOLERANCE) => {
890
902
  self.note("dgn.raster_outside_range", || {
891
903
  "raster transform places the image outside its element range; the range was used"
892
904
  .into()
893
905
  });
894
- Some(range_placement(&r, axes))
906
+ (Some(range_placement(&r, axes)), None)
895
907
  }
896
- (Some(p), _) => Some(p),
908
+ (Some(p), _) => (Some(p), pixels),
897
909
  (None, Some(r)) => {
898
910
  self.note("dgn.raster_range_placement", || {
899
911
  "raster frame placed from its range (no usable transform)".into()
900
912
  });
901
- Some(range_placement(&r, axes))
913
+ (Some(range_placement(&r, axes)), None)
902
914
  }
903
- (None, None) => None,
915
+ (None, None) => (None, None),
904
916
  };
905
917
  let Some((origin, u, v)) = placement else {
906
918
  return EntityKind::Unknown {
@@ -921,7 +933,7 @@ impl Mapper {
921
933
  position: xf.pt(origin),
922
934
  u_vector: tidy(u.scaled(xf.scale)),
923
935
  v_vector: tidy(v.scaled(xf.scale)),
924
- size_px: None,
936
+ size_px,
925
937
  }
926
938
  }
927
939
 
@@ -1109,30 +1121,63 @@ fn curve(points: &[UorPoint], xf: &Xf) -> EntityKind {
1109
1121
  }
1110
1122
  }
1111
1123
 
1124
+ /// Pixel X and Y axes of a raster frame matrix (row-major 4x4): its first two columns,
1125
+ /// one pixel long each, in UOR.
1126
+ pub(crate) fn frame_axes(m: &[f64; 16]) -> (Vec3, Vec3) {
1127
+ let [m00, m01, _, _, m10, m11, _, _, m20, m21, ..] = *m;
1128
+ (Vec3::new(m00, m10, m20), Vec3::new(m01, m11, m21))
1129
+ }
1130
+
1112
1131
  /// Raster frame placement from its pixel-to-UOR matrix (row-major, translation in the
1113
- /// last column): the image starts at the translation and its edges follow the matrix X / Y
1114
- /// columns, scaled so that the frame reaches its stored extent corner. Handles rotated and
1115
- /// sheared frames; `None` when the corner is missing or the system is degenerate.
1132
+ /// last column) and its edge lengths (`0x108`/`0x110`, UOR): the image starts at the
1133
+ /// translation (its lower-left corner) and its edges run along the unit matrix X / Y
1134
+ /// columns for those lengths, `u = â·ext_x`, `v = b̂·ext_y`. The lengths are measured
1135
+ /// along the image axes (width·|a| and height·|b|), not as an XY extent, so rotated and
1136
+ /// sheared frames need no solving. `None` when an axis or a length is degenerate.
1116
1137
  pub(crate) fn frame_placement(
1117
1138
  m: &[f64; 16],
1118
- corner: Option<[f64; 2]>,
1139
+ edges: Option<[f64; 2]>,
1119
1140
  ) -> Option<(UorPoint, Vec3, Vec3)> {
1120
- let [m00, m01, _, m03, m10, m11, _, m13, m20, m21, _, m23, ..] = *m;
1121
- let a = Vec3::new(m00, m10, m20);
1122
- let b = Vec3::new(m01, m11, m21);
1123
- // `0x108`/`0x110` is the frame's extent from its origin (the same values as the
1124
- // element range extent on the private sample), not an absolute corner.
1125
- let [dx, dy] = corner?;
1126
- let det = a.x * b.y - a.y * b.x;
1127
- if !det.is_finite() || det.abs() <= 1e-12 * a.length() * b.length() {
1141
+ let [_, _, _, m03, _, _, _, m13, _, _, _, m23, ..] = *m;
1142
+ let (a, b) = frame_axes(m);
1143
+ let [ex, ey] = edges?;
1144
+ if !(ex.is_finite() && ey.is_finite() && ex > 0.0 && ey > 0.0) {
1128
1145
  return None;
1129
1146
  }
1130
- let w = (dx * b.y - dy * b.x) / det;
1131
- let h = (a.x * dy - a.y * dx) / det;
1132
- if !(w.is_finite() && h.is_finite() && w > 0.0 && h > 0.0) {
1147
+ let (ua, ub) = (a.normalized()?, b.normalized()?);
1148
+ // Parallel axes span no rectangle.
1149
+ if ua.cross(ub).length() <= 1e-9 {
1133
1150
  return None;
1134
1151
  }
1135
- Some(([m03, m13, m23], a.scaled(w), b.scaled(h)))
1152
+ if ![m03, m13, m23].iter().all(|v| v.is_finite()) {
1153
+ return None;
1154
+ }
1155
+ Some(([m03, m13, m23], ua.scaled(ex), ub.scaled(ey)))
1156
+ }
1157
+
1158
+ /// Pixel size of a raster frame: its edge lengths over the pixel lengths `|a|` and `|b|`
1159
+ /// of the matrix axes, rounded. `None` when that is not a positive `u32`.
1160
+ pub(crate) fn frame_pixels(m: &[f64; 16], edges: Option<[f64; 2]>) -> Option<[u32; 2]> {
1161
+ let (a, b) = frame_axes(m);
1162
+ let [ex, ey] = edges?;
1163
+ let count = |edge: f64, axis: Vec3| {
1164
+ let n = (edge / axis.length()).round();
1165
+ (n.is_finite() && n >= 1.0 && n <= f64::from(u32::MAX)).then_some(n as u32)
1166
+ };
1167
+ Some([count(ex, a)?, count(ey, b)?])
1168
+ }
1169
+
1170
+ /// Rotation of the image X axis and skew of its Y axis from the perpendicular, both in
1171
+ /// the XY plane, in radians within (-π, π].
1172
+ fn frame_angles(m: &[f64; 16]) -> Option<(f64, f64)> {
1173
+ let (a, b) = frame_axes(m);
1174
+ if a.normalized().is_none() || b.normalized().is_none() {
1175
+ return None;
1176
+ }
1177
+ let rotation = a.y.atan2(a.x);
1178
+ let skew = b.y.atan2(b.x) - rotation - FRAC_PI_2;
1179
+ let skew = skew.sin().atan2(skew.cos());
1180
+ (rotation.is_finite() && skew.is_finite()).then_some((rotation, skew))
1136
1181
  }
1137
1182
 
1138
1183
  /// Most XAttribute kinds listed on one entity (the count is always complete).
@@ -1529,23 +1574,46 @@ mod tests {
1529
1574
 
1530
1575
  #[test]
1531
1576
  fn raster_placement_follows_the_matrix() {
1532
- // 30 degree rotation, 2 UOR per pixel, translation (100, 50); a 300 x 200 pixel image.
1533
- // The frame stores its extent from the translation (rotated frames unverified).
1577
+ // 30 degree rotation, 2 UOR per pixel, translation (100, 50); a 300 x 200 pixel
1578
+ // image. `0x108`/`0x110` hold the edge lengths along the image axes (W·|a|, H·|b|).
1534
1579
  let (s, c) = 30f64.to_radians().sin_cos();
1535
1580
  let (a, b) = ([2.0 * c, 2.0 * s], [-2.0 * s, 2.0 * c]);
1536
1581
  let m = [
1537
1582
  a[0], b[0], 0.0, 100.0, a[1], b[1], 0.0, 50.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1538
1583
  ];
1539
- let corner = [300.0 * a[0] + 200.0 * b[0], 300.0 * a[1] + 200.0 * b[1]];
1540
- let (o, u, v) = frame_placement(&m, Some(corner)).unwrap();
1584
+ let edges = [600.0, 400.0];
1585
+ let (o, u, v) = frame_placement(&m, Some(edges)).unwrap();
1541
1586
  assert_eq!(o, [100.0, 50.0, 0.0]);
1542
1587
  assert!(close(u.x, 600.0 * c) && close(u.y, 600.0 * s));
1543
1588
  assert!(close(v.x, -400.0 * s) && close(v.y, 400.0 * c));
1589
+ assert_eq!(frame_pixels(&m, Some(edges)), Some([300, 200]));
1590
+ let (rotation, skew) = frame_angles(&m).unwrap();
1591
+ assert!(close(rotation, 30f64.to_radians()) && close(skew, 0.0));
1544
1592
  assert!(frame_placement(&m, None).is_none());
1593
+ assert!(frame_placement(&m, Some([0.0, 400.0])).is_none());
1545
1594
  let mut flat = m;
1546
1595
  flat[1] = flat[0];
1547
1596
  flat[5] = flat[4];
1548
- assert!(frame_placement(&flat, Some(corner)).is_none(), "degenerate");
1597
+ assert!(frame_placement(&flat, Some(edges)).is_none(), "degenerate");
1598
+ }
1599
+
1600
+ #[test]
1601
+ fn sheared_raster_frames_keep_their_axes() {
1602
+ // X axis 3 UOR per pixel along +X; Y axis 2 UOR per pixel tilted 10 degrees left of
1603
+ // +Y. The Y edge runs along the tilted axis for H·|b| UOR.
1604
+ let (s, c) = 10f64.to_radians().sin_cos();
1605
+ let (a, b) = ([3.0, 0.0], [-2.0 * s, 2.0 * c]);
1606
+ let m = [
1607
+ a[0], b[0], 0.0, -5.0, a[1], b[1], 0.0, 7.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1608
+ ];
1609
+ let edges = [3.0 * 120.0, 2.0 * 80.0];
1610
+ let (o, u, v) = frame_placement(&m, Some(edges)).unwrap();
1611
+ assert_eq!(o, [-5.0, 7.0, 0.0]);
1612
+ assert!(close(u.x, 360.0) && close(u.y, 0.0));
1613
+ assert!(close(v.x, -160.0 * s) && close(v.y, 160.0 * c));
1614
+ assert_eq!(frame_pixels(&m, Some(edges)), Some([120, 80]));
1615
+ let (rotation, skew) = frame_angles(&m).unwrap();
1616
+ assert!(close(rotation, 0.0) && close(skew, 10f64.to_radians()));
1549
1617
  }
1550
1618
 
1551
1619
  #[test]
@@ -240,7 +240,12 @@ pub(super) fn utf16_unterminated(value: &str) -> Result<Vec<u8>> {
240
240
  }
241
241
 
242
242
  pub(super) fn string_link(id: u32, value: &str) -> Result<Vec<u8>> {
243
- let text = utf16_unterminated(value)?;
243
+ string_link_bytes(id, &utf16_unterminated(value)?)
244
+ }
245
+
246
+ /// String linkage holding `text` as already encoded (`ff fd` + UTF-16LE or 8-bit bytes),
247
+ /// zero padded to 8 bytes; its length counts no terminator.
248
+ pub(super) fn string_link_bytes(id: u32, text: &[u8]) -> Result<Vec<u8>> {
244
249
  let size = (12 + text.len() + 7) & !7;
245
250
  if size > 512 {
246
251
  return Err(Error::LimitExceeded(
@@ -252,7 +257,7 @@ pub(super) fn string_link(id: u32, value: &str) -> Result<Vec<u8>> {
252
257
  put(&mut b, 2, &0x56d2u16.to_le_bytes())?;
253
258
  u32_at(&mut b, 4, id)?;
254
259
  u32_at(&mut b, 8, text.len() as u32)?;
255
- put(&mut b, 12, &text)?;
260
+ put(&mut b, 12, text)?;
256
261
  Ok(b)
257
262
  }
258
263
  pub(super) fn dependency(app: u16, id: u64) -> Result<Vec<u8>> {
@@ -4,12 +4,13 @@ mod container;
4
4
  mod encode;
5
5
  mod polyline;
6
6
  mod raster;
7
+ mod raster_template;
7
8
  mod tables;
8
9
  mod text;
9
10
 
10
11
  use crate::{
11
12
  le,
12
- native::v8::{self, ModelHeader, RawElement},
13
+ native::v8::{self, AuxRecord, ModelHeader, PageHeader, RawElement},
13
14
  };
14
15
  use cadkit_core::{
15
16
  Attribute, Document, Entity, EntityKind, Error, GroupKind, Limits, Point3, ReadOptions, Result,
@@ -39,7 +40,9 @@ pub struct WriteOptions {
39
40
  /// drawing units. Defaults to 0.01 (1 cm for metres), allowing coordinate rounding.
40
41
  pub planarity_tolerance: f64,
41
42
  /// Preserve unchanged raster attachments from the same seed, including their
42
- /// opaque control and auxiliary records. New or edited attachments are rejected.
43
+ /// opaque control and auxiliary records. Edited attachments are rejected; images
44
+ /// without an id are new attachments cloned from a seed attachment (see
45
+ /// [`seed_has_raster_template`]) whatever this option says.
43
46
  pub preserve_seed_rasters: bool,
44
47
  /// Keep the seed model's control records that reference no seed graphic (coordinate
45
48
  /// system, model settings and similar) with their auxiliary data, byte for byte.
@@ -68,6 +71,20 @@ pub fn repack_v8(seed: &[u8], options: &ReadOptions) -> Result<Vec<u8>> {
68
71
  container::rewrite(seed, &BTreeMap::new(), options)
69
72
  }
70
73
 
74
+ /// Whether `seed` can receive new raster attachments: its single model holds at least one
75
+ /// complete raster attachment (the records 92, 93, 93, 90, 91 x 5 in the control pages,
76
+ /// the frame 94 they point at and its XAttribute 170) that [`write_v8`] clones for every
77
+ /// [`EntityKind::Image`] without an id. Seeds with several models return `false`, as
78
+ /// `write_v8` accepts single-model seeds only. `Err` when the seed is not a readable
79
+ /// DGN V8 file.
80
+ pub fn seed_has_raster_template(seed: &[u8], options: &ReadOptions) -> Result<bool> {
81
+ let native = v8::read(seed, options)?;
82
+ Ok(match native.models.as_slice() {
83
+ [model] => raster_template::Template::capture(model).is_some(),
84
+ _ => false,
85
+ })
86
+ }
87
+
71
88
  fn compressed(bytes: &[u8]) -> Result<Vec<u8>> {
72
89
  let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
73
90
  encoder.write_all(bytes)?;
@@ -232,6 +249,8 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
232
249
  rasters: &rasters,
233
250
  records: vec![],
234
251
  tags: vec![],
252
+ new_controls: vec![],
253
+ new_xattributes: vec![],
235
254
  work: 0,
236
255
  vertices: 0,
237
256
  bytes: rasters.bytes,
@@ -245,6 +264,8 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
245
264
  state.entity(entity, 0, false)?;
246
265
  }
247
266
  state.records.append(&mut state.tags);
267
+ let new_controls = std::mem::take(&mut state.new_controls);
268
+ let new_xattributes = std::mem::take(&mut state.new_xattributes);
248
269
  let records = state.records;
249
270
  let named = state.tables.finish(options.timestamp_ms)?;
250
271
  let max_id = records
@@ -305,14 +326,50 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
305
326
  .map(|p| p.header.format_version)
306
327
  .filter(|v| matches!(v, 2 | 3))
307
328
  .unwrap_or(3);
308
- put_pages(
329
+ let record_pages = put_pages(
309
330
  &mut replacements,
310
331
  &format!("Dgn-Md/{}/Dgn^G", model.storage),
311
332
  &records,
312
333
  version,
313
334
  options.limits.max_decompressed_bytes,
314
335
  )?;
315
- rasters.write_auxiliary(&mut replacements)?;
336
+ rasters.write_auxiliary(&mut replacements, &new_controls)?;
337
+ if let Some(template) = rasters
338
+ .template
339
+ .as_ref()
340
+ .filter(|_| !new_xattributes.is_empty())
341
+ {
342
+ // An XAttribute sits in the auxiliary page numbered like the graphic page of its
343
+ // element, as in every observed attachment.
344
+ let mut pages: BTreeMap<u32, Vec<AuxRecord>> = BTreeMap::new();
345
+ for (index, record) in new_xattributes {
346
+ let number = record_pages
347
+ .get(index)
348
+ .copied()
349
+ .ok_or_else(|| Error::invalid(0, "raster frame record outside the pages"))?;
350
+ pages.entry(number).or_default().push(record);
351
+ }
352
+ let mut last = 0;
353
+ for (number, records) in &pages {
354
+ let header = PageHeader {
355
+ record_count: 0,
356
+ format_version: template.aux_version,
357
+ page_number: *number,
358
+ population: 0,
359
+ };
360
+ replacements.insert(
361
+ format!("Dgn-Md/{}/Dgn^GA/${number}", model.storage),
362
+ Some(raster::aux_page(records, header)?),
363
+ );
364
+ last = *number;
365
+ }
366
+ // `^AH` is at least the highest page number in every observed file; the next free
367
+ // number is the most common value.
368
+ replacements.insert(
369
+ format!("Dgn-Md/{}/Dgn^GA/^AH", model.storage),
370
+ Some(last.saturating_add(1).to_le_bytes().to_vec()),
371
+ );
372
+ }
316
373
  if rasters.keep_control_counter {
317
374
  // Every control auxiliary page is retained as is; so is its page counter.
318
375
  replacements.remove(&format!("Dgn-Md/{}/Dgn^CA/^AH", model.storage));
@@ -391,13 +448,15 @@ pub fn write_v8(doc: &Document, seed: &[u8], options: &WriteOptions) -> Result<V
391
448
  #[cfg(test)]
392
449
  mod tests;
393
450
 
451
+ /// Writes `records` to pages `{prefix}/$1`, `$2`, ... of at most 256 KiB and returns the
452
+ /// page number of every record.
394
453
  fn put_pages(
395
454
  replacements: &mut BTreeMap<String, Option<Vec<u8>>>,
396
455
  prefix: &str,
397
456
  records: &[Vec<u8>],
398
457
  version: u32,
399
458
  max: u64,
400
- ) -> Result<()> {
459
+ ) -> Result<Vec<u32>> {
401
460
  let total = records
402
461
  .iter()
403
462
  .try_fold(0u64, |sum, r| sum.checked_add(r.len() as u64 + 4))
@@ -408,6 +467,7 @@ fn put_pages(
408
467
  let mut first = 0;
409
468
  let mut bytes = 0usize;
410
469
  let mut number = 1u32;
470
+ let mut numbers = Vec::with_capacity(records.len());
411
471
  for (i, r) in records.iter().enumerate() {
412
472
  if bytes > 0 && bytes.saturating_add(r.len() + 4) > 256 * 1024 {
413
473
  replacements.insert(
@@ -421,12 +481,13 @@ fn put_pages(
421
481
  .ok_or_else(|| Error::LimitExceeded("DGN page count".into()))?;
422
482
  }
423
483
  bytes = bytes.saturating_add(r.len() + 4);
484
+ numbers.push(number);
424
485
  }
425
486
  replacements.insert(
426
487
  format!("{prefix}/${number}"),
427
488
  Some(page(records.get(first..).unwrap_or(&[]), version, number)?),
428
489
  );
429
- Ok(())
490
+ Ok(numbers)
430
491
  }
431
492
 
432
493
  /// An absent tag set is distinct from every explicitly named tag set.
@@ -476,6 +537,10 @@ struct State<'a> {
476
537
  rasters: &'a raster::Preserved,
477
538
  records: Vec<Vec<u8>>,
478
539
  tags: Vec<Vec<u8>>,
540
+ /// Control records of new raster attachments, in stored order.
541
+ new_controls: Vec<Vec<u8>>,
542
+ /// XAttributes of new raster frames with the index of the frame in `records`.
543
+ new_xattributes: Vec<(usize, AuxRecord)>,
479
544
  work: u64,
480
545
  vertices: u64,
481
546
  bytes: u64,
@@ -584,9 +649,14 @@ impl State<'_> {
584
649
 
585
650
  fn add(&mut self, b: Vec<u8>, tag: bool) -> Result<()> {
586
651
  self.bytes = self.bytes.saturating_add(b.len() as u64);
652
+ let new_rasters = self
653
+ .new_controls
654
+ .len()
655
+ .saturating_add(self.new_xattributes.len()) as u64;
587
656
  if self.bytes > self.options.limits.max_decompressed_bytes
588
657
  || (self.records.len().saturating_add(self.tags.len()) as u64)
589
658
  .saturating_add(self.rasters.objects)
659
+ .saturating_add(new_rasters)
590
660
  >= self.options.limits.max_objects
591
661
  {
592
662
  return Err(Error::LimitExceeded("DGN output record budget".into()));
@@ -607,6 +677,9 @@ impl State<'_> {
607
677
  .tables
608
678
  .level(e.layer.as_deref(), self.options.timestamp_ms)?;
609
679
  if matches!(e.kind, EntityKind::Image { .. }) {
680
+ if e.id.is_none() {
681
+ return self.new_raster(e, level, component);
682
+ }
610
683
  let (id, record) = self.rasters.frame(e, component)?;
611
684
  self.add(record, false)?;
612
685
  return self.attributes(e, id, level);
@@ -964,6 +1037,36 @@ impl State<'_> {
964
1037
  self.add(finish(b, &links)?, false)?;
965
1038
  self.attributes(e, id, level)
966
1039
  }
1040
+ /// A new raster attachment cloned from the seed template (see `raster_template`).
1041
+ fn new_raster(&mut self, e: &Entity, level: u32, component: bool) -> Result<()> {
1042
+ if component {
1043
+ return Err(Error::Unsupported(
1044
+ "new DGN raster attachments cannot be complex group components".into(),
1045
+ ));
1046
+ }
1047
+ let template = self
1048
+ .rasters
1049
+ .template
1050
+ .as_ref()
1051
+ .ok_or_else(|| Error::Unsupported(raster_template::NO_TEMPLATE.into()))?;
1052
+ let mut ids = [0u64; 10];
1053
+ for id in &mut ids {
1054
+ *id = self.tables.id()?;
1055
+ }
1056
+ let group = template.instantiate(e, ids, level, self.model, self.options.timestamp_ms)?;
1057
+ let extra = group
1058
+ .controls
1059
+ .iter()
1060
+ .map(|c| c.len() as u64)
1061
+ .sum::<u64>()
1062
+ .saturating_add(group.xattribute.payload.len() as u64 + 28);
1063
+ self.bytes = self.bytes.saturating_add(extra);
1064
+ let index = self.records.len();
1065
+ self.new_controls.extend(group.controls);
1066
+ self.new_xattributes.push((index, group.xattribute));
1067
+ self.add(group.frame, false)?;
1068
+ self.attributes(e, group.frame_id, level)
1069
+ }
967
1070
  fn points(
968
1071
  &mut self,
969
1072
  e: &Entity,