1 /*
2 * Copyright 2012, 2013 Pete Curtis
3 * Copyright 2013-2019 Kai Pastor
4 *
5 * This file is part of OpenOrienteering.
6 *
7 * OpenOrienteering is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * OpenOrienteering is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "ocad8_file_format.h"
22 #include "ocad8_file_format_p.h"
23
24 #include <QtMath>
25 #include <QCoreApplication>
26 #include <QDir>
27 #include <QFileInfo>
28 #include <QImage>
29 #include <QImageReader>
30 #include <QRgb>
31 #include <QTextCodec>
32
33 #include "settings.h"
34 #include "core/georeferencing.h"
35 #include "core/map.h"
36 #include "core/map_color.h"
37 #include "core/map_coord.h"
38 #include "core/map_part.h"
39 #include "core/map_view.h"
40 #include "core/objects/object.h"
41 #include "core/objects/text_object.h"
42 #include "core/symbols/symbol.h"
43 #include "core/symbols/area_symbol.h"
44 #include "core/symbols/combined_symbol.h"
45 #include "core/symbols/line_symbol.h"
46 #include "core/symbols/point_symbol.h"
47 #include "core/symbols/text_symbol.h"
48 #include "fileformats/xml_file_format.h"
49 #include "fileformats/file_import_export.h"
50 #include "fileformats/ocd_file_format.h"
51 #include "templates/template.h"
52 #include "templates/template_image.h"
53 #include "templates/template_map.h"
54 #include "util/encoding.h"
55 #include "util/util.h"
56
57
58 namespace OpenOrienteering {
59
60 // ### OCAD8FileFormat ###
61
OCAD8FileFormat()62 OCAD8FileFormat::OCAD8FileFormat()
63 : FileFormat(MapFile,
64 "OCAD78",
65 ::OpenOrienteering::ImportExport::tr("OCAD Versions 7, 8"),
66 QString::fromLatin1("ocd"),
67 Feature::FileOpen | Feature::FileImport | Feature::ReadingLossy |
68 Feature::FileSave | Feature::FileSaveAs | Feature::WritingLossy )
69 {
70 // Nothing
71 }
72
73
understands(const char * buffer,int size) const74 FileFormat::ImportSupportAssumption OCAD8FileFormat::understands(const char* buffer, int size) const
75 {
76 // The first two bytes of the file must be AD 0C.
77 if (size < 2)
78 return Unknown;
79 else if (quint8(buffer[0]) == 0xAD && buffer[1] == 0x0C)
80 return FullySupported;
81 else
82 return NotSupported;
83 }
84
85
makeImporter(const QString & path,Map * map,MapView * view) const86 std::unique_ptr<Importer> OCAD8FileFormat::makeImporter(const QString& path, Map *map, MapView *view) const
87 {
88 return std::make_unique<OCAD8FileImport>(path, map, view);
89 }
90
makeExporter(const QString & path,const Map * map,const MapView * view) const91 std::unique_ptr<Exporter> OCAD8FileFormat::makeExporter(const QString& path, const Map* map, const MapView* view) const
92 {
93 return std::make_unique<OCAD8FileExport>(path, map, view);
94 }
95
96
97
98 // ### OCAD8FileImport ###
99
OCAD8FileImport(const QString & path,Map * map,MapView * view)100 OCAD8FileImport::OCAD8FileImport(const QString& path, Map* map, MapView* view) : Importer(path, map, view), file(nullptr)
101 {
102 ocad_init();
103 const QByteArray enc_name = Settings::getInstance().getSetting(Settings::General_Local8BitEncoding).toByteArray();
104 encoding_1byte = Util::codecForName(enc_name);
105 if (!encoding_1byte) encoding_1byte = QTextCodec::codecForLocale();
106 encoding_2byte = QTextCodec::codecForName("UTF-16LE");
107 offset_x = offset_y = 0;
108 }
109
~OCAD8FileImport()110 OCAD8FileImport::~OCAD8FileImport()
111 {
112 ocad_shutdown();
113 }
114
isRasterImageFile(const QString & filename)115 bool OCAD8FileImport::isRasterImageFile(const QString &filename)
116 {
117 int dot_pos = filename.lastIndexOf(QLatin1Char('.'));
118 if (dot_pos < 0)
119 return false;
120 QString extension = filename.right(filename.length() - dot_pos - 1).toLower();
121 return QImageReader::supportedImageFormats().contains(extension.toLatin1());
122 }
123
importImplementation()124 bool OCAD8FileImport::importImplementation()
125 {
126 //qint64 start = QDateTime::currentMSecsSinceEpoch();
127
128 auto& device = *this->device();
129 u32 size = device.bytesAvailable();
130 u8* buffer = (u8*)malloc(size);
131 if (!buffer)
132 throw FileFormatException(tr("Could not allocate buffer."));
133 if (device.read((char*)buffer, size) != size)
134 throw FileFormatException(device.errorString());
135 int err = ocad_file_open_memory(&file, buffer, size);
136 if (err != 0) throw FileFormatException(tr("libocad returned %1").arg(err));
137
138 if (file->header->major <= 5 || file->header->major >= 9)
139 throw FileFormatException(tr("OCAD files of version %1 are not supported!").arg(file->header->major));
140
141 //qDebug() << "file version is" << file->header->major << ", type is"
142 // << ((file->header->ftype == 2) ? "normal" : "other");
143 //qDebug() << "map scale is" << file->setup->scale;
144
145 map->setProperty(OcdFileFormat::versionProperty(), file->header->major);
146
147 // Scale and georeferencing parameters
148 Georeferencing georef;
149 georef.setScaleDenominator(file->setup->scale);
150 georef.setProjectedRefPoint(QPointF(file->setup->offsetx, file->setup->offsety));
151 if (qAbs(file->setup->angle) >= 0.01) /* degrees */
152 {
153 georef.setGrivation(file->setup->angle);
154 }
155 map->setGeoreferencing(georef);
156
157 map->setMapNotes(convertCString((const char*)file->buffer + file->header->infopos, file->header->infosize, false));
158
159 // TODO: print parameters
160
161 // Load the separations to a temporary stack
162 std::vector< MapColor* > separations;
163 int num_separations = ocad_separation_count(file);
164 #if 1
165 addWarning(tr("%n color separation(s) were skipped, reason: Import disabled.", "", num_separations));
166 num_separations = 0;
167 #endif
168 if (num_separations < 0)
169 {
170 addWarning(tr("Could not load the spot color definitions, error: %1").arg(num_separations));
171 num_separations = 0;
172 }
173 separations.reserve(num_separations);
174 for (int i = 0; i < num_separations; i++)
175 {
176 const OCADColorSeparation *ocad_separation = ocad_separation_at(file, i);
177 MapColor* color = new MapColor(convertPascalString(ocad_separation->sep_name), MapColor::Reserved);
178 color->setSpotColorName(convertPascalString(ocad_separation->sep_name).toUpper());
179 // OCD stores CMYK values as integers from 0-200.
180 const MapColorCmyk cmyk(
181 0.005f * ocad_separation->cyan,
182 0.005f * ocad_separation->magenta,
183 0.005f * ocad_separation->yellow,
184 0.005f * ocad_separation->black );
185 color->setCmyk(cmyk);
186 color->setOpacity(1.0f);
187 separations.push_back(color);
188 }
189
190 // Load colors
191 int num_colors = ocad_color_count(file);
192 for (int i = 0; i < num_colors; i++)
193 {
194 OCADColor *ocad_color = ocad_color_at(file, i);
195 MapColor* color = new MapColor(convertPascalString(ocad_color->name), map->color_set->colors.size());
196 // OCD stores CMYK values as integers from 0-200.
197 MapColorCmyk cmyk(
198 0.005f * ocad_color->cyan,
199 0.005f * ocad_color->magenta,
200 0.005f * ocad_color->yellow,
201 0.005f * ocad_color->black );
202 color->setCmyk(cmyk);
203 color->setOpacity(1.0f);
204
205 SpotColorComponents components;
206 for (int j = 0; j < num_separations; ++j)
207 {
208 const u8& ocad_halftone = ocad_color->spot[j];
209 if (ocad_halftone <= 200)
210 {
211 float halftone = 0.005f * ocad_halftone;
212 components.reserve(std::size_t(num_separations)); // reserves only once for same capacity
213 components.push_back(SpotColorComponent(separations[j], halftone)); // clazy:exclude=reserve-candidates
214 }
215 }
216 if (!components.empty())
217 {
218 color->setSpotColorComposition(components);
219 const MapColorCmyk cmyk(color->getCmyk());
220 color->setCmykFromSpotColors();
221 if (cmyk != color->getCmyk())
222 // The color's CMYK was customized.
223 color->setCmyk(cmyk);
224 }
225
226 if (i == 0 && color->isBlack() && color->getName() == QLatin1String("Registration black")
227 && XMLFileFormat::active_version >= 6 )
228 {
229 delete color; color = nullptr;
230 color_index[ocad_color->number] = Map::getRegistrationColor();
231 addWarning(tr("Color \"Registration black\" is imported as a special color."));
232 // NOTE: This does not make a difference in output
233 // as long as no spot colors are created,
234 // but as a special color, it is protected from modification,
235 // and it will be saved as number 0 in OCD export.
236 }
237 else
238 {
239 map->color_set->colors.push_back(color);
240 color_index[ocad_color->number] = color;
241 }
242 }
243
244 // Insert the spot colors into the map
245 for (int i = 0; i < num_separations; ++i)
246 {
247 map->addColor(separations[i], map->color_set->colors.size());
248 }
249
250 // Load symbols
251 for (OCADSymbolIndex *idx = ocad_symidx_first(file); idx; idx = ocad_symidx_next(file, idx))
252 {
253 for (int i = 0; i < 256; i++)
254 {
255 OCADSymbol *ocad_symbol = ocad_symbol_at(file, idx, i);
256 if (ocad_symbol && ocad_symbol->number != 0)
257 {
258 Symbol *symbol = nullptr;
259 if (ocad_symbol->type == OCAD_POINT_SYMBOL)
260 {
261 symbol = importPointSymbol((OCADPointSymbol *)ocad_symbol);
262 }
263 else if (ocad_symbol->type == OCAD_LINE_SYMBOL)
264 {
265 symbol = importLineSymbol((OCADLineSymbol *)ocad_symbol);
266 }
267 else if (ocad_symbol->type == OCAD_AREA_SYMBOL)
268 {
269 symbol = importAreaSymbol((OCADAreaSymbol *)ocad_symbol);
270 }
271 else if (ocad_symbol->type == OCAD_TEXT_SYMBOL)
272 {
273 symbol = importTextSymbol((OCADTextSymbol *)ocad_symbol);
274 }
275 else if (ocad_symbol->type == OCAD_RECT_SYMBOL)
276 {
277 RectangleInfo* rect = importRectSymbol((OCADRectSymbol *)ocad_symbol);
278 map->symbols.push_back(rect->border_line);
279 if (rect->has_grid)
280 {
281 map->symbols.push_back(rect->inner_line);
282 map->symbols.push_back(rect->text);
283 }
284 continue;
285 }
286
287
288 if (symbol)
289 {
290 map->symbols.push_back(symbol);
291 symbol_index[ocad_symbol->number] = symbol;
292 }
293 else
294 {
295 addWarning(tr("Unable to import symbol \"%3\" (%1.%2)")
296 .arg(ocad_symbol->number / 10).arg(ocad_symbol->number % 10)
297 .arg(convertPascalString(ocad_symbol->name)));
298 }
299 }
300 }
301 }
302
303 if (!loadSymbolsOnly())
304 {
305 // Load objects
306
307 // Place all objects into a single OCAD import part
308 MapPart* part = new MapPart(tr("OCAD import layer"), map);
309 for (OCADObjectIndex *idx = ocad_objidx_first(file); idx; idx = ocad_objidx_next(file, idx))
310 {
311 for (int i = 0; i < 256; i++)
312 {
313 OCADObjectEntry *entry = ocad_object_entry_at(file, idx, i);
314 OCADObject *ocad_obj = ocad_object(file, entry);
315 if (ocad_obj)
316 {
317 Object *object = importObject(ocad_obj, part);
318 if (object) {
319 part->objects.push_back(object);
320 }
321 }
322 }
323 }
324 delete map->parts[0];
325 map->parts[0] = part;
326 map->current_part_index = 0;
327
328 // Load templates
329 map->templates.clear();
330 for (OCADStringIndex *idx = ocad_string_index_first(file); idx; idx = ocad_string_index_next(file, idx))
331 {
332 for (int i = 0; i < 256; i++)
333 {
334 OCADStringEntry *entry = ocad_string_entry_at(file, idx, i);
335 if (entry->type != 0 && entry->size > 0)
336 importString(entry);
337 }
338 }
339 map->first_front_template = map->templates.size(); // Templates in front of the map are not supported by OCD
340
341 // Fill view with relevant fields from OCD file
342 if (view)
343 {
344 if (file->setup->zoom >= MapView::zoom_out_limit && file->setup->zoom <= MapView::zoom_in_limit)
345 view->setZoom(file->setup->zoom);
346
347 s32 buf[3];
348 ocad_point(buf, &file->setup->center);
349 MapCoord center_pos;
350 convertPoint(center_pos, buf[0], buf[1]);
351 view->setCenter(center_pos);
352 }
353
354 // TODO: read template visibilities
355 /*
356 int num_template_visibilities;
357 file->read((char*)&num_template_visibilities, sizeof(int));
358
359 for (int i = 0; i < num_template_visibilities; ++i)
360 {
361 int pos;
362 file->read((char*)&pos, sizeof(int));
363
364 TemplateVisibility* vis = getTemplateVisibility(map->getTemplate(pos));
365 file->read((char*)&vis->visible, sizeof(bool));
366 file->read((char*)&vis->opacity, sizeof(float));
367 }
368 }
369 */
370
371 // Undo steps are not supported in OCAD
372 }
373
374 ocad_file_close(file);
375
376 //qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - start;
377 //qDebug() << "OCAD map imported:"<<map->getNumSymbols()<<"symbols and"<<map->getNumObjects()<<"objects in"<<elapsed<<"milliseconds";
378
379 emit map->currentMapPartIndexChanged(map->current_part_index);
380 emit map->currentMapPartChanged(map->getPart(map->current_part_index));
381
382 return true;
383 }
384
setStringEncodings(const char * narrow,const char * wide)385 void OCAD8FileImport::setStringEncodings(const char *narrow, const char *wide) {
386 encoding_1byte = QTextCodec::codecForName(narrow);
387 encoding_2byte = QTextCodec::codecForName(wide);
388 }
389
importPointSymbol(const OCADPointSymbol * ocad_symbol)390 Symbol *OCAD8FileImport::importPointSymbol(const OCADPointSymbol *ocad_symbol)
391 {
392 PointSymbol *symbol = importPattern(ocad_symbol->ngrp, (OCADPoint *)ocad_symbol->pts);
393 fillCommonSymbolFields(symbol, (OCADSymbol *)ocad_symbol);
394 symbol->setRotatable(ocad_symbol->base_flags & 1);
395
396 return symbol;
397 }
398
importLineSymbol(const OCADLineSymbol * ocad_symbol)399 Symbol *OCAD8FileImport::importLineSymbol(const OCADLineSymbol *ocad_symbol)
400 {
401 LineSymbol* line_for_borders = nullptr;
402
403 // Import a main line?
404 LineSymbol* main_line = nullptr;
405 if (ocad_symbol->dmode == 0 || ocad_symbol->width > 0)
406 {
407 main_line = new LineSymbol();
408 line_for_borders = main_line;
409 fillCommonSymbolFields(main_line, (OCADSymbol *)ocad_symbol);
410
411 // Basic line options
412 main_line->line_width = convertSize(ocad_symbol->width);
413 main_line->color = convertColor(ocad_symbol->color);
414
415 // Cap and join styles
416 if (ocad_symbol->ends == 0)
417 {
418 main_line->cap_style = LineSymbol::FlatCap;
419 main_line->join_style = LineSymbol::BevelJoin;
420 }
421 else if (ocad_symbol->ends == 1)
422 {
423 main_line->cap_style = LineSymbol::RoundCap;
424 main_line->join_style = LineSymbol::RoundJoin;
425 }
426 else if (ocad_symbol->ends == 2)
427 {
428 main_line->cap_style = LineSymbol::PointedCap;
429 main_line->join_style = LineSymbol::BevelJoin;
430 }
431 else if (ocad_symbol->ends == 3)
432 {
433 main_line->cap_style = LineSymbol::PointedCap;
434 main_line->join_style = LineSymbol::RoundJoin;
435 }
436 else if (ocad_symbol->ends == 4)
437 {
438 main_line->cap_style = LineSymbol::FlatCap;
439 main_line->join_style = LineSymbol::MiterJoin;
440 }
441 else if (ocad_symbol->ends == 6)
442 {
443 main_line->cap_style = LineSymbol::PointedCap;
444 main_line->join_style = LineSymbol::MiterJoin;
445 }
446
447 main_line->start_offset = convertSize(ocad_symbol->bdist);
448 main_line->end_offset = convertSize(ocad_symbol->edist);
449 if (main_line->cap_style == LineSymbol::PointedCap)
450 {
451 // Note: While the property in the file may be different
452 // (cf. what is set in the first place), OC*D always
453 // draws round joins if the line cap is pointed!
454 main_line->join_style = LineSymbol::RoundJoin;
455 }
456
457 // Handle the dash pattern
458 if( ocad_symbol->gap > 0 || ocad_symbol->gap2 > 0 )
459 {
460 main_line->dashed = true;
461
462 // Detect special case
463 if (ocad_symbol->gap2 > 0 && ocad_symbol->gap == 0)
464 {
465 main_line->dash_length = convertSize(ocad_symbol->len - ocad_symbol->gap2);
466 main_line->break_length = convertSize(ocad_symbol->gap2);
467 if (!(ocad_symbol->elen >= ocad_symbol->len / 2 - 1 && ocad_symbol->elen <= ocad_symbol->len / 2 + 1))
468 addWarning(tr("In dashed line symbol %1, the end length cannot be imported correctly.").arg(0.1 * ocad_symbol->number));
469 if (ocad_symbol->egap != 0)
470 addWarning(tr("In dashed line symbol %1, the end gap cannot be imported correctly.").arg(0.1 * ocad_symbol->number));
471 }
472 else
473 {
474 if (ocad_symbol->len != ocad_symbol->elen)
475 {
476 if (ocad_symbol->elen >= ocad_symbol->len / 2 - 1 && ocad_symbol->elen <= ocad_symbol->len / 2 + 1)
477 main_line->half_outer_dashes = true;
478 else
479 addWarning(tr("In dashed line symbol %1, main and end length are different (%2 and %3). Using %4.")
480 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->len).arg(ocad_symbol->elen).arg(ocad_symbol->len));
481 }
482
483 main_line->dash_length = convertSize(ocad_symbol->len);
484 main_line->break_length = convertSize(ocad_symbol->gap);
485 if (ocad_symbol->gap2 > 0)
486 {
487 main_line->dashes_in_group = 2;
488 if (ocad_symbol->gap2 != ocad_symbol->egap)
489 addWarning(tr("In dashed line symbol %1, gaps D and E are different (%2 and %3). Using %4.")
490 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->gap2).arg(ocad_symbol->egap).arg(ocad_symbol->gap2));
491 main_line->in_group_break_length = convertSize(ocad_symbol->gap2);
492 main_line->dash_length = (main_line->dash_length - main_line->in_group_break_length) / 2;
493 }
494 }
495 }
496 else
497 {
498 main_line->segment_length = convertSize(ocad_symbol->len);
499 main_line->end_length = convertSize(ocad_symbol->elen);
500 }
501 }
502
503 // Import a 'framing' line?
504 LineSymbol* framing_line = nullptr;
505 if (ocad_symbol->fwidth > 0)
506 {
507 framing_line = new LineSymbol();
508 if (!line_for_borders)
509 line_for_borders = framing_line;
510 fillCommonSymbolFields(framing_line, (OCADSymbol *)ocad_symbol);
511
512 // Basic line options
513 framing_line->line_width = convertSize(ocad_symbol->fwidth);
514 framing_line->color = convertColor(ocad_symbol->fcolor);
515
516 // Cap and join styles
517 if (ocad_symbol->fstyle == 0)
518 {
519 framing_line->cap_style = LineSymbol::FlatCap;
520 framing_line->join_style = LineSymbol::BevelJoin;
521 }
522 else if (ocad_symbol->fstyle == 1)
523 {
524 framing_line->cap_style = LineSymbol::RoundCap;
525 framing_line->join_style = LineSymbol::RoundJoin;
526 }
527 else if (ocad_symbol->fstyle == 4)
528 {
529 framing_line->cap_style = LineSymbol::FlatCap;
530 framing_line->join_style = LineSymbol::MiterJoin;
531 }
532 }
533
534 // Import a 'double' line?
535 bool has_border_line = ocad_symbol->lwidth > 0 || ocad_symbol->rwidth > 0;
536 LineSymbol *double_line = nullptr;
537 if (ocad_symbol->dmode != 0 && (ocad_symbol->dflags & 1 || (has_border_line && !line_for_borders)))
538 {
539 double_line = new LineSymbol();
540 line_for_borders = double_line;
541 fillCommonSymbolFields(double_line, (OCADSymbol *)ocad_symbol);
542
543 double_line->line_width = convertSize(ocad_symbol->dwidth);
544 if (ocad_symbol->dflags & 1)
545 double_line->color = convertColor(ocad_symbol->dcolor);
546 else
547 double_line->color = nullptr;
548
549 double_line->cap_style = LineSymbol::FlatCap;
550 double_line->join_style = LineSymbol::MiterJoin;
551
552 double_line->segment_length = convertSize(ocad_symbol->len);
553 double_line->end_length = convertSize(ocad_symbol->elen);
554 }
555
556 // Border lines
557 if (has_border_line)
558 {
559 Q_ASSERT(line_for_borders);
560 line_for_borders->have_border_lines = true;
561 LineSymbolBorder& border = line_for_borders->getBorder();
562 LineSymbolBorder& right_border = line_for_borders->getRightBorder();
563
564 // Border color and width
565 border.color = convertColor(ocad_symbol->lcolor);
566 border.width = convertSize(ocad_symbol->lwidth);
567 border.shift = convertSize(ocad_symbol->lwidth) / 2 + (convertSize(ocad_symbol->dwidth) - line_for_borders->line_width) / 2;
568
569 right_border.color = convertColor(ocad_symbol->rcolor);
570 right_border.width = convertSize(ocad_symbol->rwidth);
571 right_border.shift = convertSize(ocad_symbol->rwidth) / 2 + (convertSize(ocad_symbol->dwidth) - line_for_borders->line_width) / 2;
572
573 // The borders may be dashed
574 if (ocad_symbol->dgap > 0 && ocad_symbol->dmode > 1)
575 {
576 border.dashed = true;
577 border.dash_length = convertSize(ocad_symbol->dlen);
578 border.break_length = convertSize(ocad_symbol->dgap);
579
580 // If ocad_symbol->dmode == 2, only the left border should be dashed
581 if (ocad_symbol->dmode > 2)
582 {
583 right_border.dashed = border.dashed;
584 right_border.dash_length = border.dash_length;
585 right_border.break_length = border.break_length;
586 }
587 }
588 }
589
590 // Create point symbols along line; middle ("normal") dash, corners, start, and end.
591 LineSymbol* symbol_line = main_line ? main_line : double_line; // Find the line to attach the symbols to
592 if (!symbol_line)
593 {
594 main_line = new LineSymbol();
595 symbol_line = main_line;
596 fillCommonSymbolFields(main_line, (OCADSymbol *)ocad_symbol);
597
598 main_line->segment_length = convertSize(ocad_symbol->len);
599 main_line->end_length = convertSize(ocad_symbol->elen);
600 }
601 OCADPoint * symbolptr = (OCADPoint *)ocad_symbol->pts;
602 symbol_line->mid_symbol = importPattern( ocad_symbol->smnpts, symbolptr);
603 symbol_line->mid_symbols_per_spot = ocad_symbol->snum;
604 symbol_line->mid_symbol_distance = convertSize(ocad_symbol->sdist);
605 symbolptr += ocad_symbol->smnpts;
606 if( ocad_symbol->ssnpts > 0 )
607 {
608 //symbol_line->dash_symbol = importPattern( ocad_symbol->ssnpts, symbolptr);
609 symbolptr += ocad_symbol->ssnpts;
610 }
611 if( ocad_symbol->scnpts > 0 )
612 {
613 symbol_line->dash_symbol = importPattern( ocad_symbol->scnpts, symbolptr);
614 symbol_line->dash_symbol->setName(QCoreApplication::translate("OpenOrienteering::LineSymbolSettings", "Dash symbol"));
615 symbolptr += ocad_symbol->scnpts;
616 }
617 if( ocad_symbol->sbnpts > 0 )
618 {
619 symbol_line->start_symbol = importPattern( ocad_symbol->sbnpts, symbolptr);
620 symbol_line->start_symbol->setName(QCoreApplication::translate("OpenOrienteering::LineSymbolSettings", "Start symbol"));
621 symbolptr += ocad_symbol->sbnpts;
622 }
623 if( ocad_symbol->senpts > 0 )
624 {
625 symbol_line->end_symbol = importPattern( ocad_symbol->senpts, symbolptr);
626 }
627 // FIXME: not really sure how this translates... need test cases
628 symbol_line->minimum_mid_symbol_count = 0; //1 + ocad_symbol->smin;
629 symbol_line->minimum_mid_symbol_count_when_closed = 0; //1 + ocad_symbol->smin;
630 symbol_line->show_at_least_one_symbol = false; // NOTE: this works in a different way than OCAD's 'at least X symbols' setting (per-segment instead of per-object)
631
632 // Suppress dash symbol at line ends if both start symbol and end symbol exist,
633 // but don't create a warning unless a dash symbol is actually defined
634 // and the line symbol is not Mapper's 799 Simple orienteering course.
635 if (symbol_line->start_symbol && symbol_line->end_symbol)
636 {
637 symbol_line->setSuppressDashSymbolAtLineEnds(true);
638 if (symbol_line->dash_symbol && symbol_line->getNumberComponent(0) != 799)
639 addWarning(tr("Line symbol %1: suppressing dash symbol at line ends.").arg(QString::number(0.1 * ocad_symbol->number) + QLatin1Char(' ') + symbol_line->getName()));
640 }
641
642 // TODO: taper fields (tmode and tlast)
643
644 if (!main_line && !framing_line)
645 return double_line;
646 else if (!double_line && !framing_line)
647 return main_line;
648 else if (!main_line && !double_line)
649 return framing_line;
650 else
651 {
652 CombinedSymbol* full_line = new CombinedSymbol();
653 fillCommonSymbolFields(full_line, (OCADSymbol *)ocad_symbol);
654 full_line->setNumParts(3);
655 int part = 0;
656 if (main_line)
657 {
658 full_line->setPart(part++, main_line, true);
659 main_line->setHidden(false);
660 main_line->setProtected(false);
661 }
662 if (double_line)
663 {
664 full_line->setPart(part++, double_line, true);
665 double_line->setHidden(false);
666 double_line->setProtected(false);
667 }
668 if (framing_line)
669 {
670 full_line->setPart(part++, framing_line, true);
671 framing_line->setHidden(false);
672 framing_line->setProtected(false);
673 }
674 full_line->setNumParts(part);
675 return full_line;
676 }
677 }
678
importAreaSymbol(const OCADAreaSymbol * ocad_symbol)679 Symbol *OCAD8FileImport::importAreaSymbol(const OCADAreaSymbol *ocad_symbol)
680 {
681 AreaSymbol *symbol = new AreaSymbol();
682 fillCommonSymbolFields(symbol, (OCADSymbol *)ocad_symbol);
683
684 // Basic area symbol fields: minimum_area, color
685 symbol->minimum_area = 0;
686 symbol->color = ocad_symbol->fill ? convertColor(ocad_symbol->color) : nullptr;
687 symbol->patterns.clear();
688 AreaSymbol::FillPattern *pat = nullptr;
689
690 // Hatching
691 if (ocad_symbol->hmode > 0)
692 {
693 int n = symbol->patterns.size(); symbol->patterns.resize(n + 1); pat = &(symbol->patterns[n]);
694 pat->type = AreaSymbol::FillPattern::LinePattern;
695 pat->angle = convertRotation(ocad_symbol->hangle1);
696 pat->flags = AreaSymbol::FillPattern::Option::Rotatable;
697 pat->line_spacing = convertSize(ocad_symbol->hdist + ocad_symbol->hwidth);
698 pat->line_offset = 0;
699 pat->line_color = convertColor(ocad_symbol->hcolor);
700 pat->line_width = convertSize(ocad_symbol->hwidth);
701 if (ocad_symbol->hmode == 2)
702 {
703 // Second hatch, same as the first, just a different angle
704 symbol->patterns.push_back(*pat);
705 symbol->patterns.back().angle = convertRotation(ocad_symbol->hangle2);
706 }
707 }
708
709 if (ocad_symbol->pmode > 0)
710 {
711 // OCAD 8 has a "staggered" pattern mode, where successive rows are shifted width/2 relative
712 // to each other. We need to simulate this in Mapper with two overlapping patterns, each with
713 // twice the height. The second is then offset by width/2, height/2.
714 auto spacing = convertSize(ocad_symbol->pheight);
715 if (ocad_symbol->pmode == 2) spacing *= 2;
716 int n = symbol->patterns.size(); symbol->patterns.resize(n + 1); pat = &(symbol->patterns[n]);
717 pat->type = AreaSymbol::FillPattern::PointPattern;
718 pat->angle = convertRotation(ocad_symbol->pangle);
719 pat->flags = AreaSymbol::FillPattern::Option::Rotatable;
720 pat->point_distance = convertSize(ocad_symbol->pwidth);
721 pat->line_spacing = spacing;
722 pat->line_offset = 0;
723 pat->offset_along_line = 0;
724 // FIXME: somebody needs to own this symbol and be responsible for deleting it
725 // Right now it looks like a potential memory leak
726 pat->point = importPattern(ocad_symbol->npts, (OCADPoint *)ocad_symbol->pts);
727 if (ocad_symbol->pmode == 2)
728 {
729 int n = symbol->patterns.size(); symbol->patterns.resize(n + 1); pat = &(symbol->patterns[n]);
730 pat->type = AreaSymbol::FillPattern::PointPattern;
731 pat->angle = convertRotation(ocad_symbol->pangle);
732 pat->flags = AreaSymbol::FillPattern::Option::Rotatable;
733 pat->point_distance = convertSize(ocad_symbol->pwidth);
734 pat->line_spacing = spacing;
735 pat->line_offset = pat->line_spacing / 2;
736 pat->offset_along_line = pat->point_distance / 2;
737 pat->point = importPattern(ocad_symbol->npts, (OCADPoint *)ocad_symbol->pts);
738 }
739 }
740
741 return symbol;
742 }
743
importTextSymbol(const OCADTextSymbol * ocad_symbol)744 Symbol *OCAD8FileImport::importTextSymbol(const OCADTextSymbol *ocad_symbol)
745 {
746 TextSymbol *symbol = new TextSymbol();
747 fillCommonSymbolFields(symbol, (OCADSymbol *)ocad_symbol);
748
749 symbol->font_family = convertPascalString(ocad_symbol->font); // FIXME: font mapping?
750 symbol->color = convertColor(ocad_symbol->color);
751 double d_font_size = (0.1 * ocad_symbol->dpts) / 72.0 * 25.4;
752 symbol->font_size = qRound(1000 * d_font_size);
753 symbol->bold = (ocad_symbol->bold >= 550) ? true : false;
754 symbol->italic = (ocad_symbol->italic) ? true : false;
755 symbol->underline = false;
756 symbol->paragraph_spacing = convertSize(ocad_symbol->pspace);
757 symbol->character_spacing = ocad_symbol->cspace / 100.0;
758 symbol->kerning = false;
759 symbol->line_below = ocad_symbol->under;
760 symbol->line_below_color = convertColor(ocad_symbol->ucolor);
761 symbol->line_below_width = convertSize(ocad_symbol->uwidth);
762 symbol->line_below_distance = convertSize(ocad_symbol->udist);
763 symbol->custom_tabs.resize(ocad_symbol->ntabs);
764 for (int i = 0; i < ocad_symbol->ntabs; ++i)
765 symbol->custom_tabs[i] = convertSize(ocad_symbol->tab[i]);
766
767 int halign = (int)TextObject::AlignHCenter;
768 if (ocad_symbol->halign == 0)
769 halign = (int)TextObject::AlignLeft;
770 else if (ocad_symbol->halign == 1)
771 halign = (int)TextObject::AlignHCenter;
772 else if (ocad_symbol->halign == 2)
773 halign = (int)TextObject::AlignRight;
774 else if (ocad_symbol->halign == 3)
775 {
776 // TODO: implement justified alignment
777 addWarning(tr("During import of text symbol %1: ignoring justified alignment").arg(0.1 * ocad_symbol->number));
778 }
779 text_halign_map[symbol] = halign;
780
781 if (ocad_symbol->bold != 400 && ocad_symbol->bold != 700)
782 {
783 addWarning(tr("During import of text symbol %1: ignoring custom weight (%2)")
784 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->bold));
785 }
786 if (ocad_symbol->cspace != 0)
787 {
788 addWarning(tr("During import of text symbol %1: custom character spacing is set, its implementation does not match OCAD's behavior yet")
789 .arg(0.1 * ocad_symbol->number));
790 }
791 if (ocad_symbol->wspace != 100)
792 {
793 addWarning(tr("During import of text symbol %1: ignoring custom word spacing (%2%)")
794 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->wspace));
795 }
796 if (ocad_symbol->indent1 != 0 || ocad_symbol->indent2 != 0)
797 {
798 addWarning(tr("During import of text symbol %1: ignoring custom indents (%2/%3)")
799 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->indent1).arg(ocad_symbol->indent2));
800 }
801
802 if (ocad_symbol->fmode > 0)
803 {
804 symbol->framing = true;
805 symbol->framing_color = convertColor(ocad_symbol->fcolor);
806 if (ocad_symbol->fmode == 1)
807 {
808 symbol->framing_mode = TextSymbol::ShadowFraming;
809 symbol->framing_shadow_x_offset = convertSize(ocad_symbol->fdx);
810 symbol->framing_shadow_y_offset = -1 * convertSize(ocad_symbol->fdy);
811 }
812 else if (ocad_symbol->fmode == 2)
813 {
814 symbol->framing_mode = TextSymbol::LineFraming;
815 symbol->framing_line_half_width = convertSize(ocad_symbol->fdpts);
816 }
817 else
818 {
819 addWarning(tr("During import of text symbol %1: ignoring text framing (mode %2)")
820 .arg(0.1 * ocad_symbol->number).arg(ocad_symbol->fmode));
821 }
822 }
823
824 symbol->updateQFont();
825
826 // Convert line spacing
827 double absolute_line_spacing = d_font_size * 0.01 * ocad_symbol->lspace;
828 symbol->line_spacing = absolute_line_spacing / (symbol->getFontMetrics().lineSpacing() / symbol->calculateInternalScaling());
829
830 return symbol;
831 }
importRectSymbol(const OCADRectSymbol * ocad_symbol)832 OCAD8FileImport::RectangleInfo* OCAD8FileImport::importRectSymbol(const OCADRectSymbol* ocad_symbol)
833 {
834 RectangleInfo rect;
835 rect.border_line = new LineSymbol();
836 fillCommonSymbolFields(rect.border_line, (OCADSymbol *)ocad_symbol);
837 rect.border_line->line_width = convertSize(ocad_symbol->width);
838 rect.border_line->color = convertColor(ocad_symbol->color);
839 rect.border_line->cap_style = LineSymbol::FlatCap;
840 rect.border_line->join_style = LineSymbol::RoundJoin;
841 rect.corner_radius = 0.001 * convertSize(ocad_symbol->corner);
842 rect.has_grid = ocad_symbol->flags & 1;
843
844 if (rect.has_grid)
845 {
846 rect.inner_line = new LineSymbol();
847 fillCommonSymbolFields(rect.inner_line, (OCADSymbol *)ocad_symbol);
848 rect.inner_line->setNumberComponent(2, 1);
849 rect.inner_line->line_width = qRound(1000 * 0.15);
850 rect.inner_line->color = rect.border_line->color;
851
852 rect.text = new TextSymbol();
853 fillCommonSymbolFields(rect.text, (OCADSymbol *)ocad_symbol);
854 rect.text->setNumberComponent(2, 2);
855 rect.text->font_family = QString::fromLatin1("Arial");
856 rect.text->font_size = qRound(1000 * (15 / 72.0 * 25.4));
857 rect.text->color = rect.border_line->color;
858 rect.text->bold = true;
859 rect.text->updateQFont();
860
861 rect.number_from_bottom = ocad_symbol->flags & 2;
862 rect.cell_width = 0.001 * convertSize(ocad_symbol->cwidth);
863 rect.cell_height = 0.001 * convertSize(ocad_symbol->cheight);
864 rect.unnumbered_cells = ocad_symbol->gcells;
865 rect.unnumbered_text = convertPascalString(ocad_symbol->gtext);
866 }
867
868 return &rectangle_info.insert(ocad_symbol->number, rect).value();
869 }
870
importPattern(s16 npts,OCADPoint * pts)871 PointSymbol *OCAD8FileImport::importPattern(s16 npts, OCADPoint *pts)
872 {
873 PointSymbol *symbol = new PointSymbol();
874 symbol->setRotatable(true);
875 OCADPoint *p = pts, *end = pts + npts;
876 while (p < end) {
877 OCADSymbolElement *elt = (OCADSymbolElement *)p;
878 int element_index = symbol->getNumElements();
879 bool multiple_elements = p + (2 + elt->npts) < end || p > pts;
880 if (elt->type == OCAD_DOT_ELEMENT)
881 {
882 int inner_radius = (int)convertSize(elt->diameter) / 2;
883 if (inner_radius > 0)
884 {
885 PointSymbol* element_symbol = multiple_elements ? (new PointSymbol()) : symbol;
886 element_symbol->inner_color = convertColor(elt->color);
887 element_symbol->inner_radius = inner_radius;
888 element_symbol->outer_color = nullptr;
889 element_symbol->outer_width = 0;
890 if (multiple_elements)
891 {
892 element_symbol->setRotatable(false);
893 PointObject* element_object = new PointObject(element_symbol);
894 element_object->coords.resize(1);
895 symbol->addElement(element_index, element_object, element_symbol);
896 }
897 }
898 }
899 else if (elt->type == OCAD_CIRCLE_ELEMENT)
900 {
901 int inner_radius = (int)convertSize(elt->diameter) / 2 - (int)convertSize(elt->width);
902 int outer_width = (int)convertSize(elt->width);
903 if (outer_width > 0 && inner_radius > 0)
904 {
905 PointSymbol* element_symbol = (multiple_elements) ? (new PointSymbol()) : symbol;
906 element_symbol->inner_color = nullptr;
907 element_symbol->inner_radius = inner_radius;
908 element_symbol->outer_color = convertColor(elt->color);
909 element_symbol->outer_width = outer_width;
910 if (multiple_elements)
911 {
912 element_symbol->setRotatable(false);
913 PointObject* element_object = new PointObject(element_symbol);
914 element_object->coords.resize(1);
915 symbol->addElement(element_index, element_object, element_symbol);
916 }
917 }
918 }
919 else if (elt->type == OCAD_LINE_ELEMENT)
920 {
921 LineSymbol* element_symbol = new LineSymbol();
922 element_symbol->line_width = convertSize(elt->width);
923 element_symbol->color = convertColor(elt->color);
924 PathObject* element_object = new PathObject(element_symbol);
925 fillPathCoords(element_object, false, elt->npts, elt->pts);
926 element_object->recalculateParts();
927 symbol->addElement(element_index, element_object, element_symbol);
928 }
929 else if (elt->type == OCAD_AREA_ELEMENT)
930 {
931 AreaSymbol* element_symbol = new AreaSymbol();
932 element_symbol->color = convertColor(elt->color);
933 PathObject* element_object = new PathObject(element_symbol);
934 fillPathCoords(element_object, true, elt->npts, elt->pts);
935 element_object->recalculateParts();
936 symbol->addElement(element_index, element_object, element_symbol);
937 }
938 p += (2 + elt->npts);
939 }
940 return symbol;
941 }
942
943
fillCommonSymbolFields(Symbol * symbol,const OCADSymbol * ocad_symbol)944 void OCAD8FileImport::fillCommonSymbolFields(Symbol *symbol, const OCADSymbol *ocad_symbol)
945 {
946 // common fields are name, number, description, helper_symbol, hidden/protected status
947 symbol->setName(convertPascalString(ocad_symbol->name));
948 symbol->setNumberComponent(0, ocad_symbol->number / 10);
949 symbol->setNumberComponent(1, ocad_symbol->number % 10);
950 symbol->setNumberComponent(2, -1);
951 symbol->setIsHelperSymbol(false); // no such thing in OCAD
952 if (ocad_symbol->status & 1)
953 symbol->setProtected(true);
954 if (ocad_symbol->status & 2)
955 symbol->setHidden(true);
956 }
957
importObject(const OCADObject * ocad_object,MapPart * part)958 Object *OCAD8FileImport::importObject(const OCADObject* ocad_object, MapPart* part)
959 {
960 Symbol* symbol;
961 if (!symbol_index.contains(ocad_object->symbol))
962 {
963 if (!rectangle_info.contains(ocad_object->symbol))
964 {
965 if (ocad_object->type == 1)
966 symbol = map->getUndefinedPoint();
967 else if (ocad_object->type == 2 || ocad_object->type == 3)
968 symbol = map->getUndefinedLine();
969 else if (ocad_object->type == 4 || ocad_object->type == 5)
970 symbol = map->getUndefinedText();
971 else
972 {
973 addWarning(tr("Unable to load object"));
974 return nullptr;
975 }
976 }
977 else
978 {
979 if (!importRectangleObject(ocad_object, part, rectangle_info[ocad_object->symbol]))
980 addWarning(tr("Unable to import rectangle object"));
981 return nullptr;
982 }
983 }
984 else
985 symbol = symbol_index[ocad_object->symbol];
986
987 if (symbol->getType() == Symbol::Point)
988 {
989 PointObject *p = new PointObject();
990 p->symbol = symbol;
991
992 // extra properties: rotation
993 PointSymbol* point_symbol = reinterpret_cast<PointSymbol*>(symbol);
994 if (point_symbol->isRotatable())
995 p->setRotation(convertRotation(ocad_object->angle));
996 else if (ocad_object->angle != 0)
997 {
998 if (!point_symbol->isSymmetrical())
999 {
1000 point_symbol->setRotatable(true);
1001 p->setRotation(convertRotation(ocad_object->angle));
1002 }
1003 }
1004
1005 // only 1 coordinate is allowed, enforce it even if the OCAD object claims more.
1006 fillPathCoords(p, false, 1, ocad_object->pts);
1007 p->setMap(map);
1008 return p;
1009 }
1010 else if (symbol->getType() == Symbol::Text)
1011 {
1012 TextObject *t = new TextObject(symbol);
1013
1014 // extra properties: rotation, horizontalAlignment, verticalAlignment, text
1015 t->setRotation(convertRotation(ocad_object->angle));
1016 t->setHorizontalAlignment((TextObject::HorizontalAlignment)text_halign_map.value(symbol));
1017 t->setVerticalAlignment(TextObject::AlignBaseline);
1018
1019 const char *text_ptr = (const char *)(ocad_object->pts + ocad_object->npts);
1020 std::size_t text_len = sizeof(OCADPoint) * ocad_object->ntext;
1021 if (ocad_object->unicode) t->setText(convertWideCString(text_ptr, text_len, true));
1022 else t->setText(convertCString(text_ptr, text_len, true));
1023
1024 // Text objects need special path translation
1025 if (!fillTextPathCoords(t, reinterpret_cast<TextSymbol*>(symbol), ocad_object->npts, (OCADPoint *)ocad_object->pts))
1026 {
1027 addWarning(tr("Not importing text symbol, couldn't figure out path' (npts=%1): %2")
1028 .arg(ocad_object->npts).arg(t->getText()));
1029 delete t;
1030 return nullptr;
1031 }
1032 t->setMap(map);
1033 return t;
1034 }
1035 else if (symbol->getType() == Symbol::Line || symbol->getType() == Symbol::Area || symbol->getType() == Symbol::Combined) {
1036 PathObject *p = new PathObject(symbol);
1037
1038 p->setPatternRotation(convertRotation(ocad_object->angle));
1039
1040 // Normal path
1041 fillPathCoords(p, symbol->getType() == Symbol::Area, ocad_object->npts, ocad_object->pts);
1042 p->recalculateParts();
1043 p->setMap(map);
1044 return p;
1045 }
1046
1047 return nullptr;
1048 }
1049
importRectangleObject(const OCADObject * ocad_object,MapPart * part,const OCAD8FileImport::RectangleInfo & rect)1050 bool OCAD8FileImport::importRectangleObject(const OCADObject* ocad_object, MapPart* part, const OCAD8FileImport::RectangleInfo& rect)
1051 {
1052 if (ocad_object->npts != 4)
1053 return false;
1054
1055 // Convert corner points
1056 #ifdef Q_CC_CLANG
1057 #pragma clang diagnostic push
1058 #pragma clang diagnostic ignored "-Warray-bounds"
1059 #endif
1060 s32 buf[3];
1061 ocad_point(buf, &(ocad_object->pts[3]));
1062 MapCoord top_left;
1063 convertPoint(top_left, buf[0], buf[1]);
1064 ocad_point(buf, &(ocad_object->pts[0]));
1065 MapCoord bottom_left;
1066 convertPoint(bottom_left, buf[0], buf[1]);
1067 ocad_point(buf, &(ocad_object->pts[2]));
1068 MapCoord top_right;
1069 convertPoint(top_right, buf[0], buf[1]);
1070 ocad_point(buf, &(ocad_object->pts[1]));
1071 MapCoord bottom_right;
1072 convertPoint(bottom_right, buf[0], buf[1]);
1073 #ifdef Q_CC_CLANG
1074 #pragma clang diagnostic pop
1075 #endif
1076
1077 MapCoordF top_left_f = MapCoordF(top_left);
1078 MapCoordF top_right_f = MapCoordF(top_right);
1079 MapCoordF bottom_left_f = MapCoordF(bottom_left);
1080 MapCoordF bottom_right_f = MapCoordF(bottom_right);
1081 MapCoordF right = MapCoordF(top_right.x() - top_left.x(), top_right.y() - top_left.y());
1082 double angle = right.angle();
1083 MapCoordF down = MapCoordF(bottom_left.x() - top_left.x(), bottom_left.y() - top_left.y());
1084 right.normalize();
1085 down.normalize();
1086
1087 // Create border line
1088 MapCoordVector coords;
1089 if (rect.corner_radius == 0)
1090 {
1091 coords.emplace_back(top_left);
1092 coords.emplace_back(top_right);
1093 coords.emplace_back(bottom_right);
1094 coords.emplace_back(bottom_left);
1095 }
1096 else
1097 {
1098 double handle_radius = (1 - BEZIER_KAPPA) * rect.corner_radius;
1099 coords.emplace_back(top_right_f - right * rect.corner_radius, MapCoord::CurveStart);
1100 coords.emplace_back(top_right_f - right * handle_radius);
1101 coords.emplace_back(top_right_f + down * handle_radius);
1102 coords.emplace_back(top_right_f + down * rect.corner_radius);
1103 coords.emplace_back(bottom_right_f - down * rect.corner_radius, MapCoord::CurveStart);
1104 coords.emplace_back(bottom_right_f - down * handle_radius);
1105 coords.emplace_back(bottom_right_f - right * handle_radius);
1106 coords.emplace_back(bottom_right_f - right * rect.corner_radius);
1107 coords.emplace_back(bottom_left_f + right * rect.corner_radius, MapCoord::CurveStart);
1108 coords.emplace_back(bottom_left_f + right * handle_radius);
1109 coords.emplace_back(bottom_left_f - down * handle_radius);
1110 coords.emplace_back(bottom_left_f - down * rect.corner_radius);
1111 coords.emplace_back(top_left_f + down * rect.corner_radius, MapCoord::CurveStart);
1112 coords.emplace_back(top_left_f + down * handle_radius);
1113 coords.emplace_back(top_left_f + right * handle_radius);
1114 coords.emplace_back(top_left_f + right * rect.corner_radius);
1115 }
1116 PathObject *border_path = new PathObject(rect.border_line, coords, map);
1117 border_path->parts().front().setClosed(true, false);
1118 part->objects.push_back(border_path);
1119
1120 if (rect.has_grid && rect.cell_width > 0 && rect.cell_height > 0)
1121 {
1122 // Calculate grid sizes
1123 double width = top_left.distanceTo(top_right);
1124 double height = top_left.distanceTo(bottom_left);
1125 int num_cells_x = qMax(1, qRound(width / rect.cell_width));
1126 int num_cells_y = qMax(1, qRound(height / rect.cell_height));
1127
1128 float cell_width = width / num_cells_x;
1129 float cell_height = height / num_cells_y;
1130
1131 // Create grid lines
1132 coords.resize(2);
1133 for (int x = 1; x < num_cells_x; ++x)
1134 {
1135 coords[0] = MapCoord(top_left_f + x * cell_width * right);
1136 coords[1] = MapCoord(bottom_left_f + x * cell_width * right);
1137
1138 PathObject *path = new PathObject(rect.inner_line, coords, map);
1139 part->objects.push_back(path);
1140 }
1141 for (int y = 1; y < num_cells_y; ++y)
1142 {
1143 coords[0] = MapCoord(top_left_f + y * cell_height * down);
1144 coords[1] = MapCoord(top_right_f + y * cell_height * down);
1145
1146 PathObject *path = new PathObject(rect.inner_line, coords, map);
1147 part->objects.push_back(path);
1148 }
1149
1150 // Create grid text
1151 if (height >= rect.cell_height / 2)
1152 {
1153 for (int y = 0; y < num_cells_y; ++y)
1154 {
1155 for (int x = 0; x < num_cells_x; ++x)
1156 {
1157 int cell_num;
1158 QString cell_text;
1159
1160 if (rect.number_from_bottom)
1161 cell_num = y * num_cells_x + x + 1;
1162 else
1163 cell_num = (num_cells_y - 1 - y) * num_cells_x + x + 1;
1164
1165 if (cell_num > num_cells_x * num_cells_y - rect.unnumbered_cells)
1166 cell_text = rect.unnumbered_text;
1167 else
1168 cell_text = QString::number(cell_num);
1169
1170 TextObject* object = new TextObject(rect.text);
1171 object->setMap(map);
1172 object->setText(cell_text);
1173 object->setRotation(-angle);
1174 object->setHorizontalAlignment(TextObject::AlignLeft);
1175 object->setVerticalAlignment(TextObject::AlignTop);
1176 double position_x = (x + 0.07f) * cell_width;
1177 double position_y = (y + 0.04f) * cell_height + rect.text->getFontMetrics().ascent() / rect.text->calculateInternalScaling() - rect.text->getFontSize();
1178 object->setAnchorPosition(top_left_f + position_x * right + position_y * down);
1179 part->objects.push_back(object);
1180
1181 //pts[0].Y -= rectinfo.gridText.FontAscent - rectinfo.gridText.FontEmHeight;
1182 }
1183 }
1184 }
1185 }
1186
1187 return true;
1188 }
1189
importString(OCADStringEntry * entry)1190 void OCAD8FileImport::importString(OCADStringEntry *entry)
1191 {
1192 OCADCString *ocad_str = ocad_string(file, entry);
1193 if (entry->type == 8)
1194 {
1195 // Template
1196 importTemplate(ocad_str);
1197 }
1198
1199 // TODO: parse more types of strings, maybe the print parameters?
1200 }
1201
importTemplate(OCADCString * ocad_str)1202 Template *OCAD8FileImport::importTemplate(OCADCString* ocad_str)
1203 {
1204 Template* templ = nullptr;
1205 QByteArray data(ocad_str->str); // copies the data.
1206 QString filename = encoding_1byte->toUnicode(data.left(data.indexOf('\t', 0)));
1207 QString clean_path = QDir::cleanPath(QString(filename).replace(QLatin1Char('\\'), QLatin1Char('/')));
1208 QString extension = QFileInfo(clean_path).suffix();
1209 if (extension.compare(QLatin1String("ocd"), Qt::CaseInsensitive) == 0)
1210 {
1211 templ = new TemplateMap(clean_path, map);
1212 }
1213 else if (QImageReader::supportedImageFormats().contains(extension.toLatin1()))
1214 {
1215 templ = new TemplateImage(clean_path, map);
1216 }
1217 else
1218 {
1219 addWarning(tr("Unable to import template: background \"%1\" doesn't seem to be a raster image").arg(filename));
1220 return nullptr;
1221 }
1222
1223 OCADBackground background = importBackground(data);
1224 MapCoord c;
1225 convertPoint(c, background.trnx, background.trny);
1226 templ->setTemplatePosition(c);
1227 templ->setTemplateRotation(M_PI / 180 * background.angle);
1228 templ->setTemplateScaleX(convertTemplateScale(background.sclx));
1229 templ->setTemplateScaleY(convertTemplateScale(background.scly));
1230 templ->setTemplateShear(0.0);
1231
1232 map->templates.insert(map->templates.begin(), templ);
1233
1234 if (view)
1235 {
1236 auto opacity = qMax(0.0, qMin(1.0, 0.01 * (100 - background.dimming)));
1237 view->setTemplateVisibility(templ, { float(opacity), bool(background.s) });
1238 }
1239
1240 return templ;
1241 }
1242
1243 // A more flexible reimplementation of libocad's ocad_to_background().
importBackground(const QByteArray & data)1244 OCADBackground OCAD8FileImport::importBackground(const QByteArray& data)
1245 {
1246 unsigned int num_angles = 0;
1247 OCADBackground background;
1248 background.filename = data.data(); // tab-terminated, not 0-terminated!
1249 background.trnx = 0;
1250 background.trny = 0;
1251 background.angle = 0.0;
1252 background.sclx = 100.0;
1253 background.scly = 100.0;
1254 background.dimming = 0;
1255 background.s = 1;
1256
1257 int i = data.indexOf('\t', 0);
1258 while (i >= 0)
1259 {
1260 double value;
1261 bool ok;
1262 int next_i = data.indexOf('\t', i+1);
1263 int len = (next_i > 0 ? next_i : data.length()) - i - 2;
1264 switch (data[i+1])
1265 {
1266 case 'x':
1267 background.trnx = qRound(data.mid(i + 2, len).toDouble());
1268 break;
1269 case 'y':
1270 background.trny = qRound(data.mid(i + 2, len).toDouble());
1271 break;
1272 case 'a':
1273 case 'b':
1274 // TODO: use the distinct angles correctly, not just the average
1275 background.angle += data.mid(i + 2, len).toDouble(&ok);
1276 if (ok)
1277 ++num_angles;
1278 break;
1279 case 'u':
1280 value = data.mid(i + 2, len).toDouble(&ok);
1281 if (ok && qAbs(value) >= 0.0001)
1282 background.sclx = value;
1283 break;
1284 case 'v':
1285 value = data.mid(i + 2, len).toDouble(&ok);
1286 if (ok && qAbs(value) >= 0.0001)
1287 background.scly = value;
1288 break;
1289 case 'd':
1290 background.dimming = data.mid(i + 2, len).toInt();
1291 break;
1292 case 's':
1293 background.s = data.mid(i + 2, len).toInt();
1294 break;
1295 default:
1296 ; // nothing
1297 }
1298 i = next_i;
1299 }
1300
1301 if (num_angles)
1302 background.angle = background.angle / num_angles;
1303
1304 return background;
1305 }
1306
importRasterTemplate(const OCADBackground & background)1307 Template *OCAD8FileImport::importRasterTemplate(const OCADBackground &background)
1308 {
1309 QString filename(encoding_1byte->toUnicode(background.filename));
1310 filename = QDir::cleanPath(filename.replace(QLatin1Char('\\'), QLatin1Char('/')));
1311 if (isRasterImageFile(filename))
1312 {
1313 TemplateImage* templ = new TemplateImage(filename, map);
1314 MapCoord c;
1315 convertPoint(c, background.trnx, background.trny);
1316 templ->setTemplateX(c.nativeX());
1317 templ->setTemplateY(c.nativeY());
1318 templ->setTemplateRotation(M_PI / 180 * background.angle);
1319 templ->setTemplateScaleX(convertTemplateScale(background.sclx));
1320 templ->setTemplateScaleY(convertTemplateScale(background.scly));
1321 templ->setTemplateShear(0.0);
1322 // FIXME: import template view parameters: background.dimming and background.transparent
1323 // TODO: import template visibility
1324 return templ;
1325 }
1326 else
1327 {
1328 addWarning(tr("Unable to import template: background \"%1\" doesn't seem to be a raster image").arg(filename));
1329 }
1330 return nullptr;
1331 }
1332
setPathHolePoint(Object * object,int i)1333 void OCAD8FileImport::setPathHolePoint(Object *object, int i)
1334 {
1335 // Look for curve start points before the current point and apply hole point only if no such point is there.
1336 // This prevents hole points in the middle of a curve caused by incorrect map objects.
1337 if (i >= 1 && object->coords[i].isCurveStart())
1338 ; //object->coords[i-1].setHolePoint(true);
1339 else if (i >= 2 && object->coords[i-1].isCurveStart())
1340 ; //object->coords[i-2].setHolePoint(true);
1341 else if (i >= 3 && object->coords[i-2].isCurveStart())
1342 ; //object->coords[i-3].setHolePoint(true);
1343 else
1344 object->coords[i].setHolePoint(true);
1345 }
1346
1347 /** Translates the OCAD path given in the last two arguments into an Object.
1348 */
fillPathCoords(Object * object,bool is_area,u16 npts,const OCADPoint * pts)1349 void OCAD8FileImport::fillPathCoords(Object *object, bool is_area, u16 npts, const OCADPoint *pts)
1350 {
1351 object->coords.resize(npts);
1352 s32 buf[3];
1353 for (int i = 0; i < npts; i++)
1354 {
1355 ocad_point(buf, &(pts[i]));
1356 MapCoord &coord = object->coords[i];
1357 convertPoint(coord, buf[0], buf[1]);
1358 // We can support CurveStart, HolePoint, DashPoint.
1359 // CurveStart needs to be applied to the main point though, not the control point, and
1360 // hole points need to bet set as the last point of a part of an area object instead of the first point of the next part
1361 if (buf[2] & PX_CTL1 && i > 0)
1362 object->coords[i-1].setCurveStart(true);
1363 if ((buf[2] & (PY_DASH << 8)) || (buf[2] & (PY_CORNER << 8)))
1364 coord.setDashPoint(true);
1365 if (buf[2] & (PY_HOLE << 8))
1366 setPathHolePoint(object, is_area ? (i - 1) : i);
1367 }
1368
1369 // For path objects, create closed parts where the position of the last point is equal to that of the first point
1370 if (object->getType() == Object::Path)
1371 {
1372 int start = 0;
1373 for (int i = 0; i < (int)object->coords.size(); ++i)
1374 {
1375 if (!object->coords[i].isHolePoint() && i < (int)object->coords.size() - 1)
1376 continue;
1377
1378 if (object->coords[i].isPositionEqualTo(object->coords[start]))
1379 {
1380 MapCoord coord = object->coords[start];
1381 coord.setCurveStart(false);
1382 coord.setHolePoint(true);
1383 coord.setClosePoint(true);
1384 object->coords[i] = coord;
1385 }
1386
1387 start = i + 1;
1388 }
1389 }
1390 }
1391
1392 /** Translates an OCAD text object path into a Mapper text object specifier, if possible.
1393 * If successful, sets either 1 or 2 coordinates in the text object and returns true.
1394 * If the OCAD path was not importable, leaves the TextObject alone and returns false.
1395 */
fillTextPathCoords(TextObject * object,TextSymbol * symbol,u16 npts,OCADPoint * pts)1396 bool OCAD8FileImport::fillTextPathCoords(TextObject *object, TextSymbol *symbol, u16 npts, OCADPoint *pts)
1397 {
1398 // text objects either have 1 point (free anchor) or 2 (midpoint/size)
1399 // OCAD appears to always have 5 or 4 points (possible single anchor, then 4 corner coordinates going clockwise from anchor).
1400 if (npts == 0) return false;
1401
1402 if (npts == 4)
1403 {
1404 // Box text
1405 s32 buf[3];
1406 ocad_point(buf, &(pts[3]));
1407 MapCoord top_left;
1408 convertPoint(top_left, buf[0], buf[1]);
1409 ocad_point(buf, &(pts[0]));
1410 MapCoord bottom_left;
1411 convertPoint(bottom_left, buf[0], buf[1]);
1412 ocad_point(buf, &(pts[2]));
1413 MapCoord top_right;
1414 convertPoint(top_right, buf[0], buf[1]);
1415
1416 // According to Purple Pen source code: OCAD adds an extra internal leading (incorrectly).
1417 QFontMetricsF metrics = symbol->getFontMetrics();
1418 double top_adjust = -symbol->getFontSize() + (metrics.ascent() + metrics.descent() + 0.5) / symbol->calculateInternalScaling();
1419
1420 MapCoordF adjust_vector = MapCoordF(top_adjust * sin(object->getRotation()), top_adjust * cos(object->getRotation()));
1421 top_left = MapCoord(top_left.x() + adjust_vector.x(), top_left.y() + adjust_vector.y());
1422 top_right = MapCoord(top_right.x() + adjust_vector.x(), top_right.y() + adjust_vector.y());
1423
1424 object->setBox((bottom_left.nativeX() + top_right.nativeX()) / 2, (bottom_left.nativeY() + top_right.nativeY()) / 2,
1425 top_left.distanceTo(top_right), top_left.distanceTo(bottom_left));
1426
1427 object->setVerticalAlignment(TextObject::AlignTop);
1428 }
1429 else
1430 {
1431 // Single anchor text
1432 if (npts != 5)
1433 addWarning(tr("Trying to import a text object with unknown coordinate format"));
1434
1435 s32 buf[3];
1436 ocad_point(buf, &(pts[0])); // anchor point
1437
1438 MapCoord coord;
1439 convertPoint(coord, buf[0], buf[1]);
1440 object->setAnchorPosition(coord.nativeX(), coord.nativeY());
1441
1442 object->setVerticalAlignment(TextObject::AlignBaseline);
1443 }
1444
1445 return true;
1446 }
1447
1448 /** Converts a single-byte-per-character, length-payload string to a QString.
1449 *
1450 * The byte sequence will be: LEN C0 C1 C2 C3...
1451 *
1452 * Obviously this will only hold up to 255 characters. By default, we interpret the
1453 * bytes using Windows-1252, as that's the most likely candidate for OCAD files in
1454 * the wild.
1455 */
convertPascalString(const char * p)1456 QString OCAD8FileImport::convertPascalString(const char *p) {
1457 int len = *((unsigned char *)p);
1458 return encoding_1byte->toUnicode((p + 1), len);
1459 }
1460
1461 /** Converts a single-byte-per-character, zero-terminated string to a QString.
1462 *
1463 * The byte sequence will be: C0 C1 C2 C3 ... 00. n describes the maximum
1464 * length (in bytes) that will be scanned for a zero terminator; if none is found,
1465 * the string will be truncated at that location.
1466 */
convertCString(const char * p,std::size_t n,bool ignore_first_newline)1467 QString OCAD8FileImport::convertCString(const char *p, std::size_t n, bool ignore_first_newline) {
1468 size_t i = 0;
1469 for (; i < n; i++) {
1470 if (p[i] == 0) break;
1471 }
1472 if (ignore_first_newline && n >= 2 && p[0] == '\r' && p[1] == '\n')
1473 {
1474 // Remove "\r\n" at the beginning of texts, somehow OCAD seems to add this sometimes but ignores it
1475 p += 2;
1476 i -= 2;
1477 }
1478 return encoding_1byte->toUnicode(p, i);
1479 }
1480
1481 /** Converts a two-byte-per-character, zero-terminated string to a QString. By default,
1482 * we interpret the bytes using UTF-16LE, as that's the most likely candidate for
1483 * OCAD files in the wild.
1484 *
1485 * The byte sequence will be: L0 H0 L1 H1 L2 H2... 00 00. n describes the maximum
1486 * length (in bytes) that will be scanned for a zero terminator; if none is found,
1487 * the string will be truncated at that location.
1488 */
convertWideCString(const char * p,std::size_t n,bool ignore_first_newline)1489 QString OCAD8FileImport::convertWideCString(const char *p, std::size_t n, bool ignore_first_newline) {
1490 const u16 *q = (const u16 *)p;
1491 size_t i = 0;
1492 for (; i < n; i++) {
1493 if (q[i] == 0) break;
1494 }
1495 if (ignore_first_newline && n >= 4 && p[0] == '\r' && p[2] == '\n')
1496 {
1497 // Remove "\r\n" at the beginning of texts, somehow OCAD seems to add this sometimes but ignores it
1498 p += 4;
1499 i -= 2;
1500 }
1501 return encoding_2byte->toUnicode(p, i * 2);
1502 }
1503
convertRotation(int angle)1504 float OCAD8FileImport::convertRotation(int angle) {
1505 // OCAD uses tenths of a degree, counterclockwise
1506 // BUG: if sin(rotation) is < 0 for a hatched area pattern, the pattern's createRenderables() will go into an infinite loop.
1507 // So until that's fixed, we keep a between 0 and PI
1508 double a = (M_PI / 180) * (0.1f * angle);
1509 while (a < 0) a += 2 * M_PI;
1510 //if (a < 0 || a > M_PI) qDebug() << "Found angle" << a;
1511 return (float)a;
1512 }
1513
convertPoint(MapCoord & coord,s32 ocad_x,s32 ocad_y)1514 void OCAD8FileImport::convertPoint(MapCoord &coord, s32 ocad_x, s32 ocad_y)
1515 {
1516 // Recover from broken coordinate export from Mapper 0.6.2 ... 0.6.4 (#749)
1517 // Cf. broken::convertPointMember below:
1518 // The values -4 ... -1 (-0.004 mm ... -0.001 mm) were converted to 0x80000000u instead of 0.
1519 // This is the maximum value. Thus it is okay to assume it won't occur in regular data,
1520 // and we can safely replace it with 0 here.
1521 // But the input parameter were already subject to right shift in ocad_point ...
1522 constexpr auto invalid_value = s32(0x80000000u) >> 8; // ... so we use this value here.
1523 if (ocad_x == invalid_value)
1524 ocad_x = 0;
1525 if (ocad_y == invalid_value)
1526 ocad_y = 0;
1527
1528 // OCAD uses hundredths of a millimeter.
1529 // oo-mapper uses 1/1000 mm
1530 coord.setNativeX(offset_x + (qint32)ocad_x * 10);
1531 // Y-axis is flipped.
1532 coord.setNativeY(offset_y + (qint32)ocad_y * (-10));
1533 }
1534
convertSize(int ocad_size)1535 qint32 OCAD8FileImport::convertSize(int ocad_size) {
1536 // OCAD uses hundredths of a millimeter.
1537 // oo-mapper uses 1/1000 mm
1538 return ((qint32)ocad_size) * 10;
1539 }
1540
convertColor(int color)1541 const MapColor *OCAD8FileImport::convertColor(int color) {
1542 if (!color_index.contains(color))
1543 {
1544 addWarning(tr("Color id not found: %1, ignoring this color").arg(color));
1545 return nullptr;
1546 }
1547 else
1548 return color_index[color];
1549 }
1550
convertTemplateScale(double ocad_scale)1551 double OCAD8FileImport::convertTemplateScale(double ocad_scale)
1552 {
1553 return ocad_scale * 0.01; // millimeters(on map) per pixel
1554 }
1555
1556
1557 // ### OCAD8FileExport ###
1558
OCAD8FileExport(const QString & path,const Map * map,const MapView * view)1559 OCAD8FileExport::OCAD8FileExport(const QString& path, const Map* map, const MapView* view)
1560 : Exporter(path, map, view),
1561 uses_registration_color(false),
1562 file(nullptr)
1563 {
1564 ocad_init();
1565 encoding_1byte = QTextCodec::codecForName("Windows-1252");
1566 encoding_2byte = QTextCodec::codecForName("UTF-16LE");
1567
1568 origin_point_object = new PointObject();
1569 }
1570
~OCAD8FileExport()1571 OCAD8FileExport::~OCAD8FileExport()
1572 {
1573 delete origin_point_object;
1574 }
1575
exportImplementation()1576 bool OCAD8FileExport::exportImplementation()
1577 {
1578 uses_registration_color = map->isColorUsedByASymbol(map->getRegistrationColor());
1579 if (map->getNumColors() > (uses_registration_color ? 255 : 256))
1580 throw FileFormatException(tr("The map contains more than 256 colors which is not supported by ocd version 8."));
1581
1582 // Create struct in memory
1583 int err = ocad_file_new(&file);
1584 if (err != 0) throw FileFormatException(tr("libocad returned %1").arg(err));
1585
1586 // Check for a necessary offset (and add related warnings early).
1587 auto area_offset = calculateAreaOffset();
1588
1589 // Fill header struct
1590 OCADFileHeader* header = file->header;
1591 *(((u8*)&header->magic) + 0) = 0xAD;
1592 *(((u8*)&header->magic) + 1) = 0x0C;
1593 header->ftype = 2;
1594 header->major = 8;
1595 header->minor = 0;
1596 if (map->getMapNotes().size() > 0)
1597 {
1598 header->infosize = map->getMapNotes().length() + 1;
1599 ocad_file_reserve(file, header->infosize);
1600 header->infopos = &file->buffer[file->size] - file->buffer;
1601 convertCString(map->getMapNotes(), &file->buffer[file->size], header->infosize);
1602 file->size += header->infosize;
1603 }
1604
1605 // Fill setup struct
1606 OCADSetup* setup = file->setup;
1607 if (view)
1608 {
1609 setup->center = convertPoint(view->center() - area_offset);
1610 setup->zoom = view->getZoom();
1611 }
1612 else
1613 setup->zoom = 1;
1614
1615 // Scale and georeferencing parameters
1616 const Georeferencing& georef = map->getGeoreferencing();
1617 setup->scale = georef.getScaleDenominator();
1618 const QPointF offset(georef.toProjectedCoords(area_offset));
1619 setup->offsetx = offset.x();
1620 setup->offsety = offset.y();
1621 setup->angle = georef.getGrivation();
1622
1623 // TODO: print parameters
1624
1625 // Colors
1626 int ocad_color_index = 0;
1627 if (uses_registration_color)
1628 {
1629 addWarning(tr("Registration black is exported as a regular color."));
1630
1631 ++file->header->ncolors;
1632 OCADColor *ocad_color = ocad_color_at(file, ocad_color_index);
1633 ocad_color->number = ocad_color_index;
1634
1635 const MapColor* color = Map::getRegistrationColor();
1636 const MapColorCmyk& cmyk = color->getCmyk();
1637 ocad_color->cyan = qRound(1 / 0.005f * cmyk.c);
1638 ocad_color->magenta = qRound(1 / 0.005f * cmyk.m);
1639 ocad_color->yellow = qRound(1 / 0.005f * cmyk.y);
1640 ocad_color->black = qRound(1 / 0.005f * cmyk.k);
1641 convertPascalString(QString::fromLatin1("Registration black"), ocad_color->name, 32); // not translated
1642
1643 ++ocad_color_index;
1644 }
1645 for (int i = 0; i < map->getNumColors(); i++)
1646 {
1647 ++file->header->ncolors;
1648 OCADColor *ocad_color = ocad_color_at(file, ocad_color_index);
1649 ocad_color->number = ocad_color_index;
1650
1651 const MapColor* color = map->getColor(i);
1652 const MapColorCmyk& cmyk = color->getCmyk();
1653 ocad_color->cyan = qRound(1 / 0.005f * cmyk.c);
1654 ocad_color->magenta = qRound(1 / 0.005f * cmyk.m);
1655 ocad_color->yellow = qRound(1 / 0.005f * cmyk.y);
1656 ocad_color->black = qRound(1 / 0.005f * cmyk.k);
1657 convertPascalString(color->getName(), ocad_color->name, 32);
1658
1659 ++ocad_color_index;
1660 }
1661
1662 // Symbols
1663 for (int i = 0; i < map->getNumSymbols(); ++i)
1664 {
1665 const Symbol* symbol = map->getSymbol(i);
1666
1667 s16 index = -1;
1668 if (symbol->getType() == Symbol::Point)
1669 index = exportPointSymbol(symbol->asPoint());
1670 else if (symbol->getType() == Symbol::Line)
1671 index = exportLineSymbol(symbol->asLine());
1672 else if (symbol->getType() == Symbol::Area)
1673 index = exportAreaSymbol(symbol->asArea());
1674 else if (symbol->getType() == Symbol::Text)
1675 index = exportTextSymbol(symbol->asText());
1676 else if (symbol->getType() == Symbol::Combined)
1677 ; // This is done as a second pass to ensure that all dependencies are added to the symbol_index
1678 else
1679 Q_ASSERT(false);
1680
1681 if (index >= 0)
1682 {
1683 std::set<s16> number;
1684 number.insert(index);
1685 symbol_index.insert(symbol, number);
1686 }
1687 }
1688
1689 // Separate pass for combined symbols
1690 for (int i = 0; i < map->getNumSymbols(); ++i)
1691 {
1692 const Symbol* symbol = map->getSymbol(i);
1693 if (symbol->getType() == Symbol::Combined)
1694 symbol_index.insert(symbol, exportCombinedSymbol(static_cast<const CombinedSymbol*>(symbol)));
1695 }
1696
1697 // Objects
1698 OCADObject* ocad_object = ocad_object_alloc(nullptr);
1699 for (int l = 0; l < map->getNumParts(); ++l)
1700 {
1701 for (int o = 0; o < map->getPart(l)->getNumObjects(); ++o)
1702 {
1703 memset(ocad_object, 0, sizeof(OCADObject) - sizeof(OCADPoint) + 8 * (ocad_object->npts + ocad_object->ntext));
1704 Object* object = map->getPart(l)->getObject(o);
1705 std::unique_ptr<Object> duplicate;
1706 if (area_offset.nativeX() != 0 || area_offset.nativeY() != 0)
1707 {
1708 // Create a safely managed duplicate and move it as needed.
1709 duplicate.reset(object->duplicate());
1710 duplicate->move(-area_offset);
1711 object = duplicate.get();
1712 }
1713 object->update();
1714
1715 // Fill some common entries of object struct
1716 OCADPoint* coord_buffer = ocad_object->pts;
1717 if (object->getType() != Object::Text)
1718 ocad_object->npts = exportCoordinates(object->getRawCoordinateVector(), &coord_buffer, object->getSymbol());
1719 else
1720 ocad_object->npts = exportTextCoordinates(static_cast<TextObject*>(object), &coord_buffer);
1721
1722 if (object->getType() == Object::Point)
1723 {
1724 PointObject* point = static_cast<PointObject*>(object);
1725 ocad_object->angle = convertRotation(point->getRotation());
1726 }
1727 else if (object->getType() == Object::Path)
1728 {
1729 if (object->getSymbol()->getType() == Symbol::Area)
1730 {
1731 PathObject* path = static_cast<PathObject*>(object);
1732 // Known issue: In OCD format, pattern rotatability is all
1733 // or nothing. In Mapper, it is an option per pattern.
1734 if (path->getSymbol()->asArea()->hasRotatableFillPattern())
1735 ocad_object->angle = convertRotation(path->getPatternRotation());
1736 if (path->getPatternOrigin() != MapCoord(0, 0))
1737 addWarning(tr("Unable to export fill pattern shift for an area object"));
1738 }
1739 }
1740 else if (object->getType() == Object::Text)
1741 {
1742 TextObject* text = static_cast<TextObject*>(object);
1743 ocad_object->unicode = 1;
1744 ocad_object->angle = convertRotation(text->getRotation());
1745 int num_letters = convertWideCString(text->getText(), (unsigned char*)coord_buffer, 8 * (OCAD_MAX_OBJECT_PTS - ocad_object->npts));
1746 ocad_object->ntext = qCeil(num_letters / 4.0f);
1747 }
1748
1749 // Insert an object into the map for every symbol contained in the symbol_index
1750 std::set<s16> index_set;
1751 if (symbol_index.contains(object->getSymbol()))
1752 index_set = symbol_index[object->getSymbol()];
1753 else
1754 index_set.insert(-1); // export as undefined symbol
1755
1756 for (const auto index : index_set)
1757 {
1758 s16 index_to_use = index;
1759
1760 // For text objects, check if we have to change / create a new text symbol because of the formatting
1761 if (object->getType() == Object::Text && symbol_index.contains(object->getSymbol()))
1762 {
1763 TextObject* text_object = static_cast<TextObject*>(object);
1764 const TextSymbol* text_symbol = static_cast<const TextSymbol*>(object->getSymbol());
1765 if (!text_format_map.contains(text_symbol))
1766 {
1767 // Adjust the formatting in the first created symbol to this object
1768 OCADTextSymbol* ocad_text_symbol = (OCADTextSymbol*)ocad_symbol(file, index);
1769 setTextSymbolFormatting(ocad_text_symbol, text_object);
1770
1771 TextFormatList new_list;
1772 new_list.push_back(std::make_pair(text_object->getHorizontalAlignment(), index));
1773 text_format_map.insert(text_symbol, new_list);
1774 }
1775 else
1776 {
1777 // Check if this formatting has already been created as symbol.
1778 // If yes, use this symbol, else create a new symbol
1779 TextFormatList& format_list = text_format_map[text_symbol];
1780 bool found = false;
1781 for (size_t i = 0, end = format_list.size(); i < end; ++i)
1782 {
1783 if (format_list[i].first == text_object->getHorizontalAlignment())
1784 {
1785 index_to_use = format_list[i].second;
1786 found = true;
1787 break;
1788 }
1789 }
1790 if (!found)
1791 {
1792 // Copy the symbol and adjust the formatting
1793 // TODO: insert these symbols directly after the original symbols
1794 OCADTextSymbol* ocad_text_symbol = (OCADTextSymbol*)ocad_symbol(file, index);
1795 OCADTextSymbol* new_symbol = (OCADTextSymbol*)ocad_symbol_new(file, ocad_text_symbol->size);
1796 // Get the pointer to the first symbol again as it might have changed during ocad_symbol_new()
1797 ocad_text_symbol = (OCADTextSymbol*)ocad_symbol(file, index);
1798
1799 memcpy(new_symbol, ocad_text_symbol, ocad_text_symbol->size);
1800 setTextSymbolFormatting(new_symbol, text_object);
1801
1802 // Give the new symbol a unique number
1803 while (symbol_numbers.find(new_symbol->number) != symbol_numbers.end())
1804 ++new_symbol->number;
1805 symbol_numbers.insert(new_symbol->number);
1806 index_to_use = new_symbol->number;
1807
1808 // Store packed new_symbol->number in separate variable first,
1809 // otherwise when compiling for Android this causes the error:
1810 // cannot bind packed field 'new_symbol->_OCADTextSymbol::number' to 'short int&'
1811 s16 new_symbol_number = new_symbol->number;
1812 format_list.push_back(std::make_pair(text_object->getHorizontalAlignment(), new_symbol_number));
1813 }
1814 }
1815 }
1816
1817 ocad_object->symbol = index_to_use;
1818 if (object->getType() == Object::Point)
1819 ocad_object->type = 1;
1820 else if (object->getType() == Object::Path)
1821 {
1822 OCADSymbol* ocad_sym = ocad_symbol(file, index_to_use);
1823 if (!ocad_sym)
1824 ocad_object->type = 2; // This case is for undefined lines; TODO: make another case for undefined areas, as soon as they are implemented
1825 else if (ocad_sym->type == 2)
1826 ocad_object->type = 2; // Line
1827 else //if (ocad_symbol->type == 3)
1828 ocad_object->type = 3; // Area
1829 }
1830 else if (object->getType() == Object::Text)
1831 {
1832 TextObject* text_object = static_cast<TextObject*>(object);
1833 if (text_object->hasSingleAnchor())
1834 ocad_object->type = 4;
1835 else
1836 ocad_object->type = 5;
1837 }
1838
1839 OCADObjectEntry* entry;
1840 ocad_object_add(file, ocad_object, &entry);
1841 // This is done internally by libocad (in a slightly more imprecise way using the extent specified in the symbol)
1842 //entry->rect.min = convertPoint(MapCoord(object->getExtent().topLeft()));
1843 //entry->rect.max = convertPoint(MapCoord(object->getExtent().bottomRight()));
1844 entry->npts = ocad_object->npts + ocad_object->ntext;
1845 //entry->symbol = index_to_use;
1846 }
1847 }
1848 }
1849
1850 // Templates
1851 for (int i = map->getNumTemplates() - 1; i >= 0; --i)
1852 {
1853 const Template* temp = map->getTemplate(i);
1854
1855 QString template_path = temp->getTemplatePath();
1856
1857 auto supported_by_ocd = false;
1858 if (qstrcmp(temp->getTemplateType(), "TemplateImage") == 0)
1859 {
1860 supported_by_ocd = true;
1861
1862 if (temp->isTemplateGeoreferenced())
1863 {
1864 if (temp->getTemplateState() == Template::Unloaded)
1865 {
1866 // Try to load the template, so that the positioning gets set.
1867 const_cast<Template*>(temp)->loadTemplateFile(false);
1868 }
1869
1870 if (temp->getTemplateState() != Template::Loaded)
1871 {
1872 addWarning(tr("Unable to save correct position of missing template: \"%1\"")
1873 .arg(temp->getTemplateFilename()));
1874 }
1875 }
1876 }
1877 else if (QFileInfo(template_path).suffix().compare(QLatin1String("ocd"), Qt::CaseInsensitive) == 0)
1878 {
1879 supported_by_ocd = true;
1880 }
1881
1882 if (supported_by_ocd)
1883 {
1884 // FIXME: export template view parameters
1885
1886 double a = temp->getTemplateRotation() * 180 / M_PI;
1887 int d = 0;
1888 int o = 0;
1889 int p = 0;
1890 int s = 1; // enabled
1891 int t = 0;
1892 OCADPoint pos = convertPoint(temp->getTemplateX()-area_offset.nativeX(), temp->getTemplateY()-area_offset.nativeY());
1893 int x = pos.x >> 8;
1894 int y = pos.y >> 8;
1895 double u = convertTemplateScale(temp->getTemplateScaleX());
1896 double v = convertTemplateScale(temp->getTemplateScaleY());
1897
1898 template_path.replace(QLatin1Char('/'), QLatin1Char('\\'));
1899
1900 QString string;
1901 string.sprintf("\ts%d\tx%d\ty%d\ta%f\tu%f\tv%f\td%d\tp%d\tt%d\to%d",
1902 s, x, y, a, u, v, d, p, t, o
1903 );
1904 string.prepend(template_path);
1905
1906 OCADStringEntry* entry = ocad_string_entry_new(file, string.length() + 1);
1907 entry->type = 8;
1908 convertCString(string, file->buffer + entry->ptr, entry->size);
1909 }
1910 else
1911 {
1912 addWarning(tr("Unable to export template: file type of \"%1\" is not supported yet").arg(temp->getTemplateFilename()));
1913 }
1914 }
1915
1916 device()->write((char*)file->buffer, file->size);
1917
1918 ocad_file_close(file);
1919 return true;
1920 }
1921
1922
calculateAreaOffset()1923 MapCoord OCAD8FileExport::calculateAreaOffset()
1924 {
1925 auto area_offset = QPointF{};
1926
1927 // Attention: When changing ocd_bounds, update the warning messages, too.
1928 auto ocd_bounds = QRectF{QPointF{-2000, -2000}, QPointF{2000, 2000}};
1929 auto objects_extent = map->calculateExtent();
1930 if (!ocd_bounds.contains(objects_extent))
1931 {
1932 if (objects_extent.width() < ocd_bounds.width()
1933 && objects_extent.height() < ocd_bounds.height())
1934 {
1935 // The extent fits into the limited area.
1936 addWarning(tr("Coordinates are adjusted to fit into the OCAD 8 drawing area (-2 m ... 2 m)."));
1937 area_offset = objects_extent.center();
1938 }
1939 else
1940 {
1941 // The extent is too wide to fit.
1942
1943 // Only move the objects if they are completely outside the drawing area.
1944 // This avoids repeated moves on open/save/close cycles.
1945 if (!objects_extent.intersects(ocd_bounds))
1946 {
1947 addWarning(tr("Coordinates are adjusted to fit into the OCAD 8 drawing area (-2 m ... 2 m)."));
1948 std::size_t count = 0;
1949 auto calculate_average_center = [&area_offset, &count](const Object* object)
1950 {
1951 area_offset *= qreal(count)/qreal(count+1);
1952 ++count;
1953 area_offset += object->getExtent().center() / count;
1954 };
1955 map->applyOnAllObjects(calculate_average_center);
1956 }
1957
1958 addWarning(tr("Some coordinates remain outside of the OCAD 8 drawing area."
1959 " They might be unreachable in OCAD."));
1960 }
1961
1962 if (area_offset.manhattanLength() > 0)
1963 {
1964 // Round offset to 100 m in projected coordinates, to avoid crude grid offset.
1965 constexpr auto unit = 100;
1966 auto projected_offset = map->getGeoreferencing().toProjectedCoords(MapCoordF(area_offset));
1967 projected_offset.rx() = qreal(qRound(projected_offset.x()/unit)) * unit;
1968 projected_offset.ry() = qreal(qRound(projected_offset.y()/unit)) * unit;
1969 area_offset = map->getGeoreferencing().toMapCoordF(projected_offset);
1970 }
1971 }
1972
1973 return MapCoord{area_offset};
1974 }
1975
1976
exportCommonSymbolFields(const Symbol * symbol,OCADSymbol * ocad_symbol,int size)1977 void OCAD8FileExport::exportCommonSymbolFields(const Symbol* symbol, OCADSymbol* ocad_symbol, int size)
1978 {
1979 ocad_symbol->size = (s16)size;
1980 convertPascalString(symbol->getPlainTextName(), ocad_symbol->name, 32);
1981 ocad_symbol->number = symbol->getNumberComponent(0) * 10;
1982 if (symbol->getNumberComponent(1) >= 0)
1983 ocad_symbol->number += (symbol->getNumberComponent(1) % 10);
1984 // Symbol number 0.0 is not valid
1985 if (ocad_symbol->number == 0)
1986 ocad_symbol->number = 1;
1987 // Ensure uniqueness of the symbol number
1988 while (symbol_numbers.find(ocad_symbol->number) != symbol_numbers.end())
1989 ++ocad_symbol->number;
1990 symbol_numbers.insert(ocad_symbol->number);
1991
1992 if (symbol->isProtected())
1993 ocad_symbol->status |= 1;
1994 if (symbol->isHidden())
1995 ocad_symbol->status |= 2;
1996
1997 // Set of used colors
1998 u8 bitmask = 1;
1999 u8* bitpos = ocad_symbol->colors;
2000 for (int c = 0; c < map->getNumColors(); ++c)
2001 {
2002 if (symbol->containsColor(map->getColor(c)))
2003 *bitpos |= bitmask;
2004
2005 bitmask = bitmask << 1;
2006 if (bitmask == 0)
2007 {
2008 bitmask = 1;
2009 ++bitpos;
2010 }
2011 }
2012
2013 exportSymbolIcon(symbol, ocad_symbol->icon);
2014 }
2015
exportSymbolIcon(const Symbol * symbol,u8 ocad_icon[])2016 void OCAD8FileExport::exportSymbolIcon(const Symbol* symbol, u8 ocad_icon[])
2017 {
2018 // Icon: 22x22 with 4 bit color code, origin at bottom left
2019 constexpr int icon_size = 22;
2020 QImage image = symbol->createIcon(*map, icon_size, false)
2021 .convertToFormat(QImage::Format_ARGB32_Premultiplied);
2022
2023 auto process_pixel = [&image](int x, int y)->int
2024 {
2025 // Apply premultiplied pixel on white background
2026 auto premultiplied = image.pixel(x, y);
2027 auto alpha = qAlpha(premultiplied);
2028 auto r = 255 - alpha + qRed(premultiplied);
2029 auto g = 255 - alpha + qGreen(premultiplied);
2030 auto b = 255 - alpha + qBlue(premultiplied);
2031 auto pixel = qRgb(r, g, b);
2032
2033 // Ordered dithering 2x2 threshold matrix, adjusted for o-map halftones
2034 static int threshold[4] = { 24, 192, 136, 80 };
2035 auto palette_color = getOcadColor(pixel);
2036 switch (palette_color)
2037 {
2038 case 0:
2039 // Black to gray (50%)
2040 return qGray(pixel) < 128-threshold[(x%2 + 2*(y%2))]/2 ? 0 : 7;
2041
2042 case 7:
2043 // Gray (50%) to light gray
2044 return qGray(pixel) < 192-threshold[(x%2 + 2*(y%2))]/4 ? 7 : 8;
2045
2046 case 8:
2047 // Light gray to white
2048 return qGray(pixel) < 256-threshold[(x%2 + 2*(y%2))]/4 ? 8 : 15;
2049
2050 case 15:
2051 // Pure white
2052 return palette_color;
2053
2054 default:
2055 // Color to white
2056 return QColor(pixel).saturation() >= threshold[(x%2 + 2*(y%2))] ? palette_color : 15;
2057 }
2058 };
2059
2060 for (int y = icon_size - 1; y >= 0; --y)
2061 {
2062 for (int x = 0; x < icon_size; x += 2)
2063 {
2064 auto first = process_pixel(x, y);
2065 auto second = process_pixel(x+1, y);
2066 *(ocad_icon++) = u8((first << 4) + second);
2067 }
2068 ocad_icon++;
2069 }
2070 }
2071
getPatternSize(const PointSymbol * point)2072 int OCAD8FileExport::getPatternSize(const PointSymbol* point)
2073 {
2074 if (!point)
2075 return 0;
2076
2077 int npts = 0;
2078 for (int i = 0; i < point->getNumElements(); ++i)
2079 {
2080 int factor = 1;
2081 if (point->getElementSymbol(i)->getType() == Symbol::Point)
2082 {
2083 factor = 0;
2084 const PointSymbol* point_symbol = static_cast<const PointSymbol*>(point->getElementSymbol(i));
2085 if (point_symbol->getInnerRadius() > 0 && point_symbol->getInnerColor())
2086 ++factor;
2087 if (point_symbol->getOuterWidth() > 0 && point_symbol->getOuterColor())
2088 ++factor;
2089 }
2090 npts += factor * (2 + point->getElementObject(i)->getRawCoordinateVector().size());
2091 }
2092 if (point->getInnerRadius() > 0 && point->getInnerColor())
2093 npts += 2 + 1;
2094 if (point->getOuterWidth() > 0 && point->getOuterColor())
2095 npts += 2 + 1;
2096
2097 return npts * sizeof(OCADPoint);
2098 }
2099
exportPattern(const PointSymbol * point,OCADPoint ** buffer)2100 s16 OCAD8FileExport::exportPattern(const PointSymbol* point, OCADPoint** buffer)
2101 {
2102 if (!point)
2103 return 0;
2104
2105 s16 num_coords = exportSubPattern(origin_point_object, point, buffer);
2106 for (int i = 0; i < point->getNumElements(); ++i)
2107 {
2108 num_coords += exportSubPattern(point->getElementObject(i), point->getElementSymbol(i), buffer);
2109 }
2110 return num_coords;
2111 }
2112
exportSubPattern(const Object * object,const Symbol * symbol,OCADPoint ** buffer)2113 s16 OCAD8FileExport::exportSubPattern(const Object* object, const Symbol* symbol, OCADPoint** buffer)
2114 {
2115 s16 num_coords = 0;
2116 OCADSymbolElement* element = (OCADSymbolElement*)*buffer;
2117
2118 if (symbol->getType() == Symbol::Point)
2119 {
2120 const PointSymbol* point_symbol = static_cast<const PointSymbol*>(symbol);
2121 if (point_symbol->getInnerRadius() > 0 && point_symbol->getInnerColor())
2122 {
2123 element->type = 4;
2124 element->color = convertColor(point_symbol->getInnerColor());
2125 element->diameter = convertSize(2 * point_symbol->getInnerRadius());
2126 (*buffer) += 2;
2127 element->npts = exportCoordinates(object->getRawCoordinateVector(), buffer, point_symbol);
2128 num_coords += 2 + element->npts;
2129 }
2130 if (point_symbol->getOuterWidth() > 0 && point_symbol->getOuterColor())
2131 {
2132 element = (OCADSymbolElement*)*buffer;
2133 element->type = 3;
2134 element->color = convertColor(point_symbol->getOuterColor());
2135 element->width = convertSize(point_symbol->getOuterWidth());
2136 element->diameter = convertSize(2 * point_symbol->getInnerRadius() + 2 * point_symbol->getOuterWidth());
2137 (*buffer) += 2;
2138 element->npts = exportCoordinates(object->getRawCoordinateVector(), buffer, point_symbol);
2139 num_coords += 2 + element->npts;
2140 }
2141 }
2142 else if (symbol->getType() == Symbol::Line)
2143 {
2144 const LineSymbol* line_symbol = static_cast<const LineSymbol*>(symbol);
2145 element->type = 1;
2146 if (line_symbol->getCapStyle() == LineSymbol::RoundCap)
2147 element->flags |= 1;
2148 else if (line_symbol->getJoinStyle() == LineSymbol::MiterJoin)
2149 element->flags |= 4;
2150 element->color = convertColor(line_symbol->getColor());
2151 element->width = convertSize(line_symbol->getLineWidth());
2152 (*buffer) += 2;
2153 element->npts = exportCoordinates(object->getRawCoordinateVector(), buffer, line_symbol);
2154 num_coords += 2 + element->npts;
2155 }
2156 else if (symbol->getType() == Symbol::Area)
2157 {
2158 const AreaSymbol* area_symbol = static_cast<const AreaSymbol*>(symbol);
2159 element->type = 2;
2160 element->color = convertColor(area_symbol->getColor());
2161 (*buffer) += 2;
2162 element->npts = exportCoordinates(object->getRawCoordinateVector(), buffer, area_symbol);
2163 num_coords += 2 + element->npts;
2164 }
2165 else
2166 Q_ASSERT(false);
2167 return num_coords;
2168 }
2169
exportPointSymbol(const PointSymbol * point)2170 s16 OCAD8FileExport::exportPointSymbol(const PointSymbol* point)
2171 {
2172 int data_size = (sizeof(OCADPointSymbol) - sizeof(OCADPoint)) + getPatternSize(point);
2173 OCADPointSymbol* ocad_symbol = (OCADPointSymbol*)ocad_symbol_new(file, data_size);
2174 exportCommonSymbolFields(point, (OCADSymbol*)ocad_symbol, data_size);
2175
2176 ocad_symbol->type = OCAD_POINT_SYMBOL;
2177 ocad_symbol->extent = getPointSymbolExtent(point);
2178 if (ocad_symbol->extent <= 0)
2179 ocad_symbol->extent = 100;
2180 if (point->isRotatable())
2181 ocad_symbol->base_flags |= 1;
2182 ocad_symbol->ngrp = (data_size - (sizeof(OCADPointSymbol) - sizeof(OCADPoint))) / 8;
2183
2184 OCADPoint* pattern_buffer = ocad_symbol->pts;
2185 exportPattern(point, &pattern_buffer);
2186 Q_ASSERT((u8*)ocad_symbol + data_size == (u8*)pattern_buffer);
2187 return ocad_symbol->number;
2188 }
2189
exportLineSymbol(const LineSymbol * line)2190 s16 OCAD8FileExport::exportLineSymbol(const LineSymbol* line)
2191 {
2192 int data_size = (sizeof(OCADLineSymbol) - sizeof(OCADPoint)) +
2193 getPatternSize(line->getStartSymbol()) +
2194 getPatternSize(line->getEndSymbol()) +
2195 getPatternSize(line->getMidSymbol()) +
2196 getPatternSize(line->getDashSymbol());
2197 OCADLineSymbol* ocad_symbol = (OCADLineSymbol*)ocad_symbol_new(file, data_size);
2198 exportCommonSymbolFields(line, (OCADSymbol*)ocad_symbol, data_size);
2199
2200 // Basic settings
2201 ocad_symbol->type = OCAD_LINE_SYMBOL;
2202 s16 extent = convertSize(0.5f * line->getLineWidth());
2203 if (line->hasBorder())
2204 extent = qMax(extent, (s16)convertSize(0.5f * line->getLineWidth() + line->getBorder().shift + 0.5f * line->getBorder().width));
2205 extent = qMax(extent, getPointSymbolExtent(line->getStartSymbol()));
2206 extent = qMax(extent, getPointSymbolExtent(line->getEndSymbol()));
2207 extent = qMax(extent, getPointSymbolExtent(line->getMidSymbol()));
2208 extent = qMax(extent, getPointSymbolExtent(line->getDashSymbol()));
2209 ocad_symbol->extent = extent;
2210 ocad_symbol->color = convertColor(line->getColor());
2211 if (line->getColor())
2212 ocad_symbol->width = convertSize(line->getLineWidth());
2213
2214 // Cap and Join
2215 if (line->getCapStyle() == LineSymbol::FlatCap && line->getJoinStyle() == LineSymbol::BevelJoin)
2216 ocad_symbol->ends = 0;
2217 else if (line->getCapStyle() == LineSymbol::RoundCap && line->getJoinStyle() == LineSymbol::RoundJoin)
2218 ocad_symbol->ends = 1;
2219 else if (line->getCapStyle() == LineSymbol::PointedCap && line->getJoinStyle() == LineSymbol::BevelJoin)
2220 ocad_symbol->ends = 2;
2221 else if (line->getCapStyle() == LineSymbol::PointedCap && line->getJoinStyle() == LineSymbol::RoundJoin)
2222 ocad_symbol->ends = 3;
2223 else if (line->getCapStyle() == LineSymbol::FlatCap && line->getJoinStyle() == LineSymbol::MiterJoin)
2224 ocad_symbol->ends = 4;
2225 else if (line->getCapStyle() == LineSymbol::PointedCap && line->getJoinStyle() == LineSymbol::MiterJoin)
2226 ocad_symbol->ends = 6;
2227 else
2228 {
2229 addWarning(tr("In line symbol \"%1\", cannot represent cap/join combination.").arg(line->getPlainTextName()));
2230 // Decide based on the caps
2231 if (line->getCapStyle() == LineSymbol::FlatCap)
2232 ocad_symbol->ends = 0;
2233 else if (line->getCapStyle() == LineSymbol::RoundCap)
2234 ocad_symbol->ends = 1;
2235 else if (line->getCapStyle() == LineSymbol::PointedCap)
2236 ocad_symbol->ends = 3;
2237 else if (line->getCapStyle() == LineSymbol::SquareCap)
2238 ocad_symbol->ends = 0;
2239 }
2240
2241 ocad_symbol->bdist = convertSize(line->startOffset());
2242 ocad_symbol->edist = convertSize(line->endOffset());
2243
2244 // Dash pattern
2245 if (line->isDashed())
2246 {
2247 if (line->getMidSymbol() && !line->getMidSymbol()->isEmpty())
2248 {
2249 if (line->getDashesInGroup() > 1)
2250 addWarning(tr("In line symbol \"%1\", neglecting the dash grouping.").arg(line->getPlainTextName()));
2251
2252 ocad_symbol->len = convertSize(line->getDashLength() + line->getBreakLength());
2253 ocad_symbol->elen = ocad_symbol->len / 2;
2254 ocad_symbol->gap2 = convertSize(line->getBreakLength());
2255 }
2256 else
2257 {
2258 if (line->getDashesInGroup() > 1)
2259 {
2260 if (line->getDashesInGroup() > 2)
2261 addWarning(tr("In line symbol \"%1\", the number of dashes in a group has been reduced to 2.").arg(line->getPlainTextName()));
2262
2263 ocad_symbol->len = convertSize(2 * line->getDashLength() + line->getInGroupBreakLength());
2264 ocad_symbol->elen = convertSize(2 * line->getDashLength() + line->getInGroupBreakLength());
2265 ocad_symbol->gap = convertSize(line->getBreakLength());
2266 ocad_symbol->gap2 = convertSize(line->getInGroupBreakLength());
2267 ocad_symbol->egap = ocad_symbol->gap2;
2268 }
2269 else
2270 {
2271 ocad_symbol->len = convertSize(line->getDashLength());
2272 ocad_symbol->elen = ocad_symbol->len / (line->getHalfOuterDashes() ? 2 : 1);
2273 ocad_symbol->gap = convertSize(line->getBreakLength());
2274 }
2275 }
2276 }
2277 else
2278 {
2279 ocad_symbol->len = convertSize(line->getSegmentLength());
2280 ocad_symbol->elen = convertSize(line->getEndLength());
2281 }
2282
2283 ocad_symbol->smin = line->getShowAtLeastOneSymbol() ? 0 : -1;
2284
2285 // Double line
2286 if (line->hasBorder() && (line->getBorder().isVisible() || line->getRightBorder().isVisible()))
2287 {
2288 ocad_symbol->dwidth = convertSize(line->getLineWidth() - line->getBorder().width + 2 * line->getBorder().shift);
2289 if (line->getBorder().dashed && !line->getRightBorder().dashed)
2290 ocad_symbol->dmode = 2;
2291 else
2292 ocad_symbol->dmode = line->getBorder().dashed ? 3 : 1;
2293 // ocad_symbol->dflags
2294
2295 ocad_symbol->lwidth = convertSize(line->getBorder().width);
2296 ocad_symbol->rwidth = convertSize(line->getRightBorder().width);
2297
2298 ocad_symbol->lcolor = convertColor(line->getBorder().color);
2299 ocad_symbol->rcolor = convertColor(line->getRightBorder().color);
2300
2301 if (line->getBorder().dashed)
2302 {
2303 ocad_symbol->dlen = convertSize(line->getBorder().dash_length);
2304 ocad_symbol->dgap = convertSize(line->getBorder().break_length);
2305 }
2306 else if (line->getRightBorder().dashed)
2307 {
2308 ocad_symbol->dlen = convertSize(line->getRightBorder().dash_length);
2309 ocad_symbol->dgap = convertSize(line->getRightBorder().break_length);
2310 }
2311
2312 if (((line->getBorder().dashed && line->getRightBorder().dashed) &&
2313 (line->getBorder().dash_length != line->getRightBorder().dash_length ||
2314 line->getBorder().break_length != line->getRightBorder().break_length)) ||
2315 (!line->getBorder().dashed && line->getRightBorder().dashed))
2316 {
2317 addWarning(tr("In line symbol \"%1\", cannot export the borders correctly.").arg(line->getPlainTextName()));
2318 }
2319 }
2320
2321 // Mid symbol
2322 OCADPoint* pattern_buffer = ocad_symbol->pts;
2323 ocad_symbol->smnpts = exportPattern(line->getMidSymbol(), &pattern_buffer);
2324 ocad_symbol->snum = line->getMidSymbolsPerSpot();
2325 ocad_symbol->sdist = convertSize(line->getMidSymbolDistance());
2326
2327 // No secondary symbol
2328 ocad_symbol->ssnpts = 0;
2329
2330 // Export dash symbol as corner symbol
2331 ocad_symbol->scnpts = exportPattern(line->getDashSymbol(), &pattern_buffer);
2332
2333 // Start symbol
2334 ocad_symbol->sbnpts = exportPattern(line->getStartSymbol(), &pattern_buffer);
2335
2336 // End symbol
2337 ocad_symbol->senpts = exportPattern(line->getEndSymbol(), &pattern_buffer);
2338
2339 Q_ASSERT((u8*)ocad_symbol + data_size == (u8*)pattern_buffer);
2340 return ocad_symbol->number;
2341 }
2342
exportAreaSymbol(const AreaSymbol * area)2343 s16 OCAD8FileExport::exportAreaSymbol(const AreaSymbol* area)
2344 {
2345 int data_size = (sizeof(OCADAreaSymbol) - sizeof(OCADPoint));
2346 for (int i = 0, end = area->getNumFillPatterns(); i < end; ++i)
2347 {
2348 if (area->getFillPattern(i).type == AreaSymbol::FillPattern::PointPattern)
2349 {
2350 data_size += getPatternSize(area->getFillPattern(i).point);
2351 break;
2352 }
2353 }
2354 OCADAreaSymbol* ocad_symbol = (OCADAreaSymbol*)ocad_symbol_new(file, data_size);
2355 exportCommonSymbolFields(area, (OCADSymbol*)ocad_symbol, data_size);
2356
2357 // Basic settings
2358 ocad_symbol->type = OCAD_AREA_SYMBOL;
2359 ocad_symbol->extent = 0;
2360 if (area->getColor())
2361 {
2362 ocad_symbol->fill = 1;
2363 ocad_symbol->color = convertColor(area->getColor());
2364 }
2365
2366 // Hatch
2367 ocad_symbol->hmode = 0;
2368 for (int i = 0, end = area->getNumFillPatterns(); i < end; ++i)
2369 {
2370 const AreaSymbol::FillPattern& pattern = area->getFillPattern(i);
2371 if (pattern.type == AreaSymbol::FillPattern::LinePattern)
2372 {
2373 if ( (ocad_symbol->hmode == 1 && ocad_symbol->hcolor != convertColor(pattern.line_color)) ||
2374 ocad_symbol->hmode == 2 )
2375 {
2376 addWarning(tr("In area symbol \"%1\", skipping a fill pattern.").arg(area->getPlainTextName()));
2377 continue;
2378 }
2379
2380 if (pattern.rotatable())
2381 ocad_symbol->base_flags |= 1;
2382
2383 ++ocad_symbol->hmode;
2384 if (ocad_symbol->hmode == 1)
2385 {
2386 ocad_symbol->hcolor = convertColor(pattern.line_color);
2387 ocad_symbol->hwidth = convertSize(pattern.line_width);
2388 ocad_symbol->hdist = convertSize(pattern.line_spacing - pattern.line_width);
2389 ocad_symbol->hangle1 = convertRotation(pattern.angle);
2390 }
2391 else if (ocad_symbol->hmode == 2)
2392 {
2393 ocad_symbol->hwidth = (ocad_symbol->hwidth + convertSize(pattern.line_width)) / 2;
2394 ocad_symbol->hdist = (ocad_symbol->hdist + convertSize(pattern.line_spacing - pattern.line_width)) / 2;
2395 ocad_symbol->hangle2 = convertRotation(pattern.angle);
2396 }
2397 }
2398 }
2399
2400 // Struct
2401 PointSymbol* point_pattern = nullptr;
2402 for (int i = 0, end = area->getNumFillPatterns(); i < end; ++i)
2403 {
2404 const AreaSymbol::FillPattern& pattern = area->getFillPattern(i);
2405 if (pattern.type == AreaSymbol::FillPattern::PointPattern)
2406 {
2407 if (pattern.rotatable())
2408 ocad_symbol->base_flags |= 1;
2409
2410 ++ocad_symbol->pmode;
2411 if (ocad_symbol->pmode == 1)
2412 {
2413 ocad_symbol->pwidth = convertSize(pattern.point_distance);
2414 ocad_symbol->pheight = convertSize(pattern.line_spacing);
2415 ocad_symbol->pangle = convertRotation(pattern.angle);
2416 point_pattern = pattern.point;
2417 }
2418 else if (ocad_symbol->pmode == 2)
2419 {
2420 // NOTE: This is only a heuristic which works for the orienteering symbol sets, not a real conversion, which would be impossible in most cases.
2421 // There are no further checks done to find out if the conversion is applicable because with these checks, already a tiny (not noticeable) error
2422 // in the symbol definition would make it take the wrong choice.
2423 addWarning(tr("In area symbol \"%1\", assuming a \"shifted rows\" point pattern. This might be correct as well as incorrect.").arg(area->getPlainTextName()));
2424
2425 if (pattern.line_offset != 0)
2426 ocad_symbol->pheight /= 2;
2427 else
2428 ocad_symbol->pwidth /= 2;
2429
2430 break;
2431 }
2432 }
2433 }
2434
2435 if (point_pattern)
2436 {
2437 OCADPoint* pattern_buffer = ocad_symbol->pts;
2438 ocad_symbol->npts = exportPattern(point_pattern, &pattern_buffer);
2439 Q_ASSERT((u8*)ocad_symbol + data_size == (u8*)pattern_buffer);
2440 }
2441 return ocad_symbol->number;
2442 }
2443
exportTextSymbol(const TextSymbol * text)2444 s16 OCAD8FileExport::exportTextSymbol(const TextSymbol* text)
2445 {
2446 int data_size = sizeof(OCADTextSymbol);
2447 OCADTextSymbol* ocad_symbol = (OCADTextSymbol*)ocad_symbol_new(file, data_size);
2448 exportCommonSymbolFields(text, (OCADSymbol*)ocad_symbol, data_size);
2449
2450 ocad_symbol->type = OCAD_TEXT_SYMBOL;
2451 ocad_symbol->subtype = 1;
2452 ocad_symbol->extent = 0;
2453
2454 convertPascalString(text->getFontFamily(), ocad_symbol->font, 32);
2455 ocad_symbol->color = convertColor(text->getColor());
2456 ocad_symbol->dpts = qRound(10 * text->getFontSize() / 25.4 * 72.0);
2457 ocad_symbol->bold = text->isBold() ? 700 : 400;
2458 ocad_symbol->italic = text->isItalic() ? 1 : 0;
2459 //ocad_symbol->charset
2460 ocad_symbol->cspace = convertSize(1000 * text->getCharacterSpacing());
2461 if (ocad_symbol->cspace != 0)
2462 addWarning(tr("In text symbol %1: custom character spacing is set, its implementation does not match OCAD's behavior yet").arg(text->getPlainTextName()));
2463 ocad_symbol->wspace = 100;
2464 ocad_symbol->halign = 0; // Default value, we might have to change this or even create copies of this symbol with other alignments later
2465 double absolute_line_spacing = text->getLineSpacing() * (text->getFontMetrics().lineSpacing() / text->calculateInternalScaling());
2466 ocad_symbol->lspace = qRound(absolute_line_spacing / (text->getFontSize() * 0.01));
2467 ocad_symbol->pspace = convertSize(1000 * text->getParagraphSpacing());
2468 if (text->isUnderlined())
2469 addWarning(tr("In text symbol %1: ignoring underlining").arg(text->getPlainTextName()));
2470 if (text->usesKerning())
2471 addWarning(tr("In text symbol %1: ignoring kerning").arg(text->getPlainTextName()));
2472
2473 ocad_symbol->under = text->hasLineBelow() ? 1 : 0;
2474 ocad_symbol->ucolor = convertColor(text->getLineBelowColor());
2475 ocad_symbol->uwidth = convertSize(1000 * text->getLineBelowWidth());
2476 ocad_symbol->udist = convertSize(1000 * text->getLineBelowDistance());
2477
2478 ocad_symbol->ntabs = text->getNumCustomTabs();
2479 for (int i = 0; i < qMin((s16)32, ocad_symbol->ntabs); ++i)
2480 ocad_symbol->tab[i] = convertSize(text->getCustomTab(i));
2481
2482 if (text->getFramingMode() != TextSymbol::NoFraming && text->getFramingColor())
2483 {
2484 ocad_symbol->fcolor = convertColor(text->getFramingColor());
2485 if (text->getFramingMode() == TextSymbol::ShadowFraming)
2486 {
2487 ocad_symbol->fmode = 1;
2488 ocad_symbol->fdx = convertSize(text->getFramingShadowXOffset());
2489 ocad_symbol->fdy = -1 * convertSize(text->getFramingShadowYOffset());
2490 }
2491 else if (text->getFramingMode() == TextSymbol::LineFraming)
2492 {
2493 ocad_symbol->fmode = 2;
2494 ocad_symbol->fdpts = convertSize(text->getFramingLineHalfWidth());
2495 }
2496 else
2497 Q_ASSERT(false);
2498 }
2499
2500 return ocad_symbol->number;
2501 }
2502
setTextSymbolFormatting(OCADTextSymbol * ocad_symbol,TextObject * formatting)2503 void OCAD8FileExport::setTextSymbolFormatting(OCADTextSymbol* ocad_symbol, TextObject* formatting)
2504 {
2505 if (formatting->getHorizontalAlignment() == TextObject::AlignLeft)
2506 ocad_symbol->halign = 0;
2507 else if (formatting->getHorizontalAlignment() == TextObject::AlignHCenter)
2508 ocad_symbol->halign = 1;
2509 else if (formatting->getHorizontalAlignment() == TextObject::AlignRight)
2510 ocad_symbol->halign = 2;
2511 }
2512
exportCombinedSymbol(const CombinedSymbol * combination)2513 std::set< s16 > OCAD8FileExport::exportCombinedSymbol(const CombinedSymbol* combination)
2514 {
2515 // Insert public parts
2516 std::vector<bool> map_bitfield;
2517 map_bitfield.assign(map->getNumSymbols(), false);
2518 map_bitfield[map->findSymbolIndex(combination)] = true;
2519 map->determineSymbolUseClosure(map_bitfield);
2520
2521 std::set<s16> result;
2522 for (size_t i = 0, end = map_bitfield.size(); i < end; ++i)
2523 {
2524 if (map_bitfield[i] && symbol_index.contains(map->getSymbol(i)))
2525 {
2526 result.insert(symbol_index[map->getSymbol(i)].begin(),
2527 symbol_index[map->getSymbol(i)].end());
2528 }
2529 }
2530
2531 // Insert private parts
2532 for (int i = 0; i < combination->getNumParts(); ++i)
2533 {
2534 if (combination->isPartPrivate(i))
2535 {
2536 const Symbol* part = combination->getPart(i);
2537 int index = 0;
2538 if (part->getType() == Symbol::Line)
2539 index = exportLineSymbol(part->asLine());
2540 else if (part->getType() == Symbol::Area)
2541 index = exportAreaSymbol(part->asArea());
2542 else
2543 Q_ASSERT(false);
2544 result.insert(index);
2545 }
2546 }
2547
2548 return result;
2549 }
2550
exportCoordinates(const MapCoordVector & coords,OCADPoint ** buffer,const Symbol * symbol)2551 u16 OCAD8FileExport::exportCoordinates(const MapCoordVector& coords, OCADPoint** buffer, const Symbol* symbol)
2552 {
2553 s16 num_points = 0;
2554 bool curve_start = false;
2555 bool hole_point = false;
2556 bool curve_continue = false;
2557 for (size_t i = 0, end = coords.size(); i < end; ++i)
2558 {
2559 const MapCoord& point = coords[i];
2560 OCADPoint p = convertPoint(point);
2561 if (point.isDashPoint())
2562 {
2563 if (!symbol || symbol->getType() != Symbol::Line)
2564 p.y |= PY_CORNER;
2565 else
2566 {
2567 const LineSymbol* line_symbol = static_cast<const LineSymbol*>(symbol);
2568 if ((line_symbol->getDashSymbol() == nullptr || line_symbol->getDashSymbol()->isEmpty()) && line_symbol->isDashed())
2569 p.y |= PY_DASH;
2570 else
2571 p.y |= PY_CORNER;
2572 }
2573 }
2574 if (curve_start)
2575 p.x |= PX_CTL1;
2576 if (hole_point)
2577 p.y |= PY_HOLE;
2578 if (curve_continue)
2579 p.x |= PX_CTL2;
2580
2581 curve_continue = curve_start;
2582 curve_start = point.isCurveStart();
2583 hole_point = point.isHolePoint();
2584
2585 **buffer = p;
2586 ++(*buffer);
2587 ++num_points;
2588 }
2589 return num_points;
2590 }
2591
exportTextCoordinates(TextObject * object,OCADPoint ** buffer)2592 u16 OCAD8FileExport::exportTextCoordinates(TextObject* object, OCADPoint** buffer)
2593 {
2594 if (object->getNumLines() == 0)
2595 return 0;
2596
2597 QTransform text_to_map = object->calcTextToMapTransform();
2598 QTransform map_to_text = object->calcMapToTextTransform();
2599
2600 if (object->hasSingleAnchor())
2601 {
2602 // Create 5 coordinates:
2603 // 0 - baseline anchor point
2604 // 1 - bottom left
2605 // 2 - bottom right
2606 // 3 - top right
2607 // 4 - top left
2608
2609 QPointF anchor = QPointF(object->getAnchorCoordF());
2610 QPointF anchor_text = map_to_text.map(anchor);
2611
2612 TextObjectLineInfo* line0 = object->getLineInfo(0);
2613 **buffer = convertPoint(MapCoord(text_to_map.map(QPointF(anchor_text.x(), line0->line_y))));
2614 ++(*buffer);
2615
2616 QRectF bounding_box_text;
2617 for (int i = 0; i < object->getNumLines(); ++i)
2618 {
2619 TextObjectLineInfo* info = object->getLineInfo(i);
2620 rectIncludeSafe(bounding_box_text, QPointF(info->line_x, info->line_y - info->ascent));
2621 rectIncludeSafe(bounding_box_text, QPointF(info->line_x + info->width, info->line_y + info->descent));
2622 }
2623
2624 **buffer = convertPoint(MapCoord(text_to_map.map(bounding_box_text.bottomLeft())));
2625 ++(*buffer);
2626 **buffer = convertPoint(MapCoord(text_to_map.map(bounding_box_text.bottomRight())));
2627 ++(*buffer);
2628 **buffer = convertPoint(MapCoord(text_to_map.map(bounding_box_text.topRight())));
2629 ++(*buffer);
2630 **buffer = convertPoint(MapCoord(text_to_map.map(bounding_box_text.topLeft())));
2631 ++(*buffer);
2632
2633 return 5;
2634 }
2635 else
2636 {
2637 // As OCD 8 only supports Top alignment, we have to replace the top box coordinates by the top coordinates of the first line
2638 const TextSymbol* text_symbol = static_cast<const TextSymbol*>(object->getSymbol());
2639 QFontMetricsF metrics = text_symbol->getFontMetrics();
2640 double internal_scaling = text_symbol->calculateInternalScaling();
2641 TextObjectLineInfo* line0 = object->getLineInfo(0);
2642
2643 double new_top = (object->getVerticalAlignment() == TextObject::AlignTop) ? (-object->getBoxHeight() / 2) : ((line0->line_y - line0->ascent) / internal_scaling);
2644 // Account for extra internal leading
2645 double top_adjust = -text_symbol->getFontSize() + (metrics.ascent() + metrics.descent() + 0.5) / internal_scaling;
2646 new_top = new_top - top_adjust;
2647
2648 QTransform transform;
2649 transform.rotate(-object->getRotation() * 180 / M_PI);
2650 **buffer = convertPoint(MapCoord(transform.map(QPointF(-object->getBoxWidth() / 2, object->getBoxHeight() / 2)) + object->getAnchorCoordF()));
2651 ++(*buffer);
2652 **buffer = convertPoint(MapCoord(transform.map(QPointF(object->getBoxWidth() / 2, object->getBoxHeight() / 2)) + object->getAnchorCoordF()));
2653 ++(*buffer);
2654 **buffer = convertPoint(MapCoord(transform.map(QPointF(object->getBoxWidth() / 2, new_top)) + object->getAnchorCoordF()));
2655 ++(*buffer);
2656 **buffer = convertPoint(MapCoord(transform.map(QPointF(-object->getBoxWidth() / 2, new_top)) + object->getAnchorCoordF()));
2657 ++(*buffer);
2658
2659 return 4;
2660 }
2661 }
2662
2663 // static
getOcadColor(QRgb rgb)2664 int OCAD8FileExport::getOcadColor(QRgb rgb)
2665 {
2666 static const QColor ocad_colors[16] = {
2667 QColor( 0, 0, 0).toHsv(),
2668 QColor(128, 0, 0).toHsv(),
2669 QColor(0, 128, 0).toHsv(),
2670 QColor(128, 128, 0).toHsv(),
2671 QColor( 0, 0, 128).toHsv(),
2672 QColor(128, 0, 128).toHsv(),
2673 QColor( 0, 128, 128).toHsv(),
2674 QColor(128, 128, 128).toHsv(),
2675 QColor(192, 192, 192).toHsv(),
2676 QColor(255, 0, 0).toHsv(),
2677 QColor( 0, 255, 0).toHsv(),
2678 QColor(255, 255, 0).toHsv(),
2679 QColor( 0, 0, 255).toHsv(),
2680 QColor(255, 0, 255).toHsv(),
2681 QColor( 0, 255, 255).toHsv(),
2682 QColor(255, 255, 255).toHsv()
2683 };
2684
2685 Q_ASSERT(qAlpha(rgb) == 255);
2686
2687 // Quick return for frequent values
2688 if (rgb == qRgb(255, 255, 255))
2689 return 15;
2690 else if (rgb == qRgb(0, 0, 0))
2691 return 0;
2692
2693 QColor color = QColor(rgb).toHsv();
2694 if (color.hue() == -1 || color.saturation() < 32)
2695 {
2696 auto gray = qGray(rgb); // qGray is used for dithering
2697 if (gray >= 192)
2698 return 8;
2699 if (gray >= 128)
2700 return 7;
2701 return 0;
2702 }
2703
2704 int best_index = 0;
2705 auto best_distance = std::numeric_limits<qreal>::max();
2706 for (auto i : { 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14 })
2707 {
2708 // True color
2709 int hue_dist = qAbs(color.hue() - ocad_colors[i].hue());
2710 hue_dist = qMin(hue_dist, 360 - hue_dist);
2711 auto distance = qPow(hue_dist, 2)
2712 + 0.1 * qPow(color.saturation() - ocad_colors[i].saturation(), 2)
2713 + 0.1 * qPow(color.value() - ocad_colors[i].value(), 2);
2714
2715 // (Too much) manual tweaking for orienteering colors
2716 if (i == 1)
2717 distance *= 1.5; // Dark red
2718 else if (i == 3)
2719 distance *= 2; // Olive
2720 else if (i == 11)
2721 distance *= 2; // Yellow
2722 else if (i == 9)
2723 distance *= 3; // Red is unlikely
2724
2725 if (distance < best_distance)
2726 {
2727 best_distance = distance;
2728 best_index = i;
2729 }
2730 }
2731 return best_index;
2732 }
2733
getPointSymbolExtent(const PointSymbol * symbol)2734 s16 OCAD8FileExport::getPointSymbolExtent(const PointSymbol* symbol)
2735 {
2736 if (!symbol)
2737 return 0;
2738 QRectF extent;
2739 for (int i = 0; i < symbol->getNumElements(); ++i)
2740 {
2741 QScopedPointer<Object> object(symbol->getElementObject(i)->duplicate());
2742 object->setSymbol(symbol->getElementSymbol(i), true);
2743 object->update();
2744 rectIncludeSafe(extent, object->getExtent());
2745 object->clearRenderables();
2746 }
2747 float float_extent = 0.5f * qMax(extent.width(), extent.height());
2748 if (symbol->getInnerColor())
2749 float_extent = qMax(float_extent, 0.001f * symbol->getInnerRadius());
2750 if (symbol->getOuterColor())
2751 float_extent = qMax(float_extent, 0.001f * (symbol->getInnerRadius() + symbol->getOuterWidth()));
2752 return convertSize(1000 * float_extent);
2753 }
2754
convertPascalString(const QString & text,char * buffer,int buffer_size)2755 void OCAD8FileExport::convertPascalString(const QString& text, char* buffer, int buffer_size)
2756 {
2757 Q_ASSERT(buffer_size <= 256); // not possible to store a bigger length in the first byte
2758 int max_size = buffer_size - 1;
2759
2760 if (text.length() > max_size)
2761 addStringTruncationWarning(text, max_size);
2762
2763 QByteArray data = encoding_1byte->fromUnicode(text);
2764 int min_size = qMin(text.length(), max_size);
2765 *((unsigned char *)buffer) = min_size;
2766 memcpy(buffer + 1, data.data(), min_size);
2767 }
2768
convertCString(const QString & text,unsigned char * buffer,int buffer_size)2769 void OCAD8FileExport::convertCString(const QString& text, unsigned char* buffer, int buffer_size)
2770 {
2771 if (text.length() + 1 > buffer_size)
2772 addStringTruncationWarning(text, buffer_size - 1);
2773
2774 QByteArray data = encoding_1byte->fromUnicode(text);
2775 int min_size = qMin(buffer_size - 1, data.length());
2776 memcpy(buffer, data.data(), min_size);
2777 buffer[min_size] = 0;
2778 }
2779
convertWideCString(const QString & text,unsigned char * buffer,int buffer_size)2780 int OCAD8FileExport::convertWideCString(const QString& text, unsigned char* buffer, int buffer_size)
2781 {
2782 // Convert text to Windows-OCAD format:
2783 // - if it starts with a newline, add another
2784 // - convert \n to \r\n
2785 QString exported_text;
2786 if (text.startsWith(QLatin1Char('\n')))
2787 exported_text = QLatin1Char('\n') + text;
2788 else
2789 exported_text = text;
2790 exported_text.replace(QLatin1Char('\n'), QLatin1String("\r\n"));
2791
2792 if (2 * (exported_text.length() + 1) > buffer_size)
2793 addStringTruncationWarning(exported_text, buffer_size - 1);
2794
2795 // Do not add a byte order mark by using QTextCodec::IgnoreHeader
2796 QTextEncoder* encoder = encoding_2byte->makeEncoder(QTextCodec::IgnoreHeader);
2797 QByteArray data = encoder->fromUnicode(exported_text);
2798 delete encoder;
2799
2800 int min_size = qMin(buffer_size - 2, data.length());
2801 memcpy(buffer, data.data(), min_size);
2802 buffer[min_size] = 0;
2803 buffer[min_size + 1] = 0;
2804 return min_size + 2;
2805 }
2806
convertRotation(float angle)2807 int OCAD8FileExport::convertRotation(float angle)
2808 {
2809 return qRound(10 * (angle * 180 / M_PI));
2810 }
2811
2812 namespace
2813 {
convertPointMember(s32 value)2814 constexpr s32 convertPointMember(s32 value)
2815 {
2816 return (value < -5) ? s32(0x80000000u | ((0x7fffffu & u32((value-4)/10)) << 8)) : s32((0x7fffffu & u32((value+5)/10)) << 8);
2817 }
2818
2819 // convertPointMember() shall round half up.
2820 Q_STATIC_ASSERT(convertPointMember(-16) == s32(0xfffffe00u)); // __ down __
2821 Q_STATIC_ASSERT(convertPointMember(-15) == s32(0xffffff00u)); // up
2822 Q_STATIC_ASSERT(convertPointMember( -6) == s32(0xffffff00u)); // __ down __
2823 Q_STATIC_ASSERT(convertPointMember( -5) == s32(0x00000000u)); // up
2824 Q_STATIC_ASSERT(convertPointMember( -1) == s32(0x00000000u)); // up
2825 Q_STATIC_ASSERT(convertPointMember( 0) == s32(0x00000000u)); // unchanged
2826 Q_STATIC_ASSERT(convertPointMember( +1) == s32(0x00000000u)); // down
2827 Q_STATIC_ASSERT(convertPointMember( +4) == s32(0x00000000u)); // __ down __
2828 Q_STATIC_ASSERT(convertPointMember( +5) == s32(0x00000100u)); // up
2829 Q_STATIC_ASSERT(convertPointMember(+14) == s32(0x00000100u)); // __ down __
2830 Q_STATIC_ASSERT(convertPointMember(+15) == s32(0x00000200u)); // up
2831
2832 #ifdef MAPPER_DEVELOPMENT_BUILD
2833 namespace broken
2834 {
2835 // Previous, broken implementation (#749)
2836 // Left here for reference.
convertPointMember(s32 value)2837 constexpr s32 convertPointMember(s32 value)
2838 {
2839 return (value < 0) ? (0x80000000 | ((0x7fffffu & ((value-5)/10)) << 8)) : ((0x7fffffu & ((value+5)/10)) << 8);
2840 }
2841 }
2842 // Actual behaviour of the broken implementation
2843 Q_STATIC_ASSERT(broken::convertPointMember(-16) == s32(0xfffffe00u)); // down
2844 Q_STATIC_ASSERT(broken::convertPointMember(-15) == s32(0xfffffe00u)); // __ down __ (should be up)
2845 Q_STATIC_ASSERT(broken::convertPointMember(-14) == s32(0xffffff00u)); // up
2846 Q_STATIC_ASSERT(broken::convertPointMember( -6) == s32(0xffffff00u)); // down
2847 Q_STATIC_ASSERT(broken::convertPointMember( -5) == s32(0xffffff00u)); // __ down __ (should be up)
2848 Q_STATIC_ASSERT(broken::convertPointMember( -4) == s32(0x80000000u)); // wrong (should be 0x00000000u)
2849 Q_STATIC_ASSERT(broken::convertPointMember( -3) == s32(0x80000000u)); // wrong (should be 0x00000000u)
2850 Q_STATIC_ASSERT(broken::convertPointMember( -2) == s32(0x80000000u)); // wrong (should be 0x00000000u)
2851 Q_STATIC_ASSERT(broken::convertPointMember( -1) == s32(0x80000000u)); // wrong (should be 0x00000000u)
2852 Q_STATIC_ASSERT(broken::convertPointMember( 0) == s32(0x00000000u)); // unchanged
2853 Q_STATIC_ASSERT(broken::convertPointMember( +1) == s32(0x00000000u)); // down
2854 Q_STATIC_ASSERT(broken::convertPointMember( +4) == s32(0x00000000u)); // __ down __
2855 Q_STATIC_ASSERT(broken::convertPointMember( +5) == s32(0x00000100u)); // up
2856 Q_STATIC_ASSERT(broken::convertPointMember(+14) == s32(0x00000100u)); // __ down __
2857 Q_STATIC_ASSERT(broken::convertPointMember(+15) == s32(0x00000200u)); // up
2858 #endif
2859 }
2860
convertPoint(qint32 x,qint32 y)2861 OCADPoint OCAD8FileExport::convertPoint(qint32 x, qint32 y)
2862 {
2863 return { convertPointMember(x), convertPointMember(-y) };
2864 }
2865
convertPoint(const MapCoord & coord)2866 OCADPoint OCAD8FileExport::convertPoint(const MapCoord& coord)
2867 {
2868 return convertPoint(coord.nativeX(), coord.nativeY());
2869 }
2870
convertSize(qint32 size)2871 s32 OCAD8FileExport::convertSize(qint32 size)
2872 {
2873 return (s32)((size+5) / 10);
2874 }
2875
convertColor(const MapColor * color) const2876 s16 OCAD8FileExport::convertColor(const MapColor* color) const
2877 {
2878 int index = map->findColorIndex(color);
2879 if (index >= 0)
2880 {
2881 return uses_registration_color ? (index + 1) : index;
2882 }
2883 return 0;
2884 }
2885
convertTemplateScale(double mapper_scale)2886 double OCAD8FileExport::convertTemplateScale(double mapper_scale)
2887 {
2888 return mapper_scale * (1 / 0.01);
2889 }
2890
addStringTruncationWarning(const QString & text,int truncation_pos)2891 void OCAD8FileExport::addStringTruncationWarning(const QString& text, int truncation_pos)
2892 {
2893 QString temp = text;
2894 temp.insert(truncation_pos, QLatin1String("|||"));
2895 addWarning(tr("String truncated (truncation marked with three '|'): %1").arg(temp));
2896 }
2897
2898
2899 } // namespace OpenOrienteering
2900