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

DomCR / ACadSharp / 16495001507

24 Jul 2025 10:57AM UTC coverage: 75.172% (-0.006%) from 75.178%
16495001507

Pull #721

github

web-flow
Merge 641226dfb into aea407c1e
Pull Request #721: SVG paper units

6037 of 8826 branches covered (68.4%)

Branch coverage included in aggregate %.

55 of 66 new or added lines in 5 files covered. (83.33%)

11 existing lines in 2 files now uncovered.

23971 of 31093 relevant lines covered (77.09%)

78825.0 hits per line

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

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

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

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

22
                public Layout Layout { get; set; }
3,089✔
23

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

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

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

64

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

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

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

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

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

88
                        double paperWidth = layout.PaperWidth;
5✔
89
                        double paperHeight = layout.PaperHeight;
5✔
90

91
                        switch (layout.PaperRotation)
5✔
92
                        {
93
                                case PlotRotation.Degrees90:
94
                                case PlotRotation.Degrees270:
95
                                        paperWidth = layout.PaperHeight;
3✔
96
                                        paperHeight = layout.PaperWidth;
3✔
97
                                        break;
3✔
98
                        }
99

100
                        XYZ lowerCorner = XYZ.Zero;
5✔
101
                        XYZ upperCorner = new XYZ(paperWidth, paperHeight, 0.0);
5✔
102
                        BoundingBox corners = new BoundingBox(lowerCorner, upperCorner);
5✔
103

104
                        this.startDocument(corners, PlotPaperUnits.Milimeters);
5✔
105

106
                        var printScale = layout.PrintScale;
5✔
107

108
                        foreach (var e in layout.AssociatedBlock.Entities)
223✔
109
                        {
104✔
110
                                this.writeEntity(e);
104✔
111
                        }
104✔
112

113
                        this.endDocument();
5✔
114
                }
5✔
115

116
                public override void WriteValue(double value)
117
                {
50✔
118
                        base.WriteValue(SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits));
50✔
119
                }
50✔
120

121
                private string colorSvg(Color color)
122
                {
119✔
123
                        if (this.Layout != null && color.Equals(Color.Default))
119✔
124
                        {
77✔
125
                                color = Color.Black;
77✔
126
                        }
77✔
127

128
                        return $"rgb({color.R},{color.G},{color.B})";
119✔
129
                }
119✔
130

131
                private void endDocument()
132
                {
6✔
133
                        this.WriteEndElement();
6✔
134
                        this.WriteEndDocument();
6✔
135
                        this.Close();
6✔
136
                }
6✔
137

138
                private void notify(string message, NotificationType type, Exception ex = null)
139
                {
8✔
140
                        this.OnNotification?.Invoke(this, new NotificationEventArgs(message, type, ex));
8!
141
                }
8✔
142

143
                private void startDocument(BoundingBox box, PlotPaperUnits unit = PlotPaperUnits.Pixels)
144
                {
6✔
145
                        this.WriteStartDocument();
6✔
146

147
                        this.WriteStartElement("svg");
6✔
148
                        this.WriteAttributeString("xmlns", "http://www.w3.org/2000/svg");
6✔
149

150
                        this.WriteAttributeString("width", box.Max.X - box.Min.X, unit);
6✔
151
                        this.WriteAttributeString("height", box.Max.Y - box.Min.Y, unit);
6✔
152

153
                        this.WriteStartAttribute("viewBox");
6✔
154
                        this.WriteValue(box.Min.X);
6✔
155
                        this.WriteValue(" ");
6✔
156
                        this.WriteValue(box.Min.Y);
6✔
157
                        this.WriteValue(" ");
6✔
158
                        this.WriteValue(box.Max.X - box.Min.X);
6✔
159
                        this.WriteValue(" ");
6✔
160
                        this.WriteValue(box.Max.Y - box.Min.Y);
6✔
161
                        this.WriteEndAttribute();
6✔
162

163
                        this.WriteAttributeString("transform", $"scale(1,-1)");
6✔
164

165
                        if (this.Layout != null)
6✔
166
                        {
5✔
167
                                this.WriteAttributeString("style", "background-color:white");
5✔
168
                        }
5✔
169
                }
6✔
170

171
                private string svgPoints(IEnumerable<IVector> points, Transform transform)
