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

DomCR / ACadSharp / 19643235357

24 Nov 2025 05:25PM UTC coverage: 78.033% (-0.2%) from 78.204%
19643235357

push

github

web-flow
Merge pull request #890 from DomCR/Issue-250_Text-MText-special-characters

issue 250

7457 of 10365 branches covered (71.94%)

Branch coverage included in aggregate %.

75 of 140 new or added lines in 2 files covered. (53.57%)

54 existing lines in 3 files now uncovered.

27717 of 34711 relevant lines covered (79.85%)

97757.47 hits per line

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

58.33
/src/ACadSharp/Entities/MText.cs
1
using ACadSharp.Attributes;
2
using ACadSharp.Tables;
3
using ACadSharp.Text;
4
using CSMath;
5
using System;
6
using System.Collections.Generic;
7
using System.Text;
8
using System.Text.RegularExpressions;
9

10
namespace ACadSharp.Entities
11
{
12
        /// <summary>
13
        /// Represents a <see cref="MText"/> entity.
14
        /// </summary>
15
        /// <remarks>
16
        /// Object name <see cref="DxfFileToken.EntityMText"/> <br/>
17
        /// Dxf class name <see cref="DxfSubclassMarker.MText"/>
18
        /// </remarks>
19
        [DxfName(DxfFileToken.EntityMText)]
20
        [DxfSubClass(DxfSubclassMarker.MText)]
21
        public partial class MText : Entity, IText
22
        {
23
                /// <inheritdoc/>
24
                [DxfCodeValue(11, 21, 31)]
25
                public XYZ AlignmentPoint { get; set; } = XYZ.AxisX;
28,459✔
26

27
                /// <summary>
28
                /// Attachment point
29
                /// </summary>
30
                [DxfCodeValue(71)]
31
                public AttachmentPointType AttachmentPoint { get; set; } = AttachmentPointType.TopLeft;
27,891✔
32

33
                /// <summary>
34
                /// Background fill color
35
                /// </summary>
36
                /// <remarks>
37
                /// Color to use for background fill when group code 90 is 1.
38
                /// </remarks>
39
                [DxfCodeValue(63, 421, 430)]
40
                public Color BackgroundColor { get; set; }
196✔
41

42
                /// <summary>
43
                /// Background fill setting
44
                /// </summary>
45
                [DxfCodeValue(90)]
46
                public BackgroundFillFlags BackgroundFillFlags { get; set; } = BackgroundFillFlags.None;
21,519✔
47

48
                /// <summary>
49
                /// Determines how much border there is around the text.
50
                /// </summary>
51
                [DxfCodeValue(45)]
52
                public double BackgroundScale { get; set; } = 1.5;
14,098✔
53

54
                /// <summary>
55
                /// Transparency of background fill color
56
                /// </summary>
57
                [DxfCodeValue(441)]
58
                public Transparency BackgroundTransparency { get; set; }
576✔
59

60
                public TextColumn Column { get; set; } = new TextColumn();
16,717✔
61

62
                /// <summary>
63
                /// Drawing direction
64
                /// </summary>
65
                [DxfCodeValue(72)]
66
                public DrawingDirectionType DrawingDirection { get; set; } = DrawingDirectionType.LeftToRight;
27,725✔
67

68
                /// <inheritdoc/>
69
                [DxfCodeValue(40)]
70
                public double Height
71
                {
72
                        get => this._height;
1,040✔
73
                        set
74
                        {
13,456✔
75
                                if (value < 0)
13,456!
76
                                        throw new ArgumentOutOfRangeException("Height value cannot be negative.");
×
77
                                else
78
                                        this._height = value;
13,456✔
79
                        }
13,456✔
80
                }
81

82
                /// <summary>
83
                /// Horizontal width of the characters that make up the mtext entity.
84
                /// This value will always be equal to or less than the value of group code 41
85
                /// </summary>
86
                /// <remarks>
87
                /// read-only, ignored if supplied
88
                /// </remarks>
89
                [DxfCodeValue(DxfReferenceType.Ignored, 42)]
90
                public double HorizontalWidth { get; set; } = 0.9;
13,546✔
91

92
                /// <summary>
93
                /// A 3D WCS coordinate representing the insertion or origin point.
94
                /// </summary>
95
                [DxfCodeValue(10, 20, 30)]
96
                public XYZ InsertPoint { get; set; } = XYZ.Zero;
70,412✔
97

98
                public bool IsAnnotative { get; set; } = false;
14,858✔
99

100
                /// <summary>
101
                /// Mtext line spacing factor.
102
                /// </summary>
103
                /// <remarks>
104
                /// Percentage of default (3-on-5) line spacing to be applied.Valid values range from 0.25 to 4.00
105
                /// </remarks>
106
                [DxfCodeValue(44)]
107
                public double LineSpacing { get; set; } = 1.0;
27,047✔
108

109
                /// <summary>
110
                /// Mtext line spacing style.
111
                /// </summary>
112
                [DxfCodeValue(73)]
113
                public LineSpacingStyleType LineSpacingStyle { get; set; }
13,679✔
114

115
                /// <summary>
116
                /// Specifies the three-dimensional normal unit vector for the object.
117
                /// </summary>
118
                [DxfCodeValue(210, 220, 230)]
119
                public XYZ Normal { get; set; } = XYZ.AxisZ;
19,261✔
120

121
                /// <inheritdoc/>
122
                public override string ObjectName => DxfFileToken.EntityMText;
39,249✔
123

124
                /// <inheritdoc/>
125
                public override ObjectType ObjectType => ObjectType.MTEXT;
146✔
126

127
                /// <summary>
128
                /// Gets the plain text representation of the processed value.
129
                /// </summary>
130
                /// <remarks>This property processes the underlying value and returns its plain text equivalent. The parsing
131
                /// operation may involve removing formatting or extracting meaningful content.</remarks>
132
                public string PlainText
133
                {
134
                        get
135
                        {
7✔
136
                                return TextProcessor.Parse(this.Value, out _);
7✔
137
                        }
7✔
138
                }
139

140
                /// <summary>
141
                /// Reference rectangle height.
142
                /// </summary>
143
                [DxfCodeValue(46)]
144
                public double RectangleHeight { get; set; }
9,243✔
145

146
                /// <summary>
147
                /// Reference rectangle width.
148
                /// </summary>
149
                [DxfCodeValue(41)]
150
                public double RectangleWidth { get; set; }
14,405✔
151

152
                /// <inheritdoc/>
153
                /// <remarks>
154
                /// The rotation is only valid if the <see cref="Normal"/> is set to the Z axis.
155
                /// </remarks>
156
                [DxfCodeValue(DxfReferenceType.IsAngle | DxfReferenceType.Ignored, 50)]
157
                public double Rotation
158
                {
159
                        get
160
                        {
15✔
161
                                return new XY(this.AlignmentPoint.X, this.AlignmentPoint.Y).GetAngle();
15✔
162
                        }
15✔
163
                }
164

165
                /// <inheritdoc/>
166
                [DxfCodeValue(DxfReferenceType.Name | DxfReferenceType.Optional, 7)]
167
                public TextStyle Style
168
                {
169
                        get { return this._style; }
47,280✔
170
                        set
171
                        {
5,619✔
172
                                if (value == null)
5,619!
173
                                {
×
174
                                        throw new ArgumentNullException(nameof(value));
×
175
                                }
176

177
                                if (this.Document != null)
5,619✔
178
                                {
4,793✔
179
                                        this._style = CadObject.updateCollection(value, this.Document.TextStyles);
4,793✔
180
                                }
4,793✔
181
                                else
182
                                {
826✔
183
                                        this._style = value;
826✔
184
                                }
826✔
185
                        }
5,619✔
186
                }
187

188
                /// <inheritdoc/>
189
                public override string SubclassMarker => DxfSubclassMarker.MText;
158,427✔
190

191
                /// <inheritdoc/>
192
                [DxfCodeValue(1)]
193
                public string Value { get; set; } = string.Empty;
42,337✔
194

195
                /// <summary>
196
                /// Vertical height of the mtext entity
197
                /// </summary>
198
                /// <remarks>
199
                /// read-only, ignored if supplied
200
                /// </remarks>
201
                [DxfCodeValue(DxfReferenceType.Ignored, 43)]
202
                public double VerticalHeight { get; set; } = 0.2;
13,546✔
203

204
                private double _height = 0.18;
13,362✔
205

206
                private TextStyle _style = TextStyle.Default;
13,362✔
207

208
                /// <inheritdoc/>
209
                public MText() : base() { }
40,065✔
210

211
                /// <summary>
212
                /// Initializes a new instance of the <see cref="MText"/> class with the specified text value.
213
                /// </summary>
214
                /// <param name="value">The text value to initialize the instance with. Cannot be <see langword="null"/>.</param>
215
                public MText(string value) : base()
7✔
216
                {
7✔
217
                        this.Value = value;
7✔
218
                }
7✔
219

220
                /// <inheritdoc/>
221
                public override void ApplyTransform(Transform transform)
222
                {
9✔
223
                        XYZ newInsert = transform.ApplyTransform(this.InsertPoint);
9✔
224
                        XYZ newNormal = this.transformNormal(transform, this.Normal);
9✔
225

226
                        var transformation = this.getWorldMatrix(transform, Normal, newNormal, out Matrix3 transOW, out Matrix3 transWO);
9✔
227

228
                        transWO = transWO.Transpose();
9✔
229

230
                        List<XY> uv = applyRotation(
9✔
231
                                new[]
9✔
232
                                {
9✔
233
                                        XY.AxisX, XY.AxisY
9✔
234
                                },
9✔
235
                                this.Rotation);
9✔
236

237
                        XYZ v;
238
                        v = transOW * new XYZ(uv[0].X, uv[0].Y, 0.0);
9✔
239
                        v = transformation * v;
9✔
240
                        v = transWO * v;
9✔
241
                        XY newUvector = new XY(v.X, v.Y);
9✔
242

243
                        // the MText entity does not support non-uniform scaling
244
                        double scale = newUvector.GetLength();
9✔
245

246
                        v = transOW * new XYZ(uv[1].X, uv[1].Y, 0.0);
9✔
247
                        v = transformation * v;
9✔
248
                        v = transWO * v;
9✔
249
                        XY newVvector = new XY(v.X, v.Y);
9✔
250

251
                        double newRotation = newUvector.GetAngle();
9✔
252

253
                        if (XY.Cross(newUvector, newVvector) < 0.0)
9!
254
                        {
×
255
                                if (newUvector.Dot(uv[0]) < 0.0)
×
256
                                {
×
257
                                        newRotation += 180;
×
258

259
                                        switch (this.AttachmentPoint)
×
260
                                        {
261
                                                case AttachmentPointType.TopLeft:
262
                                                        this.AttachmentPoint = AttachmentPointType.TopRight;
×
263
                                                        break;
×
264
                                                case AttachmentPointType.TopRight:
265
                                                        this.AttachmentPoint = AttachmentPointType.TopLeft;
×
266
                                                        break;
×
267
                                                case AttachmentPointType.MiddleLeft:
268
                                                        this.AttachmentPoint = AttachmentPointType.MiddleRight;
×
269
                                                        break;
×
270
                                                case AttachmentPointType.MiddleRight:
271
                                                        this.AttachmentPoint = AttachmentPointType.MiddleLeft;
×
272
                                                        break;
×
273
                                                case AttachmentPointType.BottomLeft:
274
                                                        this.AttachmentPoint = AttachmentPointType.BottomRight;
×
275
                                                        break;
×
276
                                                case AttachmentPointType.BottomRight:
277
                                                        this.AttachmentPoint = AttachmentPointType.BottomLeft;
×
278
                                                        break;
×
279
                                        }
280
                                }
×
281
                                else
282
                                {
×
283
                                        switch (this.AttachmentPoint)
×
284
                                        {
285
                                                case AttachmentPointType.TopLeft:
286
                                                        this.AttachmentPoint = AttachmentPointType.BottomLeft;
×
287
                                                        break;
×
288
                                                case AttachmentPointType.TopCenter:
289
                                                        this.AttachmentPoint = AttachmentPointType.BottomCenter;
×
290
                                                        break;
×
291
                                                case AttachmentPointType.TopRight:
292
                                                        this.AttachmentPoint = AttachmentPointType.BottomRight;
×
293
                                                        break;
×
294
                                                case AttachmentPointType.BottomLeft:
295
                                                        this.AttachmentPoint = AttachmentPointType.TopLeft;
×
296
                                                        break;
×
297
                                                case AttachmentPointType.BottomCenter:
298
                                                        this.AttachmentPoint = AttachmentPointType.TopCenter;
×
299
                                                        break;
×
300
                                                case AttachmentPointType.BottomRight:
301
                                                        this.AttachmentPoint = AttachmentPointType.TopRight;
×
302
                                                        break;
×
303
                                        }
304
                                }
×
305
                        }
×
306

307
                        double newHeight = this.Height * scale;
9✔
308
                        newHeight = MathHelper.IsZero(newHeight) ? MathHelper.Epsilon : newHeight;
9!
309

310
                        this.InsertPoint = newInsert;
9✔
311
                        this.Normal = newNormal;
9✔
312
                        this.Height = newHeight;
9✔
313
                        this.RectangleWidth *= scale;
9✔
314
                }
9✔
315

316
                /// <inheritdoc/>
317
                public override CadObject Clone()
318
                {
649✔
319
                        MText clone = (MText)base.Clone();
649✔
320

321
                        clone.Style = (TextStyle)(this.Style?.Clone());
649!
322
                        clone.Column = this.Column?.Clone();
649!
323

324
                        return clone;
649✔
325
                }
649✔
326

327
                /// <inheritdoc/>
328
                public override BoundingBox GetBoundingBox()
329
                {
6✔
330
                        return new BoundingBox(this.InsertPoint);
6✔
331
                }
6✔
332

333
                /// <summary>
334
                /// Splits the plain text into an array of lines based on common line break sequences.
335
                /// </summary>
336
                /// <remarks>The method splits the text using the following line break sequences:  carriage return and line feed
337
                /// ("\r\n"), carriage return ("\r"), line feed ("\n"), and the Unicode paragraph separator ("\P").
338
                /// The resulting array includes all lines, including empty ones.
339
                /// </remarks>
340
                /// <returns>An array of strings, where each string represents a line of text. The array may contain empty strings if the plain
341
                /// text includes consecutive line break sequences.</returns>
342
                public string[] GetPlainTextLines()
NEW
343
                {
×
NEW
344
                        return this.PlainText.Split(
×
NEW
345
                                new string[] { "\r\n", "\r", "\n", "\\P" },
×
NEW
346
                                StringSplitOptions.None
×
NEW
347
                        );
×
NEW
348
                }
×
349

350
                /// <summary>
351
                /// Splits the text into an array of lines based on common line break sequences.
352
                /// </summary>
353
                /// <remarks>The method splits the text using the following line break sequences: carriage return and line feed
354
                /// ("\r\n"), carriage return ("\r"),  line feed ("\n"), and the Unicode paragraph separator ("\P").
355
                /// The resulting array includes all lines, including empty ones.
356
                /// </remarks>
357
                /// <returns>
358
                /// An array of strings, where each string represents a line of text. The array may contain empty strings if the
359
                /// text includes consecutive line break sequences.
360
                /// </returns>
361
                public string[] GetTextLines()
362
                {
5✔
363
                        return this.Value.Split(
5✔
364
                                new string[] { "\r\n", "\r", "\n", "\\P" },
5✔
365
                                StringSplitOptions.None
5✔
366
                        );
5✔
367
                }
5✔
368

369
                internal override void AssignDocument(CadDocument doc)
370
                {
13,940✔
371
                        base.AssignDocument(doc);
13,940✔
372

373
                        this._style = CadObject.updateCollection(this.Style, doc.TextStyles);
13,940✔
374

375
                        doc.DimensionStyles.OnRemove += this.tableOnRemove;
13,940✔
376
                }
13,940✔
377

378
                internal override void UnassignDocument()
379
                {
130✔
380
                        this.Document.DimensionStyles.OnRemove -= this.tableOnRemove;
130✔
381

382
                        base.UnassignDocument();
130✔
383

384
                        this.Style = (TextStyle)this.Style.Clone();
130✔
385
                }
130✔
386

387
                protected override void tableOnRemove(object sender, CollectionChangedEventArgs e)
388
                {
×
389
                        base.tableOnRemove(sender, e);
×
390

391
                        if (e.Item.Equals(this.Style))
×
392
                        {
×
393
                                this.Style = this.Document.TextStyles[TextStyle.DefaultName];
×
394
                        }
×
395
                }
×
396
        }
397
}
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