• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DomCR / ACadSharp / 16494678346

24 Jul 2025 10:40AM UTC coverage: 75.178% (+0.1%) from 75.057%
16494678346

push

github

web-flow
Merge pull request #718 from DomCR/svg-tests

SVG tests

6032 of 8820 branches covered (68.39%)

Branch coverage included in aggregate %.

148 of 167 new or added lines in 6 files covered. (88.62%)

8 existing lines in 3 files now uncovered.

23967 of 31084 relevant lines covered (77.1%)

78848.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.46
/src/ACadSharp/IO/SVG/SvgXmlWriter.cs
1
using ACadSharp.Entities;
2
using ACadSharp.Objects;
3
using ACadSharp.Tables;
4
using CSMath;
5
using System;
6
using System.Collections.Generic;
7
using System.Globalization;
8
using System.IO;
9
using System.Linq;
10
using System.Text;
11
using System.Xml;
12

13
namespace ACadSharp.IO.SVG
14
{
15
        internal class SvgXmlWriter : XmlTextWriter
16
        {
17
                public event NotificationEventHandler OnNotification;
18

19
                public SvgConfiguration Configuration { get; } = new();
99✔
20

21
                public Layout Layout { get; set; }
3,094✔
22

23
                public PlotPaperUnits PlotPaperUnits
24
                {
25
                        get
26
                        {
2,546✔
27
                                if (this.Layout == null)
2,546✔
28
                                {
2,133✔
29
                                        return PlotPaperUnits.Milimeters;
2,133✔
30
                                }
31
                                else
32
                                {
413✔
33
                                        return this.Layout.PaperUnits;
413✔
34
                                }
35
                        }
2,546✔
36
                }
37

38
                public SvgXmlWriter(Stream w, Encoding encoding, SvgConfiguration configuration) : base(w, encoding)
6✔
39
                {
6✔
40
                        this.Configuration = configuration;
6✔
41
                }
6✔
42

43
                public void WriteAttributeString(string localName, double value, PlotPaperUnits? units = null)
44
                {
352✔
45
                        string unit = string.Empty;
352✔
46
                        if (units == null)
352✔
47
                        {
340✔
48
                                value = SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits);
340✔
49
                        }
340✔
50
                        else
51
                        {
12✔
52
                                switch (units.Value)
12✔
53
                                {
54
                                        case PlotPaperUnits.Milimeters:
55
                                                unit = "mm";
8✔
56
                                                break;
8✔
57
                                        case PlotPaperUnits.Inches:
58
                                                unit = "in";
2✔
59
                                                break;
2✔
60
                                }
61
                        }
12✔
62

63

64
                        this.WriteAttributeString(
352✔
65
                                localName,
352✔
66
                                $"{value.ToString(CultureInfo.InvariantCulture)}{unit}");
352✔
67
                }
352✔
68

69
                public void WriteBlock(BlockRecord record)
70
                {
1✔
71
                        BoundingBox box = record.GetBoundingBox();
1✔
72
                        this.startDocument(box);
1✔
73

74
                        Transform transform = new Transform(-box.Min, new XYZ(1), XYZ.Zero);
1✔
75
                        foreach (var e in record.Entities)
47✔
76
                        {
22✔
77
                                this.writeEntity(e);
22✔
78
                        }
22✔
79

80
                        this.endDocument();
1✔
81
                }
1✔
82

83
                public void WriteLayout(Layout layout)
84
                {
5✔
85
                        this.Layout = layout;
5✔
86

87
                        XYZ lowerCorner = XYZ.Zero;
5✔
88
                        XYZ upperCorner = new XYZ(layout.PaperWidth, layout.PaperHeight, 0.0);
5✔
89

90
                        BoundingBox corners = new BoundingBox(lowerCorner, upperCorner);
5✔
91

92
                        BoundingBox boxExtend = new BoundingBox(layout.MinExtents, layout.MaxExtents);
5✔
93

94
                        this.startDocument(corners, Layout.PaperUnits);
5✔
95

96
                        foreach (var e in layout.AssociatedBlock.Entities)
223✔
97
                        {
104✔
98
                                this.writeEntity(e);
104✔
99
                        }
104✔
100

101
                        this.endDocument();
5✔
102
                }
5✔
103

104
                public override void WriteValue(double value)
105
                {
50✔
106
                        base.WriteValue(SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits));
50✔
107
                }