172
                {
11✔
173
                        if (!points.Any())
11!
174
                        {
×
175
                                return string.Empty;
×
176
                        }
177

178
                        StringBuilder sb = new StringBuilder();
11✔
179
                        sb.Append(this.toStringFormat(points.First()));
11✔
180
                        foreach (IVector point in points.Skip(1))
2,115✔
181
                        {
1,041✔
182
                                sb.Append(' ');
1,041✔
183
                                sb.Append(this.toStringFormat(point));
1,041✔
184
                        }
1,041✔
185

186
                        return sb.ToString();
11✔
187
                }
11✔
188

189
                private string toStringFormat(double value)
190
                {
2,156✔
191
                        return SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits).ToString(CultureInfo.InvariantCulture);
2,156✔
192
                }
2,156✔
193

194
                private string toStringFormat(IVector vector)
195
                {
1,052✔
196
                        var value = vector.Convert<XY>();
1,052✔
197
                        string x = this.toStringFormat(value.X);
1,052✔
198
                        string y = this.toStringFormat(value.Y);
1,052✔
199

200
                        return $"{x},{y}";
1,052✔
201
                }
1,052✔
202

203
                private void writeArc(Arc arc, Transform transform)
204
                {
3✔
205
                        //A rx ry rotation large-arc-flag sweep-flag x y
206

207
                        this.WriteStartElement("polyline");
3✔
208

209
                        this.writeEntityHeader(arc, transform);
3✔
210

211
                        IEnumerable<IVector> vertices = arc.PolygonalVertexes(256).OfType<IVector>();
3✔
212
                        string pts = this.svgPoints(vertices, transform);
3✔
213
                        this.WriteAttributeString("points", pts);
3✔
214
                        this.WriteAttributeString("fill", "none");
3✔
215

216
                        this.WriteEndElement();
3✔
217
                }
3✔
218

219
                private void writeCircle(Circle circle, Transform transform)
220
                {
2✔
221
                        var loc = transform.ApplyTransform(circle.Center);
2✔
222

223
                        this.WriteStartElement("circle");
2✔
224

225
                        this.writeEntityHeader(circle, transform);
2✔
226

227
                        this.WriteAttributeString("r", circle.Radius);
2✔
228
                        this.WriteAttributeString("cx", loc.X);
2✔
229
                        this.WriteAttributeString("cy", loc.Y);
2✔
230

231
                        this.WriteAttributeString("fill", "none");
2✔
232

233
                        this.WriteEndElement();
2✔
234
                }
2✔
235

236
                private void writeEllipse(Ellipse ellipse, Transform transform)
237
                {
1✔
238
                        this.WriteStartElement("polygon");
1✔
239

240
                        this.writeEntityHeader(ellipse, transform);
1✔
241

242
                        IEnumerable<IVector> vertices = ellipse.PolygonalVertexes(256).OfType<IVector>();
1✔
243
                        string pts = this.svgPoints(vertices, transform);
1✔
244
                        this.WriteAttributeString("points", pts);
1✔
245
                        this.WriteAttributeString("fill", "none");
1✔
246

247
                        this.WriteEndElement();
1✔
248
                }
1✔
249

250
                private void writeEntity(Entity entity)
251
                {
127✔
252
                        this.writeEntity(entity, new Transform());
127✔
253
                }
127✔
254

255
                private void writeEntity(Entity entity, Transform transform)
256
                {
127✔
257
                        switch (entity)
127✔
258
                        {
259
                                case Arc arc:
260
                                        this.writeArc(arc, transform);
3✔
261
                                        break;
3✔
262
                                case Line line:
263
                                        this.writeLine(line, transform);
78✔
264
                                        break;
78✔
265
                                case Point point:
266
                                        this.writePoint(point, transform);
1✔
267
                                        break;
1✔
268
                                case Circle circle:
269
                                        this.writeCircle(circle, transform);
2✔
270
                                        break;
2✔
271
                                case Ellipse ellipse:
272
                                        this.writeEllipse(ellipse, transform);
1✔
273
                                        break;
1✔
274
                                //case Hatch hatch:
275
                                //        this.writeHatch(hatch, transform);
276
                                //        break;
277
                                case Insert insert:
278
                                        this.writeInsert(insert, transform);
1✔
279
                                        break;
1✔
280
                                case IPolyline polyline:
281
                                        this.writePolyline(polyline, transform);
7✔
282
                                        break;
7✔
283
                                case IText text:
284
                                        this.writeText(text, transform);
26✔
285
                                        break;
26✔
286
                                default:
287
                                        this.notify($"[{entity.ObjectName}] Entity not implemented.", NotificationType.NotImplemented);
8✔
288
                                        break;
8✔
289
                        }
290
                }
