Bug Summary

File:librecad/src/lib/filters/rs_filtershp.cpp
Warning:line 445, column 25
Dereference of null pointer

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_filtershp.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-21/lib/clang/21 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D LC_WITH_OPENTYPE=0 -D QC_APPDIR=librecad -D LC_VERSION=2.2.2_alpha1-853-ge2a8c2931 -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/libdxfrw/src/intern -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I ../../libraries/qtbidi -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/linetypes -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/x86_64-linux-gnu/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15/backward -internal-isystem /usr/lib/llvm-21/lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-11-06-031751-4837-1 -x c++ lib/filters/rs_filtershp.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2026 LibreCAD (librecad.org)
6** Copyright (C) 2026 Dongxu Li (github.com/dxli)
7**
8** This program is free software; you can redistribute it and/or
9** modify it under the terms of the GNU General Public License
10** as published by the Free Software Foundation; either version 2
11** of the License, or (at your option) any later version.
12**
13** This program is distributed in the hope that it will be useful,
14** but WITHOUT ANY WARRANTY; without even the implied warranty of
15** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16** GNU General Public License for more details.
17**
18**********************************************************************/
19
20/**
21 * RS_FilterSHP — native ESRI Shapefile import filter implementation.
22 * See rs_filtershp.h for design overview.
23 */
24
25#include "rs_filtershp.h"
26
27#include <algorithm>
28#include <cmath>
29#include <cstddef>
30#include <optional>
31#include <string>
32#include <vector>
33
34#include <QByteArray>
35#include <QFile>
36#include <QFileInfo>
37#include <QHash>
38#include <QObject>
39#include <QString>
40#include <QStringConverter>
41
42#include "shapefil.h"
43
44#include "intern/drw_textcodec.h"
45#include "lc_colornumbers.h"
46#include "lc_linetypenames.h"
47#include "rs_color.h"
48#include "rs_debug.h"
49#include "rs_graphic.h"
50#include "rs_layer.h"
51#include "rs_mtext.h"
52#include "rs_pen.h"
53#include "rs_point.h"
54#include "rs_polyline.h"
55#include "rs_settings.h"
56#include "rs_vector.h"
57
58namespace {
59
60// ---------------------------------------------------------------------------
61// RAII wrappers for shapelib handles — mirror the pattern the retired
62// importshp plugin used (plugins/importshp/importshp.h:42-59), rewritten
63// filter-local so nothing depends on the plugin's headers.
64// ---------------------------------------------------------------------------
65
66class ScopedSHP {
67public:
68 ScopedSHP() = default;
69 explicit ScopedSHP(SHPHandle h) : m_h(h) {}
70 ~ScopedSHP() { if (m_h) SHPClose(m_h); }
71 ScopedSHP(const ScopedSHP&) = delete;
72 ScopedSHP& operator=(const ScopedSHP&) = delete;
73 SHPHandle get() const { return m_h; }
74 explicit operator bool() const { return m_h != nullptr; }
75private:
76 SHPHandle m_h{nullptr};
77};
78
79class ScopedDBF {
80public:
81 ScopedDBF() = default;
82 explicit ScopedDBF(DBFHandle h) : m_h(h) {}
83 ~ScopedDBF() { if (m_h) DBFClose(m_h); }
84 ScopedDBF(const ScopedDBF&) = delete;
85 ScopedDBF& operator=(const ScopedDBF&) = delete;
86 DBFHandle get() const { return m_h; }
87 explicit operator bool() const { return m_h != nullptr; }
88private:
89 DBFHandle m_h{nullptr};
90};
91
92class ScopedShape {
93public:
94 ScopedShape() = default;
95 explicit ScopedShape(SHPObject* o) : m_o(o) {}
96 ~ScopedShape() { if (m_o) SHPDestroyObject(m_o); }
97 ScopedShape(const ScopedShape&) = delete;
98 ScopedShape& operator=(const ScopedShape&) = delete;
99 SHPObject* get() const { return m_o; }
100 SHPObject* operator->() const { return m_o; }
101 explicit operator bool() const { return m_o != nullptr; }
102private:
103 SHPObject* m_o{nullptr};
104};
105
106// ---------------------------------------------------------------------------
107// Import options — auto-detected field names + label defaults. A future
108// options dialog can override these via the RS_Settings group "/ShpImport".
109// Kept in one struct so the seam is obvious.
110// ---------------------------------------------------------------------------
111
112struct LC_ShpImportOptions {
113 QString layerField; // DBF field carrying the target layer name
114 QString colorField; // ACI index (0-255) or 24-bit RGB
115 QString ltypeField; // linetype name (e.g. "DASHED")
116 QString widthField; // numeric line weight
117 QString labelField; // text to render as RS_MText next to points
118 double labelHeight{2.0}; // drawing units
119 bool importLabels{true}; // add MText for point shapes when labelField set
120};
121
122// Case-insensitive lookup of a field by name; returns -1 if not present.
123int findFieldCI(DBFHandle dbf, const std::initializer_list<const char*>& names) {
124 if (!dbf) return -1;
125 const int nFields = DBFGetFieldCount(dbf);
126 for (int f = 0; f < nFields; ++f) {
127 char name[16] = {};
128 int w = 0, d = 0;
129 (void)DBFGetFieldInfo(dbf, f, name, &w, &d);
130 for (const char* candidate : names) {
131 if (qstricmp(name, candidate) == 0) return f;
132 }
133 }
134 return -1;
135}
136
137// Populate options: read RS_Settings overrides first, else auto-detect from
138// the DBF header. Returns the resolved field-index bundle for the caller.
139struct ResolvedFields {
140 int layer{-1};
141 int color{-1};
142 int ltype{-1};
143 int width{-1};
144 int label{-1};
145};
146
147ResolvedFields resolveFields(DBFHandle dbf, LC_ShpImportOptions& opt) {
148 // Settings-driven overrides (empty = auto-detect).
149 LC_GROUPRS_Settings::instance()->beginGroup("ShpImport");
150 if (opt.layerField.isEmpty())
151 opt.layerField = LC_GET_STRRS_Settings::instance()->readStr("LayerField", "");
152 if (opt.colorField.isEmpty())
153 opt.colorField = LC_GET_STRRS_Settings::instance()->readStr("ColorField", "");
154 if (opt.ltypeField.isEmpty())
155 opt.ltypeField = LC_GET_STRRS_Settings::instance()->readStr("LineTypeField", "");
156 if (opt.widthField.isEmpty())
157 opt.widthField = LC_GET_STRRS_Settings::instance()->readStr("WidthField", "");
158 if (opt.labelField.isEmpty())
159 opt.labelField = LC_GET_STRRS_Settings::instance()->readStr("LabelField", "");
160 // opt.labelHeight left at the default 2.0; RS_Settings has no double
161 // accessor, so plumbing a numeric override into the /ShpImport group is
162 // deferred to a future options dialog (Phase 2c non-goal).
163 LC_GROUP_ENDRS_Settings::instance()->endGroup();
164 opt.importLabels = LC_GET_ONE_BOOLRS_Settings::instance()->readBoolSingle("ShpImport", "ImportLabels", true);
165
166 ResolvedFields r;
167 if (!dbf) return r;
168
169 // Explicit override wins over auto-detect for each field.
170 auto lookupOrDetect = [&](const QString& explicitName,
171 std::initializer_list<const char*> defaults) {
172 if (!explicitName.isEmpty()) {
173 const QByteArray bytes = explicitName.toLatin1();
174 return findFieldCI(dbf, {bytes.constData()});
175 }
176 return findFieldCI(dbf, defaults);
177 };
178
179 r.layer = lookupOrDetect(opt.layerField, {"LAYER", "LEVEL", "LYR"});
180 r.color = lookupOrDetect(opt.colorField, {"COLOR", "COLOUR"});
181 r.ltype = lookupOrDetect(opt.ltypeField, {"LINETYPE", "LTYPE"});
182 r.width = lookupOrDetect(opt.widthField, {"WIDTH", "LWEIGHT", "LINEWT"});
183 r.label = lookupOrDetect(opt.labelField, {"NAME", "LABEL", "TEXT"});
184 return r;
185}
186
187// ---------------------------------------------------------------------------
188// DBF codepage → QString.
189// ---------------------------------------------------------------------------
190
191// The Windows code page a DBF declares, or 0. shapelib gives the content of
192// the .cpg file, a number or a name, or else "LDID/<n>" from the language
193// driver byte of the DBF header, which ESRI's table maps to a code page. A
194// name is compared as Qt compares codec names: without case, '-' and '_'.
195int windowsCodePage(const QString& codepage) {
196 QString name = codepage.toUpper().remove(QLatin1Char('-'))
197 .remove(QLatin1Char('_'));
198 if (name.startsWith(QLatin1String("LDID/"))) {
199 switch (name.mid(5).toInt()) {
200 case 3: case 87: case 88: case 89: return 1252;
201 case 19: case 123: return 932;
202 case 77: case 122: return 936;
203 case 78: case 121: return 949;
204 case 79: case 120: return 950;
205 case 80: case 124: return 874;
206 case 200: return 1250;
207 case 201: return 1251;
208 case 202: return 1254;
209 case 203: return 1253;
210 case 204: return 1257;
211 default: return 0; // a DOS code page, or none
212 }
213 }
214 // A name of the code page, or of a part of it.
215 static const struct { const char* name; int codePage; } named[] = {
216 {"ISO88591", 1252}, {"LATIN1", 1252}, {"SHIFTJIS", 932}, {"SJIS", 932},
217 {"GBK", 936}, {"GB2312", 936}, {"EUCKR", 949}, {"BIG5", 950}};
218 for (const auto& known : named) {
219 if (name == QLatin1String(known.name)) return known.codePage;
220 }
221 if (name.startsWith(QLatin1String("WINDOWS"))) name.remove(0, 7);
222 else if (name.startsWith(QLatin1String("CP"))) name.remove(0, 2);
223 return name.toInt();
224}
225
226// Decodes text of a Windows code page with libdxfrw's tables, which need
227// neither ICU nor a Qt codec; no value if it has no table for the code page.
228// Its codec also reads the DXF codes \U+XXXX and \M+nXXXX, and drops a byte
229// the code page leaves undefined. So a two-byte code page (932 to 950) goes
230// in runs that end at a backslash, which is never the first byte of a
231// character, and a single-byte one byte by byte: a dropped byte stays as
232// ISO-8859-1 has it.
233std::optional<QString> decodeCodePage(const QByteArray& bytes, int codePage) {
234 const std::string name = "ANSI_" + std::to_string(codePage);
235 DRW_TextCodec codec;
236 codec.setCodePage(name, false);
237 if (codec.getCodePage() != name) return std::nullopt; // no table for it
238 const bool twoByte = codePage >= 932 && codePage <= 950;
239 std::string utf8;
240 for (qsizetype start = 0, end = 0; start < bytes.size(); start = end) {
241 end = twoByte ? bytes.indexOf('\\', start) + 1 : start + 1;
242 if (end == 0) end = bytes.size();
243 const std::string part = codec.toUtf8(
244 {bytes.constData() + start, static_cast<std::size_t>(end - start)});
245 utf8 += part.empty() ? QString(QLatin1Char(bytes[start])).toStdString()
246 : part;
247 }
248 return QString::fromStdString(utf8);
249}
250
251// Decode a DBF string field. Text in a Windows code page the DBF declares is
252// read with that code page's table; any other is tried as UTF-8 and, with an
253// invalid sequence in it, read as Latin-1.
254QString decodeDbfString(const char* raw, const QString& codepage) {
255 if (raw == nullptr || *raw == '\0') return {};
256 const QByteArray bytes(raw);
257
258 if (const int codePage = windowsCodePage(codepage)) {
259 if (const auto text = decodeCodePage(bytes, codePage)) return *text;
260 }
261 // UTF-8 first, then Latin-1.
262 {
263 QStringDecoder utf8{QStringConverter::Utf8};
264 QString result = utf8(bytes);
265 if (!utf8.hasError()) return result;
266 }
267 QStringDecoder latin1{QStringConverter::Latin1};
268 return latin1(bytes);
269}
270
271// ---------------------------------------------------------------------------
272// Field-value → RS_Pen fragments.
273// ---------------------------------------------------------------------------
274
275// Numeric COLOR field: 0-255 → ACI palette (reuse LC_ColorNumbers::numberToColor),
276// larger → 24-bit RGB (0xRRGGBB).
277RS_Color colorFromNumber(int v) {
278 if (v >= 0 && v <= 255) return LC_ColorNumbers::numberToColor(v);
279 return RS_Color((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
280}
281
282// Nearest-neighbour: pick the RS2::LineWidth enum value closest to the
283// requested numeric weight (interpreted as tenths-of-mm, matching the
284// enum's own integer encoding — see rs.h:867-897).
285RS2::LineWidth widthFromNumber(double v) {
286 const int enumVal = static_cast<int>(std::lround(v));
287 const RS2::LineWidth candidates[] = {
288 RS2::Width00, RS2::Width01, RS2::Width02, RS2::Width03, RS2::Width04,
289 RS2::Width05, RS2::Width06, RS2::Width07, RS2::Width08, RS2::Width09,
290 RS2::Width10, RS2::Width11, RS2::Width12, RS2::Width13, RS2::Width14,
291 RS2::Width15, RS2::Width16, RS2::Width17, RS2::Width18, RS2::Width19,
292 RS2::Width20, RS2::Width21, RS2::Width22, RS2::Width23};
293 RS2::LineWidth best = RS2::Width00;
294 int bestDist = std::abs(static_cast<int>(RS2::Width00) - enumVal);
295 for (RS2::LineWidth w : candidates) {
296 const int d = std::abs(static_cast<int>(w) - enumVal);
297 if (d < bestDist) { bestDist = d; best = w; }
298 }
299 return best;
300}
301
302// Reject C0 controls and reserved symbol-name punctuation, but keep C1
303// characters preserved by the DBF decoder's Latin-1 fallback.
304bool notInSymbolName(const QChar c) {
305 static const QString illegal = QStringLiteral("<>/\\\":;?*|,=`")(QString(QtPrivate::qMakeStringPrivate(u"" "<>/\\\":;?*|,=`"
)))
;
306 return c.unicode() < 0x20 || illegal.contains(c);
307}
308
309// Bound session-wide RS_Pen name interning to 256 identities per import.
310// Reuse the first spelling for ASCII-case and NFC-equivalent names.
311constexpr int kMaxLineTypeNames = 256;
312
313bool keepLineTypeName(QString& name, QHash<QString, QString>& kept) {
314 const QString fold = LC_LineTypeNames::foldName(name);
315 const auto known = kept.constFind(fold);
316 if (known != kept.constEnd()) {
317 name = known.value();
318 return true;
319 }
320 if (kept.size() >= kMaxLineTypeNames) return false;
321 kept.insert(fold, name);
322 return true;
323}
324
325// Build an RS_Pen from resolved fields for one DBF record. If no styling
326// fields are present, returns std::nullopt so callers can leave the entity
327// at ByLayer defaults. @p lineTypeNames holds the names the import has kept.
328std::optional<RS_Pen> penFromRecord(DBFHandle dbf, int record,
329 const ResolvedFields& rf,
330 const QString& codepage,
331 QHash<QString, QString>& lineTypeNames) {
332 if (!dbf || record < 0) return std::nullopt;
333 bool any = false;
334 RS_Pen pen{RS_Color(RS2::FlagByLayer), RS2::WidthByLayer, RS2::LineByLayer};
335
336 if (rf.color >= 0 && !DBFIsAttributeNULL(dbf, record, rf.color)) {
337 const int v = DBFReadIntegerAttribute(dbf, record, rf.color);
338 pen.setColor(colorFromNumber(v));
339 any = true;
340 }
341 if (rf.ltype >= 0 && !DBFIsAttributeNULL(dbf, record, rf.ltype)) {
342 // Trim DBF padding before replacing reserved characters.
343 QString name = decodeDbfString(
344 DBFReadStringAttribute(dbf, record, rf.ltype), codepage).trimmed();
345 std::replace_if(name.begin(), name.end(), notInSymbolName,
346 QLatin1Char('_'));
347 if (!name.isEmpty()) {
348 if (keepLineTypeName(name, lineTypeNames))
349 pen.setLineTypeName(name);
350 else
351 pen.setLineType(LC_LineTypeNames::nameToLineType(name));
352 any = true;
353 }
354 }
355 if (rf.width >= 0 && !DBFIsAttributeNULL(dbf, record, rf.width)) {
356 const double w = DBFReadDoubleAttribute(dbf, record, rf.width);
357 pen.setWidth(widthFromNumber(w));
358 any = true;
359 }
360 if (!any) return std::nullopt;
361 return pen;
362}
363
364// Ensure a layer with the given name exists on @p g; return it. A character
365// DXF cannot hold in a symbol name becomes '_': AutoCAD refuses a layer with
366// one, and the DXF writer fails on a line break.
367RS_Layer* ensureLayer(RS_Graphic& g, QString name) {
368 std::replace_if(name.begin(), name.end(), notInSymbolName,
369 QLatin1Char('_'));
370 if (RS_Layer* existing = g.findLayer(name)) return existing;
371 auto* layer = new RS_Layer(name);
372 // Default pen — ByBlock via RS_Layer's default constructor.
373 g.addLayer(layer);
374 return layer;
375}
376
377// ---------------------------------------------------------------------------
378// Geometry emission helpers.
379// ---------------------------------------------------------------------------
380
381void emitPoint(RS_Graphic& g, RS_Layer* layer,
382 const std::optional<RS_Pen>& pen,
383 double x, double y, double z) {
384 RS_Vector v(x, y, z);
385 auto* p = new RS_Point(&g, RS_PointData(v));
386 p->setLayer(layer);
387 if (pen) p->setPen(*pen);
388 g.addEntity(p);
389}
390
391void emitMultiPoint(RS_Graphic& g, RS_Layer* layer,
392 const std::optional<RS_Pen>& pen,
393 const SHPObject& o) {
394 for (int i = 0; i < o.nVertices; ++i) {
395 const double z = (o.padfZ != nullptr) ? o.padfZ[i] : 0.0;
396 emitPoint(g, layer, pen, o.padfX[i], o.padfY[i], z);
397 }
398}
399
400// Emit one polyline covering the [start, start+count) vertex slice of @p o.
401// Ring convention: for closed polylines the SHP-required duplicated closing
402// vertex (first==last) is dropped before setClosed(true). Degenerate parts
403// (< 2 vertices open, < 3 closed) are skipped.
404void emitOnePart(RS_Graphic& g, RS_Layer* layer,
405 const std::optional<RS_Pen>& pen,
406 const SHPObject& o, int start, int count, bool closed,
407 int& partsEmitted, int& partsSkipped) {
408 if (start < 0 || count <= 0 || start + count > o.nVertices) {
409 ++partsSkipped;
410 return;
411 }
412 if (closed && count >= 2) {
413 const RS_Vector first(o.padfX[start], o.padfY[start]);
414 const RS_Vector last(o.padfX[start + count - 1],
415 o.padfY[start + count - 1]);
416 if (first.squaredTo(last) < 1e-20) --count;
417 }
418 const int minVerts = closed ? 3 : 2;
419 if (count < minVerts) { ++partsSkipped; return; }
420
421 auto* pl = new RS_Polyline(&g, RS_PolylineData(
422 RS_Vector(false), RS_Vector(false), false));
423 pl->setLayer(layer);
424 if (pen) pl->setPen(*pen);
425 for (int i = 0; i < count; ++i) {
426 const int idx = start + i;
427 const double z = (o.padfZ != nullptr) ? o.padfZ[idx] : 0.0;
428 pl->addVertex(RS_Vector(o.padfX[idx], o.padfY[idx], z));
429 }
430 if (closed) pl->setClosed(true);
431 pl->endPolyline();
432 g.addEntity(pl);
433 ++partsEmitted;
434}
435
436// Emit every ARC/POLYGON part. ARC parts open, POLYGON parts closed.
437void emitParts(RS_Graphic& g, RS_Layer* layer,
438 const std::optional<RS_Pen>& pen,
439 const SHPObject& o, bool closed,
440 int& partsEmitted, int& partsSkipped) {
441 const int nParts = std::max(1, o.nParts);
442 for (int p = 0; p
11.1
'p' is < 'nParts'
< nParts; ++p) {
443 const int start = (o.panPartStart && o.nParts > 0)
12
Assuming field 'panPartStart' is null
444 ? o.panPartStart[p] : 0;
445 const int end = (p + 1 < nParts)
13
'?' condition is true
14
Dereference of null pointer
446 ? o.panPartStart[p + 1]
447 : o.nVertices;
448 emitOnePart(g, layer, pen, o, start, end - start, closed,
449 partsEmitted, partsSkipped);
450 }
451}
452
453// Emit a label MText next to a point, if a label field resolved a non-empty
454// string for this record.
455void emitLabel(RS_Graphic& g, RS_Layer* layer,
456 const std::optional<RS_Pen>& pen,
457 double x, double y, double z,
458 const QString& text, double height) {
459 if (text.isEmpty()) return;
460 RS_MTextData d(RS_Vector(x + height * 0.5, y + height * 0.5, z),
461 height, /*width=*/0.0,
462 RS_MTextData::VABottom, RS_MTextData::HALeft,
463 RS_MTextData::LeftToRight, RS_MTextData::AtLeast,
464 /*lineSpacingFactor=*/1.0,
465 text, /*style=*/"standard", /*angle=*/0.0,
466 RS2::NoUpdate);
467 auto* m = new RS_MText(&g, d);
468 m->setLayer(layer);
469 if (pen) m->setPen(*pen);
470 m->update();
471 g.addEntity(m);
472}
473
474// Trim trailing spaces (DBF strings are space-padded to field width).
475QString trimTrailingSpaces(QString s) {
476 while (!s.isEmpty() && s.back().isSpace()) s.chop(1);
477 return s;
478}
479
480} // namespace
481
482// ---------------------------------------------------------------------------
483// RS_FilterSHP::fileImport
484// ---------------------------------------------------------------------------
485
486bool RS_FilterSHP::fileImport(RS_Graphic& g, const QString& file,
487 RS2::FormatType /*type*/) {
488 m_lastError.clear();
489
490 const QByteArray path = QFile::encodeName(file);
491 ScopedSHP shp{SHPOpen(path.constData(), "rb")};
492 if (!shp) {
1
Taking false branch
493 m_lastError = QObject::tr("Cannot open shapefile %1 (missing or corrupt .shx?)")
494 .arg(file);
495 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "%s: %s",
496 __func__, m_lastError.toLatin1().constData());
497 return false;
498 }
499
500 int nEntities = 0;
501 int nShapeType = 0;
502 double bboxMin[4] = {0}, bboxMax[4] = {0};
503 SHPGetInfo(shp.get(), &nEntities, &nShapeType, bboxMin, bboxMax);
504 RS_DEBUGRS_Debug::instance()->print("%s: opened %s, %d records, shape type %s (%d)",
505 __func__, path.constData(), nEntities,
506 SHPTypeName(nShapeType), nShapeType);
507
508 // Companion .dbf — geometry-only import is fine if it's missing.
509 QString dbfPath = file;
510 if (dbfPath.endsWith(".shp", Qt::CaseInsensitive))
2
Assuming the condition is false
3
Taking false branch
511 dbfPath.chop(4);
512 dbfPath += ".dbf";
513 const QByteArray dbfBytes = QFile::encodeName(dbfPath);
514 ScopedDBF dbf{DBFOpen(dbfBytes.constData(), "rb")};
515
516 QString codepage;
517 if (dbf) {
4
Taking false branch
518 const char* cp = DBFGetCodePage(dbf.get());
519 codepage = cp ? QString::fromLatin1(cp) : QString();
520 }
521
522 LC_ShpImportOptions opts;
523 const ResolvedFields rf = resolveFields(dbf.get(), opts);
524 QHash<QString, QString> lineTypeNames;
525
526 RS_Layer* defaultLayer = ensureLayer(g, "0");
527 int emitted = 0, skipped = 0;
528 int partsEmitted = 0, partsSkipped = 0;
529
530 for (int i = 0; i < nEntities; ++i) {
5
Assuming 'i' is < 'nEntities'
6
Loop condition is true. Entering loop body
531 ScopedShape rec{SHPReadObject(shp.get(), i)};
532 if (!rec) { ++skipped; continue; }
7
Taking false branch
533 if (rec->nSHPType == SHPT_NULL0) { ++skipped; continue; }
8
Assuming field 'nSHPType' is not equal to SHPT_NULL
9
Taking false branch
534
535 // Layer for this record: DBF-driven → auto-created; otherwise "0".
536 RS_Layer* layer = defaultLayer;
537 if (dbf && rf.layer >= 0
538 && !DBFIsAttributeNULL(dbf.get(), i, rf.layer)) {
539 const QString rawName = decodeDbfString(
540 DBFReadStringAttribute(dbf.get(), i, rf.layer), codepage);
541 const QString name = trimTrailingSpaces(rawName);
542 if (!name.isEmpty()) layer = ensureLayer(g, name);
543 }
544
545 const std::optional<RS_Pen> pen =
546 penFromRecord(dbf.get(), i, rf, codepage, lineTypeNames);
547
548 switch (rec->nSHPType) {
10
Control jumps to 'case 25:' at line 579
549 case SHPT_POINT1:
550 case SHPT_POINTZ11:
551 case SHPT_POINTM21: {
552 const double z = (rec->padfZ != nullptr) ? rec->padfZ[0] : 0.0;
553 emitPoint(g, layer, pen, rec->padfX[0], rec->padfY[0], z);
554 if (opts.importLabels && rf.label >= 0 && dbf
555 && !DBFIsAttributeNULL(dbf.get(), i, rf.label)) {
556 const QString text = trimTrailingSpaces(decodeDbfString(
557 DBFReadStringAttribute(dbf.get(), i, rf.label), codepage));
558 emitLabel(g, layer, pen, rec->padfX[0], rec->padfY[0], z,
559 text, opts.labelHeight);
560 }
561 ++emitted;
562 break;
563 }
564 case SHPT_MULTIPOINT8:
565 case SHPT_MULTIPOINTZ18:
566 case SHPT_MULTIPOINTM28:
567 emitMultiPoint(g, layer, pen, *rec.get());
568 ++emitted;
569 break;
570 case SHPT_ARC3:
571 case SHPT_ARCZ13:
572 case SHPT_ARCM23:
573 emitParts(g, layer, pen, *rec.get(), /*closed=*/false,
574 partsEmitted, partsSkipped);
575 ++emitted;
576 break;
577 case SHPT_POLYGON5:
578 case SHPT_POLYGONZ15:
579 case SHPT_POLYGONM25:
580 emitParts(g, layer, pen, *rec.get(), /*closed=*/true,
11
Calling 'emitParts'
581 partsEmitted, partsSkipped);
582 ++emitted;
583 break;
584 case SHPT_MULTIPATCH31:
585 // Simplified 2.5D handling: rings become closed polylines; strips
586 // and fans render as an open-polyline wireframe. Full triangle-
587 // mesh support is out of scope for the first cut.
588 for (int p = 0; p < rec->nParts; ++p) {
589 const int ptype = (rec->panPartType != nullptr)
590 ? rec->panPartType[p] : SHPP_RING5;
591 const bool ringLike = (ptype == SHPP_OUTERRING2
592 || ptype == SHPP_INNERRING3
593 || ptype == SHPP_RING5
594 || ptype == SHPP_FIRSTRING4);
595 const int start = rec->panPartStart[p];
596 const int end = (p + 1 < rec->nParts)
597 ? rec->panPartStart[p + 1]
598 : rec->nVertices;
599 emitOnePart(g, layer, pen, *rec.get(), start, end - start,
600 ringLike, partsEmitted, partsSkipped);
601 }
602 ++emitted;
603 break;
604 default:
605 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
606 "%s: skipping unknown shape type %d at record %d",
607 __func__, rec->nSHPType, i);
608 ++skipped;
609 break;
610 }
611 }
612
613 if (skipped > 0) {
614 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
615 "%s: %d record(s) skipped, %d part(s) skipped",
616 __func__, skipped, partsSkipped);
617 }
618 RS_DEBUGRS_Debug::instance()->print("%s: imported %d record(s) (%d polyline part(s))",
619 __func__, emitted, partsEmitted);
620
621 // Fail only if nothing readable was produced from a non-empty file.
622 if (emitted == 0 && nEntities > 0) {
623 m_lastError = QObject::tr("Shapefile %1 contained %2 records but none were readable")
624 .arg(file).arg(nEntities);
625 return false;
626 }
627 return true;
628}