Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: DXF Translator
4 : * Purpose: Implements translation support for LEADER and MULTILEADER
5 : * elements as a part of the OGRDXFLayer class.
6 : * Author: Alan Thomas, alant@outlook.com.au
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogr_dxf.h"
31 : #include "cpl_conv.h"
32 : #include "../../../alg/gdallinearsystem.h"
33 : #include <stdexcept>
34 : #include <algorithm>
35 :
36 : static void InterpolateSpline(OGRLineString *const poLine,
37 : const DXFTriple &oEndTangentDirection);
38 :
39 : /************************************************************************/
40 : /* PointDist() */
41 : /************************************************************************/
42 :
43 : #ifndef PointDist_defined
44 : #define PointDist_defined
45 :
46 64 : inline static double PointDist(double x1, double y1, double x2, double y2)
47 : {
48 64 : return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
49 : }
50 : #endif
51 :
52 10 : inline static double PointDist(double x1, double y1, double z1, double x2,
53 : double y2, double z2)
54 : {
55 10 : return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) +
56 10 : (z2 - z1) * (z2 - z1));
57 : }
58 :
59 : /************************************************************************/
60 : /* TranslateLEADER() */
61 : /************************************************************************/
62 :
63 14 : OGRDXFFeature *OGRDXFLayer::TranslateLEADER()
64 :
65 : {
66 : char szLineBuf[257];
67 : int nCode;
68 14 : OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
69 :
70 14 : OGRLineString *poLine = new OGRLineString();
71 14 : bool bHaveX = false;
72 14 : bool bHaveY = false;
73 14 : bool bHaveZ = false;
74 14 : double dfCurrentX = 0.0;
75 14 : double dfCurrentY = 0.0;
76 14 : double dfCurrentZ = 0.0;
77 14 : int nNumVertices = 0;
78 :
79 14 : bool bHorizontalDirectionFlip = true;
80 14 : double dfHorizontalDirectionX = 1.0;
81 14 : double dfHorizontalDirectionY = 0.0;
82 14 : double dfHorizontalDirectionZ = 0.0;
83 14 : bool bHasTextAnnotation = false;
84 14 : double dfTextAnnotationWidth = 0.0;
85 14 : bool bIsSpline = false;
86 :
87 : // spec is silent as to default, but AutoCAD assumes true
88 14 : bool bWantArrowhead = true;
89 :
90 14 : bool bReadyForDimstyleOverride = false;
91 :
92 28 : std::map<CPLString, CPLString> oDimStyleProperties;
93 14 : poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
94 :
95 400 : while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
96 : {
97 386 : switch (nCode)
98 : {
99 14 : case 3:
100 : // 3 is the dimension style name. We don't need to store it,
101 : // let's just fetch the dimension style properties
102 14 : poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
103 14 : break;
104 :
105 52 : case 10:
106 : // add the previous point onto the linestring
107 52 : if (bHaveX && bHaveY && bHaveZ)
108 : {
109 38 : poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
110 : dfCurrentZ);
111 38 : bHaveY = bHaveZ = false;
112 : }
113 52 : dfCurrentX = CPLAtof(szLineBuf);
114 52 : bHaveX = true;
115 52 : break;
116 :
117 52 : case 20:
118 : // add the previous point onto the linestring
119 52 : if (bHaveX && bHaveY && bHaveZ)
120 : {
121 0 : poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
122 : dfCurrentZ);
123 0 : bHaveX = bHaveZ = false;
124 : }
125 52 : dfCurrentY = CPLAtof(szLineBuf);
126 52 : bHaveY = true;
127 52 : break;
128 :
129 52 : case 30:
130 : // add the previous point onto the linestring
131 52 : if (bHaveX && bHaveY && bHaveZ)
132 : {
133 0 : poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
134 : dfCurrentZ);
135 0 : bHaveX = bHaveY = false;
136 : }
137 52 : dfCurrentZ = CPLAtof(szLineBuf);
138 52 : bHaveZ = true;
139 52 : break;
140 :
141 6 : case 41:
142 6 : dfTextAnnotationWidth = CPLAtof(szLineBuf);
143 6 : break;
144 :
145 2 : case 71:
146 2 : bWantArrowhead = atoi(szLineBuf) != 0;
147 2 : break;
148 :
149 2 : case 72:
150 2 : bIsSpline = atoi(szLineBuf) != 0;
151 2 : break;
152 :
153 6 : case 73:
154 6 : bHasTextAnnotation = atoi(szLineBuf) == 0;
155 6 : break;
156 :
157 4 : case 74:
158 : // DXF spec seems to have this backwards. A value of 0 actually
159 : // indicates no flipping occurs, and 1 (flip) is the default
160 4 : bHorizontalDirectionFlip = atoi(szLineBuf) != 0;
161 4 : break;
162 :
163 2 : case 211:
164 2 : dfHorizontalDirectionX = CPLAtof(szLineBuf);
165 2 : break;
166 :
167 2 : case 221:
168 2 : dfHorizontalDirectionY = CPLAtof(szLineBuf);
169 2 : break;
170 :
171 2 : case 231:
172 2 : dfHorizontalDirectionZ = CPLAtof(szLineBuf);
173 2 : break;
174 :
175 10 : case 1001:
176 10 : bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
177 10 : break;
178 :
179 28 : case 1070:
180 28 : if (bReadyForDimstyleOverride)
181 : {
182 : // Store DIMSTYLE override values in the dimension
183 : // style property map. The nInnerCode values match the
184 : // group codes used in the DIMSTYLE table.
185 28 : const int nInnerCode = atoi(szLineBuf);
186 : const char *pszProperty =
187 28 : ACGetDimStylePropertyName(nInnerCode);
188 28 : if (pszProperty)
189 : {
190 24 : nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
191 24 : if (nCode == 1005 || nCode == 1040 || nCode == 1070)
192 24 : oDimStyleProperties[pszProperty] = szLineBuf;
193 : }
194 : }
195 28 : break;
196 :
197 152 : default:
198 152 : TranslateGenericProperty(poFeature, nCode, szLineBuf);
199 152 : break;
200 : }
201 : }
202 :
203 14 : if (nCode == 0)
204 14 : poDS->UnreadValue();
205 :
206 14 : if (bHaveX && bHaveY && bHaveZ)
207 14 : poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY, dfCurrentZ);
208 :
209 : // Unpack the dimension style
210 14 : bool bWantExtension = atoi(oDimStyleProperties["DIMTAD"]) > 0;
211 14 : double dfTextOffset = CPLAtof(oDimStyleProperties["DIMGAP"]);
212 14 : double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
213 14 : double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
214 14 : int nLeaderColor = atoi(oDimStyleProperties["DIMCLRD"]);
215 : // DIMLDRBLK is the entity handle of the BLOCK_RECORD table entry that
216 : // corresponds to the arrowhead block.
217 14 : CPLString osArrowheadBlockHandle = oDimStyleProperties["DIMLDRBLK"];
218 :
219 : // Zero scale has a special meaning which we aren't interested in,
220 : // so we can change it to 1.0
221 14 : if (dfScale == 0.0)
222 0 : dfScale = 1.0;
223 :
224 : // Use the color from the dimension style if it is not ByBlock
225 14 : if (nLeaderColor > 0)
226 2 : poFeature->oStyleProperties["Color"] = oDimStyleProperties["DIMCLRD"];
227 :
228 : /* -------------------------------------------------------------------- */
229 : /* Add an arrowhead to the start of the leader line. */
230 : /* -------------------------------------------------------------------- */
231 :
232 14 : if (bWantArrowhead && nNumVertices >= 2)
233 : {
234 12 : InsertArrowhead(poFeature, osArrowheadBlockHandle, poLine,
235 : dfArrowheadSize * dfScale);
236 : }
237 :
238 14 : if (bHorizontalDirectionFlip)
239 : {
240 10 : dfHorizontalDirectionX *= -1;
241 10 : dfHorizontalDirectionX *= -1;
242 10 : dfHorizontalDirectionX *= -1;
243 : }
244 :
245 : /* -------------------------------------------------------------------- */
246 : /* For a spline leader, determine the end tangent direction */
247 : /* and interpolate the spline vertices. */
248 : /* -------------------------------------------------------------------- */
249 :
250 14 : if (bIsSpline)
251 : {
252 2 : DXFTriple oEndTangent;
253 2 : if (bHasTextAnnotation)
254 : {
255 0 : oEndTangent =
256 0 : DXFTriple(dfHorizontalDirectionX, dfHorizontalDirectionY,
257 : dfHorizontalDirectionZ);
258 : }
259 2 : InterpolateSpline(poLine, oEndTangent);
260 : }
261 :
262 : /* -------------------------------------------------------------------- */
263 : /* Add an extension to the end of the leader line. This is not */
264 : /* properly documented in the DXF spec, but it is needed to */
265 : /* replicate the way AutoCAD displays leader objects. */
266 : /* */
267 : /* When $DIMTAD (77) is nonzero, the leader line is extended */
268 : /* under the text annotation. This extension is not stored as an */
269 : /* additional vertex, so we need to create it ourselves. */
270 : /* -------------------------------------------------------------------- */
271 :
272 14 : if (bWantExtension && bHasTextAnnotation && poLine->getNumPoints() >= 2)
273 : {
274 8 : OGRPoint oLastVertex;
275 4 : poLine->getPoint(poLine->getNumPoints() - 1, &oLastVertex);
276 :
277 4 : double dfExtensionX = oLastVertex.getX();
278 4 : double dfExtensionY = oLastVertex.getY();
279 4 : double dfExtensionZ = oLastVertex.getZ();
280 :
281 4 : double dfExtensionLength =
282 4 : (dfTextOffset * dfScale) + dfTextAnnotationWidth;
283 4 : dfExtensionX += dfHorizontalDirectionX * dfExtensionLength;
284 4 : dfExtensionY += dfHorizontalDirectionY * dfExtensionLength;
285 4 : dfExtensionZ += dfHorizontalDirectionZ * dfExtensionLength;
286 :
287 4 : poLine->setPoint(poLine->getNumPoints(), dfExtensionX, dfExtensionY,
288 : dfExtensionZ);
289 : }
290 :
291 14 : poFeature->SetGeometryDirectly(poLine);
292 :
293 14 : PrepareLineStyle(poFeature);
294 :
295 28 : return poFeature;
296 : }
297 :
298 : /************************************************************************/
299 : /* DXFMLEADERVertex, DXFMLEADERLeaderLine, DXFMLEADERLeader */
300 : /************************************************************************/
301 :
302 : struct DXFMLEADERVertex
303 : {
304 : DXFTriple oCoords;
305 : std::vector<std::pair<DXFTriple, DXFTriple>> aoBreaks;
306 :
307 34 : DXFMLEADERVertex(double dfX, double dfY) : oCoords(DXFTriple(dfX, dfY, 0.0))
308 : {
309 34 : }
310 : };
311 :
312 : struct DXFMLEADERLeader
313 : {
314 : double dfLandingX = 0;
315 : double dfLandingY = 0;
316 : double dfDoglegVectorX = 0;
317 : double dfDoglegVectorY = 0;
318 : double dfDoglegLength = 0;
319 : std::vector<std::pair<DXFTriple, DXFTriple>> aoDoglegBreaks;
320 : std::vector<std::vector<DXFMLEADERVertex>> aaoLeaderLines;
321 : };
322 :
323 : /************************************************************************/
324 : /* TranslateMLEADER() */
325 : /************************************************************************/
326 :
327 18 : OGRDXFFeature *OGRDXFLayer::TranslateMLEADER()
328 :
329 : {
330 : // The MLEADER line buffer has to be very large, as the text contents
331 : // (group code 304) do not wrap and may be arbitrarily long
332 : char szLineBuf[4096];
333 18 : int nCode = 0;
334 :
335 : // This is a dummy feature object used to store style properties
336 : // and the like. We end up deleting it without returning it
337 18 : OGRDXFFeature *poOverallFeature = new OGRDXFFeature(poFeatureDefn);
338 :
339 36 : DXFMLEADERLeader oLeader;
340 36 : std::vector<DXFMLEADERLeader> aoLeaders;
341 :
342 36 : std::vector<DXFMLEADERVertex> oLeaderLine;
343 18 : double dfCurrentX = 0.0;
344 18 : double dfCurrentY = 0.0;
345 18 : double dfCurrentX2 = 0.0;
346 18 : double dfCurrentY2 = 0.0;
347 18 : size_t nCurrentVertex = 0;
348 :
349 18 : double dfScale = 1.0;
350 18 : bool bHasDogleg = true;
351 36 : CPLString osLeaderColor = "0";
352 :
353 36 : CPLString osText;
354 36 : CPLString osTextStyleHandle;
355 18 : double dfTextX = 0.0;
356 18 : double dfTextY = 0.0;
357 18 : int nTextAlignment = 1; // 1 = left, 2 = center, 3 = right
358 18 : double dfTextAngle = 0.0;
359 18 : double dfTextHeight = 4.0;
360 :
361 36 : CPLString osBlockHandle;
362 18 : OGRDXFInsertTransformer oBlockTransformer;
363 36 : CPLString osBlockAttributeHandle;
364 : // Map of ATTDEF handles to attribute text
365 36 : std::map<CPLString, CPLString> oBlockAttributes;
366 :
367 36 : CPLString osArrowheadBlockHandle;
368 18 : double dfArrowheadSize = 4.0;
369 :
370 : // The different leader line types
371 18 : const int MLT_NONE = 0;
372 18 : const int MLT_STRAIGHT = 1;
373 18 : const int MLT_SPLINE = 2;
374 18 : int nLeaderLineType = MLT_STRAIGHT;
375 :
376 : // Group codes mean different things in different sections of the
377 : // MLEADER entity. We need to keep track of the section we are in.
378 18 : const int MLS_COMMON = 0;
379 18 : const int MLS_CONTEXT_DATA = 1;
380 18 : const int MLS_LEADER = 2;
381 18 : const int MLS_LEADER_LINE = 3;
382 18 : int nSection = MLS_COMMON;
383 :
384 : // The way the 30x group codes work is missing from the DXF docs.
385 : // We assume that the sections are always nested as follows:
386 :
387 : // ... [this part is identified as MLS_COMMON]
388 : // 300 CONTEXT_DATA{
389 : // ...
390 : // 302 LEADER{
391 : // ...
392 : // 304 LEADER_LINE{
393 : // ...
394 : // 305 }
395 : // 304 LEADER_LINE{
396 : // ...
397 : // 305 }
398 : // ...
399 : // 303 }
400 : // 302 LEADER{
401 : // ...
402 : // 303 }
403 : // ...
404 : // 301 }
405 : // ... [MLS_COMMON]
406 :
407 2210 : while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
408 : {
409 2192 : switch (nSection)
410 : {
411 758 : case MLS_COMMON:
412 : switch (nCode)
413 : {
414 18 : case 300:
415 18 : nSection = MLS_CONTEXT_DATA;
416 18 : break;
417 :
418 4 : case 342:
419 : // 342 is the entity handle of the BLOCK_RECORD table
420 : // entry that corresponds to the arrowhead block.
421 4 : osArrowheadBlockHandle = szLineBuf;
422 4 : break;
423 :
424 18 : case 42:
425 : // TODO figure out difference between 42 and 140 for
426 : // arrowheadsize
427 18 : dfArrowheadSize = CPLAtof(szLineBuf);
428 18 : break;
429 :
430 24 : case 330:
431 24 : osBlockAttributeHandle = szLineBuf;
432 24 : break;
433 :
434 6 : case 302:
435 6 : if (osBlockAttributeHandle != "")
436 : {
437 6 : oBlockAttributes[osBlockAttributeHandle] =
438 12 : TextUnescape(szLineBuf, true);
439 6 : osBlockAttributeHandle = "";
440 : }
441 6 : break;
442 :
443 18 : case 91:
444 18 : osLeaderColor = szLineBuf;
445 18 : break;
446 :
447 18 : case 170:
448 18 : nLeaderLineType = atoi(szLineBuf);
449 18 : break;
450 :
451 18 : case 291:
452 18 : bHasDogleg = atoi(szLineBuf) != 0;
453 18 : break;
454 :
455 634 : default:
456 634 : TranslateGenericProperty(poOverallFeature, nCode,
457 : szLineBuf);
458 634 : break;
459 : }
460 758 : break;
461 :
462 956 : case MLS_CONTEXT_DATA:
463 : switch (nCode)
464 : {
465 18 : case 301:
466 18 : nSection = MLS_COMMON;
467 18 : break;
468 :
469 20 : case 302:
470 20 : nSection = MLS_LEADER;
471 20 : break;
472 :
473 10 : case 304:
474 10 : osText = TextUnescape(szLineBuf, true);
475 10 : break;
476 :
477 18 : case 40:
478 18 : dfScale = CPLAtof(szLineBuf);
479 18 : break;
480 :
481 10 : case 340:
482 : // 340 is the entity handle of the STYLE table entry
483 : // that corresponds to the text style.
484 10 : osTextStyleHandle = szLineBuf;
485 10 : break;
486 :
487 10 : case 12:
488 10 : dfTextX = CPLAtof(szLineBuf);
489 10 : break;
490 :
491 10 : case 22:
492 10 : dfTextY = CPLAtof(szLineBuf);
493 10 : break;
494 :
495 18 : case 41:
496 18 : dfTextHeight = CPLAtof(szLineBuf);
497 18 : break;
498 :
499 10 : case 42:
500 10 : dfTextAngle = CPLAtof(szLineBuf) * 180 / M_PI;
501 10 : break;
502 :
503 10 : case 171:
504 10 : nTextAlignment = atoi(szLineBuf);
505 10 : break;
506 :
507 6 : case 341:
508 : // 341 is the entity handle of the BLOCK_RECORD table
509 : // entry that corresponds to the block content of this
510 : // MLEADER.
511 6 : osBlockHandle = szLineBuf;
512 6 : break;
513 :
514 6 : case 15:
515 6 : oBlockTransformer.dfXOffset = CPLAtof(szLineBuf);
516 6 : break;
517 :
518 6 : case 25:
519 6 : oBlockTransformer.dfYOffset = CPLAtof(szLineBuf);
520 6 : break;
521 :
522 6 : case 16:
523 6 : oBlockTransformer.dfXScale = CPLAtof(szLineBuf);
524 6 : break;
525 :
526 6 : case 26:
527 6 : oBlockTransformer.dfYScale = CPLAtof(szLineBuf);
528 6 : break;
529 :
530 6 : case 46:
531 6 : oBlockTransformer.dfAngle = CPLAtof(szLineBuf);
532 6 : break;
533 : }
534 956 : break;
535 :
536 288 : case MLS_LEADER:
537 : switch (nCode)
538 : {
539 20 : case 303:
540 20 : nSection = MLS_CONTEXT_DATA;
541 20 : aoLeaders.emplace_back(std::move(oLeader));
542 20 : oLeader = DXFMLEADERLeader();
543 20 : break;
544 :
545 24 : case 304:
546 24 : nSection = MLS_LEADER_LINE;
547 24 : break;
548 :
549 20 : case 10:
550 20 : oLeader.dfLandingX = CPLAtof(szLineBuf);
551 20 : break;
552 :
553 20 : case 20:
554 20 : oLeader.dfLandingY = CPLAtof(szLineBuf);
555 20 : break;
556 :
557 20 : case 11:
558 20 : oLeader.dfDoglegVectorX = CPLAtof(szLineBuf);
559 20 : break;
560 :
561 20 : case 21:
562 20 : oLeader.dfDoglegVectorY = CPLAtof(szLineBuf);
563 20 : break;
564 :
565 4 : case 12:
566 4 : dfCurrentX = CPLAtof(szLineBuf);
567 4 : break;
568 :
569 4 : case 22:
570 4 : dfCurrentY = CPLAtof(szLineBuf);
571 4 : break;
572 :
573 4 : case 13:
574 4 : dfCurrentX2 = CPLAtof(szLineBuf);
575 4 : break;
576 :
577 4 : case 23:
578 4 : dfCurrentY2 = CPLAtof(szLineBuf);
579 4 : oLeader.aoDoglegBreaks.push_back(std::make_pair(
580 4 : DXFTriple(dfCurrentX, dfCurrentY, 0.0),
581 8 : DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
582 4 : break;
583 :
584 20 : case 40:
585 20 : oLeader.dfDoglegLength = CPLAtof(szLineBuf);
586 20 : break;
587 : }
588 288 : break;
589 :
590 190 : case MLS_LEADER_LINE:
591 : switch (nCode)
592 : {
593 24 : case 305:
594 24 : nSection = MLS_LEADER;
595 : oLeader.aaoLeaderLines.emplace_back(
596 24 : std::move(oLeaderLine));
597 24 : oLeaderLine = std::vector<DXFMLEADERVertex>();
598 24 : break;
599 :
600 34 : case 10:
601 34 : dfCurrentX = CPLAtof(szLineBuf);
602 34 : break;
603 :
604 34 : case 20:
605 34 : dfCurrentY = CPLAtof(szLineBuf);
606 34 : oLeaderLine.push_back(
607 68 : DXFMLEADERVertex(dfCurrentX, dfCurrentY));
608 34 : break;
609 :
610 4 : case 90:
611 4 : nCurrentVertex = atoi(szLineBuf);
612 4 : if (nCurrentVertex >= oLeaderLine.size())
613 : {
614 0 : CPLError(CE_Warning, CPLE_AppDefined,
615 : "Wrong group code 90 in LEADER_LINE: %s",
616 : szLineBuf);
617 0 : DXF_LAYER_READER_ERROR();
618 0 : delete poOverallFeature;
619 0 : return nullptr;
620 : }
621 4 : break;
622 :
623 6 : case 11:
624 6 : dfCurrentX = CPLAtof(szLineBuf);
625 6 : break;
626 :
627 6 : case 21:
628 6 : dfCurrentY = CPLAtof(szLineBuf);
629 6 : break;
630 :
631 6 : case 12:
632 6 : dfCurrentX2 = CPLAtof(szLineBuf);
633 6 : break;
634 :
635 6 : case 22:
636 6 : if (nCurrentVertex >= oLeaderLine.size())
637 : {
638 0 : CPLError(CE_Warning, CPLE_AppDefined,
639 : "Misplaced group code 22 in LEADER_LINE");
640 0 : DXF_LAYER_READER_ERROR();
641 0 : delete poOverallFeature;
642 0 : return nullptr;
643 : }
644 6 : dfCurrentY2 = CPLAtof(szLineBuf);
645 12 : oLeaderLine[nCurrentVertex].aoBreaks.push_back(
646 0 : std::make_pair(
647 6 : DXFTriple(dfCurrentX, dfCurrentY, 0.0),
648 12 : DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
649 6 : break;
650 : }
651 190 : break;
652 : }
653 : }
654 :
655 18 : if (nCode < 0)
656 : {
657 0 : DXF_LAYER_READER_ERROR();
658 0 : delete poOverallFeature;
659 0 : return nullptr;
660 : }
661 18 : if (nCode == 0)
662 18 : poDS->UnreadValue();
663 :
664 : // Convert the block handle to a block name. If there is no block,
665 : // osBlockName will remain empty.
666 36 : CPLString osBlockName;
667 :
668 18 : if (osBlockHandle != "")
669 6 : osBlockName = poDS->GetBlockNameByRecordHandle(osBlockHandle);
670 :
671 : /* -------------------------------------------------------------------- */
672 : /* Add the landing and arrowhead onto each leader line, and add */
673 : /* the dogleg, if present, onto the leader. */
674 : /* -------------------------------------------------------------------- */
675 18 : OGRDXFFeature *poLeaderFeature = poOverallFeature->CloneDXFFeature();
676 18 : poLeaderFeature->oStyleProperties["Color"] = osLeaderColor;
677 :
678 18 : OGRMultiLineString *poMLS = new OGRMultiLineString();
679 :
680 : // Arrowheads should be the same color as the leader line. If the leader
681 : // line is ByBlock or ByLayer then the arrowhead should be "owned" by the
682 : // overall feature for styling purposes.
683 18 : OGRDXFFeature *poArrowheadOwningFeature = poLeaderFeature;
684 18 : if ((atoi(osLeaderColor) & 0xC2000000) == 0xC0000000)
685 16 : poArrowheadOwningFeature = poOverallFeature;
686 :
687 38 : for (std::vector<DXFMLEADERLeader>::iterator oIt = aoLeaders.begin();
688 58 : nLeaderLineType != MLT_NONE && oIt != aoLeaders.end(); ++oIt)
689 : {
690 : const bool bLeaderHasDogleg =
691 16 : bHasDogleg && nLeaderLineType != MLT_SPLINE &&
692 50 : oIt->dfDoglegLength != 0.0 &&
693 14 : (oIt->dfDoglegVectorX != 0.0 || oIt->dfDoglegVectorY != 0.0);
694 :
695 : // We assume that the dogleg vector in the DXF is a unit vector.
696 : // Safe assumption? Who knows. The documentation is so bad.
697 : const double dfDoglegX =
698 20 : oIt->dfLandingX + oIt->dfDoglegVectorX * oIt->dfDoglegLength;
699 : const double dfDoglegY =
700 20 : oIt->dfLandingY + oIt->dfDoglegVectorY * oIt->dfDoglegLength;
701 :
702 : // When the dogleg is turned off or we are in spline mode, it seems
703 : // that the dogleg and landing data are still present in the DXF file,
704 : // but they are not supposed to be drawn.
705 20 : if (!bHasDogleg || nLeaderLineType == MLT_SPLINE)
706 : {
707 6 : oIt->dfLandingX = dfDoglegX;
708 6 : oIt->dfLandingY = dfDoglegY;
709 : }
710 :
711 : // Iterate through each leader line
712 44 : for (const auto &aoLineVertices : oIt->aaoLeaderLines)
713 : {
714 24 : if (aoLineVertices.empty())
715 0 : continue;
716 :
717 24 : OGRLineString *poLeaderLine = new OGRLineString();
718 :
719 : // Get the first line segment for arrowhead purposes
720 24 : poLeaderLine->addPoint(aoLineVertices[0].oCoords.dfX,
721 24 : aoLineVertices[0].oCoords.dfY);
722 :
723 24 : if (aoLineVertices.size() > 1)
724 : {
725 6 : poLeaderLine->addPoint(aoLineVertices[1].oCoords.dfX,
726 6 : aoLineVertices[1].oCoords.dfY);
727 : }
728 : else
729 : {
730 18 : poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
731 : }
732 :
733 : // Add an arrowhead if required
734 24 : InsertArrowhead(poArrowheadOwningFeature, osArrowheadBlockHandle,
735 : poLeaderLine, dfArrowheadSize * dfScale);
736 :
737 24 : poLeaderLine->setNumPoints(1);
738 :
739 : // Go through the vertices of the leader line, adding them,
740 : // as well as break start and end points, to the linestring.
741 58 : for (size_t iVertex = 0; iVertex < aoLineVertices.size(); iVertex++)
742 : {
743 34 : if (iVertex > 0)
744 : {
745 10 : poLeaderLine->addPoint(aoLineVertices[iVertex].oCoords.dfX,
746 10 : aoLineVertices[iVertex].oCoords.dfY);
747 : }
748 :
749 : // Breaks are ignored for spline leaders
750 34 : if (nLeaderLineType != MLT_SPLINE)
751 : {
752 34 : for (const auto &oBreak : aoLineVertices[iVertex].aoBreaks)
753 : {
754 6 : poLeaderLine->addPoint(oBreak.first.dfX,
755 6 : oBreak.first.dfY);
756 :
757 6 : poMLS->addGeometryDirectly(poLeaderLine);
758 6 : poLeaderLine = new OGRLineString();
759 :
760 6 : poLeaderLine->addPoint(oBreak.second.dfX,
761 6 : oBreak.second.dfY);
762 : }
763 : }
764 : }
765 :
766 : // Add the final vertex (the landing) to the end of the line
767 24 : poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
768 :
769 : // Make the spline geometry for spline leaders
770 24 : if (nLeaderLineType == MLT_SPLINE)
771 : {
772 4 : DXFTriple oEndTangent;
773 4 : if (osBlockName.empty())
774 : {
775 4 : oEndTangent = DXFTriple(oIt->dfDoglegVectorX,
776 4 : oIt->dfDoglegVectorY, 0);
777 : }
778 4 : InterpolateSpline(poLeaderLine, oEndTangent);
779 : }
780 :
781 24 : poMLS->addGeometryDirectly(poLeaderLine);
782 : }
783 :
784 : // Add the dogleg as a separate line in the MLS
785 20 : if (bLeaderHasDogleg)
786 : {
787 14 : OGRLineString *poDoglegLine = new OGRLineString();
788 14 : poDoglegLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
789 :
790 : // Interrupt the dogleg line at breaks
791 18 : for (const auto &oBreak : oIt->aoDoglegBreaks)
792 : {
793 4 : poDoglegLine->addPoint(oBreak.first.dfX, oBreak.first.dfY);
794 :
795 4 : poMLS->addGeometryDirectly(poDoglegLine);
796 4 : poDoglegLine = new OGRLineString();
797 :
798 4 : poDoglegLine->addPoint(oBreak.second.dfX, oBreak.second.dfY);
799 : }
800 :
801 14 : poDoglegLine->addPoint(dfDoglegX, dfDoglegY);
802 14 : poMLS->addGeometryDirectly(poDoglegLine);
803 : }
804 : }
805 :
806 18 : poLeaderFeature->SetGeometryDirectly(poMLS);
807 :
808 18 : PrepareLineStyle(poLeaderFeature, poOverallFeature);
809 :
810 : /* -------------------------------------------------------------------- */
811 : /* If we have block content, insert that block. */
812 : /* -------------------------------------------------------------------- */
813 :
814 18 : if (osBlockName != "")
815 : {
816 6 : oBlockTransformer.dfXScale *= dfScale;
817 6 : oBlockTransformer.dfYScale *= dfScale;
818 :
819 6 : DXFBlockDefinition *poBlock = poDS->LookupBlock(osBlockName);
820 :
821 12 : std::map<OGRDXFFeature *, CPLString> oBlockAttributeValues;
822 :
823 : // If we have block attributes and will need to output them,
824 : // go through all the features on this block, looking for
825 : // ATTDEFs whose handle is in our list of attribute handles
826 12 : if (poBlock && !oBlockAttributes.empty() &&
827 6 : (poDS->InlineBlocks() ||
828 0 : poOverallFeature->GetFieldIndex("BlockAttributes") != -1))
829 : {
830 12 : for (std::vector<OGRDXFFeature *>::iterator oIt =
831 6 : poBlock->apoFeatures.begin();
832 30 : oIt != poBlock->apoFeatures.end(); ++oIt)
833 : {
834 : const char *pszHandle =
835 12 : (*oIt)->GetFieldAsString("EntityHandle");
836 :
837 12 : if (pszHandle && oBlockAttributes.count(pszHandle) > 0)
838 6 : oBlockAttributeValues[*oIt] = oBlockAttributes[pszHandle];
839 : }
840 : }
841 :
842 6 : OGRDXFFeature *poBlockFeature = poOverallFeature->CloneDXFFeature();
843 :
844 : // If not inlining the block, insert a reference and add attributes
845 : // to this feature.
846 6 : if (!poDS->InlineBlocks())
847 : {
848 0 : poBlockFeature = InsertBlockReference(
849 : osBlockName, oBlockTransformer, poBlockFeature);
850 :
851 0 : if (!oBlockAttributes.empty() &&
852 0 : poOverallFeature->GetFieldIndex("BlockAttributes") != -1)
853 : {
854 0 : std::vector<char *> apszAttribs;
855 :
856 0 : for (std::map<OGRDXFFeature *, CPLString>::iterator oIt =
857 0 : oBlockAttributeValues.begin();
858 0 : oIt != oBlockAttributeValues.end(); ++oIt)
859 : {
860 : // Store the attribute tag and the text value as
861 : // a space-separated entry in the BlockAttributes field
862 0 : CPLString osAttribString = oIt->first->osAttributeTag;
863 0 : osAttribString += " ";
864 0 : osAttribString += oIt->second;
865 :
866 0 : apszAttribs.push_back(
867 0 : new char[osAttribString.length() + 1]);
868 0 : CPLStrlcpy(apszAttribs.back(), osAttribString.c_str(),
869 0 : osAttribString.length() + 1);
870 : }
871 :
872 0 : apszAttribs.push_back(nullptr);
873 :
874 0 : poBlockFeature->SetField("BlockAttributes", &apszAttribs[0]);
875 : }
876 :
877 0 : apoPendingFeatures.push(poBlockFeature);
878 : }
879 : else
880 : {
881 : // Insert the block inline.
882 12 : OGRDXFFeatureQueue apoExtraFeatures;
883 : try
884 : {
885 6 : poBlockFeature = InsertBlockInline(
886 : CPLGetErrorCounter(), osBlockName, oBlockTransformer,
887 : poBlockFeature, apoExtraFeatures, true,
888 6 : poDS->ShouldMergeBlockGeometries());
889 : }
890 0 : catch (const std::invalid_argument &)
891 : {
892 : // Block doesn't exist
893 0 : delete poBlockFeature;
894 0 : poBlockFeature = nullptr;
895 : }
896 :
897 : // Add the block geometries to the pending feature stack.
898 6 : if (poBlockFeature)
899 : {
900 6 : apoPendingFeatures.push(poBlockFeature);
901 : }
902 6 : while (!apoExtraFeatures.empty())
903 : {
904 0 : apoPendingFeatures.push(apoExtraFeatures.front());
905 0 : apoExtraFeatures.pop();
906 : }
907 :
908 : // Also add any attributes to the pending feature stack.
909 6 : for (std::map<OGRDXFFeature *, CPLString>::iterator oIt =
910 6 : oBlockAttributeValues.begin();
911 18 : oIt != oBlockAttributeValues.end(); ++oIt)
912 : {
913 6 : OGRDXFFeature *poAttribFeature = oIt->first->CloneDXFFeature();
914 :
915 6 : poAttribFeature->SetField("Text", oIt->second);
916 :
917 : // Replace text in the style string
918 6 : const char *poStyleString = poAttribFeature->GetStyleString();
919 6 : if (poStyleString && STARTS_WITH(poStyleString, "LABEL("))
920 : {
921 12 : CPLString osNewStyle = poStyleString;
922 6 : const size_t nTextStartPos = osNewStyle.find(",t:\"");
923 6 : if (nTextStartPos != std::string::npos)
924 : {
925 6 : size_t nTextEndPos = nTextStartPos + 4;
926 12 : while (nTextEndPos < osNewStyle.size() &&
927 6 : osNewStyle[nTextEndPos] != '\"')
928 : {
929 0 : nTextEndPos++;
930 0 : if (osNewStyle[nTextEndPos] == '\\')
931 0 : nTextEndPos++;
932 : }
933 :
934 6 : if (nTextEndPos < osNewStyle.size())
935 : {
936 : osNewStyle.replace(
937 : nTextStartPos + 4,
938 6 : nTextEndPos - (nTextStartPos + 4), oIt->second);
939 6 : poAttribFeature->SetStyleString(osNewStyle);
940 : }
941 : }
942 : }
943 :
944 : // The following bits are copied from
945 : // OGRDXFLayer::InsertBlockInline
946 6 : if (poAttribFeature->GetGeometryRef())
947 : {
948 6 : poAttribFeature->GetGeometryRef()->transform(
949 6 : &oBlockTransformer);
950 : }
951 :
952 12 : if (EQUAL(poAttribFeature->GetFieldAsString("Layer"), "0") &&
953 6 : !EQUAL(poOverallFeature->GetFieldAsString("Layer"), ""))
954 : {
955 6 : poAttribFeature->SetField(
956 : "Layer", poOverallFeature->GetFieldAsString("Layer"));
957 : }
958 :
959 6 : PrepareFeatureStyle(poAttribFeature, poOverallFeature);
960 :
961 6 : ACAdjustText(oBlockTransformer.dfAngle * 180 / M_PI,
962 : oBlockTransformer.dfXScale,
963 : oBlockTransformer.dfYScale, poAttribFeature);
964 :
965 6 : if (!EQUAL(poOverallFeature->GetFieldAsString("EntityHandle"),
966 : ""))
967 : {
968 6 : poAttribFeature->SetField(
969 : "EntityHandle",
970 : poOverallFeature->GetFieldAsString("EntityHandle"));
971 : }
972 :
973 6 : apoPendingFeatures.push(poAttribFeature);
974 : }
975 : }
976 : }
977 :
978 : /* -------------------------------------------------------------------- */
979 : /* Prepare a new feature to serve as the leader text label */
980 : /* refeature. We will push it onto the layer as a pending */
981 : /* feature for the next feature read. */
982 : /* -------------------------------------------------------------------- */
983 :
984 18 : if (osText.empty() || osText == " ")
985 : {
986 8 : delete poOverallFeature;
987 8 : return poLeaderFeature;
988 : }
989 :
990 10 : OGRDXFFeature *poLabelFeature = poOverallFeature->CloneDXFFeature();
991 :
992 10 : poLabelFeature->SetField("Text", osText);
993 10 : poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
994 :
995 20 : CPLString osStyle;
996 : char szBuffer[64];
997 :
998 : const CPLString osStyleName =
999 10 : poDS->GetTextStyleNameByHandle(osTextStyleHandle);
1000 :
1001 : // Font name
1002 10 : osStyle.Printf("LABEL(f:\"");
1003 :
1004 : // Preserve legacy behavior of specifying "Arial" as a default font name.
1005 10 : osStyle += poDS->LookupTextStyleProperty(osStyleName, "Font", "Arial");
1006 :
1007 10 : osStyle += "\"";
1008 :
1009 : // Bold, italic
1010 10 : if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Bold", "0"), "1"))
1011 : {
1012 0 : osStyle += ",bo:1";
1013 : }
1014 10 : if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Italic", "0"), "1"))
1015 : {
1016 2 : osStyle += ",it:1";
1017 : }
1018 :
1019 : osStyle +=
1020 20 : CPLString().Printf(",t:\"%s\",p:%d", osText.c_str(),
1021 10 : nTextAlignment + 6); // 7,8,9: vertical align top
1022 :
1023 10 : if (dfTextAngle != 0.0)
1024 : {
1025 2 : CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
1026 2 : osStyle += CPLString().Printf(",a:%s", szBuffer);
1027 : }
1028 :
1029 10 : if (dfTextHeight != 0.0)
1030 : {
1031 10 : CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight);
1032 10 : osStyle += CPLString().Printf(",s:%sg", szBuffer);
1033 : }
1034 :
1035 : const char *pszWidthFactor =
1036 10 : poDS->LookupTextStyleProperty(osStyleName, "Width", "1");
1037 10 : if (pszWidthFactor && CPLAtof(pszWidthFactor) != 1.0)
1038 : {
1039 2 : CPLsnprintf(szBuffer, sizeof(szBuffer), "%.4g",
1040 2 : CPLAtof(pszWidthFactor) * 100.0);
1041 2 : osStyle += CPLString().Printf(",w:%s", szBuffer);
1042 : }
1043 :
1044 : // Color
1045 10 : osStyle += ",c:";
1046 10 : osStyle += poLabelFeature->GetColor(poDS);
1047 :
1048 10 : osStyle += ")";
1049 :
1050 10 : poLabelFeature->SetStyleString(osStyle);
1051 :
1052 10 : apoPendingFeatures.push(poLabelFeature);
1053 :
1054 10 : delete poOverallFeature;
1055 10 : return poLeaderFeature;
1056 : }
1057 :
1058 : /************************************************************************/
1059 : /* GenerateDefaultArrowhead() */
1060 : /* */
1061 : /* Generates the default DWG/DXF arrowhead (a filled triangle */
1062 : /* with a 3:1 aspect ratio) on the end of the line segment */
1063 : /* defined by the two points. */
1064 : /************************************************************************/
1065 42 : static void GenerateDefaultArrowhead(OGRDXFFeature *const poArrowheadFeature,
1066 : const OGRPoint &oPoint1,
1067 : const OGRPoint &oPoint2,
1068 : const double dfArrowheadScale)
1069 :
1070 : {
1071 : // calculate the baseline to be expanded out into arrowheads
1072 : const double dfParallelPartX =
1073 42 : dfArrowheadScale * (oPoint2.getX() - oPoint1.getX());
1074 : const double dfParallelPartY =
1075 42 : dfArrowheadScale * (oPoint2.getY() - oPoint1.getY());
1076 : // ...and drop a perpendicular
1077 42 : const double dfPerpPartX = dfParallelPartY;
1078 42 : const double dfPerpPartY = -dfParallelPartX;
1079 :
1080 42 : OGRLinearRing *poLinearRing = new OGRLinearRing();
1081 42 : poLinearRing->setPoint(
1082 42 : 0, oPoint1.getX() + dfParallelPartX + dfPerpPartX / 6,
1083 42 : oPoint1.getY() + dfParallelPartY + dfPerpPartY / 6, oPoint1.getZ());
1084 42 : poLinearRing->setPoint(1, oPoint1.getX(), oPoint1.getY(), oPoint1.getZ());
1085 42 : poLinearRing->setPoint(
1086 42 : 2, oPoint1.getX() + dfParallelPartX - dfPerpPartX / 6,
1087 42 : oPoint1.getY() + dfParallelPartY - dfPerpPartY / 6, oPoint1.getZ());
1088 42 : poLinearRing->closeRings();
1089 :
1090 42 : OGRPolygon *poPoly = new OGRPolygon();
1091 42 : poPoly->addRingDirectly(poLinearRing);
1092 :
1093 42 : poArrowheadFeature->SetGeometryDirectly(poPoly);
1094 42 : }
1095 :
1096 : /************************************************************************/
1097 : /* InsertArrowhead() */
1098 : /* */
1099 : /* Inserts the specified arrowhead block at the start of the */
1100 : /* first segment of the given line string (or the end of the */
1101 : /* last segment if bReverse is false). 2D only. */
1102 : /* */
1103 : /* The first (last) point of the line string may be updated. */
1104 : /************************************************************************/
1105 64 : void OGRDXFLayer::InsertArrowhead(OGRDXFFeature *const poFeature,
1106 : const CPLString &osBlockHandle,
1107 : OGRLineString *const poLine,
1108 : const double dfArrowheadSize,
1109 : const bool bReverse /* = false */)
1110 : {
1111 64 : OGRPoint oPoint1, oPoint2;
1112 64 : poLine->getPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
1113 64 : poLine->getPoint(bReverse ? poLine->getNumPoints() - 2 : 1, &oPoint2);
1114 :
1115 64 : const double dfFirstSegmentLength = PointDist(
1116 : oPoint1.getX(), oPoint1.getY(), oPoint2.getX(), oPoint2.getY());
1117 :
1118 : // AutoCAD only displays an arrowhead if the length of the arrowhead
1119 : // is less than or equal to half the length of the line segment
1120 64 : if (dfArrowheadSize == 0.0 || dfFirstSegmentLength == 0.0 ||
1121 62 : dfArrowheadSize > 0.5 * dfFirstSegmentLength)
1122 : {
1123 12 : return;
1124 : }
1125 :
1126 52 : OGRDXFFeature *poArrowheadFeature = poFeature->CloneDXFFeature();
1127 :
1128 : // Convert the block handle to a block name.
1129 104 : CPLString osBlockName = "";
1130 :
1131 52 : if (osBlockHandle != "")
1132 12 : osBlockName = poDS->GetBlockNameByRecordHandle(osBlockHandle);
1133 :
1134 104 : OGRDXFFeatureQueue apoExtraFeatures;
1135 :
1136 : // If the block doesn't exist, we need to fall back to the
1137 : // default arrowhead.
1138 52 : if (osBlockName == "")
1139 : {
1140 42 : GenerateDefaultArrowhead(poArrowheadFeature, oPoint1, oPoint2,
1141 : dfArrowheadSize / dfFirstSegmentLength);
1142 :
1143 42 : PrepareBrushStyle(poArrowheadFeature);
1144 : }
1145 : else
1146 : {
1147 : // Build a transformer to insert the arrowhead block with the
1148 : // required location, angle and scale.
1149 10 : OGRDXFInsertTransformer oTransformer;
1150 10 : oTransformer.dfXOffset = oPoint1.getX();
1151 10 : oTransformer.dfYOffset = oPoint1.getY();
1152 10 : oTransformer.dfZOffset = oPoint1.getZ();
1153 : // Arrowhead blocks always point to the right (--->)
1154 10 : oTransformer.dfAngle = atan2(oPoint2.getY() - oPoint1.getY(),
1155 10 : oPoint2.getX() - oPoint1.getX()) +
1156 : M_PI;
1157 10 : oTransformer.dfXScale = oTransformer.dfYScale = oTransformer.dfZScale =
1158 : dfArrowheadSize;
1159 :
1160 : // Insert the block.
1161 : try
1162 : {
1163 16 : poArrowheadFeature = InsertBlockInline(
1164 10 : CPLGetErrorCounter(), osBlockName, std::move(oTransformer),
1165 : poArrowheadFeature, apoExtraFeatures, true, false);
1166 : }
1167 6 : catch (const std::invalid_argument &)
1168 : {
1169 : // Supposedly the block doesn't exist. But what has probably
1170 : // happened is that the block exists in the DXF, but it contains
1171 : // no entities, so the data source didn't read it in.
1172 : // In this case, no arrowhead is required.
1173 6 : delete poArrowheadFeature;
1174 6 : poArrowheadFeature = nullptr;
1175 : }
1176 : }
1177 :
1178 : // Add the arrowhead geometries to the pending feature stack.
1179 52 : if (poArrowheadFeature)
1180 : {
1181 42 : apoPendingFeatures.push(poArrowheadFeature);
1182 : }
1183 60 : while (!apoExtraFeatures.empty())
1184 : {
1185 8 : apoPendingFeatures.push(apoExtraFeatures.front());
1186 8 : apoExtraFeatures.pop();
1187 : }
1188 :
1189 : // Move the endpoint of the line out of the way of the arrowhead.
1190 : // We assume that arrowheads are 1 unit long, except for a list
1191 : // of specific block names which are treated as having no length
1192 :
1193 : static const char *apszSpecialArrowheads[] = {
1194 : "_ArchTick", "_DotSmall", "_Integral", "_None", "_Oblique", "_Small"};
1195 :
1196 52 : if (std::find(apszSpecialArrowheads, apszSpecialArrowheads + 6,
1197 52 : osBlockName) == (apszSpecialArrowheads + 6))
1198 : {
1199 46 : oPoint1.setX(oPoint1.getX() + dfArrowheadSize *
1200 46 : (oPoint2.getX() - oPoint1.getX()) /
1201 : dfFirstSegmentLength);
1202 46 : oPoint1.setY(oPoint1.getY() + dfArrowheadSize *
1203 46 : (oPoint2.getY() - oPoint1.getY()) /
1204 : dfFirstSegmentLength);
1205 :
1206 46 : poLine->setPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
1207 : }
1208 : }
1209 :
1210 : /************************************************************************/
1211 : /* basis(), rbspline2() */
1212 : /* */
1213 : /* Spline calculation functions defined in intronurbs.cpp. */
1214 : /************************************************************************/
1215 : void basis(int c, double t, int npts, double x[], double N[]);
1216 : void rbspline2(int npts, int k, int p1, double b[], double h[],
1217 : bool bCalculateKnots, double x[], double p[]);
1218 :
1219 : #if defined(__GNUC__) && __GNUC__ >= 6
1220 : #pragma GCC diagnostic push
1221 : #pragma GCC diagnostic ignored "-Wnull-dereference"
1222 : #endif
1223 :
1224 : namespace
1225 : {
1226 14 : inline void setRow(GDALMatrix &m, int row, DXFTriple const &t)
1227 : {
1228 14 : m(row, 0) = t.dfX;
1229 14 : m(row, 1) = t.dfY;
1230 14 : m(row, 2) = t.dfZ;
1231 14 : }
1232 : } // namespace
1233 :
1234 : /************************************************************************/
1235 : /* GetBSplineControlPoints() */
1236 : /* */
1237 : /* Evaluates the control points for the B-spline of given degree */
1238 : /* that interpolates the given data points, using the given */
1239 : /* parameters, start tangent and end tangent. The parameters */
1240 : /* and knot vector must be increasing sequences with first */
1241 : /* element 0 and last element 1. Given n data points, there */
1242 : /* must be n parameters and n + nDegree + 3 knots. */
1243 : /* */
1244 : /* It is recommended to match AutoCAD by generating a knot */
1245 : /* vector from the parameters as follows: */
1246 : /* 0 0 ... 0 adfParameters 1 1 ... 1 */
1247 : /* (nDegree zeros) (nDegree ones) */
1248 : /* To fully match AutoCAD's behavior, a chord-length */
1249 : /* parameterisation should be used, and the start and end */
1250 : /* tangent vectors should be multiplied by the total chord */
1251 : /* length of all chords. */
1252 : /* */
1253 : /* Reference: Piegl, L., Tiller, W. (1995), The NURBS Book, */
1254 : /* 2nd ed. (Springer), sections 2.2 and 9.2. */
1255 : /* Although this book contains implementations of algorithms, */
1256 : /* this function is an original implementation based on the */
1257 : /* concepts discussed in the book and was written without */
1258 : /* reference to Piegl and Tiller's implementations. */
1259 : /************************************************************************/
1260 : static std::vector<DXFTriple>
1261 6 : GetBSplineControlPoints(const std::vector<double> &adfParameters,
1262 : const std::vector<double> &adfKnots,
1263 : const std::vector<DXFTriple> &aoDataPoints,
1264 : const int nDegree, DXFTriple oStartTangent,
1265 : DXFTriple oEndTangent)
1266 : {
1267 6 : CPLAssert(nDegree > 1);
1268 :
1269 : // Count the number of data points
1270 : // Note: The literature often sets n to one less than the number of data
1271 : // points for some reason, but we don't do that here
1272 6 : const int nPoints = static_cast<int>(aoDataPoints.size());
1273 :
1274 6 : CPLAssert(nPoints > 0);
1275 6 : CPLAssert(nPoints == static_cast<int>(adfParameters.size()));
1276 :
1277 : // RAM consumption is quadratic in the number of control points.
1278 6 : if (nPoints >
1279 6 : atoi(CPLGetConfigOption("DXF_MAX_BSPLINE_CONTROL_POINTS", "2000")))
1280 : {
1281 3 : CPLError(CE_Failure, CPLE_AppDefined,
1282 : "Too many control points (%d) for spline leader. "
1283 : "Set DXF_MAX_BSPLINE_CONTROL_POINTS configuration "
1284 : "option to a higher value to remove this limitation "
1285 : "(at the cost of significant RAM consumption)",
1286 : nPoints);
1287 3 : return std::vector<DXFTriple>();
1288 : }
1289 :
1290 : // We want to solve the linear system NP=D for P, where N is a coefficient
1291 : // matrix made up of values of the basis functions at each parameter
1292 : // value, with two additional rows for the endpoint tangent information.
1293 : // Each row relates to a different parameter.
1294 :
1295 : // Set up D as a matrix consisting initially of the data points
1296 6 : GDALMatrix D(nPoints + 2, 3);
1297 :
1298 3 : setRow(D, 0, aoDataPoints[0]);
1299 5 : for (int iIndex = 1; iIndex < nPoints - 1; iIndex++)
1300 2 : setRow(D, iIndex + 1, aoDataPoints[iIndex]);
1301 3 : setRow(D, nPoints + 1, aoDataPoints[nPoints - 1]);
1302 :
1303 3 : const double dfStartMultiplier = adfKnots[nDegree + 1] / nDegree;
1304 3 : oStartTangent *= dfStartMultiplier;
1305 3 : setRow(D, 1, oStartTangent);
1306 :
1307 3 : const double dfEndMultiplier = (1.0 - adfKnots[nPoints + 1]) / nDegree;
1308 3 : oEndTangent *= dfEndMultiplier;
1309 3 : setRow(D, nPoints, oEndTangent);
1310 :
1311 6 : GDALMatrix N(nPoints + 2, nPoints + 2);
1312 : // First control point will be the first data point
1313 3 : N(0, 0) = 1.0;
1314 :
1315 : // Start tangent determines the second control point
1316 3 : N(1, 0) = -1.0;
1317 3 : N(1, 1) = 1.0;
1318 :
1319 : // Fill the middle rows of the matrix with basis function values. We
1320 : // have to use a temporary vector, because intronurbs' basis function
1321 : // requires an additional nDegree entries for temporary storage.
1322 6 : std::vector<double> adfTempRow(nPoints + 2 + nDegree, 0.0);
1323 5 : for (int iRow = 2; iRow < nPoints; iRow++)
1324 : {
1325 2 : basis(nDegree + 1, adfParameters[iRow - 1], nPoints + 2,
1326 2 : const_cast<double *>(&adfKnots[0]) - 1, &adfTempRow[0] - 1);
1327 12 : for (int iCol = 0; iCol < nPoints + 2; ++iCol)
1328 10 : N(iRow, iCol) = adfTempRow[iCol];
1329 : }
1330 :
1331 : // End tangent determines the second-last control point
1332 3 : N(nPoints, nPoints) = -1.0;
1333 3 : N(nPoints, nPoints + 1) = 1.0;
1334 :
1335 : // Last control point will be the last data point
1336 3 : N(nPoints + 1, nPoints + 1) = 1.0;
1337 :
1338 : // Solve the linear system
1339 6 : GDALMatrix P(nPoints + 2, 3);
1340 3 : GDALLinearSystemSolve(N, D, P);
1341 :
1342 6 : std::vector<DXFTriple> aoControlPoints(nPoints + 2);
1343 17 : for (int iRow = 0; iRow < nPoints + 2; iRow++)
1344 : {
1345 14 : aoControlPoints[iRow].dfX = P(iRow, 0);
1346 14 : aoControlPoints[iRow].dfY = P(iRow, 1);
1347 14 : aoControlPoints[iRow].dfZ = P(iRow, 2);
1348 : }
1349 :
1350 3 : return aoControlPoints;
1351 : }
1352 :
1353 : #if defined(__GNUC__) && __GNUC__ >= 6
1354 : #pragma GCC diagnostic pop
1355 : #endif
1356 :
1357 : /************************************************************************/
1358 : /* InterpolateSpline() */
1359 : /* */
1360 : /* Interpolates a cubic spline between the data points of the */
1361 : /* given line string. The line string is updated with the new */
1362 : /* spline geometry. */
1363 : /* */
1364 : /* If an end tangent of (0,0,0) is given, the direction vector */
1365 : /* of the last chord (line segment) is used. */
1366 : /************************************************************************/
1367 6 : static void InterpolateSpline(OGRLineString *const poLine,
1368 : const DXFTriple &oEndTangentDirection)
1369 : {
1370 6 : int nDataPoints = static_cast<int>(poLine->getNumPoints());
1371 6 : if (nDataPoints < 2)
1372 3 : return;
1373 :
1374 : // Transfer line vertices into DXFTriple objects
1375 6 : std::vector<DXFTriple> aoDataPoints;
1376 6 : OGRPoint oPrevPoint;
1377 22 : for (int iIndex = 0; iIndex < nDataPoints; iIndex++)
1378 : {
1379 16 : OGRPoint oPoint;
1380 16 : poLine->getPoint(iIndex, &oPoint);
1381 :
1382 : // Remove sequential duplicate points
1383 16 : if (iIndex > 0 && oPrevPoint.Equals(&oPoint))
1384 0 : continue;
1385 :
1386 16 : aoDataPoints.push_back(
1387 16 : DXFTriple(oPoint.getX(), oPoint.getY(), oPoint.getZ()));
1388 16 : oPrevPoint = oPoint;
1389 : }
1390 6 : nDataPoints = static_cast<int>(aoDataPoints.size());
1391 6 : if (nDataPoints < 2)
1392 0 : return;
1393 :
1394 : // Work out the chord length parameterisation
1395 6 : std::vector<double> adfParameters;
1396 6 : adfParameters.push_back(0.0);
1397 16 : for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
1398 : {
1399 : const double dfParameter =
1400 10 : adfParameters[iIndex - 1] +
1401 10 : PointDist(aoDataPoints[iIndex - 1].dfX,
1402 10 : aoDataPoints[iIndex - 1].dfY,
1403 10 : aoDataPoints[iIndex - 1].dfZ, aoDataPoints[iIndex].dfX,
1404 10 : aoDataPoints[iIndex].dfY, aoDataPoints[iIndex].dfZ);
1405 :
1406 : // Bail out in pathological cases. This will happen when
1407 : // some lengths are very large (above 10^16) and others are
1408 : // very small (such as 1)
1409 10 : if (dfParameter == adfParameters[iIndex - 1])
1410 0 : return;
1411 :
1412 10 : adfParameters.push_back(dfParameter);
1413 : }
1414 :
1415 6 : const double dfTotalChordLength = adfParameters[adfParameters.size() - 1];
1416 :
1417 : // Start tangent can be worked out from the first chord
1418 6 : DXFTriple oStartTangent(aoDataPoints[1].dfX - aoDataPoints[0].dfX,
1419 6 : aoDataPoints[1].dfY - aoDataPoints[0].dfY,
1420 18 : aoDataPoints[1].dfZ - aoDataPoints[0].dfZ);
1421 6 : oStartTangent *= dfTotalChordLength / adfParameters[1];
1422 :
1423 : // If end tangent is zero, it is worked out from the last chord
1424 6 : DXFTriple oEndTangent = oEndTangentDirection;
1425 6 : if (oEndTangent.dfX == 0.0 && oEndTangent.dfY == 0.0 &&
1426 2 : oEndTangent.dfZ == 0.0)
1427 : {
1428 10 : oEndTangent = DXFTriple(aoDataPoints[nDataPoints - 1].dfX -
1429 2 : aoDataPoints[nDataPoints - 2].dfX,
1430 4 : aoDataPoints[nDataPoints - 1].dfY -
1431 2 : aoDataPoints[nDataPoints - 2].dfY,
1432 2 : aoDataPoints[nDataPoints - 1].dfZ -
1433 2 : aoDataPoints[nDataPoints - 2].dfZ);
1434 2 : oEndTangent /= dfTotalChordLength - adfParameters[nDataPoints - 2];
1435 : }
1436 :
1437 : // End tangent direction is multiplied by total chord length
1438 6 : oEndTangent *= dfTotalChordLength;
1439 :
1440 : // Normalise the parameter vector
1441 16 : for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
1442 10 : adfParameters[iIndex] /= dfTotalChordLength;
1443 :
1444 : // Generate a knot vector
1445 6 : const int nDegree = 3;
1446 6 : std::vector<double> adfKnots(aoDataPoints.size() + nDegree + 3, 0.0);
1447 : std::copy(adfParameters.begin(), adfParameters.end(),
1448 6 : adfKnots.begin() + nDegree);
1449 6 : std::fill(adfKnots.end() - nDegree, adfKnots.end(), 1.0);
1450 :
1451 : // Calculate the spline control points
1452 : std::vector<DXFTriple> aoControlPoints =
1453 : GetBSplineControlPoints(adfParameters, adfKnots, aoDataPoints, nDegree,
1454 6 : oStartTangent, oEndTangent);
1455 6 : const int nControlPoints = static_cast<int>(aoControlPoints.size());
1456 :
1457 6 : if (nControlPoints == 0)
1458 3 : return;
1459 :
1460 : // Interpolate the spline using the intronurbs code
1461 3 : int nWantedPoints = nControlPoints * 8;
1462 6 : std::vector<double> adfWeights(nControlPoints, 1.0);
1463 6 : std::vector<double> adfPoints(3 * nWantedPoints, 0.0);
1464 :
1465 3 : rbspline2(nControlPoints, nDegree + 1, nWantedPoints,
1466 3 : reinterpret_cast<double *>(&aoControlPoints[0]) - 1,
1467 3 : &adfWeights[0] - 1, false, &adfKnots[0] - 1, &adfPoints[0] - 1);
1468 :
1469 : // Preserve 2D/3D status as we add the interpolated points to the line
1470 3 : const int bIs3D = poLine->Is3D();
1471 3 : poLine->empty();
1472 115 : for (int iIndex = 0; iIndex < nWantedPoints; iIndex++)
1473 : {
1474 112 : poLine->addPoint(adfPoints[iIndex * 3], adfPoints[iIndex * 3 + 1],
1475 112 : adfPoints[iIndex * 3 + 2]);
1476 : }
1477 3 : if (!bIs3D)
1478 2 : poLine->flattenTo2D();
1479 : }
|