127✔
291

292
                private void writeEntityHeader(IEntity entity, Transform transform)
293
                {
92✔
294
                        Color color = entity.GetActiveColor();
92✔
295

296
                        this.WriteAttributeString("stroke", this.colorSvg(color));
92✔
297

298
                        var lineWeight = entity.LineWeight;
92✔
299
                        switch (lineWeight)
92✔
300
                        {
301
                                case LineweightType.ByLayer:
302
                                        lineWeight = entity.Layer.LineWeight;
77✔
303
                                        break;
77✔
304
                        }
305

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

308
                        this.writeTransform(transform);
92✔
309
                }
92✔
310

311
                private void writeHatch(Hatch hatch, Transform transform)
312
                {
×
313
                        this.WriteStartElement("g");
×
314

315
                        this.writePattern(hatch.Pattern);
×
316

317
                        foreach (Hatch.BoundaryPath path in hatch.Paths)
×
318
                        {
×
319
                                this.WriteStartElement("polyline");
×
320

321
                                this.writeEntityHeader(hatch, transform);
×
322

323
                                foreach (var item in path.Edges)
×
324
                                {
×
325
                                        //TODO: svg edges for hatch drawing
326
                                }
×
327

328
                                //this.WriteAttributeString("points", pts);
329

330
                                this.WriteAttributeString("fill", "none");
×
331

332
                                this.WriteEndElement();
×
333
                        }
×
334

335
                        this.WriteEndElement();
×
336
                }
×
337

338
                private void writeInsert(Insert insert, Transform transform)
339
                {
1✔
340
                        var insertTransform = insert.GetTransform();
1✔
341
                        var merged = new Transform(transform.Matrix * insertTransform.Matrix);
1✔
342

343
                        this.WriteStartElement("g");
1✔
344
                        this.writeTransform(merged);
1✔
345

346
                        foreach (var e in insert.Block.Entities)
5✔
347
                        {
1✔
348
                                this.writeEntity(e);
1✔
349
                        }
1✔
350

351
                        this.WriteEndElement();
1✔
352
                }
1✔
353

354
                private void writeLine(Line line, Transform transform)
355
                {
78✔
356
                        this.WriteStartElement("line");
78✔
357

358
                        this.writeEntityHeader(line, transform);
78✔
359

360
                        this.WriteAttributeString("x1", line.StartPoint.X);
78✔
361
                        this.WriteAttributeString("y1", line.StartPoint.Y);
78✔
362
                        this.WriteAttributeString("x2", line.EndPoint.X);
78✔
363
                        this.WriteAttributeString("y2", line.EndPoint.Y);
78✔
364

365
                        this.WriteEndElement();
78✔
366
                }
78✔
367

368
                private void writePattern(HatchPattern pattern)
369
                {
×
370
                        this.WriteStartElement("pattern");
×
371

372
                        this.WriteEndElement();
×
373
                }
×
374

375
                private void writePoint(Point point, Transform transform)
376
                {
1✔
377
                        this.WriteStartElement("circle");
1✔
378

379
                        this.writeEntityHeader(point, transform);
1✔
380

381
                        this.WriteAttributeString("r", this.Configuration.PointRadius);
1✔
382
                        this.WriteAttributeString("cx", point.Location.X);
1✔
383
                        this.WriteAttributeString("cy", point.Location.Y);
1✔
384

385
                        this.WriteAttributeString("fill", this.colorSvg(point.GetActiveColor()));
1✔
386

387
                        this.WriteEndElement();
1✔
388
                }
1✔
389

390
                private void writePolyline(IPolyline polyline, Transform transform)