50✔
108

109
                private string colorSvg(Color color)
110
                {
119✔
111
                        if (this.Layout != null && color.Equals(Color.Default))
119✔
112
                        {
77✔
113
                                color = Color.Black;
77✔
114
                        }
77✔
115

116
                        return $"rgb({color.R},{color.G},{color.B})";
119✔
117
                }
119✔
118

119
                private void endDocument()
120
                {
6✔
121
                        this.WriteEndElement();
6✔
122
                        this.WriteEndDocument();
6✔
123
                        this.Close();
6✔
124
                }
6✔
125

126
                private void notify(string message, NotificationType type, Exception ex = null)
127
                {
8✔
128
                        this.OnNotification?.Invoke(this, new NotificationEventArgs(message, type, ex));
8!
129
                }
8✔
130

131
                private void startDocument(BoundingBox box, PlotPaperUnits unit = PlotPaperUnits.Pixels)
132
                {
6✔
133
                        this.WriteStartDocument();
6✔
134

135
                        this.WriteStartElement("svg");
6✔
136
                        this.WriteAttributeString("xmlns", "http://www.w3.org/2000/svg");
6✔
137

138
                        this.WriteAttributeString("width", box.Max.X - box.Min.X, unit);
6✔
139
                        this.WriteAttributeString("height", box.Max.Y - box.Min.Y, unit);
6✔
140

141
                        this.WriteStartAttribute("viewBox");
6✔
142
                        this.WriteValue(box.Min.X);
6✔
143
                        this.WriteValue(" ");
6✔
144
                        this.WriteValue(box.Min.Y);
6✔
145
                        this.WriteValue(" ");
6✔
146
                        this.WriteValue(box.Max.X - box.Min.X);
6✔
147
                        this.WriteValue(" ");
6✔
148
                        this.WriteValue(box.Max.Y - box.Min.Y);
6✔
149
                        this.WriteEndAttribute();
6✔
150

151
                        this.WriteAttributeString("transform", $"scale(1,-1)");
6✔
152

153
                        if (this.Layout != null)
6✔
154
                        {
5✔
155
                                this.WriteAttributeString("style", "background-color:white");
5✔
156
                        }
5✔
157
                }
6✔
158

159
                private string svgPoints(IEnumerable<IVector> points, Transform transform)
160
                {
11✔
161
                        if (!points.Any())
11!
162
                        {
×
163
                                return string.Empty;
×
164
                        }
165

166
                        StringBuilder sb = new StringBuilder();
11✔
167
                        sb.Append(this.toStringFormat(points.First()));
11✔
168
                        foreach (IVector point in points.Skip(1))
2,115✔
169
                        {
1,041✔
170
                                sb.Append(' ');
1,041✔
171
                                sb.Append(this.toStringFormat(point));
1,041✔
172
                        }
1,041✔
173

174
                        return sb.ToString();
11✔
175
                }
11✔
176

177
                private string toStringFormat(double value)
178
                {
2,156✔
179
                        return SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits).ToString(CultureInfo.InvariantCulture);
2,156✔
180
                }
2,156✔
181

182
                private string toStringFormat(IVector vector)
183
                {
1,052✔
184
                        var value = vector.Convert<XY>();
1,052✔
185
                        string x = this.toStringFormat(value.X);
1,052✔
186
                        string y = this.toStringFormat(value.Y);
1,052✔
187

188
                        return $"{x},{y}";
1,052✔
189
                }
1,052✔
190

191
                private void writeArc(Arc arc, Transform transform)
