// -*- C++ -*- // -------------------------------------------------------------------- // Ipe drawing interface // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipepainter.h" // -------------------------------------------------------------------- /*! \class IpePainter * \ingroup base * \brief Interface for drawing. IpePainter-derived classes are used for drawing to the screen and for generating PDF and Postscript output. The IpePainter maintains a stack of graphics states, which includes stroke and fill color, line width, dash style, miter limit, line cap and line join, and the current transformation matrix. The IpePainter class takes care of maintaining this stack, and setting of the attributes in the current graphics state. Setting an attribute with a symbolic value is resolved immediately using the IpeStyleSheet attached to the IpePainter, so calling the Stroke() or Fill() methods of IpePainter will return the current absolute color. It's okay to set symbolic attributes that the stylesheet does not define - they are set to a default absolute value (normal, black, solid, etc.). A null color is drawn as if it was void. Ipe objects exploit this: only group objects ever need to explicitly contain a 'void' color. A null dash style is drawn as solid. A null line width is drawn as whatever is the standard of the drawing medium. The painter is either in "general" or in "path construction" mode. The NewPath() member starts path construction mode. In this mode, only the path construction operators (MoveTo, LineTo, CurveTo, Rect, DrawArc, DrawEllipse, ClosePath), the transformation operators (Transform, Untransform, Translate), and the stack operators (Push, Pop) are admissible. (Pushs and pops must balance inside path construction. Note that pushs and pops inside path construction only affect the current (painter-internal) tranformation matrix, they do not generate Postscript gsave/grestore operators.) The path is drawn using DrawPath, this ends path construction mode. Path construction operators cannot be used in general mode. Derived classes need to implement the DoXXX functions for drawing paths, images, and texts. */ //! Constructor takes a (cascaded) style sheet, which is not owned. /*! The initial graphics state contains null attributes and default line cap and line join. */ IpePainter::IpePainter(const IpeStyleSheet *style) { iStyleSheet = style; State state; iState.push_back(state); iInPath = 0; } //! Virtual destructor. IpePainter::~IpePainter() { // nothing } //! Concatenate a matrix to current transformation matrix. void IpePainter::Transform(const IpeMatrix &m) { iState.back().iMatrix = iState.back().iMatrix * m; } //! Reset transformation to original one, but with different origin/direction. /*! This changes the current transformation matrix to the one set before the first Push operation, but maintaining the current origin. If \a direct is \c true, then the x-direction is also kept. */ void IpePainter::Untransform(bool direct) { IpeVector org = Matrix().Translation(); IpeVector dir = Matrix().Linear() * IpeVector(1,0); iState.back().iMatrix = IpeMatrix(iState.front().iMatrix.Linear()); if (direct) { // compute what direction is transformed to dir by original matrix IpeAngle alpha = (iState.back().iMatrix.Inverse() * dir).Angle(); // ensure that (1,0) is rotated into this orientation iState.back().iMatrix = iState.back().iMatrix * IpeLinear(alpha); } iState.back().iMatrix.iA[4] = org.iX; iState.back().iMatrix.iA[5] = org.iY; } //! Concatenate a translation to current transformation matrix. void IpePainter::Translate(const IpeVector &v) { IpeMatrix m; m.iA[4] = v.iX; m.iA[5] = v.iY; iState.back().iMatrix = iState.back().iMatrix * m; } //! Enter path construction mode. void IpePainter::NewPath() { assert(!iInPath); iInPath = iState.size(); // save current nesting level DoNewPath(); } //! Start a new subpath. void IpePainter::MoveTo(const IpeVector &v) { assert(iInPath > 0); DoMoveTo(v); } //! Add line segment to current subpath. void IpePainter::LineTo(const IpeVector &v) { assert(iInPath > 0); DoLineTo(v); } //! Add a Bezier segment to current subpath. void IpePainter::CurveTo(const IpeVector &v1, const IpeVector &v2, const IpeVector &v3) { assert(iInPath > 0); DoCurveTo(v1, v2, v3); } //! Add a rectangle subpath to the path. /*! Has a default implementation in terms of MoveTo and LineTo, but derived classes can reimplement for efficiency. */ void IpePainter::Rect(const IpeRect &re) { MoveTo(re.Min()); LineTo(re.BottomRight()); LineTo(re.Max()); LineTo(re.TopLeft()); ClosePath(); } //! Close the current subpath. void IpePainter::ClosePath() { assert(iInPath > 0); DoClosePath(); } //! Save current graphics state. void IpePainter::Push() { State state = iState.back(); iState.push_back(state); if (!iInPath) DoPush(); } //! Restore previous graphics state. void IpePainter::Pop() { assert(int(iState.size()) > IpeMax(1, iInPath)); iState.pop_back(); if (!iInPath) DoPop(); } //! Fill and/or stroke a path (depending on color). /*! As in PDF, an "path" can consist of several subpaths. */ void IpePainter::DrawPath() { // check that pushs and pops next inside path construction assert(iInPath == int(iState.size())); DoDrawPath(); iInPath = 0; } //! Render a bitmap. /*! Assumes the transformation matrix has been set up to map the unit square to the image area on the paper. */ void IpePainter::DrawBitmap(IpeBitmap bitmap) { assert(!iInPath); DoDrawBitmap(bitmap); } //! Render a text object. /*! Stroke color is already set, and the origin is the lower-left corner of the text box. */ void IpePainter::DrawText(const IpeText *text) { assert(!iInPath); DoDrawText(text); } //! Set stroke color, resolving symbolic color. void IpePainter::SetStroke(IpeAttribute color) { iState.back().iStroke = iStyleSheet->Find(color); } //! Set fill color, resolving symbolic color. void IpePainter::SetFill(IpeAttribute color) { iState.back().iFill = iStyleSheet->Find(color); } //! Set line width, resolving symbolic value. void IpePainter::SetLineWidth(IpeAttribute wid) { iState.back().iLineWidth = iStyleSheet->Find(wid); } //! Set dash style, resolving symbolic value. void IpePainter::SetDashStyle(IpeAttribute dash) { iState.back().iDashStyle = iStyleSheet->Find(dash); } //! Set line cap. void IpePainter::SetLineCap(IpeAttribute cap) { iState.back().iLineCap = cap; } //! Set line join. void IpePainter::SetLineJoin(IpeAttribute join) { iState.back().iLineJoin = join; } //! Set wind rule (wind or even-odd). void IpePainter::SetWindRule(IpeAttribute rule) { iState.back().iWindRule = rule; } //! Set font size of text objects. /*! Paradoxically, this isn't actually used to render text, but for saving Ipegroup objects! Text goes through the IpeLatex interface, and the visitor that scans for text objects and writes them to the Latex source file finds the text size information itself. */ void IpePainter::SetTextSize(IpeAttribute size) { iState.back().iTextSize = iStyleSheet->Find(size); } //! Set size of mark objects. void IpePainter::SetMarkSize(IpeAttribute size) { iState.back().iMarkSize = iStyleSheet->Find(size); } //! Set shape of mark objects. void IpePainter::SetMarkShape(int shape) { iState.back().iMarkShape = shape; } // Coordinate for bezier approximation for quarter circle. const double BETA = 0.55228474983079334; const double PI15 = IpePi + IpeHalfPi; //! Draw the unit circle. /*! PDF does not have an "arc" or "circle" primitive, so to draw an arc, circle, or ellipse, Ipe has to translate it into a sequence of Bezier curves. The approximation is based on the following: The unit circle arc from (1,0) to (cos a, sin a) be approximated by a Bezier spline with control points (1, 0), (1, beta) and their mirror images along the line with slope a/2, where beta = 4.0 * (1.0 - cos(a/2)) / (3 * sin(a/2)) Ipe draws circles by drawing four Bezier curves for the quadrants, and arcs by patching together quarter circle approximations with a piece computed from the formula above. This does not modify the transformation matrix. The path is generated as a sequence MoveTo .. CurveTo .. ClosePath operators, but is not actually drawn (DrawPath is not called). */ void IpePainter::DrawEllipse() { IpeVector p0(1.0, 0.0); IpeVector p1(1.0, BETA); IpeVector p2(BETA, 1.0); IpeVector p3(0.0, 1.0); IpeVector q1(-BETA, 1.0); IpeVector q2(-1.0, BETA); IpeVector q3(-1.0, 0.0); MoveTo(p0); CurveTo(p1, p2, p3); CurveTo(q1, q2, q3); CurveTo(-p1, -p2, -p3); CurveTo(-q1, -q2, -q3); ClosePath(); } //! Draw an arc of the unit circle of length \a alpha. /*! \a alpha is normalized to [0, 2 pi], and applied starting from the point (1,0). The function works by generating a sequence of Bezier splines (see DrawEllipse for details of the transformation). It only generates calls to CurveTo. It is assumed that the caller has already executed a MoveTo to the beginning of the arc at (1,0). This function may modify the transformation matrix. */ void IpePainter::DrawArc(double alpha) { // IpeVector p0(1.0, 0.0); IpeVector p1(1.0, BETA); IpeVector p2(BETA, 1.0); IpeVector p3(0.0, 1.0); IpeVector q1(-BETA, 1.0); IpeVector q2(-1.0, BETA); IpeVector q3(-1.0, 0.0); alpha = IpeAngle(alpha).Normalize(0.0); double begAngle = 0.0; if (alpha > IpeHalfPi) { CurveTo(p1, p2, p3); begAngle = IpeHalfPi; } if (alpha > IpePi) { CurveTo(q1, q2, q3); begAngle = IpePi; } if (alpha > PI15) { CurveTo(-p1, -p2, -p3); begAngle = PI15; } alpha -= begAngle; double alpha2 = alpha / 2.0; double divi = 3.0 * sin(alpha2); if (divi == 0.0) return; // alpha2 is close to zero double beta = 4.0 * (1.0 - cos(alpha2)) / divi; IpeLinear m = IpeLinear(IpeAngle(begAngle)); IpeVector pp1(1.0, beta); IpeVector pp2 = IpeLinear(IpeAngle(alpha)) * IpeVector(1.0, -beta); IpeVector pp3 = IpeVector(IpeAngle(alpha)); CurveTo(m * pp1, m * pp2, m * pp3); } // -------------------------------------------------------------------- //! Perform push on output medium (if necessary). /*! This is not called when client uses Push during path construction! */ void IpePainter::DoPush() { // nothing } //! Perform pop on output medium (if necessary). /*! This is not called when client uses Pop during path construction! */ void IpePainter::DoPop() { // nothing } //! Perform new path operator. void IpePainter::DoNewPath() { // nothing } //! Perform moveto operator. void IpePainter::DoMoveTo(const IpeVector &) { // nothing } //! Perform lineto operator. void IpePainter::DoLineTo(const IpeVector &) { // nothing } //! Perform curveto operator. void IpePainter::DoCurveTo(const IpeVector &, const IpeVector &, const IpeVector &) { // nothing } //! Perform closepath operator. void IpePainter::DoClosePath() { // nothing } //! Actually draw the path. void IpePainter::DoDrawPath() { // nothing } //! Draw a bitmap. void IpePainter::DoDrawBitmap(IpeBitmap) { // nothing } //! Draw a text object. void IpePainter::DoDrawText(const IpeText *) { // nothing } // --------------------------------------------------------------------