391
                {
7✔
392
                        if (polyline.IsClosed)
7✔
393
                        {
6✔
394
                                this.WriteStartElement("polygon");
6✔
395
                        }
6✔
396
                        else
397
                        {
1✔
398
                                this.WriteStartElement("polyline");
1✔
399
                        }
1✔
400

401
                        this.writeEntityHeader(polyline, transform);
7✔
402

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

405
                        string pts = this.svgPoints(polyline.Vertices.Select(v => v.Location), transform);
49✔
406
                        this.WriteAttributeString("points", pts);
7✔
407
                        this.WriteAttributeString("fill", "none");
7✔
408

409
                        this.WriteEndElement();
7✔
410
                }
7✔
411

412
                private void writeText(IText text, Transform transform)
413
                {
26✔
414
                        XYZ insert;
415

416
                        if (text is TextEntity lineText
26✔
417
                                && (lineText.HorizontalAlignment != TextHorizontalAlignment.Left
26✔
418
                                || lineText.VerticalAlignment != TextVerticalAlignmentType.Baseline)
26✔
419
                                && !(lineText.HorizontalAlignment == TextHorizontalAlignment.Fit
26✔
420
                                || lineText.HorizontalAlignment == TextHorizontalAlignment.Aligned))
26✔
421
                        {
12✔
422
                                insert = lineText.AlignmentPoint;
12✔
423
                        }
12✔
424
                        else
425
                        {
14✔
426
                                insert = text.InsertPoint;
14✔
427
                        }
14✔
428

429
                        this.WriteStartElement("g");
26✔
430
                        this.WriteAttributeString("transform", $"translate({this.toStringFormat(insert.X)},{this.toStringFormat(insert.Y)})");
26✔
431

432
                        this.WriteStartElement("text");
26✔
433

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

436
                        this.WriteAttributeString("fill", this.colorSvg(text.GetActiveColor()));
26✔
437

438
                        //<text x="20" y="35" class="small">My</text>
439
                        this.WriteStartAttribute("style");
26✔
440
                        this.WriteValue("font:");
26✔
441
                        this.WriteValue(text.Height);
26✔
442
                        this.WriteValue("px");
26✔
443
                        this.WriteValue(" ");
26✔
444
                        this.WriteValue(Path.GetFileNameWithoutExtension(text.Style.Filename));
26✔
445
                        this.WriteEndAttribute();
26✔
446

447
                        switch (text)
26✔
448
                        {
449
                                case MText mtext:
450
                                        switch (mtext.AttachmentPoint)
5!
451
                                        {
452
                                                case AttachmentPointType.TopLeft:
453
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
4✔
454
                                                        this.WriteAttributeString("text-anchor", "start");
4✔
455
                                                        break;
4✔
456
                                                case AttachmentPointType.TopCenter:
457
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
×
458
                                                        this.WriteAttributeString("text-anchor", "middle");
×
459
                                                        break;
×
460
                                                case AttachmentPointType.TopRight:
461
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
1✔
462
                                                        this.WriteAttributeString("text-anchor", "end");
1✔
463
                                                        break;
1✔
464
                                                case AttachmentPointType.MiddleLeft:
465
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
466
                                                        this.WriteAttributeString("text-anchor", "start");
×
467
                                                        break;
×
468
                                                case AttachmentPointType.MiddleCenter:
469
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
470
                                                        this.WriteAttributeString("text-anchor", "middle");
×
471
                                                        break;
×
472
                                                case AttachmentPointType.MiddleRight:
473
                                                        this.WriteAttributeString("alignment-baseline", "middle");
×
474
                                                        this.WriteAttributeString("text-anchor", "end");
×
475
                                                        break;
×
476
                                                case AttachmentPointType.BottomLeft:
477
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
478
                                                        this.WriteAttributeString("text-anchor", "start");
×
479
                                                        break;
×
480
                                                case AttachmentPointType.BottomCenter:
481
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
482
                                                        this.WriteAttributeString("text-anchor", "middle");
×
483
                                                        break;
×
484
                                                case AttachmentPointType.BottomRight:
NEW
485
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
×
NEW
486
                                                        this.WriteAttributeString("text-anchor", "end");
×
NEW
487
                                                        break;
×
488
                                                default:
NEW
489
                                                        break;
×
490
                                        }
491

492
                                        foreach (var item in mtext.GetTextLines())
53✔
493
                                        {
19✔
494
                                                this.WriteStartElement("tspan");
19✔
495
                                                this.WriteAttributeString("x", 0);
19✔
496
                                                this.WriteAttributeString("dy", "1em");
19✔
497
                                                this.WriteString(item);
19✔
498
                                                this.WriteEndElement();
19✔
499
                                        }
19✔
500
                                        break;
5✔
501
                                case TextEntity textEntity:
502

503
                                        switch (textEntity.HorizontalAlignment)
21✔
504
                                        {
505
                                                case TextHorizontalAlignment.Left:
506
                                                        this.WriteAttributeString("text-anchor", "start");
10✔
507
                                                        break;
10✔
508
                                                case TextHorizontalAlignment.Middle:
509
                                                case TextHorizontalAlignment.Center:
510
                                                        this.WriteAttributeString("text-anchor", "middle");
5✔
511
                                                        break;
5✔
512
                                                case TextHorizontalAlignment.Right:
513
                                                        this.WriteAttributeString("text-anchor", "end");
4✔
514
                                                        break;
4✔
515
                                        }
516

517
                                        switch (textEntity.VerticalAlignment)
21✔
518
                                        {
519
                                                case TextVerticalAlignmentType.Baseline:
520
                                                case TextVerticalAlignmentType.Bottom:
521
                                                        this.WriteAttributeString("alignment-baseline", "baseline");
15✔
522
                                                        break;
15✔
523
                                                case TextVerticalAlignmentType.Middle:
524
                                                        this.WriteAttributeString("alignment-baseline", "middle");
3✔
525
                                                        break;
3✔
526
                                                case TextVerticalAlignmentType.Top:
527
                                                        this.WriteAttributeString("alignment-baseline", "hanging");
3✔
528
                                                        break;
3✔
529
                                        }
530

531
                                        this.WriteString(text.Value);
21✔
532
                                        break;
21✔
533
                        }
534

535
                        this.WriteEndElement();
26✔
536
                        this.WriteEndElement();
26✔
537
                }