192
                {
3✔
193
                        //A rx ry rotation large-arc-flag sweep-flag x y
194

195
                        this.WriteStartElement("polyline");
3✔
196

197
                        this.writeEntityHeader(arc, transform);
3✔
198

199
                        IEnumerable<IVector> vertices = arc.PolygonalVertexes(256).OfType<IVector>();
3✔
200
                        string pts = this.svgPoints(vertices, transform);
3✔
201
                        this.WriteAttributeString("points", pts);
3✔
202
                        this.WriteAttributeString("fill", "none");
3✔
203

204
                        this.WriteEndElement();
3✔
205
                }
3✔
206

207
                private void writeCircle(Circle circle, Transform transform)
208
                {
2✔
209
                        var loc = transform.ApplyTransform(circle.Center);
2✔
210

211
                        this.WriteStartElement("circle");
2✔
212

213
                        this.writeEntityHeader(circle, transform);
2✔
214

215
                        this.WriteAttributeString("r", circle.Radius);
2✔
216
                        this.WriteAttributeString("cx", loc.X);
2✔
217
                        this.WriteAttributeString("cy", loc.Y);
2✔
218

219
                        this.WriteAttributeString("fill", "none");
2✔
220

221
                        this.WriteEndElement();
2✔
222
                }
2✔
223

224
                private void writeEllipse(Ellipse ellipse, Transform transform)
225
                {
1✔
226
                        this.WriteStartElement("polygon");
1✔
227

228
                        this.writeEntityHeader(ellipse, transform);
1✔
229

230
                        IEnumerable<IVector> vertices = ellipse.PolygonalVertexes(256).OfType<IVector>();
1✔
231
                        string pts = this.svgPoints(vertices, transform);
1✔
232
                        this.WriteAttributeString("points", pts);
1✔
233
                        this.WriteAttributeString("fill", "none");
1✔
234

235
                        this.WriteEndElement();
1✔
236
                }
1✔
237

238
                private void writeEntity(Entity entity)
239
                {
127✔
240
                        this.writeEntity(entity, new Transform());
127✔
241
                }
127✔
242

243
                private void writeEntity(Entity entity, Transform transform)
244
                {
127✔
245
                        switch (entity)
127✔
246
                        {
247
                                case Arc arc:
248
                                        this.writeArc(arc, transform);
3✔
249
                                        break;
3✔
250
                                case Line line:
251
                                        this.writeLine(line, transform);
78✔
252
                                        break;
78✔
253
                                case Point point:
254
                                        this.writePoint(point, transform);
1✔
255
                                        break;
1✔
256
                                case Circle circle:
257
                                        this.writeCircle(circle, transform);
2✔
258
                                        break;
2✔
259
                                case Ellipse ellipse:
260
                                        this.writeEllipse(ellipse, transform);
1✔
261
                                        break;
1✔
262
                                //case Hatch hatch:
263
                                //        this.writeHatch(hatch, transform);
264
                                //        break;
265
                                case Insert insert:
266
                                        this.writeInsert(insert, transform);
1✔
267
                                        break;
1✔
268
                                case IPolyline polyline:
269
                                        this.writePolyline(polyline, transform);
7✔
270
                                        break;
7✔
271
                                case IText text:
272
                                        this.writeText(text, transform);
26✔
273
                                        break;
26✔
274
                                default:
275
                                        this.notify($"[{entity.ObjectName}] Entity not implemented.", NotificationType.NotImplemented);
8✔
276
                                        break;
8✔
277
                        }
278
                }
127✔
279

280
                private void writeEntityHeader(IEntity entity, Transform transform)
281
                {
92✔
282
                        Color color = entity.GetActiveColor();
92✔
283

284
                        this.WriteAttributeString("stroke", this.colorSvg(color));
92✔
285

286
                        var lineWeight = entity.LineWeight;
92✔
287
                        switch (lineWeight)
92✔
288
                        {
289
                                case LineweightType.ByLayer:
290
                                        lineWeight = entity.Layer.LineWeight;
77✔
291
                                        break;
77✔
292
                        }
293

294
                        this.WriteAttributeString("stroke-width", this.Configuration.GetLineWeightValue(lineWeight).ToString(CultureInfo.InvariantCulture));
92✔
295

296
                        this.writeTransform(transform);
92✔
297
                }
