1 // -*- C++ -*-
2 // --------------------------------------------------------------------
3 // Ipe drawing interface
4 // --------------------------------------------------------------------
5 /*
6 
7     This file is part of the extensible drawing editor Ipe.
8     Copyright (c) 1993-2020 Otfried Cheong
9 
10     Ipe is free software; you can redistribute it and/or modify it
11     under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 3 of the License, or
13     (at your option) any later version.
14 
15     As a special exception, you have permission to link Ipe with the
16     CGAL library and distribute executables, as long as you follow the
17     requirements of the Gnu General Public License in regard to all of
18     the software in the executable aside from CGAL.
19 
20     Ipe is distributed in the hope that it will be useful, but WITHOUT
21     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22     or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
23     License for more details.
24 
25     You should have received a copy of the GNU General Public License
26     along with Ipe; if not, you can find it at
27     "http://www.gnu.org/copyleft/gpl.html", or write to the Free
28     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 
30 */
31 
32 #include "ipepainter.h"
33 
34 using namespace ipe;
35 
36 // --------------------------------------------------------------------
37 
38 /*! \class ipe::Painter
39  * \ingroup base
40  * \brief Interface for drawing.
41 
42  Painter-derived classes are used for drawing to the screen and for
43  generating PDF and Postscript output.
44 
45  The Painter maintains a stack of graphics states, which includes
46  stroke and fill color, line width, dash style, miter limit, line cap
47  and line join.  It also maintains a separate stack of transformation
48  matrices.  The Painter class takes care of maintaining the stacks,
49  and setting of the attributes in the current graphics state.
50 
51  Setting an attribute with a symbolic value is resolved immediately
52  using the stylesheet Cascade attached to the Painter, so calling the
53  stroke() or fill() methods of Painter will return the current
54  absolute color.
55 
56  It's okay to set symbolic attributes that the stylesheet does not
57  define - they are set to a default absolute value (black, solid,
58  etc.).
59 
60  The painter is either in "general" or in "path construction" mode.
61  The newPath() member starts path construction mode. In this mode,
62  only the path construction operators (moveTo, lineTo, curveTo, rect,
63  drawArc, closePath), the transformation operators (transform,
64  untransform, translate), and the matrix stack operators (pushMatrix,
65  popMatrix) are admissible.  The path is drawn using drawPath, this
66  ends path construction mode.  Path construction operators cannot be
67  used in general mode.
68 
69  The graphics state for a path must be set before starting path
70  construction mode, that is, before calling newPath().
71 
72  Derived classes need to implement the doXXX functions for drawing
73  paths, images, and texts.  The transformation matrix has already been
74  applied to the coordinates passed to the doXXX functions.
75 */
76 
77 //! Constructor takes a (cascaded) style sheet, which is not owned.
78 /*! The initial graphics state contains all default attributes. */
Painter(const Cascade * style)79 Painter::Painter(const Cascade *style)
80 {
81   iCascade = style;
82   State state;
83   state.iStroke = Color(0,0,0);
84   state.iFill = Color(1000, 1000, 1000);
85   state.iPen = iCascade->find(EPen, Attribute::NORMAL()).number();
86   state.iDashStyle = "[]0"; // solid
87   state.iLineCap = style->lineCap();
88   state.iLineJoin = style->lineJoin();
89   state.iFillRule = style->fillRule();
90   state.iSymStroke = Color(0, 0, 0);
91   state.iSymFill = Color(1000, 1000, 1000);
92   state.iSymPen = Fixed(1);
93   state.iOpacity = Fixed(1);
94   state.iStrokeOpacity = Fixed(1);
95   state.iTiling = Attribute::NORMAL();
96   state.iGradient = Attribute::NORMAL();
97   iState.push_back(state);
98   iMatrix.push_back(Matrix()); // identity
99   iAttributeMap = nullptr;
100   iInPath = 0;
101 }
102 
103 //! Virtual destructor.
~Painter()104 Painter::~Painter()
105 {
106   // nothing
107 }
108 
109 //! Set a new attribute map.
setAttributeMap(const AttributeMap * map)110 void Painter::setAttributeMap(const AttributeMap *map)
111 {
112   iAttributeMap = map;
113 }
114 
115 //! Lookup a symbolic attribute
116 /*! Uses first the attribute map and then the stylesheet. */
lookup(Kind kind,Attribute sym) const117 Attribute Painter::lookup(Kind kind, Attribute sym) const
118 {
119   if (iAttributeMap && sym.isSymbolic())
120     return iCascade->find(kind, iAttributeMap->map(kind, sym));
121   else
122     return iCascade->find(kind,sym);
123 }
124 
125 //! Concatenate a matrix to current transformation matrix.
transform(const Matrix & m)126 void Painter::transform(const Matrix &m)
127 {
128   iMatrix.back() = matrix() * m;
129 }
130 
131 //! Reset transformation to original one, but with different origin/direction.
132 /*! This changes the current transformation matrix to the one set
133   before the first push operation, but maintaining the current origin.
134   Only the operations allowed in \a allowed are applied.
135 */
untransform(TTransformations trans)136 void Painter::untransform(TTransformations trans)
137 {
138   if (trans == ETransformationsAffine)
139     return;
140   Matrix m = matrix();
141   Vector org = m.translation();
142   Vector dx = Vector(m.a[0], m.a[1]);
143   // Vector dy = Vector(m.a[2], m.a[3]);
144   Linear m1(iMatrix.front().linear());
145   if (trans == ETransformationsRigidMotions) {
146     // compute what direction is transformed to dx by original matrix
147     Angle alpha = (m1.inverse() * dx).angle();
148     // ensure that (1,0) is rotated into this orientation
149     m1 = m1 * Linear(alpha);
150   }
151   iMatrix.back() = Matrix(m1, org);
152 }
153 
154 //! Concatenate a translation to current transformation matrix.
translate(const Vector & v)155 void Painter::translate(const Vector &v)
156 {
157   Matrix m;
158   m.a[4] = v.x;
159   m.a[5] = v.y;
160   iMatrix.back() = matrix() * m;
161 }
162 
163 //! Enter path construction mode.
newPath()164 void Painter::newPath()
165 {
166   assert(!iInPath);
167   iInPath = iState.size(); // save current nesting level
168   doNewPath();
169 }
170 
171 //! Start a new subpath.
moveTo(const Vector & v)172 void Painter::moveTo(const Vector &v)
173 {
174   assert(iInPath > 0);
175   doMoveTo(matrix() * v);
176 }
177 
178 //! Add line segment to current subpath.
lineTo(const Vector & v)179 void Painter::lineTo(const Vector &v)
180 {
181   assert(iInPath > 0);
182   doLineTo(matrix() * v);
183 }
184 
185 //! Add a Bezier segment to current subpath.
curveTo(const Vector & v1,const Vector & v2,const Vector & v3)186 void Painter::curveTo(const Vector &v1, const Vector &v2, const Vector &v3)
187 {
188   assert(iInPath > 0);
189   doCurveTo(matrix() * v1, matrix() * v2, matrix() * v3);
190 }
191 
192 //! Add an elliptic arc to current path.
193 /*! Assumes the current point is \a arc.beginp(). */
drawArc(const Arc & arc)194 void Painter::drawArc(const Arc &arc)
195 {
196   assert(iInPath > 0);
197   doDrawArc(arc);
198 }
199 
200 //! Add a rectangle subpath to the path.
201 /*! This is implemented in terms of moveTo() and lineTo(). */
rect(const Rect & re)202 void Painter::rect(const Rect &re)
203 {
204   moveTo(re.bottomLeft());
205   lineTo(re.bottomRight());
206   lineTo(re.topRight());
207   lineTo(re.topLeft());
208   closePath();
209 }
210 
211 //! Close the current subpath.
closePath()212 void Painter::closePath()
213 {
214   assert(iInPath > 0);
215   doClosePath();
216 }
217 
218 //! Save current graphics state.
219 /*! Cannot be called in path construction mode. */
push()220 void Painter::push()
221 {
222   assert(!iInPath);
223   State state = iState.back();
224   iState.push_back(state);
225   doPush();
226 }
227 
228 //! Restore previous graphics state.
229 /*! Cannot be called in path construction mode. */
pop()230 void Painter::pop()
231 {
232   assert(!iInPath);
233   iState.pop_back();
234   doPop();
235 }
236 
237 //! Save current transformation matrix.
pushMatrix()238 void Painter::pushMatrix()
239 {
240   iMatrix.push_back(matrix());
241 }
242 
243 //! Restore previous transformation matrix.
popMatrix()244 void Painter::popMatrix()
245 {
246   iMatrix.pop_back();
247 }
248 
249 //! Fill and/or stroke a path.
250 /*! As in PDF, a "path" can consist of several subpaths.  Whether it
251   is filled or stroked depends on \a mode.  */
drawPath(TPathMode mode)252 void Painter::drawPath(TPathMode mode)
253 {
254   assert(iInPath > 0);
255   doDrawPath(mode);
256   iInPath = 0;
257 }
258 
259 //! Render a bitmap.
260 /*! Assumes the transformation matrix has been set up to map the unit
261   square to the image area on the paper.
262 */
drawBitmap(Bitmap bitmap)263 void Painter::drawBitmap(Bitmap bitmap)
264 {
265   assert(!iInPath);
266   doDrawBitmap(bitmap);
267 }
268 
269 //! Render a text object.
270 /*! Stroke color is already set, and the origin is the lower-left
271   corner of the text box (not the reference point!). */
drawText(const Text * text)272 void Painter::drawText(const Text *text)
273 {
274   assert(!iInPath);
275   doDrawText(text);
276 }
277 
278 //! Render a symbol.
279 /*! The current coordinate system is already the symbol coordinate
280     system. If the symbol is parameterized, then sym-stroke, sym-fill,
281     and sym-pen are already set. */
drawSymbol(Attribute symbol)282 void Painter::drawSymbol(Attribute symbol)
283 {
284   assert(!iInPath);
285   doDrawSymbol(symbol);
286 }
287 
288 //! Add current path as clip path.
addClipPath()289 void Painter::addClipPath()
290 {
291   assert(iInPath > 0);
292   doAddClipPath();
293   iInPath = 0;
294 }
295 
296 // --------------------------------------------------------------------
297 
298 //! Set stroke color, resolving symbolic color and "sym-x" colors
setStroke(Attribute color)299 void Painter::setStroke(Attribute color)
300 {
301   assert(!iInPath);
302   if (color == Attribute::SYM_STROKE())
303     iState.back().iStroke = iState.back().iSymStroke;
304   else if (color == Attribute::SYM_FILL())
305     iState.back().iStroke = iState.back().iSymFill;
306   else
307     iState.back().iStroke = lookup(EColor, color).color();
308 }
309 
310 //! Set fill color, resolving symbolic color.
setFill(Attribute color)311 void Painter::setFill(Attribute color)
312 {
313   assert(!iInPath);
314   if (color == Attribute::SYM_STROKE())
315     iState.back().iFill = iState.back().iSymStroke;
316   else if (color == Attribute::SYM_FILL())
317     iState.back().iFill = iState.back().iSymFill;
318   else
319     iState.back().iFill = lookup(EColor, color).color();
320 }
321 
322 //! Set pen, resolving symbolic value.
setPen(Attribute pen)323 void Painter::setPen(Attribute pen)
324 {
325   assert(!iInPath);
326   if (pen == Attribute::SYM_PEN())
327     iState.back().iPen = iState.back().iSymPen;
328   else
329     iState.back().iPen = lookup(EPen, pen).number();
330 }
331 
332 //! Set dash style, resolving symbolic value.
setDashStyle(Attribute dash)333 void Painter::setDashStyle(Attribute dash)
334 {
335   assert(!iInPath);
336   iState.back().iDashStyle = lookup(EDashStyle, dash).string();
337 }
338 
339 //! Return dashstyle as a double sequence.
dashStyle(std::vector<double> & dashes,double & offset) const340 void Painter::dashStyle(std::vector<double> &dashes, double &offset) const
341 {
342   dashes.clear();
343   offset = 0.0;
344   String s = dashStyle();
345 
346   int i = s.find("[");
347   int j = s.find("]");
348   if (i < 0 || j < 0)
349     return;
350 
351   Lex lex(s.substr(i+1, j - i - 1));
352   while (!lex.eos())
353     dashes.push_back(lex.getDouble());
354   offset = Lex(s.substr(j+1)).getDouble();
355 }
356 
357 //! Set line cap.
358 /*! If \a cap is EDefaultCap, the current setting remains unchanged. */
setLineCap(TLineCap cap)359 void Painter::setLineCap(TLineCap cap)
360 {
361   assert(!iInPath);
362   if (cap != EDefaultCap)
363     iState.back().iLineCap = cap;
364 }
365 
366 //! Set line join.
367 /*! If \a join is EDefaultJoin, the current setting remains unchanged. */
setLineJoin(TLineJoin join)368 void Painter::setLineJoin(TLineJoin join)
369 {
370   assert(!iInPath);
371   if (join != EDefaultJoin)
372     iState.back().iLineJoin = join;
373 }
374 
375 //! Set fill rule (wind or even-odd).
376 /*! If the rule is EDefaultRule, the current setting remains unchanged. */
setFillRule(TFillRule rule)377 void Painter::setFillRule(TFillRule rule)
378 {
379   assert(!iInPath);
380   if (rule != EDefaultRule)
381     iState.back().iFillRule = rule;
382 }
383 
384 //! Set opacity.
setOpacity(Attribute opaq)385 void Painter::setOpacity(Attribute opaq)
386 {
387   assert(!iInPath);
388   iState.back().iOpacity = lookup(EOpacity, opaq).number();
389 }
390 
391 //! Set stroke opacity.
setStrokeOpacity(Attribute opaq)392 void Painter::setStrokeOpacity(Attribute opaq)
393 {
394   assert(!iInPath);
395   iState.back().iStrokeOpacity = lookup(EOpacity, opaq).number();
396 }
397 
398 //! Set tiling pattern.
399 /*! If \a tiling is not \c normal, resets the gradient pattern. */
setTiling(Attribute tiling)400 void Painter::setTiling(Attribute tiling)
401 {
402   assert(!iInPath);
403   iState.back().iTiling = tiling;
404   if (!tiling.isNormal())
405     iState.back().iGradient = Attribute::NORMAL();
406 }
407 
408 //! Set gradient fill.
409 /*! If \a grad is not \c normal, resets the tiling pattern. */
setGradient(Attribute grad)410 void Painter::setGradient(Attribute grad)
411 {
412   assert(!iInPath);
413   iState.back().iGradient = grad;
414   if (!grad.isNormal())
415     iState.back().iTiling = Attribute::NORMAL();
416 }
417 
418 //! Set symbol stroke color, resolving symbolic color.
setSymStroke(Attribute color)419 void Painter::setSymStroke(Attribute color)
420 {
421   assert(!iInPath);
422   if (color == Attribute::SYM_STROKE())
423     iState.back().iSymStroke = (++iState.rbegin())->iSymStroke;
424   else if (color == Attribute::SYM_FILL())
425     iState.back().iSymStroke = (++iState.rbegin())->iSymFill;
426   else
427     iState.back().iSymStroke = lookup(EColor, color).color();
428 }
429 
430 //! Set symbol fill color, resolving symbolic color.
setSymFill(Attribute color)431 void Painter::setSymFill(Attribute color)
432 {
433   assert(!iInPath);
434   if (color == Attribute::SYM_STROKE())
435     iState.back().iSymFill = (++iState.rbegin())->iSymStroke;
436   else if (color == Attribute::SYM_FILL())
437     iState.back().iSymFill = (++iState.rbegin())->iSymFill;
438   else
439     iState.back().iSymFill = lookup(EColor, color).color();
440 }
441 
442 //! Set symbol pen, resolving symbolic pen.
setSymPen(Attribute pen)443 void Painter::setSymPen(Attribute pen)
444 {
445   assert(!iInPath);
446   if (pen == Attribute::SYM_PEN())
447     iState.back().iSymPen = (++iState.rbegin())->iSymPen;
448   else
449     iState.back().iSymPen = lookup(EPen, pen).number();
450 }
451 
452 //! Set full graphics state at once.
setState(const State & state)453 void Painter::setState(const State &state)
454 {
455   iState.back() = state;
456 }
457 
458 // --------------------------------------------------------------------
459 
460 // Coordinate for bezier approximation for quarter circle.
461 const double BETA = 0.55228474983079334;
462 const double PI15 = IpePi + IpeHalfPi;
463 
464 //! Draw an arc of the unit circle of length \a alpha.
465 /*! PDF does not have an "arc" or "circle" primitive, so to draw an
466   arc, circle, or ellipse, Ipe has to translate it into a sequence of
467   Bezier curves.
468 
469   The approximation is based on the following: The unit circle arc
470   from (1,0) to (cos a, sin a) be approximated by a Bezier spline with
471   control points (1, 0), (1, beta) and their mirror images along the
472   line with slope a/2, where
473   beta = 4.0 * (1.0 - cos(a/2)) / (3 * sin(a/2))
474 
475   Ipe draws circles by drawing four Bezier curves for the quadrants,
476   and arcs by patching together quarter circle approximations with a
477   piece computed from the formula above.
478 
479   \a alpha is normalized to [0, 2 pi], and applied starting from the
480   point (1,0).
481 
482   The function generates a sequence of Bezier splines as calls to
483   curveTo.  It is assumed that the caller has already executed a
484   moveTo to the beginning of the arc at (1,0).
485 
486   This function may modify the transformation matrix.
487 */
drawArcAsBezier(double alpha)488 void Painter::drawArcAsBezier(double alpha)
489 {
490   // Vector p0(1.0, 0.0);
491   Vector p1(1.0, BETA);
492   Vector p2(BETA, 1.0);
493   Vector p3(0.0, 1.0);
494   Vector q1(-BETA, 1.0);
495   Vector q2(-1.0, BETA);
496   Vector q3(-1.0, 0.0);
497 
498   double begAngle = 0.0;
499   if (alpha > IpeHalfPi) {
500     curveTo(p1, p2, p3);
501     begAngle = IpeHalfPi;
502   }
503   if (alpha > IpePi) {
504     curveTo(q1, q2, q3);
505     begAngle = IpePi;
506   }
507   if (alpha > PI15) {
508     curveTo(-p1, -p2, -p3);
509     begAngle = PI15;
510   }
511   if (alpha >= IpeTwoPi) {
512     curveTo(-q1, -q2, -q3);
513   } else {
514     alpha -= begAngle;
515     double alpha2 = alpha / 2.0;
516     double divi = 3.0 * sin(alpha2);
517     if (divi == 0.0)
518       return;  // alpha2 is close to zero
519     double beta = 4.0 * (1.0 - cos(alpha2)) / divi;
520     Linear m = Linear(Angle(begAngle));
521 
522     Vector pp1(1.0, beta);
523     Vector pp2 = Linear(Angle(alpha)) * Vector(1.0, -beta);
524     Vector pp3 = Vector(Angle(alpha));
525 
526     curveTo(m * pp1, m * pp2, m * pp3);
527   }
528 }
529 
530 // --------------------------------------------------------------------
531 
532 //! Perform graphics state push on output medium.
doPush()533 void Painter::doPush()
534 {
535   // nothing
536 }
537 
538 //! Perform graphics state pop on output medium.
doPop()539 void Painter::doPop()
540 {
541   // nothing
542 }
543 
544 //! Perform new path operator.
doNewPath()545 void Painter::doNewPath()
546 {
547   // nothing
548 }
549 
550 //! Perform moveto operator.
551 /*! The transformation matrix has already been applied. */
doMoveTo(const Vector &)552 void Painter::doMoveTo(const Vector &)
553 {
554   // nothing
555 }
556 
557 //! Perform lineto operator.
558 /*! The transformation matrix has already been applied. */
doLineTo(const Vector &)559 void Painter::doLineTo(const Vector &)
560 {
561   // nothing
562 }
563 
564 //! Perform curveto operator.
565 /*! The transformation matrix has already been applied. */
doCurveTo(const Vector &,const Vector &,const Vector &)566 void Painter::doCurveTo(const Vector &, const Vector &, const Vector &)
567 {
568   // nothing
569 }
570 
571 //! Draw an elliptic arc.
572 /*! The default implementations calls drawArcAsBezier().  The
573   transformation matrix has not yet been applied to \a arc. */
doDrawArc(const Arc & arc)574 void Painter::doDrawArc(const Arc &arc)
575 {
576   pushMatrix();
577   transform(arc.iM);
578   if (arc.isEllipse()) {
579     moveTo(Vector(1,0));
580     drawArcAsBezier(IpeTwoPi);
581   } else {
582     transform(Linear(arc.iAlpha));
583     double alpha = Angle(arc.iBeta - arc.iAlpha).normalize(0.0);
584     drawArcAsBezier(alpha);
585   }
586   popMatrix();
587 }
588 
589 //! Perform closepath operator.
doClosePath()590 void Painter::doClosePath()
591 {
592   // nothing
593 }
594 
595 //! Actually draw the path.
doDrawPath(TPathMode)596 void Painter::doDrawPath(TPathMode)
597 {
598   // nothing
599 }
600 
601 //! Draw a bitmap.
doDrawBitmap(Bitmap)602 void Painter::doDrawBitmap(Bitmap)
603 {
604   // nothing
605 }
606 
607 //! Draw a text object.
doDrawText(const Text *)608 void Painter::doDrawText(const Text *)
609 {
610   // nothing
611 }
612 
613 //! Draw a symbol.
614 /*! The default implementation calls the draw method of the
615     object. Only PDF drawing overrides this to reuse a PDF XForm. */
doDrawSymbol(Attribute symbol)616 void Painter::doDrawSymbol(Attribute symbol)
617 {
618   const Symbol *sym = iAttributeMap ?
619     cascade()->findSymbol(iAttributeMap->map(ESymbol, symbol)) :
620     cascade()->findSymbol(symbol);
621   if (sym)
622     sym->iObject->draw(*this);
623 }
624 
625 //! Add a clip path
doAddClipPath()626 void Painter::doAddClipPath()
627 {
628   // nothing
629 }
630 // --------------------------------------------------------------------
631