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