diff --git a/reference/ACadFileExploration.xlsx b/reference/ACadFileExploration.xlsx deleted file mode 100644 index 3299d17a0..000000000 Binary files a/reference/ACadFileExploration.xlsx and /dev/null differ diff --git a/samples/line.shp b/samples/line.shp new file mode 100644 index 000000000..133caaea0 --- /dev/null +++ b/samples/line.shp @@ -0,0 +1,3 @@ +*1,42,SINGLE-LINE +4,125,4,80,3,125,3,80,3,128,002,9,(0,0),001,9,(127,127),(1,1),(0,0),002,9,(-127,-127),(-1,-1),(0,0),001,4,128,4,80,4,125 +3,80,3,125,0 diff --git a/samples/line.shx b/samples/line.shx new file mode 100644 index 000000000..5d32734c0 Binary files /dev/null and b/samples/line.shx differ diff --git a/samples/ltypeshp.shx b/samples/ltypeshp.shx new file mode 100644 index 000000000..15b81cf13 Binary files /dev/null and b/samples/ltypeshp.shx differ diff --git a/src/ACadSharp.Tests/IO/ShapeFileTests.cs b/src/ACadSharp.Tests/IO/ShapeFileTests.cs new file mode 100644 index 000000000..d0d4b4ec6 --- /dev/null +++ b/src/ACadSharp.Tests/IO/ShapeFileTests.cs @@ -0,0 +1,16 @@ +using ACadSharp.IO; +using System.IO; +using Xunit; + +namespace ACadSharp.Tests.IO +{ + public class ShapeFileTests + { + [Fact] + public void OpenTest() + { + ShapeFile.Open(Path.Combine(TestVariables.SamplesFolder, ShapeFile.DefaultShapeFile)); + ShapeFile.Open(Path.Combine(TestVariables.SamplesFolder, "line.shx")); + } + } +} diff --git a/src/ACadSharp/Entities/Shape.cs b/src/ACadSharp/Entities/Shape.cs index c94b4826b..5d4efecb1 100644 --- a/src/ACadSharp/Entities/Shape.cs +++ b/src/ACadSharp/Entities/Shape.cs @@ -56,15 +56,7 @@ public TextStyle ShapeStyle { throw new ArgumentNullException(nameof(value)); } - - if (this.Document != null) - { - this._style = CadObject.updateCollection(value, this.Document.TextStyles); - } - else - { - this._style = value; - } + this._style = CadObject.updateCollection(value, this.Document?.TextStyles); } } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 6f2841845..339343641 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -4470,7 +4470,7 @@ private CadTemplate readLayer() private CadTemplate readTextStyle() { TextStyle style = new TextStyle(); - CadTableEntryTemplate template = new CadTableEntryTemplate(style); + CadTextStyleTemplate template = new CadTextStyleTemplate(style); this.readCommonNonEntityData(template); diff --git a/src/ACadSharp/IO/ShapeFile.Geometry.cs b/src/ACadSharp/IO/ShapeFile.Geometry.cs new file mode 100644 index 000000000..719e5f50b --- /dev/null +++ b/src/ACadSharp/IO/ShapeFile.Geometry.cs @@ -0,0 +1,34 @@ +using CSMath; +using System.Collections.Generic; + +namespace ACadSharp.IO +{ + public partial class ShapeFile + { + public class Geometry + { + public List> Lines { get; } = new(); + + public string Name { get; } + + public Geometry(string name) + { + this.Name = name; + } + + public static Geometry Create(string name, byte[] data) + { + int index = 0; + + ShapeBuilder builder = new ShapeBuilder(); + + while (index < data.Length) + { + index += builder.ProcessValue(index, data); + } + + return builder.Build(name); + } + } + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/ShapeFile.ShapeBuilder.cs b/src/ACadSharp/IO/ShapeFile.ShapeBuilder.cs new file mode 100644 index 000000000..4147bc58c --- /dev/null +++ b/src/ACadSharp/IO/ShapeFile.ShapeBuilder.cs @@ -0,0 +1,225 @@ +using CSMath; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ACadSharp.IO +{ + public partial class ShapeFile + { + internal class ShapeBuilder + { + public List CurrentLine + { + get + { + if (this.Polylines.Count == 0) + { + this.Polylines.Add(new List()); + } + + return this.Polylines.Last(); + } + } + + public XY LastPt { get; set; } + + public bool NotStore { get; set; } + + public List> Polylines { get; } = new(); + + internal bool DrawModeOn + { + get + { + return this._drawModeOn; + } + set + { + this._drawModeOn = value; + this._isLine = false; + } + } + + private static readonly XY[] _vectors = + { + new XY(1.0, 0.0), + new XY(1.0, 0.5), + new XY(1.0, 1.0), + new XY(0.5, 1.0), + new XY(0.0, 1.0), + new XY(-0.5, 1.0), + new XY(-1.0, 1.0), + new XY(-1.0, 0.5), + new XY(-1.0, 0.0), + new XY(-1.0, -0.5), + new XY(-1.0, -1.0), + new XY(-0.5, -1.0), + new XY(0.0, -1.0), + new XY(0.5, -1.0), + new XY(1.0, -1.0), + new XY(1.0, -0.5) + }; + + private readonly Stack _locationStack = new(); + + private double _current = 1.0d; + + private bool _drawModeOn = true; + + private bool _isLine = false; + + public ShapeBuilder() + { + } + + public Geometry Build(string name) + { + Geometry geometry = new Geometry(name); + + geometry.Lines.AddRange(this.Polylines); + + return geometry; + } + + public bool HasToStore() + { + bool result = !this.NotStore; + this.NotStore = false; + + return result; + } + + public int ProcessValue(int index, byte[] data) + { + byte code = data[index]; + switch (code) + { + case 0x0: + // End of shape definition + return 1; + case 0x1: + // Activate Draw mode(pen down) + if (this.HasToStore()) + { + this.DrawModeOn = true; + } + return 1; + case 0x2: + //Deactivate Draw mode (pen up) + if (this.HasToStore()) + { + this.DrawModeOn = false; + } + return 1; + case 0x3: + if (this.HasToStore()) + { + //Divide vector lengths by next byte + double div = (sbyte)data[index + 1]; + this._current /= div; + } + return 2; + case 0x4: + if (this.HasToStore()) + { + //Multiply vector lengths by next byte + double mult = (sbyte)data[index + 1]; + this._current *= mult; + } + return 2; + case 0x5: + if (this.HasToStore()) + { + //Push current location onto stack + this._locationStack.Push(this.LastPt); + } + return 1; + case 0x6: + if (this.HasToStore()) + { + //Pop current location from stack + if (this._locationStack.Count > 0) + { + this.LastPt = this._locationStack.Pop(); + } + + this._isLine = false; + } + return 1; + case 0x7: + //Draw subshape number given by next byte + throw new NotImplementedException(); + case 0x8: + if (this.HasToStore()) + { + //X-Y displacement given by next two bytes + var x = (sbyte)data[index + 1]; + var y = (sbyte)data[index + 2]; + + this.drawLine(x, y); + } + return 3; + case 0x9: + //Multiple X-Y displacements, terminated (0,0) + bool flag = this.HasToStore(); + int i; + for (i = index + 1; data[i] != 0 || data[i + 1] != 0; i += 2) + { + if (flag) + { + this.drawLine((sbyte)data[i], (sbyte)data[i + 1]); + } + } + return i - index + 2; + case 0xA: + //Octant arc defined by next two bytes + + return 0; + case 0xB: + //Fractional arc defined by next five bytes + case 0xC: + //Arc defined by X-Y displacement and bulge + case 0xD: + //Multiple bulge-specified arcs + case 0xE: + //Process next command only if vertical text + throw new NotImplementedException(); + default: + byte b = data[index]; + double value = (b >> 4) & 0xF; + XY pt = _vectors[b & 0xF]; + if (this.HasToStore()) + { + this.drawLine(value * pt.X, value * pt.Y); + } + return 1; + } + } + + private void createNewLine() + { + if (!this._isLine) + { + this._isLine = true; + this.Polylines.Add(new List()); + } + } + + private void drawLine(double x, double y) + { + var pt = new XY( + this.LastPt.X + this._current * x, + this.LastPt.Y + this._current * y); + + if (this._drawModeOn) + { + this.createNewLine(); + this.CurrentLine.Add(pt); + } + + this.LastPt = pt; + } + } + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/ShapeFile.cs b/src/ACadSharp/IO/ShapeFile.cs new file mode 100644 index 000000000..1999a17cb --- /dev/null +++ b/src/ACadSharp/IO/ShapeFile.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace ACadSharp.IO +{ + public partial class ShapeFile + { + public const string DefaultShapeFile = "ltypeshp.shx"; + + private const string _sentinelV1 = "AutoCAD-86 shapes 1.0"; + + public Dictionary Geometries { get; } = new(); + + public static void Open(string file) + { + if (string.IsNullOrEmpty(file)) + { + throw new ArgumentNullException(nameof(file)); + } + + using (BinaryReader reader = new BinaryReader(File.OpenRead(file))) + { + byte[] sentinel = reader.ReadBytes(21); + StringBuilder sb = new StringBuilder(21); + sb.Append(sentinel.Select(b => (char)b).ToArray()); + + if (!sb.ToString().Equals(_sentinelV1, StringComparison.InvariantCulture)) + { + throw new ArgumentException("Not a valid Shape binary file .SHX.", nameof(file)); + } + + reader.ReadBytes(3); + + ushort firstShape = reader.ReadUInt16(); + ushort lastShape = reader.ReadUInt16(); + ushort nEntries = reader.ReadUInt16(); + + var shapes = new List<(ushort, ushort)>(nEntries); + for (int i = 0; i < nEntries; i++) + { + var index = reader.ReadUInt16(); + var size = reader.ReadUInt16(); + shapes.Add((index, size)); + } + + for (int i = 0; i < nEntries; i++) + { + string name = nullTerminatedString(reader, Encoding.ASCII); + byte[] shape = reader.ReadBytes(shapes[i].Item2 - (name.Length + 1)); + + Geometry.Create(name, shape); + } + } + } + + private static string nullTerminatedString(BinaryReader reader, Encoding encoding) + { + byte c = reader.ReadByte(); + List bytes = new List(); + while (c != 0) // strings always end with a 0 byte (char NULL) + { + bytes.Add(c); + c = reader.ReadByte(); + } + return encoding.GetString(bytes.ToArray(), 0, bytes.Count); + } + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/Templates/CadTextStyleTemplate.cs b/src/ACadSharp/IO/Templates/CadTextStyleTemplate.cs new file mode 100644 index 000000000..b7cd774e2 --- /dev/null +++ b/src/ACadSharp/IO/Templates/CadTextStyleTemplate.cs @@ -0,0 +1,19 @@ +using ACadSharp.Tables; + +namespace ACadSharp.IO.Templates +{ + internal class CadTextStyleTemplate : CadTableEntryTemplate + { + public CadTextStyleTemplate(TextStyle entry) : base(entry) { } + + protected override void build(CadDocumentBuilder builder) + { + base.Build(builder); + + if (this.CadObject.IsShapeFile) + { + + } + } + } +}