From b63db81e2d7023d899543b9920c7037a2ede29fa Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Thu, 8 Oct 2026 23:27:44 +0800 Subject: [PATCH 1/3] feat: read the retained data of proxy entities and objects `readCommonProxyData` stopped right before the retained data of the original object, with a TODO noting that the section cannot be read. That data is the only place where a custom object survives once it has been saved as a proxy, so leaving it unread makes the payload of any custom object inaccessible to a consumer of the file. The section has no length of its own: it extends from the end of the proxy fields up to the start of the handle section. The reader already computes that offset when it splits the object stream for R2010 and later, so the value is kept in a field and the remaining bits are read as the data. ProxyObject already exposed a Data stream, filled from group 311 in DXF. ProxyEntity had no such member, so it gets one with the same shape and DXF code. Before R2010 the handle section offset is not encoded in the file, sections without a length cannot be delimited, and the data is left unset as it was. Verified on real drawings, comparing the reader with and without the change over 21 DWG files (1,607,215 entities in total): - proxy data decoded: 0 bytes before, 2358 bytes after - entity count and a per-document fingerprint of entity types and handles: identical in both runs, no file differs, so consuming the section does not disturb the reads that follow it --- src/ACadSharp/Entities/ProxyEntity.cs | 14 +++++++ .../DWG/DwgStreamReaders/DwgObjectReader.cs | 38 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index e02b1baf0..68aca8a36 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -1,6 +1,7 @@ using ACadSharp.Attributes; using ACadSharp.Classes; using CSMath; +using System.IO; namespace ACadSharp.Entities { @@ -55,6 +56,7 @@ public class ProxyEntity : Entity, IProxy //92 Size of graphics data in bytes //310 Binary graphics data(multiple entries can appear) (optional) + //The graphics are read as common entity data and exposed through Entity.ProxyGeometries. //93 Size of entity data in bits //310 Binary entity data(multiple entries can appear) (optional) @@ -64,6 +66,18 @@ public class ProxyEntity : Entity, IProxy //An object ID(multiple entries can appear) (optional) //94 0 (indicates end of object ID section) + /// + /// Gets or sets the data of the original object as it was stored while the object is a proxy. + /// + /// + /// This is the payload the original application would read back, and it is the only place where + /// the custom object data survives once the object has been saved as a proxy. In the file the + /// section has no length of its own: it runs from the end of the proxy fields up to the start of + /// the handle section. + /// + [DxfCodeValue(311)] + public Stream Data { get; set; } + /// public ACadVersion Version { get; set; } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..650c6057f 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -72,6 +72,15 @@ internal partial class DwgObjectReader : DwgSectionIO /// private IDwgStreamReader _handlesReader; + /// + /// Bit position where the handle section of the current object starts, or -1 when it is unknown. + /// + /// + /// Some sections, notably the retained data of a proxy object, have no length of their own and + /// run until the handle section begins. + /// + private long _handleSectionOffset = -1; + private IDwgStreamReader _mergedReaders; private long _objectInitialPos = 0; @@ -236,6 +245,9 @@ private ObjectType getEntityType(long offset) //Find the handles offset ulong handleSectionOffset = (ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize; + //Keep it, some sections run until the handle section starts + this._handleSectionOffset = (long)handleSectionOffset; + //Create a handler section reader this._objectReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._objectReader.SetPositionInBits(this._crcReader.PositionInBits()); @@ -263,6 +275,9 @@ private ObjectType getEntityType(long offset) this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._textReader = this._objectReader; + //The handle section offset is not encoded before R2010, sections without a length cannot be delimited + this._handleSectionOffset = -1; + //set the initial position and get the object type this._objectInitialPos = this._objectReader.PositionInBits(); type = this._objectReader.ReadObjectType(); @@ -1181,8 +1196,29 @@ private void readCommonProxyData(IProxy proxy) //Common: //Databits X databits, however many there are to the handles + //The retained data of the original object has no length of its own, it extends from the + //current position up to the start of the handle section. + if (this._handleSectionOffset > 0) + { + long dataBits = this._handleSectionOffset - this._objectReader.PositionInBits(); + if (dataBits > 0) + { + //The section is measured in bits, read the bytes that contain them + int dataBytes = (int)((dataBits + 7L) / 8L); + byte[] data = this._objectReader.ReadBytes(dataBytes); - //TODO: Investigate how to read the data in proxies, it can contain data, strings and handles + Stream stream = new MemoryStream(data, writable: false); + switch (proxy) + { + case ProxyEntity entity: + entity.Data = stream; + break; + case ProxyObject proxyObject: + proxyObject.Data = stream; + break; + } + } + } } private CadTemplate readDbColor() From 04b5a2fec022bdb1f4e86e2941f5b1ef0fd055d5 Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Tue, 13 Oct 2026 22:29:27 +0800 Subject: [PATCH 2/3] refactor: apply review feedback on the retained data of proxies Addresses the review on #1274: - `Data` moves to `IProxy`. Both `ProxyEntity` and `ProxyObject` already carried the same property, and reading it through the interface removes the type switch from the reader. - `ProxyEntity.Data` no longer carries `DxfCodeValue(311)`. The DXF reader has no handler for code 311 on an entity and the DXF writer refuses proxy entities outright, so the attribute promised a round trip that does not exist. The property is DWG only for now. - The `-1` reset in the pre-R2010 branch is gone. A file has one version, so the field cannot carry a stale value from a previous object. - The local `handleSectionOffset` is gone, the field is assigned directly. - The xml comment on the field is gone. --- src/ACadSharp/Entities/ProxyEntity.cs | 11 ++----- .../DWG/DwgStreamReaders/DwgObjectReader.cs | 32 +++---------------- src/ACadSharp/IProxy.cs | 10 ++++++ src/ACadSharp/Objects/ProxyObject.cs | 1 + 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index 68aca8a36..e145bb86c 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -66,16 +66,11 @@ public class ProxyEntity : Entity, IProxy //An object ID(multiple entries can appear) (optional) //94 0 (indicates end of object ID section) - /// - /// Gets or sets the data of the original object as it was stored while the object is a proxy. - /// + /// /// - /// This is the payload the original application would read back, and it is the only place where - /// the custom object data survives once the object has been saved as a proxy. In the file the - /// section has no length of its own: it runs from the end of the proxy fields up to the start of - /// the handle section. + /// Only the DWG reader fills this. The DXF reader has no handler for code 311 on an entity and + /// the DXF writer does not emit proxy entities at all, so no DxfCodeValue is applied. /// - [DxfCodeValue(311)] public Stream Data { get; set; } /// diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 650c6057f..70c6cf339 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -72,13 +72,6 @@ internal partial class DwgObjectReader : DwgSectionIO /// private IDwgStreamReader _handlesReader; - /// - /// Bit position where the handle section of the current object starts, or -1 when it is unknown. - /// - /// - /// Some sections, notably the retained data of a proxy object, have no length of their own and - /// run until the handle section begins. - /// private long _handleSectionOffset = -1; private IDwgStreamReader _mergedReaders; @@ -242,11 +235,8 @@ private ObjectType getEntityType(long offset) //(the padding bits make sure the object stream ends on a byte boundary). ulong handleSize = this._crcReader.ReadModularChar(); - //Find the handles offset - ulong handleSectionOffset = (ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize; - - //Keep it, some sections run until the handle section starts - this._handleSectionOffset = (long)handleSectionOffset; + //Find the handles offset, some sections run until the handle section starts + this._handleSectionOffset = (long)((ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize); //Create a handler section reader this._objectReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); @@ -258,11 +248,11 @@ private ObjectType getEntityType(long offset) //Create a handler section reader this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); - this._handlesReader.SetPositionInBits((long)handleSectionOffset); + this._handlesReader.SetPositionInBits(this._handleSectionOffset); //Create a text section reader this._textReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); - this._textReader.SetPositionByFlag((long)handleSectionOffset - 1); + this._textReader.SetPositionByFlag(this._handleSectionOffset - 1); this._mergedReaders = new DwgMergedReader(this._objectReader, this._textReader, this._handlesReader); } @@ -275,9 +265,6 @@ private ObjectType getEntityType(long offset) this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._textReader = this._objectReader; - //The handle section offset is not encoded before R2010, sections without a length cannot be delimited - this._handleSectionOffset = -1; - //set the initial position and get the object type this._objectInitialPos = this._objectReader.PositionInBits(); type = this._objectReader.ReadObjectType(); @@ -1207,16 +1194,7 @@ private void readCommonProxyData(IProxy proxy) int dataBytes = (int)((dataBits + 7L) / 8L); byte[] data = this._objectReader.ReadBytes(dataBytes); - Stream stream = new MemoryStream(data, writable: false); - switch (proxy) - { - case ProxyEntity entity: - entity.Data = stream; - break; - case ProxyObject proxyObject: - proxyObject.Data = stream; - break; - } + proxy.Data = new MemoryStream(data, writable: false); } } } diff --git a/src/ACadSharp/IProxy.cs b/src/ACadSharp/IProxy.cs index ea6939799..eab82aede 100644 --- a/src/ACadSharp/IProxy.cs +++ b/src/ACadSharp/IProxy.cs @@ -1,4 +1,5 @@ using ACadSharp.Classes; +using System.IO; namespace ACadSharp; @@ -29,5 +30,14 @@ public interface IProxy /// int ProxyClassId { get; } + /// + /// Gets or sets the retained data of the original object. + /// + /// + /// The custom object data survives as a proxy only here. In the file the section has no length of + /// its own: it runs from the end of the proxy fields up to the start of the handle section. + /// + Stream Data { get; set; } + ACadVersion Version { get; set; } } diff --git a/src/ACadSharp/Objects/ProxyObject.cs b/src/ACadSharp/Objects/ProxyObject.cs index a0beef042..ce26d8079 100644 --- a/src/ACadSharp/Objects/ProxyObject.cs +++ b/src/ACadSharp/Objects/ProxyObject.cs @@ -53,6 +53,7 @@ public class ProxyObject : NonGraphicalObject, IProxy [DxfCodeValue(310)] public Stream BinaryData { get; set; } + /// [DxfCodeValue(311)] public Stream Data { get; set; } From 3058b3305074c4bf62f26aa8be4c16875a988bed Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Tue, 13 Oct 2026 22:34:46 +0800 Subject: [PATCH 3/3] docs: trim comments that restate the code Removes notes that a reader can infer from the code itself and shortens the rest to the part that is not obvious: - the aside about proxy graphics in `ProxyEntity` was unrelated to this change - the `remarks` on `ProxyEntity.Data` is now one line, the missing `DxfCodeValue` is the only surprising part - the `remarks` on `IProxy.Data` described the file layout, which belongs to the reader, not to the public API - `//Find the handles offset` no longer carries an appended rationale - the note in `readCommonProxyData` is one line, and the comment above the byte rounding is gone --- src/ACadSharp/Entities/ProxyEntity.cs | 6 +----- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 6 ++---- src/ACadSharp/IProxy.cs | 4 ---- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index e145bb86c..b837c48c0 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -56,7 +56,6 @@ public class ProxyEntity : Entity, IProxy //92 Size of graphics data in bytes //310 Binary graphics data(multiple entries can appear) (optional) - //The graphics are read as common entity data and exposed through Entity.ProxyGeometries. //93 Size of entity data in bits //310 Binary entity data(multiple entries can appear) (optional) @@ -67,10 +66,7 @@ public class ProxyEntity : Entity, IProxy //94 0 (indicates end of object ID section) /// - /// - /// Only the DWG reader fills this. The DXF reader has no handler for code 311 on an entity and - /// the DXF writer does not emit proxy entities at all, so no DxfCodeValue is applied. - /// + /// No DxfCodeValue: the DXF reader has no handler for code 311 on an entity. public Stream Data { get; set; } /// diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 70c6cf339..9430589e3 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -235,7 +235,7 @@ private ObjectType getEntityType(long offset) //(the padding bits make sure the object stream ends on a byte boundary). ulong handleSize = this._crcReader.ReadModularChar(); - //Find the handles offset, some sections run until the handle section starts + //Find the handles offset this._handleSectionOffset = (long)((ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize); //Create a handler section reader @@ -1183,14 +1183,12 @@ private void readCommonProxyData(IProxy proxy) //Common: //Databits X databits, however many there are to the handles - //The retained data of the original object has no length of its own, it extends from the - //current position up to the start of the handle section. + //The retained data has no length of its own, it runs up to the handle section if (this._handleSectionOffset > 0) { long dataBits = this._handleSectionOffset - this._objectReader.PositionInBits(); if (dataBits > 0) { - //The section is measured in bits, read the bytes that contain them int dataBytes = (int)((dataBits + 7L) / 8L); byte[] data = this._objectReader.ReadBytes(dataBytes); diff --git a/src/ACadSharp/IProxy.cs b/src/ACadSharp/IProxy.cs index eab82aede..df41357fe 100644 --- a/src/ACadSharp/IProxy.cs +++ b/src/ACadSharp/IProxy.cs @@ -33,10 +33,6 @@ public interface IProxy /// /// Gets or sets the retained data of the original object. /// - /// - /// The custom object data survives as a proxy only here. In the file the section has no length of - /// its own: it runs from the end of the proxy fields up to the start of the handle section. - /// Stream Data { get; set; } ACadVersion Version { get; set; }