92✔
298

299
                private void writeHatch(Hatch hatch, Transform transform)
300
                {
×
301
                        this.WriteStartElement("g");
×
302

303
                        this.writePattern(hatch.Pattern);
×
304

305
                        foreach (Hatch.BoundaryPath path in hatch.Paths)
×
306
                        {
×
307
                                this.WriteStartElement("polyline");
×
308

NEW
309
                                this.writeEntityHeader(hatch, transform);
×
310

311
                                foreach (var item in path.Edges)
×
312
                                {
×
313
                                        //TODO: svg edges for hatch drawing
314
                                }
×
315

316
                                //this.WriteAttributeString("points", pts);
317

318
                                this.WriteAttributeString("fill", "none");
×
319

320
                                this.WriteEndElement();
×
321
                        }
×
322

323
                        this.WriteEndElement();
×
324
                }
×
325

326
                private void writeInsert(Insert insert, Transform transform)
327
                {
1✔
328
                        var insertTransform = insert.GetTransform();
1✔
329
                        var merged = new Transform(transform.Matrix * insertTransform.Matrix);
1✔
330

331
                        this.WriteStartElement("g");
1✔
332
                        this.writeTransform(merged);
1✔
333

334
                        foreach (var e in insert.Block.Entities)
5✔
335
                        {
1✔
336
                                this.writeEntity(e);
1✔
337
                        }
1✔
338

339
                        this.WriteEndElement();
1✔
340
                }
1✔
341

342
                private void writeLine(Line line, Transform transform)
343
                {
78✔
344
                        this.WriteStartElement("line");
78✔
345

346
                        this.writeEntityHeader(line, transform);
78✔
347

348
                        this.WriteAttributeString("x1", line.StartPoint.X);
78✔
349
                        this.WriteAttributeString("y1", line.StartPoint.Y);
78✔
350
                        this.WriteAttributeString("x2", line.EndPoint.X);
78✔
351
                        this.WriteAttributeString("y2", line.EndPoint.Y);
78✔
352

353
                        this.WriteEndElement();
78✔
354
                }
78✔
355

356
                private void writePattern(HatchPattern pattern)
UNCOV
357
                {
×
NEW
358
                        this.WriteStartElement("pattern");
×
359

NEW
360
                        this.WriteEndElement();
×
NEW
361
                }
×
362

363
                private void writePoint(Point point, Transform transform)
364
                {
1✔
365
                        this.WriteStartElement("circle");
1✔
366

367
                        this.writeEntityHeader(point, transform);
1✔
368

369
                        this.WriteAttributeString("r", this.Configuration.PointRadius);
1✔
370
                        this.WriteAttributeString("cx", point.Location.X);
1✔
371
                        this.WriteAttributeString("cy", point.Location.Y);
1✔
372

373
                        this.WriteAttributeString("fill", this.colorSvg(point.GetActiveColor()));
1✔
374

375
                        this.WriteEndElement();
1✔
376
                }
1✔
377

378
                private void writePolyline(IPolyline polyline, Transform transform)
379
                {
7✔
380
                        if (polyline.IsClosed)
7✔
381
                        {
6✔
382
                                this.WriteStartElement("polygon");
6✔
383
                        }
6✔
384
                        else
385
                        {
1✔
386
                                this.WriteStartElement("polyline");
1✔
387
                        }
1✔
388

389
                        this.writeEntityHeader(polyline, transform);
7✔
390

391
                        var vertices = polyline.Vertices.Select(v => v.Location).ToList();
35✔
392

393
                        string pts = this.svgPoints(polyline.Vertices.Select(v => v.Location), transform);
49✔
394
                        this.WriteAttributeString("points", pts);
7✔
395
                        this.WriteAttributeString("fill", "none");
7✔
396

397
                        this.WriteEndElement();
7✔
398
                }
