Skip to content
Merged
30 changes: 30 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5230,6 +5230,36 @@ cargo run --release --example probe_shape -- ../dwg-corpus

The spec's own example (§20.4.37.1) is an R14 record; it now runs through
the decoder, and `spec_examples` records its outcome.

## 7bu. VIEWPORT ENTITY HEADER (2026-10-26, #146)

§20.4.70 prints the header the older releases keep for each viewport entity
(type `0x47`): the name and flags a symbol-table entry opens with, as
`TV` name, `B` 64-flag, `BS` xrefindex + 1 and `B` xdep, then one more `B`,
"1 flag". Only R14 and R2000 drawings hold one. The handle references (the
control object, the extension dictionary, the external reference block, the
viewport entity it describes and the previous header of the chain) are in the
handle stream and are not read. The record had no decoder, so its type
stayed `Unhandled`.

All 6 records of the corpus close on their data-stream boundary, 3 on
R14 and 3 on R2000 (the `RL` inside the common object data on R14, the
prologue's on R2000): the names read `""`, `"1"` and `""` on each, the
64-flag and the 1 flag are set and the xref index is 0, the shape of a
header with no xref behind it. This is boundary evidence alone. AutoCAD's DXF
exports of the corpus hold no record for the header (R14's is R12 DXF, which
has no OBJECTS section, and R2000's has no viewport entity table), so no value
is compared. The spec's own example (§20.4.70.1, an R14 record) now runs
through the decoder as well, and `spec_examples` records its outcome.

```sh
cargo run --release --example probe_vp_ent_hdr -- ../dwg-corpus
```

| Release | Records | Close | Values compared with a DXF |
|---|---|---|---|
| R14 | 3 | 3 | no twin |
| R2000 | 3 | 3 | the DXF holds no such record |
## 8. Write pipeline (current scope)

The inverse pipeline is partially shipped. Stage-1 (per-section
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@
This is **0.1.0-alpha.1**. Do not use it in production. Do not benchmark it against the ODA SDK. Do not tell your CAD team dwg-rs solves their interop problem today.

Entity and object decode coverage, measured in CI by the `Corpus (public DWG)`
job on `main` (<!-- corpus-run:begin -->[run 40736084515](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40736084515) on 2026-10-28<!-- corpus-run:end -->) with
job on `main` (<!-- corpus-run:begin -->[run 40739158933](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40739158933) on 2026-10-28<!-- corpus-run:end -->) with
[`examples/coverage_report.rs`](./examples/coverage_report.rs), on the public
drawings fetched by pinned sha256
([`tools/corpus/pins.tsv`](./tools/corpus/pins.tsv); none is committed):

<!-- corpus-table:begin -->
| Version | Files tested | Decoded | Skipped | Errored | Success rate | Median drawing |
|---------|--------------|---------|---------|---------|--------------|----------------|
| R14 (AC1014) | 12 | 4734 | 22 | 0 | **99.5 %** | 100.0 % |
| R2000 (AC1015) | 9 | 2782 | 3 | 0 | **99.9 %** | 100.0 % |
| R14 (AC1014) | 12 | 4737 | 19 | 0 | **99.6 %** | 100.0 % |
| R2000 (AC1015) | 9 | 2785 | 0 | 0 | **100.0 %** | 100.0 % |
| R2004 (AC1018) | 12 | 106994 | 0 | 0 | **100.0 %** | 100.0 % |
| R2007 (AC1021) | 8 | 2237 | 0 | 0 | **100.0 %** | 100.0 % |
| R2010 (AC1024) | 8 | 2188 | 0 | 0 | **100.0 %** | 100.0 % |
| R2013 (AC1027) | 8 | 1787 | 0 | 0 | **100.0 %** | 100.0 % |
| R2018 (AC1032) | 4 | 1615 | 0 | 0 | **100.0 %** | 100.0 % |
| **Aggregate** | **61** | **122337** | **25** | **0** | **> 99.9 %** | **100.0 %** |
| **Aggregate** | **61** | **122343** | **19** | **0** | **> 99.9 %** | **100.0 %** |
<!-- corpus-table:end -->

A success rate counts records, and the sources differ in size by orders of
Expand All @@ -46,14 +46,14 @@ a change came from decoding or from the corpus:
| Source | Files tested | Decoded | Skipped | Errored | Success rate | Median drawing |
|--------|--------------|---------|---------|---------|--------------|----------------|
| nextgis | 42 | 8443 | 0 | 0 | **100.0 %** | 100.0 % |
| acadsharp | 10 | 7625 | 6 | 0 | **99.9 %** | 100.0 % |
| acadsharp | 10 | 7631 | 0 | 0 | **100.0 %** | 100.0 % |
| bookviewerapp3 | 4 | 1440 | 19 | 0 | **98.7 %** | 99.4 % |
| oshw-jetson | 4 | 104577 | 0 | 0 | **100.0 %** | 100.0 % |
| planar-algorithm | 1 | 252 | 0 | 0 | **100.0 %** | 100.0 % |
| **All sources** | **61** | **122337** | **25** | **0** | **> 99.9 %** | **100.0 %** |
| **All sources** | **61** | **122343** | **19** | **0** | **> 99.9 %** | **100.0 %** |
<!-- corpus-sources:end -->

Totals: <!-- corpus-summary:begin -->122,337 decoded / 25 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.9 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %.<!-- corpus-summary:end -->
Totals: <!-- corpus-summary:begin -->122,343 decoded / 19 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 100.0 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.6 %, R2000 100.0 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %.<!-- corpus-summary:end -->

[`tools/corpus/baseline.tsv`](./tools/corpus/baseline.tsv) pins each
drawing's decoded, skipped and errored counts, its class-census shortfalls
Expand Down
4 changes: 2 additions & 2 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ the current capability matrix.
examples (`tools/ci/run_test_targets.sh`), and the Corpus job runs the
corpus-reading targets on the 19 pinned drawings; clippy and fmt clean.
- **Current real-file decode coverage, measured in CI** (the
`Corpus (public DWG)` job, <!-- corpus-run:begin -->[run 40736084515](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40736084515) on 2026-10-28<!-- corpus-run:end -->, on the public drawings
pinned in `tools/corpus/pins.tsv`): <!-- corpus-summary:begin -->122,337 decoded / 25 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 99.9 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.5 %, R2000 99.9 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %.<!-- corpus-summary:end -->
`Corpus (public DWG)` job, <!-- corpus-run:begin -->[run 40739158933](https://github.com/DrunkOnJava/dwg-rs/actions/runs/40739158933) on 2026-10-28<!-- corpus-run:end -->, on the public drawings
pinned in `tools/corpus/pins.tsv`): <!-- corpus-summary:begin -->122,343 decoded / 19 skipped / **0 errored** / > 99.9 % of all records on 61 drawings; the median drawing decodes 100.0 %. Per source (drawings): nextgis 100.0 % (42), acadsharp 100.0 % (10), bookviewerapp3 98.7 % (4), oshw-jetson 100.0 % (4), planar-algorithm 100.0 % (1). Per version: R14 99.6 %, R2000 100.0 %, R2004 100.0 %, R2007 100.0 %, R2010 100.0 %, R2013 100.0 %, R2018 100.0 %.<!-- corpus-summary:end -->
The 308 errors of the 2026-10-26 measurement were closed by #226,
#227, #228 and #71; `sample_AC1032.dwg` comes from ACadSharp's MIT
`samples/`, saved by AutoCAD 2025 (#96, CLEANROOM.md).
Expand Down
2 changes: 1 addition & 1 deletion docs/data/spec-coverage.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
20.4.67 DIMSTYLE CONTROL (68) 177 closes src/objects/control.rs #148 DXF oracle (examples/dxf_oracle): 42 records R2000-R2018, every decoded value equals AutoCAD's (84 equal, 0 by the DXF default, 0 with no DXF group); 135 DXF groups not decoded. The spec's own example (§20.4.67.1) closes on the record's boundary (#107).
20.4.68 DIMSTYLE (69) 178 closes src/tables/dimstyle.rs #148 R13/R14 block measured on all 20 R14 records of the public corpus (#74, ARCHITECTURE §7z); The 3 records each on ACadSharp's R2007 and R2010 saves that errored close since #293 (the BL reactor count of the R2007+ entry prefix); no record of any kind errors on the 52 drawings (run 40532346172 on main e7c53e1, corpus-results.json: errored 0, `errors` empty). DXF oracle (examples/dxf_oracle): 108 records R2000-R2018, every decoded value equals AutoCAD's (699 equal, 1137 by the DXF default, 216 with no DXF group); 1159 DXF groups not decoded. The spec's own example (§20.4.68.1) closes on the record's boundary (#107).
20.4.69 VIEWPORT ENTITY CONTROL (70) 182 decoder src/objects/control.rs #146 The spec's own example (§20.4.69.1) closes on the record's boundary (#107).
20.4.70 VIEWPORT ENTITY HEADER (71) 183 missing #146 The spec's own example (§20.4.70.1) has no decoder to run (#107).
20.4.70 VIEWPORT ENTITY HEADER (71) 183 closes src/objects/control_vp_ent_hdr.rs #146 Closes on all 6 VIEWPORT ENTITY HEADER records of the corpus (3 on R14, 3 on R2000; later releases hold none), and on the spec's own example (§20.4.70.1, an R14 record), which closed here for the first time (#107). Boundary evidence only: AutoCAD's DXF exports of the corpus hold no record for the header, so no value is compared (examples/probe_vp_ent_hdr). No R13 file is in the corpus.
20.4.71 AcDbAnnotScaleObjectContextData 184 missing #149
20.4.72 GROUP (72) 184 closes src/objects/acad_group.rs #149 The spec's own example (§20.4.72.1) closes on the record's boundary (#107).
20.4.73 MLINESTYLE (73) 185 closes src/objects/acad_mlinestyle.rs #147 All 10 corpus records, four release bands (ARCHITECTURE §7d.3). DXF oracle (examples/dxf_oracle): 42 records R2000-R2018, every decoded value equals AutoCAD's (462 equal, 0 by the DXF default, 84 with no DXF group); 168 DXF groups not decoded. The spec's own example (§20.4.73.1) closes on the record's boundary (#107).
Expand Down
6 changes: 3 additions & 3 deletions docs/spec-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ One row for every section of the *Open Design Specification for .dwg files* v5.4

## Summary

- All sections: 0 `complete`, 106 `closes`, 6 `decoder`, 6 `partial`, 20 `missing`.
- The 104 object prescriptions of §20.4: 0 `complete`, 86 `closes`, 6 `decoder`, 2 `partial`, 10 `missing`.
- All sections: 0 `complete`, 107 `closes`, 6 `decoder`, 6 `partial`, 19 `missing`.
- The 104 object prescriptions of §20.4: 0 `complete`, 87 `closes`, 6 `decoder`, 2 `partial`, 9 `missing`.

## Sections

Expand Down Expand Up @@ -120,7 +120,7 @@ One row for every section of the *Open Design Specification for .dwg files* v5.4
| §20.4.67 | DIMSTYLE CONTROL (68) | 177 | `closes` | src/objects/control.rs | #148 | DXF oracle (examples/dxf_oracle): 42 records R2000-R2018, every decoded value equals AutoCAD's (84 equal, 0 by the DXF default, 0 with no DXF group); 135 DXF groups not decoded. The spec's own example (§20.4.67.1) closes on the record's boundary (#107). |
| §20.4.68 | DIMSTYLE (69) | 178 | `closes` | src/tables/dimstyle.rs | #148 | R13/R14 block measured on all 20 R14 records of the public corpus (#74, ARCHITECTURE §7z); The 3 records each on ACadSharp's R2007 and R2010 saves that errored close since #293 (the BL reactor count of the R2007+ entry prefix); no record of any kind errors on the 52 drawings (run 40532346172 on main e7c53e1, corpus-results.json: errored 0, `errors` empty). DXF oracle (examples/dxf_oracle): 108 records R2000-R2018, every decoded value equals AutoCAD's (699 equal, 1137 by the DXF default, 216 with no DXF group); 1159 DXF groups not decoded. The spec's own example (§20.4.68.1) closes on the record's boundary (#107). |
| §20.4.69 | VIEWPORT ENTITY CONTROL (70) | 182 | `decoder` | src/objects/control.rs | #146 | The spec's own example (§20.4.69.1) closes on the record's boundary (#107). |
| §20.4.70 | VIEWPORT ENTITY HEADER (71) | 183 | `missing` | | #146 | The spec's own example (§20.4.70.1) has no decoder to run (#107). |
| §20.4.70 | VIEWPORT ENTITY HEADER (71) | 183 | `closes` | src/objects/control_vp_ent_hdr.rs | #146 | Closes on all 6 VIEWPORT ENTITY HEADER records of the corpus (3 on R14, 3 on R2000; later releases hold none), and on the spec's own example (§20.4.70.1, an R14 record), which closed here for the first time (#107). Boundary evidence only: AutoCAD's DXF exports of the corpus hold no record for the header, so no value is compared (examples/probe_vp_ent_hdr). No R13 file is in the corpus. |
| §20.4.71 | AcDbAnnotScaleObjectContextData | 184 | `missing` | | #149 | |
| §20.4.72 | GROUP (72) | 184 | `closes` | src/objects/acad_group.rs | #149 | The spec's own example (§20.4.72.1) closes on the record's boundary (#107). |
| §20.4.73 | MLINESTYLE (73) | 185 | `closes` | src/objects/acad_mlinestyle.rs | #147 | All 10 corpus records, four release bands (ARCHITECTURE §7d.3). DXF oracle (examples/dxf_oracle): 42 records R2000-R2018, every decoded value equals AutoCAD's (462 equal, 0 by the DXF default, 84 with no DXF group); 168 DXF groups not decoded. The spec's own example (§20.4.73.1) closes on the record's boundary (#107). |
Expand Down
52 changes: 52 additions & 0 deletions examples/probe_vp_ent_hdr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Does the §20.4.70 VIEWPORT ENTITY HEADER field list close on every corpus
//! record?
//!
//! # What this proves
//!
//! For every VP_ENT_HDR (type 0x47) of every drawing under the corpus
//! directory this reports whether the crate's decoder closed exactly on the
//! record's data-stream boundary (ARCHITECTURE.md §7i), and prints the entry
//! name and flags it read. Only R14 and R2000 drawings hold one, and
//! AutoCAD's DXF exports of the corpus (R12 for R14, R2000's own) hold no
//! record for it, so there is nothing to compare values with: the evidence is
//! the boundary.
//!
//! ```sh
//! python3 -I tools/corpus/fetch.py --dest ../dwg-corpus
//! cargo run --release --example probe_vp_ent_hdr -- ../dwg-corpus
//! ```
//!
//! Exit status is non-zero when a record errors or is not decoded.

#[path = "support/twin.rs"]
mod twin;

use dwg::ObjectType;
use dwg::entities::DecodedEntity;
use std::process::ExitCode;
use twin::{Probe, Record, Tally};

fn compare(decoded: &DecodedEntity, _rec: Option<&Record>, _t: &mut Tally) -> bool {
let DecodedEntity::VpEntHdr(ours) = decoded else {
return false;
};
println!(
" name {:?}, 64-flag {}, xref index + 1 {}, xdep {}, 1 flag {}",
ours.name, ours.flag_64, ours.xref_index_plus_1, ours.is_xref_dependent, ours.flag_1
);
true
}

fn main() -> ExitCode {
twin::run(
&Probe {
tag: "VP_ENT_HDR",
class: None,
kind: Some(ObjectType::VpEntHdr),
dxf_kind: "",
decodes_on: |_| true,
note: "; the DXF exports hold no VIEWPORT ENTITY HEADER, so only the boundary is checked",
},
compare,
)
}
9 changes: 9 additions & 0 deletions src/entities/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub enum DecodedEntity {
XRecord(crate::objects::xrecord::XRecord),
Placeholder(crate::objects::placeholder::Placeholder),
Group(crate::objects::acad_group::AcadGroup),
VpEntHdr(crate::objects::control_vp_ent_hdr::VpEntHdr),
Scale(crate::objects::acad_scale::AcadScale),
VisualStyle(crate::objects::acad_visual_style::AcadVisualStyle),
Layout(Box<crate::objects::acad_layout::AcadLayout>),
Expand Down Expand Up @@ -300,6 +301,7 @@ impl DecodedEntity {
Self::XRecord(_) => OBJECT_TYPE_XRECORD,
Self::Placeholder(_) => OBJECT_TYPE_ACDB_PLACEHOLDER,
Self::Group(_) => OBJECT_TYPE_GROUP,
Self::VpEntHdr(_) => OBJECT_TYPE_VP_ENT_HDR,
Self::Layout(_) => OBJECT_TYPE_LAYOUT,
// SORTENTSTABLE is a custom class — its code varies per file via
// AcDb:Classes.
Expand Down Expand Up @@ -432,6 +434,7 @@ const PROXY_OBJECT_ITEM_CLASS_ID: u16 = 0x1F3;
// through `dispatch_object`, not through the entity path.
const OBJECT_TYPE_DICTIONARY: u16 = 0x2A; // 42
const OBJECT_TYPE_GROUP: u16 = 0x48; // 72
const OBJECT_TYPE_VP_ENT_HDR: u16 = 0x47; // 71
const OBJECT_TYPE_XRECORD: u16 = 0x4F; // 79
const OBJECT_TYPE_ACDB_PLACEHOLDER: u16 = 0x50; // 80
const OBJECT_TYPE_LAYOUT: u16 = 0x52; // 82
Expand Down Expand Up @@ -740,6 +743,7 @@ fn dispatch_object(
| ObjectType::XRecord
| ObjectType::AcDbPlaceholder
| ObjectType::Group
| ObjectType::VpEntHdr
| ObjectType::Layout
| ObjectType::MLineStyle
| ObjectType::GeoData
Expand Down Expand Up @@ -779,6 +783,11 @@ fn dispatch_object(
.map(DecodedEntity::Group)
.map_err(|e| e.to_string())
}
ObjectType::VpEntHdr => crate::objects::control_vp_ent_hdr::decode_object(
payload, body_start, inline_end, version,
)
.map(DecodedEntity::VpEntHdr)
Comment thread
DrunkOnJava marked this conversation as resolved.
Resolved
.map_err(|e| e.to_string()),
ObjectType::Layout => {
match crate::objects::acad_layout::decode_object(
payload, body_start, inline_end, version,
Expand Down
60 changes: 60 additions & 0 deletions src/objects/control_vp_ent_hdr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! VIEWPORT ENTITY HEADER (§20.4.70, type 0x47) — the header the older
//! releases keep for each viewport entity of a paper space layout.
//!
//! Only R14 and R2000 drawings hold one; later releases do not. It is a
//! non-entity object whose data stream is the name and flags a symbol-table
//! entry opens with (§20.4.54), then one more flag:
//!
//! ```text
//! TV entry name DXF 2
//! B 64-flag DXF 70, the 64 bit
//! BS xrefindex + 1 DXF 70; subtract one, -1 means not from an xref
//! B xdep DXF 70, the 16 bit; dependent on an xref
//! B 1 flag DXF 70, the 1 bit
//! ```
//!
//! Its handle references (the viewport entity control, the extension
//! dictionary, the external reference block, the viewport entity it
//! describes and the previous header of the chain) are in the handle stream
//! and are not read here. The corpus's AutoCAD DXF exports hold no VIEWPORT
//! ENTITY HEADER, so its records are checked against their data-stream
//! boundary alone.

use crate::error::Result;
use crate::objects::modern;
use crate::version::Version;

/// A decoded VIEWPORT ENTITY HEADER.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct VpEntHdr {
/// `TV` entry name, DXF 2.
pub name: String,
/// `B` the 64 bit of DXF group 70.
pub flag_64: bool,
/// `BS` xrefindex + 1.
pub xref_index_plus_1: i16,
/// `B` xdep: dependent on an external reference.
pub is_xref_dependent: bool,
/// `B` the 1 bit of DXF group 70.
pub flag_1: bool,
}

/// Decode a VIEWPORT ENTITY HEADER (§20.4.70) from its raw object payload,
/// checking the data fields end exactly on the data-stream boundary.
pub(crate) fn decode_object(
payload: &[u8],
body_start: usize,
inline_data_end: Option<usize>,
version: Version,
) -> Result<VpEntHdr> {
let mut split = modern::open(payload, body_start, inline_data_end, version)?;
let out = VpEntHdr {
name: modern::read_tv(&mut split.data, &mut split.strings, version)?,
flag_64: split.data.read_b()?,
xref_index_plus_1: split.data.read_bs()?,
is_xref_dependent: split.data.read_b()?,
flag_1: split.data.read_b()?,
};
split.finish("VP_ENT_HDR")?;
Ok(out)
}
2 changes: 2 additions & 0 deletions src/objects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
//! | DICTIONARYVAR | [`dictionary_var`] | §19.6.x |
//! | IMAGEDEF_REACTOR | [`image_def_reactor`] | §20.4.82 |
//! | proxy entity | [`proxy_entity`] | §19.4.91 (L7-04) |
//! | VP_ENT_HDR | [`control_vp_ent_hdr`] | §20.4.70 |
//! | proxy object | [`proxy_object`] | §19.4.91 (L7-05) |
//! | XData | [`xdata`] | §3.5 (L7-01) |
//! | SPATIAL_FILTER | [`clip_spatial_filter`] | §20.4.94 |
Expand Down Expand Up @@ -105,6 +106,7 @@ pub mod class_map_extension;
pub mod clip_spatial_filter;
pub mod color;
pub mod control;
pub mod control_vp_ent_hdr;
pub mod custom_dict_entry;
pub mod data_table;
pub mod dictionary;
Expand Down
Loading
Loading