Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -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-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-607-g330d41cec -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/jwwlib/src -I ../../libraries/shapelib/src -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/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/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -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-08-04-154929-5069-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 <QObject>
38#include <QString>
39#include <QStringConverter>
40
41#include "shapefil.h"
42
43#include "rs_color.h"
44#include "rs_debug.h"
45#include "rs_filterdxfrw.h"
46#include "rs_graphic.h"
47#include "rs_layer.h"
48#include "rs_mtext.h"
49#include "rs_pen.h"
50#include "rs_point.h"
51#include "rs_polyline.h"
52#include "rs_settings.h"
53#include "rs_vector.h"
54
55namespace {
56
57// ---------------------------------------------------------------------------
58// RAII wrappers for shapelib handles — mirror the pattern the retired
59// importshp plugin used (plugins/importshp/importshp.h:42-59), rewritten
60// filter-local so nothing depends on the plugin's headers.
61// ---------------------------------------------------------------------------
62
63class ScopedSHP {
64public:
65 ScopedSHP() = default;
66 explicit ScopedSHP(SHPHandle h) : m_h(h) {}
67 ~ScopedSHP() { if (m_h) SHPClose(m_h); }
68 ScopedSHP(const ScopedSHP&) = delete;
69 ScopedSHP& operator=(const ScopedSHP&) = delete;
70 SHPHandle get() const { return m_h; }
71 explicit operator bool() const { return m_h != nullptr; }
72private:
73 SHPHandle m_h{nullptr};
74};
75
76class ScopedDBF {
77public:
78 ScopedDBF() = default;
79 explicit ScopedDBF(DBFHandle h) : m_h(h) {}
80 ~ScopedDBF() { if (m_h) DBFClose(m_h); }
81 ScopedDBF(const ScopedDBF&) = delete;
82 ScopedDBF& operator=(const ScopedDBF&) = delete;
83 DBFHandle get() const { return m_h; }
84 explicit operator bool() const { return m_h != nullptr; }
85private:
86 DBFHandle m_h{nullptr};
87};
88
89class ScopedShape {
90public:
91 ScopedShape() = default;
92 explicit ScopedShape(SHPObject* o) : m_o(o) {}
93 ~ScopedShape() { if (m_o) SHPDestroyObject(m_o); }
94 ScopedShape(const ScopedShape&) = delete;
95 ScopedShape& operator=(const ScopedShape&) = delete;
96 SHPObject* get() const { return m_o; }
97 SHPObject* operator->() const { return m_o; }
98 explicit operator bool() const { return m_o != nullptr; }
99private:
100 SHPObject* m_o{nullptr};
101};
102
103// ---------------------------------------------------------------------------
104// Import options — auto-detected field names + label defaults. A future
105// options dialog can override these via the RS_Settings group "/ShpImport".
106// Kept in one struct so the seam is obvious.
107// ---------------------------------------------------------------------------
108
109struct LC_ShpImportOptions {
110 QString layerField; // DBF field carrying the target layer name
111 QString colorField; // ACI index (0-255) or 24-bit RGB
112 QString ltypeField; // linetype name (e.g. "DASHED")
113 QString widthField; // numeric line weight
114 QString labelField; // text to render as RS_MText next to points
115 double labelHeight{2.0}; // drawing units
116 bool importLabels{true}; // add MText for point shapes when labelField set
117};
118
119// Case-insensitive lookup of a field by name; returns -1 if not present.
120int findFieldCI(DBFHandle dbf, const std::initializer_list<const char*>& names) {
121 if (!dbf) return -1;
122 const int nFields = DBFGetFieldCount(dbf);
123 for (int f = 0; f < nFields; ++f) {
124 char name[16] = {};
125 int w = 0, d = 0;
126 (void)DBFGetFieldInfo(dbf, f, name, &w, &d);
127 for (const char* candidate : names) {
128 if (qstricmp(name, candidate) == 0) return f;
129 }
130 }
131 return -1;
132}
133
134// Populate options: read RS_Settings overrides first, else auto-detect from
135// the DBF header. Returns the resolved field-index bundle for the caller.
136struct ResolvedFields {
137 int layer{-1};
138 int color{-1};
139 int ltype{-1};
140 int width{-1};
141 int label{-1};
142};
143
144ResolvedFields resolveFields(DBFHandle dbf, LC_ShpImportOptions& opt) {
145 // Settings-driven overrides (empty = auto-detect).
146 LC_GROUPRS_Settings::instance()->beginGroup("ShpImport");
147 if (opt.layerField.isEmpty())
148 opt.layerField = LC_GET_STRRS_Settings::instance()->readStr("LayerField", "");
149 if (opt.colorField.isEmpty())
150 opt.colorField = LC_GET_STRRS_Settings::instance()->readStr("ColorField", "");
151 if (opt.ltypeField.isEmpty())
152 opt.ltypeField = LC_GET_STRRS_Settings::instance()->readStr("LineTypeField", "");
153 if (opt.widthField.isEmpty())
154 opt.widthField = LC_GET_STRRS_Settings::instance()->readStr("WidthField", "");
155 if (opt.labelField.isEmpty())
156 opt.labelField = LC_GET_STRRS_Settings::instance()->readStr("LabelField", "");
157 // opt.labelHeight left at the default 2.0; RS_Settings has no double
158 // accessor, so plumbing a numeric override into the /ShpImport group is
159 // deferred to a future options dialog (Phase 2c non-goal).
160 LC_GROUP_ENDRS_Settings::instance()->endGroup();
161 opt.importLabels = LC_GET_ONE_BOOLRS_Settings::instance()->readBoolSingle("ShpImport", "ImportLabels", true);
162
163 ResolvedFields r;
164 if (!dbf) return r;
165
166 // Explicit override wins over auto-detect for each field.
167 auto lookupOrDetect = [&](const QString& explicitName,
168 std::initializer_list<const char*> defaults) {
169 if (!explicitName.isEmpty()) {
170 const QByteArray bytes = explicitName.toLatin1();
171 return findFieldCI(dbf, {bytes.constData()});
172 }
173 return findFieldCI(dbf, defaults);
174 };
175
176 r.layer = lookupOrDetect(opt.layerField, {"LAYER", "LEVEL", "LYR"});
177 r.color = lookupOrDetect(opt.colorField, {"COLOR", "COLOUR"});
178 r.ltype = lookupOrDetect(opt.ltypeField, {"LINETYPE", "LTYPE"});
179 r.width = lookupOrDetect(opt.widthField, {"WIDTH", "LWEIGHT", "LINEWT"});
180 r.label = lookupOrDetect(opt.labelField, {"NAME", "LABEL", "TEXT"});
181 return r;
182}
183
184// ---------------------------------------------------------------------------
185// DBF codepage → QStringDecoder.
186// ---------------------------------------------------------------------------
187
188// Decode a DBF string field with the DBF's declared codepage. Falls back to
189// UTF-8 → Latin-1 on invalid sequences (the same pragmatic heuristic
190// rs_filterjww.cpp uses for JWW's mixed CP932/Shift_JIS content).
191QString decodeDbfString(const char* raw, const QString& codepage) {
192 if (raw == nullptr || *raw == '\0') return {};
193 const QByteArray bytes(raw);
194
195 // Best-effort mapping from DBF codepage marker to a Qt codec name.
196 // shapelib returns strings like "UTF-8", "LDID/87" (Latin-1 / ISO-8859-1),
197 // "LDID/13" (CP850), etc. We only try to hit the common ones; anything
198 // unrecognised falls through to UTF-8 with Latin-1 fallback.
199 auto encodingFromDbf = [](const QString& cp) -> std::optional<QStringConverter::Encoding> {
200 const QString u = cp.toUpper();
201 if (u == "UTF-8" || u == "UTF8" || u == "LDID/8B") // LDID 0x8B = UTF-8 (unofficial)
202 return QStringConverter::Utf8;
203 if (u == "LDID/87" || u == "LDID/57" || u == "ISO-8859-1" || u == "LATIN1"
204 || u == "CP1252" || u == "WINDOWS-1252")
205 return QStringConverter::Latin1;
206 return std::nullopt;
207 };
208
209 if (auto enc = encodingFromDbf(codepage)) {
210 QStringDecoder decoder{*enc};
211 QString result = decoder(bytes);
212 if (!decoder.hasError()) return result;
213 }
214 // UTF-8 first, then Latin-1.
215 {
216 QStringDecoder utf8{QStringConverter::Utf8};
217 QString result = utf8(bytes);
218 if (!utf8.hasError()) return result;
219 }
220 QStringDecoder latin1{QStringConverter::Latin1};
221 return latin1(bytes);
222}
223
224// ---------------------------------------------------------------------------
225// Field-value → RS_Pen fragments.
226// ---------------------------------------------------------------------------
227
228// Numeric COLOR field: 0-255 → ACI palette (reuse RS_FilterDXFRW::numberToColor),
229// larger → 24-bit RGB (0xRRGGBB).
230RS_Color colorFromNumber(int v) {
231 if (v >= 0 && v <= 255) return RS_FilterDXFRW::numberToColor(v);
232 return RS_Color((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
233}
234
235// Nearest-neighbour: pick the RS2::LineWidth enum value closest to the
236// requested numeric weight (interpreted as tenths-of-mm, matching the
237// enum's own integer encoding — see rs.h:867-897).
238RS2::LineWidth widthFromNumber(double v) {
239 const int enumVal = static_cast<int>(std::lround(v));
240 const RS2::LineWidth candidates[] = {
241 RS2::Width00, RS2::Width01, RS2::Width02, RS2::Width03, RS2::Width04,
242 RS2::Width05, RS2::Width06, RS2::Width07, RS2::Width08, RS2::Width09,
243 RS2::Width10, RS2::Width11, RS2::Width12, RS2::Width13, RS2::Width14,
244 RS2::Width15, RS2::Width16, RS2::Width17, RS2::Width18, RS2::Width19,
245 RS2::Width20, RS2::Width21, RS2::Width22, RS2::Width23};
246 RS2::LineWidth best = RS2::Width00;
247 int bestDist = std::abs(static_cast<int>(RS2::Width00) - enumVal);
248 for (RS2::LineWidth w : candidates) {
249 const int d = std::abs(static_cast<int>(w) - enumVal);
250 if (d < bestDist) { bestDist = d; best = w; }
251 }
252 return best;
253}
254
255// Build an RS_Pen from resolved fields for one DBF record. If no styling
256// fields are present, returns std::nullopt so callers can leave the entity
257// at ByLayer defaults.
258std::optional<RS_Pen> penFromRecord(DBFHandle dbf, int record,
259 const ResolvedFields& rf,
260 const QString& codepage) {
261 if (!dbf || record < 0) return std::nullopt;
262 bool any = false;
263 RS_Pen pen{RS_Color(RS2::FlagByLayer), RS2::WidthByLayer, RS2::LineByLayer};
264
265 if (rf.color >= 0 && !DBFIsAttributeNULL(dbf, record, rf.color)) {
266 const int v = DBFReadIntegerAttribute(dbf, record, rf.color);
267 pen.setColor(colorFromNumber(v));
268 any = true;
269 }
270 if (rf.ltype >= 0 && !DBFIsAttributeNULL(dbf, record, rf.ltype)) {
271 const QString name = decodeDbfString(
272 DBFReadStringAttribute(dbf, record, rf.ltype), codepage);
273 if (!name.isEmpty()) {
274 pen.setLineType(RS_FilterDXFRW::nameToLineType(name));
275 any = true;
276 }
277 }
278 if (rf.width >= 0 && !DBFIsAttributeNULL(dbf, record, rf.width)) {
279 const double w = DBFReadDoubleAttribute(dbf, record, rf.width);
280 pen.setWidth(widthFromNumber(w));
281 any = true;
282 }
283 if (!any) return std::nullopt;
284 return pen;
285}
286
287// Ensure a layer with the given name exists on @p g; return it.
288RS_Layer* ensureLayer(RS_Graphic& g, const QString& name) {
289 if (RS_Layer* existing = g.findLayer(name)) return existing;
290 auto* layer = new RS_Layer(name);
291 // Default pen — ByBlock via RS_Layer's default constructor.
292 g.addLayer(layer);
293 return layer;
294}
295
296// ---------------------------------------------------------------------------
297// Geometry emission helpers.
298// ---------------------------------------------------------------------------
299
300void emitPoint(RS_Graphic& g, RS_Layer* layer,
301 const std::optional<RS_Pen>& pen,
302 double x, double y, double z) {
303 RS_Vector v(x, y, z);
304 auto* p = new RS_Point(&g, RS_PointData(v));
305 p->setLayer(layer);
306 if (pen) p->setPen(*pen);
307 g.addEntity(p);
308}
309
310void emitMultiPoint(RS_Graphic& g, RS_Layer* layer,
311 const std::optional<RS_Pen>& pen,
312 const SHPObject& o) {
313 for (int i = 0; i < o.nVertices; ++i) {
314 const double z = (o.padfZ != nullptr) ? o.padfZ[i] : 0.0;
315 emitPoint(g, layer, pen, o.padfX[i], o.padfY[i], z);
316 }
317}
318
319// Emit one polyline covering the [start, start+count) vertex slice of @p o.
320// Ring convention: for closed polylines the SHP-required duplicated closing
321// vertex (first==last) is dropped before setClosed(true). Degenerate parts
322// (< 2 vertices open, < 3 closed) are skipped.
323void emitOnePart(RS_Graphic& g, RS_Layer* layer,
324 const std::optional<RS_Pen>& pen,
325 const SHPObject& o, int start, int count, bool closed,
326 int& partsEmitted, int& partsSkipped) {
327 if (start < 0 || count <= 0 || start + count > o.nVertices) {
328 ++partsSkipped;
329 return;
330 }
331 if (closed && count >= 2) {
332 const RS_Vector first(o.padfX[start], o.padfY[start]);
333 const RS_Vector last(o.padfX[start + count - 1],
334 o.padfY[start + count - 1]);
335 if (first.squaredTo(last) < 1e-20) --count;
336 }
337 const int minVerts = closed ? 3 : 2;
338 if (count < minVerts) { ++partsSkipped; return; }
339
340 auto* pl = new RS_Polyline(&g, RS_PolylineData(
341 RS_Vector(false), RS_Vector(false), false));
342 pl->setLayer(layer);
343 if (pen) pl->setPen(*pen);
344 for (int i = 0; i < count; ++i) {
345 const int idx = start + i;
346 const double z = (o.padfZ != nullptr) ? o.padfZ[idx] : 0.0;
347 pl->addVertex(RS_Vector(o.padfX[idx], o.padfY[idx], z));
348 }
349 if (closed) pl->setClosed(true);
350 pl->endPolyline();
351 g.addEntity(pl);
352 ++partsEmitted;
353}
354
355// Emit every ARC/POLYGON part. ARC parts open, POLYGON parts closed.
356void emitParts(RS_Graphic& g, RS_Layer* layer,
357 const std::optional<RS_Pen>& pen,
358 const SHPObject& o, bool closed,
359 int& partsEmitted, int& partsSkipped) {
360 const int nParts = std::max(1, o.nParts);
361 for (int p = 0; p
11.1
'p' is < 'nParts'
< nParts; ++p) {
362 const int start = (o.panPartStart && o.nParts > 0)
12
Assuming field 'panPartStart' is null
363 ? o.panPartStart[p] : 0;
364 const int end = (p + 1 < nParts)
13
'?' condition is true
14
Dereference of null pointer
365 ? o.panPartStart[p + 1]
366 : o.nVertices;
367 emitOnePart(g, layer, pen, o, start, end - start, closed,
368 partsEmitted, partsSkipped);
369 }
370}
371
372// Emit a label MText next to a point, if a label field resolved a non-empty
373// string for this record.
374void emitLabel(RS_Graphic& g, RS_Layer* layer,
375 const std::optional<RS_Pen>& pen,
376 double x, double y, double z,
377 const QString& text, double height) {
378 if (text.isEmpty()) return;
379 RS_MTextData d(RS_Vector(x + height * 0.5, y + height * 0.5, z),
380 height, /*width=*/0.0,
381 RS_MTextData::VABottom, RS_MTextData::HALeft,
382 RS_MTextData::LeftToRight, RS_MTextData::AtLeast,
383 /*lineSpacingFactor=*/1.0,
384 text, /*style=*/"standard", /*angle=*/0.0,
385 RS2::NoUpdate);
386 auto* m = new RS_MText(&g, d);
387 m->setLayer(layer);
388 if (pen) m->setPen(*pen);
389 m->update();
390 g.addEntity(m);
391}
392
393// Trim trailing spaces (DBF strings are space-padded to field width).
394QString trimTrailingSpaces(QString s) {
395 while (!s.isEmpty() && s.back().isSpace()) s.chop(1);
396 return s;
397}
398
399} // namespace
400
401// ---------------------------------------------------------------------------
402// RS_FilterSHP::fileImport
403// ---------------------------------------------------------------------------
404
405bool RS_FilterSHP::fileImport(RS_Graphic& g, const QString& file,
406 RS2::FormatType /*type*/) {
407 m_lastError.clear();
408
409 const QByteArray path = QFile::encodeName(file);
410 ScopedSHP shp{SHPOpen(path.constData(), "rb")};
411 if (!shp) {
1
Taking false branch
412 m_lastError = QObject::tr("Cannot open shapefile %1 (missing or corrupt .shx?)")
413 .arg(file);
414 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "%s: %s",
415 __func__, m_lastError.toLatin1().constData());
416 return false;
417 }
418
419 int nEntities = 0;
420 int nShapeType = 0;
421 double bboxMin[4] = {0}, bboxMax[4] = {0};
422 SHPGetInfo(shp.get(), &nEntities, &nShapeType, bboxMin, bboxMax);
423 RS_DEBUGRS_Debug::instance()->print("%s: opened %s, %d records, shape type %s (%d)",
424 __func__, path.constData(), nEntities,
425 SHPTypeName(nShapeType), nShapeType);
426
427 // Companion .dbf — geometry-only import is fine if it's missing.
428 QString dbfPath = file;
429 if (dbfPath.endsWith(".shp", Qt::CaseInsensitive))
2
Assuming the condition is false
3
Taking false branch
430 dbfPath.chop(4);
431 dbfPath += ".dbf";
432 const QByteArray dbfBytes = QFile::encodeName(dbfPath);
433 ScopedDBF dbf{DBFOpen(dbfBytes.constData(), "rb")};
434
435 QString codepage;
436 if (dbf) {
4
Taking false branch
437 const char* cp = DBFGetCodePage(dbf.get());
438 codepage = cp ? QString::fromLatin1(cp) : QString();
439 }
440
441 LC_ShpImportOptions opts;
442 const ResolvedFields rf = resolveFields(dbf.get(), opts);
443
444 RS_Layer* defaultLayer = ensureLayer(g, "0");
445 int emitted = 0, skipped = 0;
446 int partsEmitted = 0, partsSkipped = 0;
447
448 for (int i = 0; i < nEntities; ++i) {
5
Assuming 'i' is < 'nEntities'
6
Loop condition is true. Entering loop body
449 ScopedShape rec{SHPReadObject(shp.get(), i)};
450 if (!rec) { ++skipped; continue; }
7
Taking false branch
451 if (rec->nSHPType == SHPT_NULL0) { ++skipped; continue; }
8
Assuming field 'nSHPType' is not equal to SHPT_NULL
9
Taking false branch
452
453 // Layer for this record: DBF-driven → auto-created; otherwise "0".
454 RS_Layer* layer = defaultLayer;
455 if (dbf && rf.layer >= 0
456 && !DBFIsAttributeNULL(dbf.get(), i, rf.layer)) {
457 const QString rawName = decodeDbfString(
458 DBFReadStringAttribute(dbf.get(), i, rf.layer), codepage);
459 const QString name = trimTrailingSpaces(rawName);
460 if (!name.isEmpty()) layer = ensureLayer(g, name);
461 }
462
463 const std::optional<RS_Pen> pen =
464 penFromRecord(dbf.get(), i, rf, codepage);
465
466 switch (rec->nSHPType) {
10
Control jumps to 'case 25:' at line 497
467 case SHPT_POINT1:
468 case SHPT_POINTZ11:
469 case SHPT_POINTM21: {
470 const double z = (rec->padfZ != nullptr) ? rec->padfZ[0] : 0.0;
471 emitPoint(g, layer, pen, rec->padfX[0], rec->padfY[0], z);
472 if (opts.importLabels && rf.label >= 0 && dbf
473 && !DBFIsAttributeNULL(dbf.get(), i, rf.label)) {
474 const QString text = trimTrailingSpaces(decodeDbfString(
475 DBFReadStringAttribute(dbf.get(), i, rf.label), codepage));
476 emitLabel(g, layer, pen, rec->padfX[0], rec->padfY[0], z,
477 text, opts.labelHeight);
478 }
479 ++emitted;
480 break;
481 }
482 case SHPT_MULTIPOINT8:
483 case SHPT_MULTIPOINTZ18:
484 case SHPT_MULTIPOINTM28:
485 emitMultiPoint(g, layer, pen, *rec.get());
486 ++emitted;
487 break;
488 case SHPT_ARC3:
489 case SHPT_ARCZ13:
490 case SHPT_ARCM23:
491 emitParts(g, layer, pen, *rec.get(), /*closed=*/false,
492 partsEmitted, partsSkipped);
493 ++emitted;
494 break;
495 case SHPT_POLYGON5:
496 case SHPT_POLYGONZ15:
497 case SHPT_POLYGONM25:
498 emitParts(g, layer, pen, *rec.get(), /*closed=*/true,
11
Calling 'emitParts'
499 partsEmitted, partsSkipped);
500 ++emitted;
501 break;
502 case SHPT_MULTIPATCH31:
503 // Simplified 2.5D handling: rings become closed polylines; strips
504 // and fans render as an open-polyline wireframe. Full triangle-
505 // mesh support is out of scope for the first cut.
506 for (int p = 0; p < rec->nParts; ++p) {
507 const int ptype = (rec->panPartType != nullptr)
508 ? rec->panPartType[p] : SHPP_RING5;
509 const bool ringLike = (ptype == SHPP_OUTERRING2
510 || ptype == SHPP_INNERRING3
511 || ptype == SHPP_RING5
512 || ptype == SHPP_FIRSTRING4);
513 const int start = rec->panPartStart[p];
514 const int end = (p + 1 < rec->nParts)
515 ? rec->panPartStart[p + 1]
516 : rec->nVertices;
517 emitOnePart(g, layer, pen, *rec.get(), start, end - start,
518 ringLike, partsEmitted, partsSkipped);
519 }
520 ++emitted;
521 break;
522 default:
523 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
524 "%s: skipping unknown shape type %d at record %d",
525 __func__, rec->nSHPType, i);
526 ++skipped;
527 break;
528 }
529 }
530
531 if (skipped > 0) {
532 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
533 "%s: %d record(s) skipped, %d part(s) skipped",
534 __func__, skipped, partsSkipped);
535 }
536 RS_DEBUGRS_Debug::instance()->print("%s: imported %d record(s) (%d polyline part(s))",
537 __func__, emitted, partsEmitted);
538
539 // Fail only if nothing readable was produced from a non-empty file.
540 if (emitted == 0 && nEntities > 0) {
541 m_lastError = QObject::tr("Shapefile %1 contained %2 records but none were readable")
542 .arg(file).arg(nEntities);
543 return false;
544 }
545 return true;
546}