7✔
399

400
                private void writeText(IText text, Transform transform)
401
                {
26✔
402
                        XYZ insert;
403

404
                        if (text is TextEntity lineText
26✔
405
                                && (lineText.HorizontalAlignment != TextHorizontalAlignment.Left
26✔
406
                                || lineText.VerticalAlignment != TextVerticalAlignmentType.Baseline)
26✔
407
                                && !(lineText.HorizontalAlignment == TextHorizontalAlignment.Fit
26✔
408
                                || lineText.HorizontalAlignment == TextHorizontalAlignment.Aligned))
26✔
409
                        {
12✔
410
                                insert = lineText.AlignmentPoint;
12✔
411
                        }
12✔
412
                        else
413
                        {
14✔
414
                                insert = text.InsertPoint;
14✔
415
                        }
14✔
416

417
                        this.WriteStartElement("g");
26✔
418
                        this.WriteAttributeString("transform", $"translate({this.toStringFormat(insert.X)},{this.toStringFormat(insert.Y)})");
26✔
419

420
                        this.WriteStartElement("text");
26✔
421

422
                        this.writeTransform(scale: new XYZ(1, -1, 0), rotation: text.Rotation != 0 ? text.Rotation : null);
26✔
423

424
                        this.WriteAttributeString("fill", this.colorSvg(text.GetActiveColor()));
26✔
425

426
                        //<text x="20" y="35" class="small">My</text>
427
                        this.WriteStartAttribute("style");
26✔
428
                        this.WriteValue("font:");
26✔
429
                        this.WriteValue(text.Height);
26✔
430
                        this.WriteValue("px");
26✔
431
                        this.WriteValue(" ");
26✔
432
                        this.WriteValue(Path.GetFileNameWithoutExtension(text.Style.Filename));
26✔
433
                        this.WriteEndAttribute();
26✔
434

435
                        switch (text)
26✔
436
                        {
437
                                case MText mtext:
438
                                        switch (mtext.AttachmentPoint)
5!
439
                                        {
440
                                                case AttachmentPointType.TopLeft:
441
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
4✔
442
                                                        this.WriteAttributeString("text-anchor", "start");
4✔
443
                                                        break;
4✔
444
                                                case AttachmentPointType.TopCenter:
445
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
×
446
                                                        this.WriteAttributeString("text-anchor", "middle");
×
447
                                                        break;
×
448
                                                case AttachmentPointType.TopRight:
449
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
1✔
450
                                                        this.WriteAttributeString("text-anchor", "end");
1✔
451
                                                        break;
1✔
452
                                                case AttachmentPointType.MiddleLeft:
453
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
454
                                                        this.WriteAttributeString("text-anchor", "start");
×
455
                                                        break;
×
456
                                                case AttachmentPointType.MiddleCenter:
457
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
458
                                                        this.WriteAttributeString("text-anchor", "middle");
×
459
                                                        break;
×
460
                                                case AttachmentPointType.MiddleRight:
461
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
462
                                                        this.WriteAttributeString("text-anchor", "end");
×
463
                                                        break;
×
464
                                                case AttachmentPointType.BottomLeft:
465
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
466
                                                        this.WriteAttributeString("text-anchor", "start");
×
467
                                                        break;
×
468
                                                case AttachmentPointType.BottomCenter:
469
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
470
                                                        this.WriteAttributeString("text-anchor", "middle");
×
471
                                                        break;
×
472
                                                case AttachmentPointType.BottomRight:
473
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
474
                                                        this.WriteAttributeString("text-anchor", "end");
×
475
                                                        break;
×
476
                                                default:
477
                                                        break;
×
478
                                        }
479

480
                                        foreach (var item in mtext.GetTextLines())
53✔
481
                                        {
19✔
482
                                                this.WriteStartElement("tspan");
19✔
483
                                                this.WriteAttributeString("x", 0);
19✔
484
                                                this.WriteAttributeString("dy", "1em");
19✔
485
                                                this.WriteString(item);
19✔
486
                                                this.WriteEndElement();
19✔
487
                                        }
19✔
488
                                        break;
5✔
489
                                case TextEntity textEntity:
490

491
                                        switch (textEntity.HorizontalAlignment)
21✔
492
                                        {
493
                                                case TextHorizontalAlignment.Left:
494
                                                        this.WriteAttributeString("text-anchor", "start");
10✔
495
                                                        break;
10✔
496
                                                case TextHorizontalAlignment.Middle:
497
                                                case TextHorizontalAlignment.Center:
498
                                                        this.WriteAttributeString("text-anchor", "middle");
5✔
499
                                                        break;
5✔
500
                                                case TextHorizontalAlignment.Right:
501
                                                        this.WriteAttributeString("text-anchor", "end");
4✔
502
                                                        break;
4✔
503
                                        }
504

505
                                        switch (textEntity.VerticalAlignment)
21✔
506
                                        {
507
                                                case TextVerticalAlignmentType.Baseline:
508
                                                case TextVerticalAlignmentType.Bottom:
509
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
15✔
510
                                                        break;
15✔
511
                                                case TextVerticalAlignmentType.Middle:
512
                                                        this.WriteAttributeString("alignment-baseline", "middle");
3✔
513
                                                        break;
3✔
514
                                                case TextVerticalAlignmentType.Top:
515
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
3✔
516
                                                        break;
3✔
517
                                        }
518

519
                                        this.WriteString(text.Value);
21✔
520
                                        break;
21✔
521
                        }
522

523
                        this.WriteEndElement();
26✔
524
                        this.WriteEndElement();
26✔
525
                }
