1 // SPDX-FileCopyrightText: 2010, 2011 Raoul Bourquin <raoulb@bluewin.ch>
2
3 // SPDX-License-Identifier: GPL-2.0-or-later
4
5 #include "asyexporterimpvisitor.h"
6
7 #include "../misc/goniometry.h"
8 #include "../objects/circle_imp.h"
9 #include "../objects/cubic_imp.h"
10 #include "../objects/bezier_imp.h"
11 #include "../objects/curve_imp.h"
12 #include "../objects/line_imp.h"
13 #include "../objects/locus_imp.h"
14 #include "../objects/object_drawer.h"
15 #include "../objects/object_holder.h"
16 #include "../objects/object_imp.h"
17 #include "../objects/other_imp.h"
18 #include "../objects/point_imp.h"
19 #include "../objects/polygon_imp.h"
20 #include "../objects/text_imp.h"
21
22
newLine()23 void AsyExporterImpVisitor::newLine()
24 {
25 mstream << "\n";
26 }
27
28
emitPenColor(const QColor & c)29 QString AsyExporterImpVisitor::emitPenColor( const QColor& c )
30 {
31 QString pencolor(QLatin1String(""));
32 // Asymptote definition of pen color
33 pencolor = "rgb(" + QString::number(c.red()/255.0) + ',' + QString::number(c.green()/255.0) + ',' + QString::number(c.blue()/255.0) + ')';
34 return pencolor;
35 }
36
37
emitPenStyle(const Qt::PenStyle & style)38 QString AsyExporterImpVisitor::emitPenStyle( const Qt::PenStyle& style )
39 {
40 QString penstyle(QLatin1String(""));
41 // Asymptote definition of pen (line) style
42 // TODO: Needs finetuning of Asymptote linestyle parameters
43 if ( style == Qt::SolidLine ) {
44 penstyle = QStringLiteral("solid");
45 } else if ( style == Qt::DashLine ) {
46 penstyle = QStringLiteral("dashed");
47 } else if ( style == Qt::DotLine ) {
48 penstyle = QStringLiteral("dotted");
49 } else if ( style == Qt::DashDotLine ) {
50 penstyle = QStringLiteral("dashdotted");
51 } else if ( style == Qt::DashDotDotLine ) {
52 penstyle = QStringLiteral("longdashdotted");
53 }
54 return penstyle;
55 }
56
57
emitPenSize(const int width)58 QString AsyExporterImpVisitor::emitPenSize( const int width )
59 {
60 // In this function we map the logical (integer) linewidth of Kig
61 // to real line widths that can be used in Asymptote.
62 // Default mapping is currently: asy_width = kig_width / 2.0
63 QString pensize(QLatin1String(""));
64 if ( width < 0 )
65 {
66 // Nothing specified, use asymptote default
67 pensize = QStringLiteral("linewidth(0.5)");
68 }
69 else
70 {
71 // Asymptote definition of pen size
72 pensize = "linewidth(" + QString::number(width/2.0) + ')';
73 }
74 return pensize;
75 }
76
77
emitPen(const QColor & c,const int width,const Qt::PenStyle & style)78 QString AsyExporterImpVisitor::emitPen( const QColor& c, const int width, const Qt::PenStyle& style )
79 {
80 QString pen(QLatin1String(""));
81 // Asymptote definition of a pen
82 pen = emitPenColor(c) + '+' + emitPenSize(width) + '+' + emitPenStyle(style);
83 return pen;
84 }
85
86
emitCoord(const Coordinate & c)87 QString AsyExporterImpVisitor::emitCoord( const Coordinate& c )
88 {
89 QString ret(QLatin1String(""));
90 ret = '(' + QString::number(c.x) + ',' + QString::number(c.y) + ')';
91 return ret;
92 }
93
94
emitLine(const Coordinate & a,const Coordinate & b,const int width,const Qt::PenStyle s,bool vector)95 void AsyExporterImpVisitor::emitLine( const Coordinate& a, const Coordinate& b,
96 const int width, const Qt::PenStyle s,
97 bool vector )
98 {
99 mstream << "path line = "
100 << emitCoord( a )
101 << "--"
102 << emitCoord( b )
103 << ";";
104 newLine();
105
106 if ( vector == true )
107 {
108 mstream << "draw(line, "
109 << emitPen( mcurobj->drawer()->color(), width, s )
110 << ", Arrow );";
111 }
112 else
113 {
114 mstream << "draw(line, "
115 << emitPen( mcurobj->drawer()->color(), width, s )
116 << " );";
117 }
118 newLine();
119 }
120
121
dimRealToCoord(int dim)122 double AsyExporterImpVisitor::dimRealToCoord( int dim )
123 {
124 QRect qr( 0, 0, dim, dim );
125 Rect r = mw.screenInfo().fromScreen( qr );
126 return fabs( r.width() );
127 }
128
129
visit(ObjectHolder * obj)130 void AsyExporterImpVisitor::visit( ObjectHolder* obj )
131 {
132 if ( ! obj->drawer()->shown() )
133 return;
134 mcurobj = obj;
135 obj->imp()->visit( this );
136 }
137
138
plotGenericCurve(const CurveImp * imp)139 void AsyExporterImpVisitor::plotGenericCurve( const CurveImp* imp )
140 {
141 std::vector< std::vector< Coordinate > > coordlist;
142 coordlist.push_back( std::vector< Coordinate >() );
143 uint curid = 0;
144
145 Coordinate c;
146 Coordinate prev = Coordinate::invalidCoord();
147 for ( double i = 0.0; i <= 1.0; i += 0.0001 )
148 {
149 c = imp->getPoint( i, mw.document() );
150 if ( !c.valid() )
151 {
152 if ( coordlist[curid].size() > 0 )
153 {
154 coordlist.push_back( std::vector< Coordinate >() );
155 ++curid;
156 prev = Coordinate::invalidCoord();
157 }
158 continue;
159 }
160 if ( ! ( ( fabs( c.x ) <= 10000 ) && ( fabs( c.y ) <= 10000 ) ) )
161 continue;
162 // if there's too much distance between this coordinate and the previous
163 // one, then it's another piece of curve not joined with the rest
164 if ( prev.valid() && ( c.distance( prev ) > 50.0 ) )
165 {
166 coordlist.push_back( std::vector< Coordinate >() );
167 ++curid;
168 }
169 coordlist[curid].push_back( c );
170 prev = c;
171 }
172 // special case for ellipse
173 if ( const ConicImp* conic = dynamic_cast< const ConicImp* >( imp ) )
174 {
175 // if ellipse, close its path
176 // THSI IS WRONG, think of ellipse arcs!!
177 if ( conic->conicType() == 1 && coordlist.size() == 1 && coordlist[0].size() > 1 )
178 {
179 coordlist[0].push_back( coordlist[0][0] );
180 }
181 }
182 for ( uint i = 0; i < coordlist.size(); ++i )
183 {
184 uint s = coordlist[i].size();
185 // there's no point in draw curves empty or with only one point
186 if ( s <= 1 )
187 continue;
188
189 uint linelength = 13;
190 QString tmp;
191 mstream << "path curve = ";
192 for ( uint j = 0; j < s; ++j )
193 {
194 tmp = emitCoord( coordlist[i][j] );
195 // Avoid too long lines in the output file
196 if(linelength + tmp.length() > maxlinelength)
197 {
198 linelength = tmp.length();
199 newLine();
200 }
201 else
202 {
203 linelength += tmp.length();
204 }
205 mstream << tmp;
206 if ( j < s-1 )
207 {
208 linelength += 2;
209 mstream << "--";
210 }
211 else
212 {
213 mstream << ";";
214 newLine();
215 linelength = 0;
216 }
217 }
218 mstream << "draw(curve, "
219 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
220 << " );";
221 newLine();
222 }
223 }
224
225
visit(const LineImp * imp)226 void AsyExporterImpVisitor::visit( const LineImp* imp )
227 {
228 Coordinate a = imp->data().a;
229 Coordinate b = imp->data().b;
230 calcBorderPoints( a, b, msr );
231 emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
232 }
233
234
visit(const PointImp * imp)235 void AsyExporterImpVisitor::visit( const PointImp* imp )
236 {
237 mstream << "pair point = "
238 << emitCoord( imp->coordinate() )
239 << ";";
240 newLine();
241 // The factor of 6 is necessary to get the asymptote default dot size
242 // which is 6*asy_default_linewidth where asy_default_linewidth = 0.5
243 int width = mcurobj->drawer()->width();
244 if ( width == -1 ) width = 6;
245 mstream << "dot(point, "
246 << emitPen( mcurobj->drawer()->color(), width, mcurobj->drawer()->style() )
247 << ");";
248 newLine();
249 }
250
251
visit(const TextImp * imp)252 void AsyExporterImpVisitor::visit( const TextImp* imp )
253 {
254 // FIXME: support multiline texts...
255 mstream << "pair anchor = "
256 << emitCoord( imp->coordinate() )
257 << ";";
258 newLine();
259 mstream << "Label l = Label(\""
260 << imp->text()
261 << "\", "
262 << emitPenColor( mcurobj-> drawer()->color() )
263 << ");";
264 newLine();
265 if ( imp->hasFrame() )
266 {
267 mstream << "draw(l, box, anchor, textboxmargin);";
268 }
269 else
270 {
271 mstream << "draw(l, anchor);";
272 }
273 newLine();
274 }
275
276
visit(const AngleImp * imp)277 void AsyExporterImpVisitor::visit( const AngleImp* imp )
278 {
279 const Coordinate center = imp->point();
280 // TODO: How to choose radius size?
281 const double radius = 0.5;
282 double startangle = imp->startAngle();
283 double endangle = startangle + imp->angle();
284
285 startangle = Goniometry::convert( startangle, Goniometry::Rad, Goniometry::Deg );
286 endangle = Goniometry::convert( endangle, Goniometry::Rad, Goniometry::Deg );
287
288 mstream << "path a = Arc("
289 << emitCoord(center)
290 << ", "
291 << radius
292 << ", "
293 << startangle
294 << ", "
295 << endangle
296 << " );";
297 newLine();
298 mstream << "draw(a, "
299 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
300 << ", Arrow );";
301 newLine();
302 }
303
304
visit(const VectorImp * imp)305 void AsyExporterImpVisitor::visit( const VectorImp* imp )
306 {
307 Coordinate a = imp->data().a;
308 Coordinate b = imp->data().b;
309 emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style(), true );
310 }
311
312
visit(const LocusImp * imp)313 void AsyExporterImpVisitor::visit( const LocusImp* imp )
314 {
315 plotGenericCurve( imp );
316 }
317
318
visit(const CircleImp * imp)319 void AsyExporterImpVisitor::visit( const CircleImp* imp )
320 {
321 mstream << "pair center = "
322 << emitCoord( imp->center() )
323 << ";";
324 newLine();
325 mstream << "real radius = " << imp->radius()
326 << ";";
327 newLine();
328 mstream << "path circle = Circle(center, radius);";
329 newLine();
330 mstream << "draw(circle, "
331 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
332 << " );";
333 newLine();
334 }
335
336
visit(const ConicImp * imp)337 void AsyExporterImpVisitor::visit( const ConicImp* imp )
338 {
339 plotGenericCurve( imp );
340 }
341
342
visit(const CubicImp * imp)343 void AsyExporterImpVisitor::visit( const CubicImp* imp )
344 {
345 // FIXME: cubic are not drawn correctly with plotGenericCurve
346 plotGenericCurve( imp );
347 }
348
349
visit(const SegmentImp * imp)350 void AsyExporterImpVisitor::visit( const SegmentImp* imp )
351 {
352 Coordinate a = imp->data().a;
353 Coordinate b = imp->data().b;
354 emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
355 }
356
357
visit(const RayImp * imp)358 void AsyExporterImpVisitor::visit( const RayImp* imp )
359 {
360 Coordinate a = imp->data().a;
361 Coordinate b = imp->data().b;
362 calcRayBorderPoints( a, b, msr );
363 emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
364 }
365
366
visit(const ArcImp * imp)367 void AsyExporterImpVisitor::visit( const ArcImp* imp )
368 {
369 const Coordinate center = imp->center();
370 const double radius = imp->radius();
371 double startangle = imp->startAngle();
372 double endangle = startangle + imp->angle();
373
374 startangle = Goniometry::convert( startangle, Goniometry::Rad, Goniometry::Deg );
375 endangle = Goniometry::convert( endangle, Goniometry::Rad, Goniometry::Deg );
376
377 mstream << "path arc = Arc("
378 << emitCoord(center)
379 << ", "
380 << radius
381 << ", "
382 << startangle
383 << ", "
384 << endangle
385 << " );";
386 newLine();
387 mstream << "draw(arc, "
388 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
389 << " );";
390 newLine();
391 }
392
393
visit(const FilledPolygonImp * imp)394 void AsyExporterImpVisitor::visit( const FilledPolygonImp* imp )
395 {
396 uint linelength = 15;
397 QString tmp;
398 mstream << "path polygon = ";
399 std::vector<Coordinate> pts = imp->points();
400 for ( uint i = 0; i < pts.size(); i++ )
401 {
402 tmp = emitCoord( pts[i] );
403 tmp.append("--");
404 if ( linelength + tmp.length() > maxlinelength )
405 {
406 newLine();
407 linelength = tmp.length();
408 }
409 else
410 {
411 linelength += tmp.length();
412 }
413 mstream << tmp;
414 }
415 mstream << "cycle;";
416 newLine();
417 mstream << "fill(polygon, "
418 << emitPenColor( mcurobj->drawer()->color() )
419 << "+opacity(0.5) );";
420 newLine();
421 mstream << "draw(polygon, "
422 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
423 << " );";
424 newLine();
425 }
426
427
visit(const ClosedPolygonalImp * imp)428 void AsyExporterImpVisitor::visit(const ClosedPolygonalImp* imp)
429 {
430 uint linelength = 15;
431 QString tmp;
432 mstream << "path polygon = ";
433 std::vector<Coordinate> pts = imp->points();
434 for ( uint i = 0; i < pts.size(); i++ )
435 {
436 tmp = emitCoord( pts[i] );
437 tmp.append("--");
438 if ( linelength + tmp.length() > maxlinelength )
439 {
440 newLine();
441 linelength = tmp.length();
442 }
443 else
444 {
445 linelength += tmp.length();
446 }
447 mstream << tmp;
448 }
449 mstream << "cycle;";
450 newLine();
451 mstream << "draw(polygon, "
452 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
453 << " );";
454 newLine();
455 }
456
457
visit(const OpenPolygonalImp * imp)458 void AsyExporterImpVisitor::visit(const OpenPolygonalImp* imp)
459 {
460 uint linelength = 15;
461 QString tmp;
462 mstream << "path polygon = ";
463 std::vector<Coordinate> pts = imp->points();
464 for ( uint i = 0; i < pts.size(); i++ )
465 {
466 tmp = emitCoord( pts[i] );
467 if ( linelength + tmp.length() > maxlinelength )
468 {
469 newLine();
470 linelength = tmp.length();
471 }
472 else
473 {
474 linelength += tmp.length();
475 }
476 mstream << tmp;
477 if ( i < pts.size()-1 )
478 {
479 linelength += 2;
480 mstream << "--";
481 }
482 else
483 {
484 linelength += 1;
485 mstream << ";";
486 }
487 }
488 newLine();
489 mstream << "draw(polygon, "
490 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
491 << " );";
492 newLine();
493 }
494
495
visit(const BezierImp * imp)496 void AsyExporterImpVisitor::visit ( const BezierImp* imp )
497 {
498 std::vector<Coordinate> pts = imp->points();
499 switch ( pts.size() )
500 {
501 case 3:
502 // Formula for cubic control points
503 // CP1 = QP0 + 2/3 *(QP1-QP0)
504 // CP2 = CP1 + 1/3 *(QP2-QP0)
505 mstream << "pair cp1 = " << emitCoord(pts.at(0)) << " +2/3*(" << emitCoord(pts.at(1)) << "-" << emitCoord(pts.at(0)) << ");";
506 newLine();
507 mstream << "pair cp2 = cp1 +1/3*(" << emitCoord(pts.at(2)) << "-" << emitCoord(pts.at(0)) << ");";
508 newLine();
509 mstream << "path bezier = ";
510 mstream << emitCoord( pts.at(0) );
511 mstream << " .. controls cp1 and cp2 .. ";
512 mstream << emitCoord( pts.at(2) );
513 mstream << ";";
514 newLine();
515 mstream << "draw(bezier, "
516 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
517 << " );";
518 newLine();
519 break;
520 case 4:
521 mstream << "path bezier = ";
522 mstream << emitCoord( pts.at(0) );
523 mstream << " .. controls ";
524 mstream << emitCoord( pts.at(1) );
525 mstream << " and ";
526 mstream << emitCoord( pts.at(2) );
527 mstream << " .. ";
528 mstream << emitCoord( pts.at(3) );
529 mstream << ";";
530 newLine();
531 mstream << "draw(bezier, "
532 << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
533 << " );";
534 newLine();
535 break;
536 default:
537 plotGenericCurve ( imp );
538 break;
539 }
540 }
541
542
visit(const RationalBezierImp * imp)543 void AsyExporterImpVisitor::visit ( const RationalBezierImp* imp )
544 {
545 plotGenericCurve ( imp );
546 }
547