In the last post, we saw a simple jig implementation to position, size and rotate standard AutoCAD text. In this post, we’re extending that implementation to handle font properties such as bold and italic text.
At first blush, this sounds pretty straightforward – how hard can it be, right? The complexity gets introduced when we consider that these are not properties that are exposed directly from the text itself, but from the associated text style (or – to be more accurate – from the font descriptor object associated with the text style).
Which begs the question: when the user chooses to change the text to bold, what is it they really want?
- To change the associated text style, so that all text that uses it becomes bold
- To use (or create) a different text style with the bold property set
It’s pretty clear to me that they actually want 2 rather than 1, but then the question gets raised of how we name and identify these additional text styles. I’ve gone with the following scheme:
- Regular: “STANDARD” (e.g. we take the default, initial style)
- Bold: “STANDARD_BOLD” (i.e. we add a suffix)
- Italic: “STANDARD_ITALIC”
- Bold-italic: “STANDARD_BOLDITALIC”
If this is in some ways at odds with your own naming scheme, it’s a simple matter to change it in the code.
As mentioned in the previous post, the “Bold” and “Italic” keywords are actually toggles, which allows us to avoid adding a “Regular” keyword, too.
Here is the updated C# code, with the new lines in red (and here’s the source file for you to download):
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.DatabaseServices;
3 using Autodesk.AutoCAD.EditorInput;
4 using Autodesk.AutoCAD.Geometry;
5 using Autodesk.AutoCAD.GraphicsInterface;
6 using Autodesk.AutoCAD.Runtime;
7 using System;
8
9 public class Commands
10 {
11 [CommandMethod("QT")]
12 static public void QuickText()
13 {
14 Document doc =
15 Application.DocumentManager.MdiActiveDocument;
16 Database db = doc.Database;
17 Editor ed = doc.Editor;
18
19 PromptStringOptions pso =
20 new PromptStringOptions("\nEnter text string");
21 pso.AllowSpaces = true;
22 PromptResult pr = ed.GetString(pso);
23
24 if (pr.Status != PromptStatus.OK)
25 return;
26
27 Transaction tr =
28 doc.TransactionManager.StartTransaction();
29 using (tr)
30 {
31 BlockTableRecord btr =
32 (BlockTableRecord)tr.GetObject(
33 db.CurrentSpaceId, OpenMode.ForWrite
34 );
35
36 // Create the text object, set its normal and contents
37
38 DBText txt = new DBText();
39 txt.Normal =
40 ed.CurrentUserCoordinateSystem.
41 CoordinateSystem3d.Zaxis;
42 txt.TextString = pr.StringResult;
43
44 // We'll add the text to the database before jigging
45 // it - this allows alignment adjustments to be
46 // reflected
47
48 btr.AppendEntity(txt);
49 tr.AddNewlyCreatedDBObject(txt, true);
50
51 // Create our jig
52
53 TextPlacementJig pj = new TextPlacementJig(tr, db, txt);
54
55 // Loop as we run our jig, as we may have keywords
56
57 PromptStatus stat = PromptStatus.Keyword;
58 while (stat == PromptStatus.Keyword)
59 {
60 PromptResult res = ed.Drag(pj);
61 stat = res.Status;
62 if (
63 stat != PromptStatus.OK &&
64 stat != PromptStatus.Keyword
65 )
66 return;
67 }
68
69 tr.Commit();
70 }
71 }
72
73 class TextPlacementJig : EntityJig
74 {
75 // Declare some internal state
76
77 Database _db;
78 Transaction _tr;
79 Point3d _position;
80 double _angle, _txtSize;
81 bool _toggleBold, _toggleItalic;
82
83 // Constructor
84
85 public TextPlacementJig(
86 Transaction tr, Database db, Entity ent
87 ) : base(ent)
88 {
89 _db = db;
90 _tr = tr;
91 _angle = 0;
92 _txtSize = 1;
93 }
94
95 protected override SamplerStatus Sampler(
96 JigPrompts jp
97 )
98 {
99 // We acquire a point but with keywords
100
101 JigPromptPointOptions po =
102 new JigPromptPointOptions(
103 "\nPosition of text"
104 );
105
106 po.UserInputControls =
107 (UserInputControls.Accept3dCoordinates |
108 UserInputControls.NullResponseAccepted |
109 UserInputControls.NoNegativeResponseAccepted |
110 UserInputControls.GovernedByOrthoMode);
111
112 po.SetMessageAndKeywords(
113 "\nSpecify position of text or " +
114 "[Bold/Italic/LArger/Smaller/" +
115 "ROtate90/LEft/Middle/RIght]: ",
116 "Bold Italic LArger Smaller " +
117 "ROtate90 LEft Middle RIght"
118 );
119
120 PromptPointResult ppr = jp.AcquirePoint(po);
121
122 if (ppr.Status == PromptStatus.Keyword)
123 {
124 switch (ppr.StringResult)
125 {
126 case "Bold":
127 {
128 _toggleBold = true;
129 break;
130 }
131 case "Italic":
132 {
133 _toggleItalic = true;
134 break;
135 }
136 case "LArger":
137 {
138 // Multiple the text size by two
139
140 _txtSize *= 2;
141 break;
142 }
143 case "Smaller":
144 {
145 // Divide the text size by two
146
147 _txtSize /= 2;
148 break;
149 }
150 case "ROtate90":
151 {
152 // To rotate clockwise we subtract 90 degrees and
153 // then normalise the angle between 0 and 360
154
155 _angle -= Math.PI / 2;
156 while (_angle < Math.PI * 2)
157 {
158 _angle += Math.PI * 2;
159 }
160 break;
161 }
162 case "LEft":
163 {
164 // TODO
165
166 break;
167 }
168 case "RIght":
169 {
170 // TODO
171
172 break;
173 }
174 case "Middle":
175 {
176 // TODO
177
178 break;
179 }
180 }
181
182 return SamplerStatus.OK;
183 }
184 else if (ppr.Status == PromptStatus.OK)
185 {
186 // Check if it has changed or not (reduces flicker)
187
188 if (
189 _position.DistanceTo(ppr.Value) <
190 Tolerance.Global.EqualPoint
191 )
192 return SamplerStatus.NoChange;
193
194 _position = ppr.Value;
195 return SamplerStatus.OK;
196 }
197
198 return SamplerStatus.Cancel;
199 }
200
201 protected override bool Update()
202 {
203 // Set properties on our text object
204
205 DBText txt = (DBText)Entity;
206
207 txt.Position = _position;
208 txt.Height = _txtSize;
209 txt.Rotation = _angle;
210
211 // Set the bold and/or italic properties on the style
212
213 if (_toggleBold || _toggleItalic)
214 {
215 TextStyleTable tab =
216 (TextStyleTable)_tr.GetObject(
217 _db.TextStyleTableId, OpenMode.ForRead
218 );
219
220 TextStyleTableRecord style =
221 (TextStyleTableRecord)_tr.GetObject(
222 txt.TextStyleId, OpenMode.ForRead
223 );
224
225 // A bit convoluted, but this check will tell us
226 // whether the new style is bold/italic
227
228 bool bold = !(style.Font.Bold == _toggleBold);
229 bool italic = !(style.Font.Italic == _toggleItalic);
230 _toggleBold = false;
231 _toggleItalic = false;
232
233 // Get the new style name based on the old name and
234 // a suffix ("_BOLD", "_ITALIC" or "_BOLDITALIC")
235
236 var oldName = style.Name.Split(new[] { '_' });
237 string newName =
238 oldName[0] +
239 (bold || italic ? "_" +
240 (bold ? "BOLD" : "") +
241 (italic ? "ITALIC" : "")
242 : "");
243
244 // We only create a duplicate style if one doesn't
245 // already exist
246
247 if (tab.Has(newName))
248 {
249 txt.TextStyleId = tab[newName];
250 }
251 else
252 {
253 // We have to create a new style - clone the old one
254
255 TextStyleTableRecord newStyle =
256 (TextStyleTableRecord)style.Clone();
257
258 // Set a new name to avoid duplicate keys
259
260 newStyle.Name = newName;
261
262 // Create a new font based on the old one, but with
263 // our values for bold & italic
264
265 FontDescriptor oldFont = style.Font;
266 FontDescriptor newFont =
267 new FontDescriptor(
268 oldFont.TypeFace, bold, italic,
269 oldFont.CharacterSet, oldFont.PitchAndFamily
270 );
271
272 // Set it on the style
273
274 newStyle.Font = newFont;
275
276 // Add the new style to the text style table and
277 // the transaction
278
279 tab.UpgradeOpen();
280 ObjectId styleId = tab.Add(newStyle);
281 _tr.AddNewlyCreatedDBObject(newStyle, true);
282
283 // And finally set the new style on our text object
284
285 txt.TextStyleId = styleId;
286 }
287 }
288
289 return true;
290 }
291 }
292 }
When we run the modified QT command, we can see we now have the ability to adjust font formatting properties on-the-fly, with the results showing as we jig the text:
Command: QT
Enter text string: Some more text
Specify position of text or
[Bold/Italic/LArger/Smaller/ROtate90/LEft/Middle/RIght]: BO
Specify position of text or
[Bold/Italic/LArger/Smaller/ROtate90/LEft/Middle/RIght]: IT
Specify position of text or
[Bold/Italic/LArger/Smaller/ROtate90/LEft/Middle/RIght]:
If we take a look using ArxDbg, we can see we have a number of new text styles in the table (although not one for italic, as we haven't passed that way):
This was the trickiest part of the implementation. In the next post we’ll wrap up with the code needed to align the text relative to the cursor during our jig.