26✔
526

527
                private void writeTransform(Transform transform)
528
                {
93✔
529
                        XYZ? translation = transform.Translation != XYZ.Zero ? transform.Translation : null;
93!
530
                        XYZ? scale = transform.Scale != new XYZ(1) ? transform.Scale : null;
93✔
531
                        double? rotation = transform.EulerRotation.Z != 0 ? transform.EulerRotation.Z : null;
93!
532

533
                        this.writeTransform(translation, scale, rotation);
93✔
534
                }
93✔
535

536
                private void writeTransform(XYZ? translation = null, XYZ? scale = null, double? rotation = null)
537
                {
119✔
538
                        StringBuilder sb = new StringBuilder();
119✔
539

540
                        if (translation.HasValue)
119!
NEW
541
                        {
×
NEW
542
                                var t = translation.Value;
×
543

NEW
544
                                sb.Append($"translate(");
×
NEW
545
                                sb.Append($"{t.X.ToString(CultureInfo.InvariantCulture)},");
×
NEW
546
                                sb.Append($"{t.Y.ToString(CultureInfo.InvariantCulture)})");
×
NEW
547
                                sb.Append(' ');
×
NEW
548
                        }
×
549

550
                        if (scale.HasValue)
119✔
551
                        {
27✔
552
                                var s = scale.Value;
27✔
553

554
                                sb.Append($"scale(");
27✔
555
                                sb.Append($"{s.X.ToString(CultureInfo.InvariantCulture)},");
27✔
556
                                sb.Append($"{s.Y.ToString(CultureInfo.InvariantCulture)})");
27✔
557
                                sb.Append(' ');
27✔
558
                        }
27✔
559

560
                        if (rotation.HasValue)
119✔
561
                        {
2✔
562
                                var r = -MathHelper.RadToDeg(rotation.Value);
2✔
563

564
                                sb.Append($"rotate(");
2✔
565
                                sb.Append($"{r.ToString(CultureInfo.InvariantCulture)})");
2✔
566
                        }
2✔
567

568
                        this.WriteAttributeString("transform", sb.ToString());
119✔
569
                }
119✔
570
        }
571
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc