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

DomCR / ACadSharp / 16495317968

24 Jul 2025 11:12AM UTC coverage: 75.199% (+0.02%) from 75.178%
16495317968

Pull #721

github

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

6040 of 8828 branches covered (68.42%)

Branch coverage included in aggregate %.

54 of 66 new or added lines in 5 files covered. (81.82%)

2 existing lines in 1 file now uncovered.

23981 of 31094 relevant lines covered (77.12%)

78794.9 hits per line

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

85.32
/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; }
2,493✔
23

24
                public PlotPaperUnits PlotPaperUnits
25
                {
26
                        get
27
                        {
2,234✔
28
                                if (this.Layout == null)
2,234✔
29
                                {
2,105✔
30
                                        return PlotPaperUnits.Milimeters;
2,105✔
31
                                }
32
                                else
33
                                {
129✔
34
                                        return this.Layout.PaperUnits;
129✔
35
                                }
36
                        }
2,234✔
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
                        {
28✔
49
                                value = SvgConfiguration.ToPixelSize(value, this.PlotPaperUnits);
28✔
50
                        }
28✔
51
                        else
52
                        {
324✔
53
                                switch (units.Value)
324!
54
                                {
55
                                        case PlotPaperUnits.Milimeters:
56
                                                unit = "mm";
322✔
57
                                                break;
322✔
58
                                        case PlotPaperUnits.Inches:
UNCOV
59
                                                unit = "in";
×
NEW
UNCOV
60
                                                break;
×
61
                                }
62
                        }
324✔
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
                        Transform transform = new Transform(XYZ.Zero, new XYZ(layout.PrintScale), XYZ.Zero);
5✔
109

110
                        foreach (var e in layout.AssociatedBlock.Entities)
223✔
111
                        {
104✔
112
                                this.writeEntity(e, transform);
104✔
113
                        }
104✔
114

115
                        this.endDocument();
5✔
116
                }
5✔
117

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

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

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

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

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

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

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

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

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

165
                        this.WriteAttributeString("transform", $"scale(1,-1)");
6✔
166

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

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

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

188
                        return sb.ToString();
11✔
189
                }
11✔
190

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

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

202
                        return $"{x},{y}";
1,052✔
203
                }
1,052✔
204

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

209
                        this.WriteStartElement("polyline");
3✔
210

211
                        this.writeEntityHeader(arc, transform);
3✔
212

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

218
                        this.WriteEndElement();
3✔
219
                }
3✔
220

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

225
                        this.WriteStartElement("circle");
2✔
226

227
                        this.writeEntityHeader(circle, transform);
2✔
228

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

233
                        this.WriteAttributeString("fill", "none");
2✔
234

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

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

242
                        this.writeEntityHeader(ellipse, transform);
1✔
243

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

249
                        this.WriteEndElement();
1✔
250
                }
1✔
251

252
                private void writeEntity(Entity entity)
253
                {
23✔
254
                        this.writeEntity(entity, new Transform());
23✔
255
                }
23✔
256

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

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

298
                        this.WriteAttributeString("stroke", this.colorSvg(color));
92✔
299

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

308
                        this.WriteAttributeString("stroke-width",$"{this.Configuration.GetLineWeightValue(lineWeight).ToString(CultureInfo.InvariantCulture)}mm" );
92✔
309

310
                        this.writeTransform(transform);
92✔
311
                }
92✔
312

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

317
                        this.writePattern(hatch.Pattern);
×
318

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

323
                                this.writeEntityHeader(hatch, transform);
×
324

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

330
                                //this.WriteAttributeString("points", pts);
331

332
                                this.WriteAttributeString("fill", "none");
×
333

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

337
                        this.WriteEndElement();
×
338
                }
×
339

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

345
                        this.WriteStartElement("g");
1✔
346
                        this.writeTransform(merged);
1✔
347

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

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

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

360
                        this.writeEntityHeader(line, transform);
78✔
361

362
                        this.WriteAttributeString("x1", line.StartPoint.X, PlotPaperUnits.Milimeters);
78✔
363
                        this.WriteAttributeString("y1", line.StartPoint.Y, PlotPaperUnits.Milimeters);
78✔
364
                        this.WriteAttributeString("x2", line.EndPoint.X, PlotPaperUnits.Milimeters);
78✔
365
                        this.WriteAttributeString("y2", line.EndPoint.Y, PlotPaperUnits.Milimeters);
78✔
366

367
                        this.WriteEndElement();
78✔
368
                }
78✔
369

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

374
                        this.WriteEndElement();
×
375
                }
×
376

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

381
                        this.writeEntityHeader(point, transform);
1✔
382

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

387
                        this.WriteAttributeString("fill", this.colorSvg(point.GetActiveColor()));
1✔
388

389
                        this.WriteEndElement();
1✔
390
                }
1✔
391

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

403
                        this.writeEntityHeader(polyline, transform);
7✔
404

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

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

411
                        this.WriteEndElement();
7✔
412
                }
7✔
413

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

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

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

434
                        this.WriteStartElement("text");
26✔
435

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

438
                        this.WriteAttributeString("fill", this.colorSvg(text.GetActiveColor()));
26✔
439

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

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

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

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

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

533
                                        this.WriteString(text.Value);
21✔
534
                                        break;
21✔
535
                        }
536

537
                        this.WriteEndElement();
26✔
538
                        this.WriteEndElement();
26✔
539
                }
26✔
540

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

547
                        this.writeTransform(translation, scale, rotation);
93✔
548
                }
93✔
549

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

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

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

564
                        if (scale.HasValue)
119✔
565
                        {
63✔
566
                                var s = scale.Value;
63✔
567

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

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

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

582
                        if (sb.ToString().IsNullOrEmpty())
119✔
583
                        {
56✔
584
                                return;
56✔
585
                        }
586

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