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