26✔
538

539
                private void writeTransform(Transform transform)
540
                {
93✔
541
                        XYZ? translation = transform.Translation != XYZ.Zero ? transform.Translation : null;
93!
542
                        XYZ? scale = transform.Scale != new XYZ(1) ? transform.Scale : null;
93✔
543
                        double? rotation = transform.EulerRotation.Z != 0 ? transform.EulerRotation.Z : null;
93!
544

545
                        this.writeTransform(translation, scale, rotation);
93✔
546
                }
93✔
547

548
                private void writeTransform(XYZ? translation = null, XYZ? scale = null, double? rotation = null)
549
                {
119✔
550
                        StringBuilder sb = new StringBuilder();
119✔
551

552
                        if (translation.HasValue)
119!
553
                        {
×
554
                                var t = translation.Value;
×
555

556
                                sb.Append($"translate(");
×
557
                                sb.Append($"{t.X.ToString(CultureInfo.InvariantCulture)},");
×
558
                                sb.Append($"{t.Y.ToString(CultureInfo.InvariantCulture)})");
×
559
                                sb.Append(' ');
×
560
                        }
×
561

562
                        if (scale.HasValue)
119✔
563
                        {
27✔
564
                                var s = scale.Value;
27✔
565

566
                                sb.Append($"scale(");
27✔
567
                                sb.Append($"{s.X.ToString(CultureInfo.InvariantCulture)},");
27✔
568
                                sb.Append($"{s.Y.ToString(CultureInfo.InvariantCulture)})");
27✔
569
                                sb.Append(' ');
27✔
570
                        }
27✔
571

572
                        if (rotation.HasValue)
119✔
573
                        {
2✔
574
                                var r = -MathHelper.RadToDeg(rotation.Value);
2✔
575

576
                                sb.Append($"rotate(");
2✔
577
                                sb.Append($"{r.ToString(CultureInfo.InvariantCulture)})");
2✔
578
                        }
2✔
579

580
                        if (sb.ToString().IsNullOrEmpty())
119✔
581
                        {
92✔
582
                                return;
92✔
583
                        }
584

585
                        this.WriteAttributeString("transform", sb.ToString());
27✔
586
                }
119✔
587
        }
588
}
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