diff --git a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs index 0cb984d96..1d3279967 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs @@ -1,6 +1,8 @@ using ACadSharp.Entities; using ACadSharp.IO; +using ACadSharp.Objects; using CSMath; +using System; using System.IO; using System.Linq; using Xunit; @@ -54,6 +56,33 @@ public void WriteCasesAC1032(SingleCaseGenerator data) this.writeDxfFile(data, ACadVersion.AC1032); } + [Fact] + public void WriteFieldDateValue() + { + DateTime date = new DateTime(2026, 9, 30, 10, 15, 30, 250); + + SingleCaseGenerator data = new SingleCaseGenerator(); + data.FieldCadValues(); + + MText text = Assert.Single(data.Document.Entities.OfType()); + Field field = text.XDictionary.GetEntry("ACAD_FIELD").GetEntry("TEXT"); + CadValue dateValue = new CadValue(); + dateValue.SetValue(date, CadValueType.Date); + field.Values.Add("date", dateValue); + + using MemoryStream output = new MemoryStream(); + DxfWriter.Write(output, data.Document); + + using MemoryStream input = new MemoryStream(output.ToArray()); + CadDocument result = DxfReader.Read(input); + Field writtenField = result.GetCadObject(field.Handle); + + Assert.NotNull(writtenField); + Assert.True(writtenField.Values.TryGetValue("date", out CadValue value)); + Assert.Equal(CadValueType.Date, value.ValueType); + Assert.Equal(date, value.Value); + } + [Theory] [InlineData(-90, 270, -90, 270)] [InlineData(180, 540, -180, 180)] diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index bd3e8fcb8..27a948dad 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -101,6 +101,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.SingleMeshWithTextureCoordinates))); Data.Add(new(nameof(SingleCaseGenerator.SingleMaterial))); Data.Add(new(nameof(SingleCaseGenerator.LeaderWithArrowHead))); + Data.Add(new(nameof(SingleCaseGenerator.FieldCadValues))); } public WriterSingleObjectTests(ITestOutputHelper output) : base(output) @@ -1699,6 +1700,68 @@ public void EntityTransparency() }; } + public void FieldCadValues() + { + MText text = new MText(); + text.Value = "cad values"; + this.Document.Entities.Add(text); + + Field field = new Field(); + field.EvaluatorId = "_text"; + field.FieldCode = "cad values"; + field.FormatString = string.Empty; + field.Value.SetValue("cad values", CadValueType.String); + + CadValue unknownEmpty = new CadValue(); + unknownEmpty.IsEmpty = true; + field.Values.Add("unknown_empty", unknownEmpty); + + //Unknown value that is not empty, as read from a pre R2007 dwg + CadValue unknown = new CadValue(); + unknown.SetValue(0); + field.Values.Add("unknown", unknown); + + CadValue longValue = new CadValue(); + longValue.SetValue(42, CadValueType.Long); + field.Values.Add("long", longValue); + + CadValue doubleValue = new CadValue(); + doubleValue.SetValue(0.5, CadValueType.Double); + field.Values.Add("double", doubleValue); + + CadValue stringValue = new CadValue(); + stringValue.SetValue("text value", CadValueType.String); + field.Values.Add("string", stringValue); + + CadValue pointValue = new CadValue(); + pointValue.SetValue(new XYZ(1, 2, 3), CadValueType.Point3D); + field.Values.Add("point", pointValue); + + CadDictionary dict = text.CreateExtendedDictionary(); + CadDictionary fields = new CadDictionary("ACAD_FIELD"); + dict.Add(fields); + fields.Add("TEXT", field); + + this.AssertRoundtrip = (doc) => + { + if (this.Format == CadFileFormat.DWG) + { + return; + } + + Field result = doc.GetCadObject(field.Handle); + + Assert.NotNull(result); + foreach (var item in field.Values) + { + Assert.True(result.Values.TryGetValue(item.Key, out CadValue value), $"Value {item.Key} not found"); + Assert.Equal(item.Value.ValueType, value.ValueType); + Assert.Equal(item.Value.IsEmpty, value.IsEmpty); + Assert.Equal(item.Value.Value, value.Value); + } + }; + } + public void GenerateExampleDxf() { this.Document.Header.UnitMode = (short)ACadSharp.Types.Units.UnitsType.Millimeters; diff --git a/src/ACadSharp/CadUtils.cs b/src/ACadSharp/CadUtils.cs index 7dc0c773e..02dc319e2 100644 --- a/src/ACadSharp/CadUtils.cs +++ b/src/ACadSharp/CadUtils.cs @@ -1,6 +1,8 @@ -using CSUtilities.Text; +using CSUtilities.Converters; +using CSUtilities.Text; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -364,4 +366,44 @@ public static void DateToJulian(DateTime date, out int jdate, out int milisecond jdate = date.Day + (int)System.Math.Floor((153.0 * (double)(month + 12 * day - 3) + 2.0) / 5.0) + 365 * year + (int)System.Math.Floor((double)year / 4.0) - (int)System.Math.Floor((double)year / 100.0) + (int)System.Math.Floor((double)year / 400.0) - 32045; miliseconds = date.Millisecond + date.Second * 1000 + date.Minute * 60000 + date.Hour * 3600000; } + + /// + /// Converts a date into the 16 bytes of a SYSTEMTIME structure: year, month, day of the week, day, hour, minute, second and millisecond, each one as a little endian short. + /// + /// The date to convert. + /// The 16 bytes that represent the date. + public static byte[] ToSystemTime(DateTime date) + { + byte[] array = new byte[16]; + var stream = new MemoryStream(array); + + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Year), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Month), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.DayOfWeek), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Day), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Hour), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Minute), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Second), 0, 2); + stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Millisecond), 0, 2); + + return array; + } + + /// + /// Converts the 16 bytes of a SYSTEMTIME structure into a date. The day of the week is ignored. + /// + /// The 16 bytes that represent the date. + /// The date represented by the bytes. + public static DateTime FromSystemTime(byte[] array) + { + int year = LittleEndianConverter.Instance.ToInt16(array, 0); + int month = LittleEndianConverter.Instance.ToInt16(array, 2); + int day = LittleEndianConverter.Instance.ToInt16(array, 6); + int hour = LittleEndianConverter.Instance.ToInt16(array, 8); + int minute = LittleEndianConverter.Instance.ToInt16(array, 10); + int second = LittleEndianConverter.Instance.ToInt16(array, 12); + int millisecond = LittleEndianConverter.Instance.ToInt16(array, 14); + + return new DateTime(year, month, day, hour, minute, second, millisecond); + } } \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs index f5a2e1ef9..560718435 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs @@ -192,17 +192,7 @@ private void readCustomTableData(CustomDataEntry entry) switch (size) { case 16: - { - int year = LittleEndianConverter.Instance.ToInt16(array, 0); - int month = LittleEndianConverter.Instance.ToInt16(array, 2); - int day = LittleEndianConverter.Instance.ToInt16(array, 6); - int hour = LittleEndianConverter.Instance.ToInt16(array, 8); - int minute = LittleEndianConverter.Instance.ToInt16(array, 10); - int second = LittleEndianConverter.Instance.ToInt16(array, 12); - int millisecond = LittleEndianConverter.Instance.ToInt16(array, 14); - - return new System.DateTime(year, month, day, hour, minute, second, millisecond); - } + return CadUtils.FromSystemTime(array); case 14: { int year = LittleEndianConverter.Instance.ToInt16(array, 0); diff --git a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs index 9cd5b527c..b4e5f0325 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs @@ -829,17 +829,7 @@ private void writeDateCadValue(System.DateTime? date) byte[] array; if (this.R2007Plus) { - array = new byte[16]; - var stream = new MemoryStream(array); - - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Year), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Month), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.DayOfWeek), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Day), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Hour), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Minute), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Second), 0, 2); - stream.Write(LittleEndianConverter.Instance.GetBytes((short)date.Value.Millisecond), 0, 2); + array = CadUtils.ToSystemTime(date.Value); } else { diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs index 5f4a2c09e..aba018afd 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs @@ -546,7 +546,18 @@ private bool readTableEntity(CadEntityTemplate template, DxfMap map, string subc tmp.CurrentCell.Rotation = this._reader.ValueAsDouble; return true; case 170: - //Has data flag + // Per-cell alignment (set when the cell flag at code 91 has the + // CellAlignment override bit). Capture it on the cell-level + // StyleOverride so callers reading the table back via DXF can + // resolve alignment exactly like they do through DWG. + if (tmp.CurrentCell != null) + { + tmp.CurrentCell.StyleOverride.HasData = true; + tmp.CurrentCell.StyleOverride.CellAlignment = + (ACadSharp.Objects.TableStyle.CellAlignmentType)this._reader.ValueAsShort; + tmp.CurrentCell.StyleOverride.PropertyOverrideFlags |= + ACadSharp.Objects.TableStyle.CellStylePropertyFlags.Alignment; + } return true; case 171: tmp.CreateCell((TableEntity.CellType)this._reader.ValueAsInt); @@ -572,6 +583,18 @@ private bool readTableEntity(CadEntityTemplate template, DxfMap map, string subc case 179: //Unknown value return true; + case 70: + // Table-level flow direction (0 = top to bottom, 1 = bottom to top). + // Stored on the table-level CellStyleOverride so callers can read it + // through the same API used for round-tripping the rest of the + // override flags. + if (this._reader.ValueAsShort == 1) + { + table.CellStyleOverride.HasData = true; + table.CellStyleOverride.TableCellStylePropertyFlags |= + ACadSharp.Objects.TableStyle.CellStylePropertyFlags.FlowDirectionBottomToTop; + } + return true; case 301: content = new TableEntity.CellContent(); var contentTemplate = new CadTableCellContentTemplate(content); @@ -634,12 +657,24 @@ protected CadValueTemplate readCadValue(CadValue value) case 91: value.SetValue(this._reader.ValueAsInt); break; + case 92: + //Size of the date data in 310 + break; case 93: value.Flags = this._reader.ValueAsInt; break; case 140: value.SetValue(this._reader.ValueAsDouble); break; + case 310 when value.ValueType == CadValueType.Date: + { + byte[] array = this._reader.ValueAsBinaryChunk; + if (array.Length == 16) + { + value.SetValue(CadUtils.FromSystemTime(array)); + } + } + break; case 330: template.ValueHandle = this._reader.ValueAsHandle; break; diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs index eecf48192..5b29a5fe6 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs @@ -45,6 +45,9 @@ protected void writeEntity(T entity) case Hatch hatch: this.writeHatch(hatch); break; + case TableEntity tableEntity: + this.writeTableEntity(tableEntity); + break; case Insert insert: this.writeInsert(insert); break; @@ -155,7 +158,6 @@ private bool isEntitySupported(Entity entity) case Shape: return this.Configuration.WriteShapes; case ProxyEntity: - case TableEntity: case Solid3D: case CadBody: case Region: @@ -644,6 +646,235 @@ private void writeHatchPattern(Hatch hatch, HatchPattern pattern) } } + private void writeTableEntity(TableEntity table) + { + // Emit the AcDbBlockReference (Insert) layer first. AutoCAD reads the block + // reference to instantiate the anonymous BlockRecord that holds the rendered + // table geometry, so it always has something to draw. + this._writer.Write(DxfCode.Subclass, DxfSubclassMarker.Insert); + if (table.Block != null) + { + this._writer.Write(2, table.Block.Name); + } + this._writer.Write(10, table.InsertPoint); + this._writer.Write(41, table.XScale); + this._writer.Write(42, table.YScale); + this._writer.Write(43, table.ZScale); + this._writer.Write(50, table.Rotation); + this._writer.Write(210, table.Normal); + + // AcDbTable subclass with the full cell data so the table round-trips through + // a DxfReader without losing rows, columns or text content. AutoCAD prefers a + // proxy graphic preview (codes 160/310 in AcDbEntity) when one is present and + // ignores the cell data for layout; without a preview, AutoCAD also renders + // the cells on top of the BlockReference, producing a second offset copy of + // the table. That visual artifact is the cost of keeping the data + // round-trippable: callers that need a single rendering should provide a + // proxy graphic via Entity.PreviewGraphic. + // + // Field order mirrors what AutoCAD emits so stricter readers do not bail with + // "Invalid index" partway through the entity. + this._writer.Write(DxfCode.Subclass, DxfSubclassMarker.TableEntity); + + if (table.Style != null) + { + this._writer.Write(342, table.Style.Handle); + } + if (table.Block != null) + { + this._writer.Write(343, table.Block.Handle); + } + + this._writer.Write(11, table.HorizontalDirection); + + int flag = table.ValueFlag; + if (table.OverrideFlag) + { + flag |= 1; + } + this._writer.Write(90, flag); + + this._writer.Write(91, table.Rows.Count); + this._writer.Write(92, table.Columns.Count); + + // Override count placeholder. The exact semantics of code 93 at the table + // level are not documented but AutoCAD-produced files always + // emit it. Emit 0 here; the reader does not error out on it. + this._writer.Write(93, 0); + + this._writer.Write(94, table.OverrideBorderColor ? 1 : 0); + this._writer.Write(95, table.OverrideBorderLineWeight ? 1 : 0); + this._writer.Write(96, table.OverrideBorderVisibility ? 1 : 0); + + // Flow direction is emitted only when set to bottom-to-top. Down-direction + // tables in AutoCAD-produced files omit the code entirely. + bool isFlowUp = table.CellStyleOverride != null + && table.CellStyleOverride.TableCellStylePropertyFlags.HasFlag(ACadSharp.Objects.TableStyle.CellStylePropertyFlags.FlowDirectionBottomToTop); + if (isFlowUp) + { + this._writer.Write(70, (short)1); + } + + // Table-level cell margins. AutoCAD reads these here even when the per-cell + // style override carries the same values; omitting them causes stricter + // readers to abort with "Invalid index". + double horMargin = 0.06; + double verMargin = 0.06; + if (table.CellStyleOverride != null + && table.CellStyleOverride.MarginOverrideFlags.HasFlag(ACadSharp.Objects.TableStyle.MarginFlags.Override)) + { + horMargin = table.CellStyleOverride.HorizontalMargin; + verMargin = table.CellStyleOverride.VerticalMargin; + } + else if (table.Style != null) + { + horMargin = table.Style.HorizontalCellMargin; + verMargin = table.Style.VerticalCellMargin; + } + this._writer.Write(40, horMargin); + this._writer.Write(41, verMargin); + + this.notify("TableEntity DXF writer emits cell data without a proxy graphic preview; AutoCAD may draw the table twice (once from the block reference, once from the cells with no transform applied).", NotificationType.Warning); + + foreach (var row in table.Rows) + { + this._writer.Write(141, row.Height); + } + + foreach (var col in table.Columns) + { + this._writer.Write(142, col.Width); + } + + // Translate table-level MergedCellRanges into the per-cell flags AutoCAD reads: + // the top-left cell of each range carries the column/row span in BorderWidth and + // BorderHeight while the remaining cells in the range have MergedValue = 1. + int rowCount = table.Rows.Count; + int colCount = table.Columns.Count; + short[,] mergeBorderWidth = new short[rowCount, colCount]; + short[,] mergeBorderHeight = new short[rowCount, colCount]; + short[,] mergeFlag = new short[rowCount, colCount]; + for (int i = 0; i < rowCount; i++) + { + for (int j = 0; j < colCount; j++) + { + mergeBorderWidth[i, j] = 1; + mergeBorderHeight[i, j] = 1; + } + } + foreach (var range in table.MergedCellRanges) + { + int top = System.Math.Max(0, range.TopRowIndex); + int bottom = System.Math.Min(rowCount - 1, range.BottomRowIndex); + int left = System.Math.Max(0, range.LeftColumnIndex); + int right = System.Math.Min(colCount - 1, range.RightColumnIndex); + int rowSpan = bottom - top + 1; + int colSpan = right - left + 1; + if (rowSpan < 1 || colSpan < 1) + { + continue; + } + mergeBorderWidth[top, left] = (short)colSpan; + mergeBorderHeight[top, left] = (short)rowSpan; + for (int i = top; i <= bottom; i++) + { + for (int j = left; j <= right; j++) + { + if (i == top && j == left) + { + continue; + } + mergeFlag[i, j] = 1; + } + } + } + + for (int i = 0; i < rowCount; i++) + { + var row = table.Rows[i]; + for (int j = 0; j < row.Cells.Count; j++) + { + var cell = row.Cells[j]; + short borderWidth = j < colCount ? mergeBorderWidth[i, j] : (short)1; + short borderHeight = j < colCount ? mergeBorderHeight[i, j] : (short)1; + short mergedValue = j < colCount ? mergeFlag[i, j] : (short)cell.MergedValue; + this._writer.Write(171, (short)cell.Type); + this._writer.Write(172, (short)cell.EdgeFlags); + this._writer.Write(173, mergedValue); + this._writer.Write(174, cell.AutoFit); + this._writer.Write(175, (int)borderWidth); + this._writer.Write(176, (int)borderHeight); + + // Cell flag (state + override bits). AutoCAD-produced files always emit + // 262192 baseline and set bit 0x01 when the cell-level CellAlignment + // override is present; code 170 with the alignment value follows when + // that bit is set. Without bit 0x01 stricter readers fall back to the + // TableStyle default and per-cell alignments leak away. + const int baseCellFlag = 262192; + bool hasAlignmentOverride = cell.StyleOverride != null + && cell.StyleOverride.HasData + && cell.StyleOverride.CellAlignment != default(ACadSharp.Objects.TableStyle.CellAlignmentType); + int cellFlag = baseCellFlag; + if (hasAlignmentOverride) + { + cellFlag |= 0x01; + } + this._writer.Write(91, cellFlag); + this._writer.Write(178, (short)cell.VirtualEdgeFlag); + + this._writer.Write(145, cell.Rotation); + + if (hasAlignmentOverride) + { + this._writer.Write(170, (short)cell.StyleOverride.CellAlignment); + } + + ACadSharp.Tables.TextStyle textStyle = cell.StyleOverride?.TextStyle + ?? (cell.Contents.Count > 0 ? cell.Contents[0].Format?.TextStyle : null); + if (textStyle != null) + { + this._writer.Write(7, textStyle.Name); + } + + double textHeight = 0.0; + if (cell.StyleOverride != null && cell.StyleOverride.TextHeight > 0) + { + textHeight = cell.StyleOverride.TextHeight; + } + else if (cell.Contents.Count > 0 && cell.Contents[0].Format != null && cell.Contents[0].Format.TextHeight > 0) + { + textHeight = cell.Contents[0].Format.TextHeight; + } + if (textHeight > 0) + { + this._writer.Write(140, textHeight); + } + + // Cell value state flag. AutoCAD-produced files always emit this before the + // CELL_VALUE block; omitting it causes stricter readers to bail with + // "Invalid index". + this._writer.Write(92, 0); + + // Cell value wrapped in the CELL_VALUE block. The reader keys on code 301 to + // enter readCadValue and exits at 304. Only the value types found in AutoCAD + // files are written, the encoding of the others is not known. + CadValue value = cell.Contents.Count > 0 ? cell.Contents[0].CadValue : null; + switch (value?.ValueType) + { + case CadValueType.Unknown: + case CadValueType.Long: + case CadValueType.Double: + case CadValueType.String: + case CadValueType.Date: + case CadValueType.Point3D: + this._writer.Write(301, "CELL_VALUE"); + this.writeCadValue(value); + break; + } + } + } + } + private void writeInsert(Insert insert) { DxfClassMap map = DxfClassMap.Create(); diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.cs index e88701cfd..ec2879fd1 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.cs @@ -50,43 +50,54 @@ protected void writeCadValue(CadValue value) this._writer.Write(93, value.Flags); this._writer.Write(90, (int)value.ValueType); - switch (value.ValueType) + if (!value.IsEmpty) { - case CadValueType.Unknown: - this._writer.Write(91, 0); - break; - case CadValueType.Double: - this._writer.Write(140, Convert.ToDouble(value.Value)); - break; - case CadValueType.Date: - //TODO: Implement date for dxf - this._writer.Write(91, 0); - break; - case CadValueType.General: - case CadValueType.String: - this.writeLongTextValue(1, 2, (string)value.Value); - break; - case CadValueType.Point2D: - case CadValueType.Point3D: - this._writer.Write(11, (XYZ)value.Value); - break; - case CadValueType.Long: - this._writer.Write(91, Convert.ToInt32(value.Value)); - break; - case CadValueType.Handle: - var handleObj = value.Value as IHandledCadObject; - if (handleObj != null) - { - this._writer.WriteHandle(330, handleObj); - } - break; - case CadValueType.Buffer: - case CadValueType.ResultBuffer: - //TODO: Implement date for dxf - this._writer.Write(91, 0); - break; + switch (value.ValueType) + { + case CadValueType.Unknown: + this._writer.Write(91, 0); + break; + case CadValueType.Double: + this._writer.Write(140, Convert.ToDouble(value.Value)); + break; + case CadValueType.Date: + if (value.Value is DateTime date) + { + byte[] array = CadUtils.ToSystemTime(date); + + this._writer.Write(92, array.Length); + this._writer.Write(310, array); + } + break; + case CadValueType.General: + case CadValueType.String: + this.writeLongTextValue(1, 2, (string)value.Value); + break; + case CadValueType.Point2D: + case CadValueType.Point3D: + this._writer.Write(11, (XYZ)value.Value); + break; + case CadValueType.Long: + this._writer.Write(91, Convert.ToInt32(value.Value)); + break; + case CadValueType.Handle: + var handleObj = value.Value as IHandledCadObject; + if (handleObj != null) + { + this._writer.WriteHandle(330, handleObj); + } + break; + case CadValueType.Buffer: + case CadValueType.ResultBuffer: + //TODO: Implement date for dxf + this._writer.Write(91, 0); + break; + } } + this._writer.Write(94, (int)value.Units); + this._writer.Write(300, value.Format ?? string.Empty); + this._writer.Write(302, value.FormattedValue ?? string.Empty); this._writer.Write(304, DxfFileToken.ValueEnd); } diff --git a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableCellTemplate.cs b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableCellTemplate.cs index 8c837c674..cb55e34f7 100644 --- a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableCellTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableCellTemplate.cs @@ -42,6 +42,28 @@ public void Build(CadDocumentBuilder builder) { contentTemplate.Build(builder); } + + // Apply the cell-level text height read from group code 140 (or 144) so + // consumers can reach it through the regular cell content/format API + // rather than poking at the template. Without this the value parsed by + // readTableEntity is silently dropped. + if (this.FormatTextHeight.HasValue && this.FormatTextHeight.Value > 0) + { + double height = this.FormatTextHeight.Value; + if (this.Cell.StyleOverride != null && this.Cell.StyleOverride.TextHeight <= 0) + { + this.Cell.StyleOverride.TextHeight = height; + this.Cell.StyleOverride.HasData = true; + } + foreach (var content in this.Cell.Contents) + { + if (content.Format != null && content.Format.TextHeight <= 0) + { + content.Format.TextHeight = height; + content.Format.HasData = true; + } + } + } } } } \ No newline at end of file diff --git a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.cs b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.cs index d73294baf..8984326f9 100644 --- a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.cs @@ -75,6 +75,54 @@ protected override void build(CadDocumentBuilder builder) cellTemplate.Build(builder); } + // DXF stores merged cells per-cell (anchor carries the span via BorderWidth / + // BorderHeight, the rest carry MergedValue=1) but never emits the + // table-level MergedCellRanges collection that callers actually consume. Walk + // the grid here and rebuild that collection so readers see the same shape + // regardless of whether the table arrived through DXF or DWG. + int rowCount = this.TableEntity.Rows.Count; + int colCount = this.TableEntity.Columns.Count; + for (int i = 0; i < rowCount; i++) + { + var row = this.TableEntity.Rows[i]; + int cellLimit = System.Math.Min(colCount, row.Cells.Count); + for (int j = 0; j < cellLimit; j++) + { + var cell = row.Cells[j]; + int colSpan = cell.BorderWidth > 1 ? cell.BorderWidth : 1; + int rowSpan = cell.BorderHeight > 1 ? cell.BorderHeight : 1; + if (colSpan == 1 && rowSpan == 1) + { + continue; + } + + int bottomRow = System.Math.Min(rowCount - 1, i + rowSpan - 1); + int rightCol = System.Math.Min(colCount - 1, j + colSpan - 1); + + bool exists = false; + foreach (var range in this.TableEntity.MergedCellRanges) + { + if (range.TopRowIndex == i && range.LeftColumnIndex == j) + { + exists = true; + break; + } + } + if (exists) + { + continue; + } + + this.TableEntity.MergedCellRanges.Add(new TableEntity.CellRange + { + TopRowIndex = i, + LeftColumnIndex = j, + BottomRowIndex = bottomRow, + RightColumnIndex = rightCol, + }); + } + } + foreach (var component in this.CadTableComponentTemplates) { component.Build(builder, this.TableEntity.Style);