// -------------------------------------------------------------------- // The Ipe object type // -------------------------------------------------------------------- /* 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. */ /*! \mainpage The Ipe library (Ipelib) documentation The Ipe program is a relatively thin layer on top of the "Ipe library" (Ipelib). Ipelib provides the geometric primitives and implements all the geometric objects that appear in Ipe. Most tasks related to modifying an Ipe document are actually performed by Ipelib. Ipelib can easily be reused by other programs to read, write, and modify Ipe documents. It requires only the standard C++ library (including the STL), and the zlib compression library. Authors of ipelets (plug-ins for Ipe) will need this documentation, as Ipelets have to be linked with Ipelib. */ // -------------------------------------------------------------------- /*! \defgroup obj Ipe Objects \brief The Ipe object model. This module deals with the actual objects inside an Ipe document. All Ipe objects are derived from IpeObject. */ /*! \defgroup ipelet The Ipelet interface \brief Implementation of Ipe plugins. Ipelets are dynamically loaded plugins for Ipe. An Ipelet contains a single class derived from Ipelet. It can use services of the Ipe program through an IpeletHelper object. */ /*! \defgroup doc Ipe Document \brief The classes managing an Ipe document. The main class, IpeDocument, represents an entire Ipe document, and allows you to load, save, access, and modify such a document. Various classes correspond to pages and layers. */ #include "ipegeo.h" #include "ipecolor.h" #include "ipeobj.h" #include "ipepainter.h" // -------------------------------------------------------------------- /*! \class IpeAllAttributes \ingroup attr \brief Collection of all object attributes. */ //! Constructor makes all null attributes. IpeAllAttributes::IpeAllAttributes() { iForwardArrow = false; iBackwardArrow = false; iTransformable = false; iMarkShape = 1; } /*! \class IpeObject \ingroup obj \brief Base class for all Ipe objects, composite or leaf. All Ipe objects are derived from this class. It provides functionality common to all objects, and carries the standard attributes. All IpeObject's provide a constant time copy constructor (and a virtual IpeObject::Clone() method). Objects of non-constant size realize this by separating the implementation and using reference counting. In particular, copying a composite objects does not create new copies of the components. The common attributes are stroke and fill color, line style and width, and the transformation matrix for coordinates. */ //! Construct from XML stream. IpeObject::IpeObject(IpeRepository *rep, const IpeXmlAttributes &attr) { IpeString str; if (attr.Has("stroke", str)) iStroke = rep->MakeColor(str); if (attr.Has("matrix", str)) iMatrix = IpeMatrix(str); } /*! Create object by taking stroke color from \a attr and setting identity matrix. */ IpeObject::IpeObject(const IpeAllAttributes &attr) { iStroke = attr.iStroke; } /*! Create object with null stroke and identity matrix. */ IpeObject::IpeObject() { // nothing } //! Copy constructor. IpeObject::IpeObject(const IpeObject &rhs) { iStroke = rhs.iStroke; iMatrix = rhs.iMatrix; } //! Pure virtual destructor. IpeObject::~IpeObject() { // nothing } //! Write layer, stroke and matrix to XML stream. void IpeObject::SaveAttributesAsXml(IpePainter &painter, IpeStream &stream, IpeString layer) const { const IpeRepository *rep = painter.StyleSheet()->Repository(); if (!layer.empty()) stream << " layer=\"" << layer << "\""; // colors are not applied if they are already set in the painter // furthermore, void doesn't need to be saved if (painter.Stroke().IsNull() && !iStroke.IsNullOrVoid()) stream << " stroke=\"" << rep->String(iStroke) << "\""; if (!iMatrix.IsIdentity()) stream << " matrix=\"" << iMatrix << "\""; } //! Return pointer to this object if it is an IpeGroup, 0 otherwise. IpeGroup *IpeObject::AsGroup() { return 0; } //! Return pointer to this object if it is an IpeFillable, 0 otherwise. IpeFillable *IpeObject::AsFillable() { return 0; } //! Return pointer to this object if it is an IpeText, 0 otherwise. IpeText *IpeObject::AsText() { return 0; } //! Return pointer to this object if it is an IpeMark, 0 otherwise. IpeMark *IpeObject::AsMark() { return 0; } //! Return pointer to this object if it is an IpePath, 0 otherwise. IpePath *IpeObject::AsPath() { return 0; } //! Return pointer to this object if it is an IpeImage , 0 otherwise. IpeImage *IpeObject::AsImage() { return 0; } //! Return pointer to this object if it is an IpeRef, 0 otherwise. IpeReference *IpeObject::AsReference() { return 0; } //! Set stroke color. void IpeObject::SetStroke(IpeAttribute stroke) { iStroke = stroke; } //! Set the transformation matrix. /*! Don't use this on the IpeObject in an IpePgObject, because it wouldn't invalidate its bounding box. Call IpePgObject::Transform instead. */ void IpeObject::SetMatrix(const IpeMatrix &matrix) { iMatrix = matrix; } //! Extend \a box to include the graphic representation of the object. /*! Unlike AddToBBox, this function takes into account the graphic representation of the object, that is, its line width, arrow size, mark size, etc. The default implementation calls AddToBBox. */ void IpeObject::AddToGraphicBBox(IpeRect &box, IpeStyleSheet *, IpeMatrix &m) const { AddToBBox(box, m); } //! Check all symbolic attributes. void IpeObject::CheckStyle(const IpeStyleSheet *sheet, IpeAttributeSeq &seq) const { CheckSymbol(iStroke, sheet, seq); } //! Snap to nearby vertex. /*! If distance between \a mouse and \a v is less than \a bound, set \a pos to \a v and \a bound to the distance, and return \c true. */ bool IpeObject::SnapVertex(const IpeVector &mouse, const IpeVector &v, IpeVector &pos, double &bound) { double d = (mouse - v).Len(); if (d < bound) { pos = v; bound = d; return true; } return false; } void IpeObject::CheckSymbol(IpeAttribute attr, const IpeStyleSheet *sheet, IpeAttributeSeq &seq) { if (attr.IsSymbolic() && !sheet->Find(attr)) { IpeAttributeSeq::const_iterator it = std::find(seq.begin(), seq.end(), attr); if (it == seq.end()) seq.push_back(attr); } } //! Compute boundary snapping position for transformed object. /*! Looks only for positions closer than \a bound. If successful, modify \a pos and \a bound. The default implementation does nothing. */ void IpeObject::SnapBnd(const IpeVector &/* mouse */, const IpeMatrix &/* m */, IpeVector &/* pos */, double &/* bound */) const { // nothing } // -------------------------------------------------------------------- /*! \class IpeFillable \ingroup obj \brief Base class for IpeFillable's with fill color and line style. */ //! Construct from XML stream. IpeFillable::IpeFillable(IpeRepository *rep, const IpeXmlAttributes &attr) : IpeObject(rep, attr) { IpeString str; if (attr.Has("fill", str)) iFill = rep->MakeColor(str); if (attr.Has("dash", str)) iDashStyle = rep->MakeDashStyle(str); if (attr.Has("pen", str)) iLineWidth = rep->MakeScalar(IpeAttribute::ELineWidth, str); if (attr.Has("cap", str)) iStrokeStyle.SetCap(IpeLex(str).GetInt()); if (attr.Has("join", str)) iStrokeStyle.SetJoin(IpeLex(str).GetInt()); if (attr.Has("fillrule", str)) iStrokeStyle.SetWindRule(str == "wind"); } /*! Create object by taking attributes from \a attr and setting identity matrix. */ IpeFillable::IpeFillable(const IpeAllAttributes &attr) : IpeObject(attr) { iFill = attr.iFill; iDashStyle = attr.iDashStyle; iLineWidth = attr.iLineWidth; } //! Create object with all null attributes. IpeFillable::IpeFillable() : IpeObject() { // nothing } //! Copy constructor. IpeFillable::IpeFillable(const IpeFillable &rhs) : IpeObject(rhs) { iFill = rhs.iFill; iLineWidth = rhs.iLineWidth; iDashStyle = rhs.iDashStyle; iStrokeStyle = rhs.iStrokeStyle; } //! Write common attributes to XML stream. void IpeFillable::SaveFillAttributesAsXml(IpePainter &painter, IpeStream &stream) const { const IpeRepository *rep = painter.StyleSheet()->Repository(); // colors are not applied if they are already set in the painter // furthermore, void doesn't need to be saved if (!painter.Fill() && !iFill.IsNullOrVoid()) stream << " fill=\"" << rep->String(iFill) << "\""; if (!painter.DashStyle() && !iDashStyle.IsNullOrSolid()) stream << " dash=\"" << rep->String(iDashStyle) << "\""; if (!painter.LineWidth() && iLineWidth) stream << " pen=\"" << rep->String(iLineWidth) << "\""; if (!painter.LineCap() && iStrokeStyle.Cap()) stream << " cap=\"" << iStrokeStyle.Cap().Index() << "\""; if (!painter.LineJoin() && iStrokeStyle.Join()) stream << " join=\"" << iStrokeStyle.Join().Index() << "\""; if (!painter.WindRule() && iStrokeStyle.WindRule()) { if (iStrokeStyle.WindRule().Index()) stream << " fillrule=\"wind\""; else stream << " fillrule=\"eofill\""; } } //! Return pointer to this object. IpeFillable *IpeFillable::AsFillable() { return this; } //! Save graphics state, and set new attributes. /*! Color, line width, and dash style are only set if the current value in the graphics state is null. Line cap, line join, and miter limit are only set if the value to be set is not the default. (I.e. a default value means to inherit the parent value.) Don't forget to pop the graphics state after doing your drawing! */ void IpeFillable::ApplyAttributes(IpePainter &painter) const { painter.Push(); if (!painter.Stroke()) painter.SetStroke(Stroke()); if (!painter.Fill()) painter.SetFill(iFill); if (!painter.DashStyle()) painter.SetDashStyle(iDashStyle); if (!painter.LineWidth()) painter.SetLineWidth(iLineWidth); if (!painter.LineCap()) painter.SetLineCap(iStrokeStyle.Cap()); if (!painter.LineJoin()) painter.SetLineJoin(iStrokeStyle.Join()); if (!painter.WindRule()) painter.SetWindRule(iStrokeStyle.WindRule()); painter.Transform(Matrix()); } //! Set fill color. void IpeFillable::SetFill(IpeAttribute fill) { iFill = fill; } //! Set line width. void IpeFillable::SetLineWidth(IpeAttribute lw) { iLineWidth = lw; } //! Set dash style. void IpeFillable::SetDashStyle(IpeAttribute dash) { iDashStyle = dash; } //! Set line cap and join. void IpeFillable::SetStrokeStyle(IpeStrokeStyle attr) { iStrokeStyle = attr; } //! Check all symbolic attributes. void IpeFillable::CheckStyle(const IpeStyleSheet *sheet, IpeAttributeSeq &seq) const { IpeObject::CheckStyle(sheet, seq); CheckSymbol(iFill, sheet, seq); CheckSymbol(iDashStyle, sheet, seq); CheckSymbol(iLineWidth, sheet, seq); } // -------------------------------------------------------------------- /*! \class IpePgObject \ingroup obj \brief A (selectable) object on an IpePage. Adds selection status and layer to IpeObject. */ //! Copy constructor clones object. IpePgObject::IpePgObject(const IpePgObject &rhs) : iSelect(rhs.iSelect), iLayer(rhs.iLayer) { iObject = rhs.iObject->Clone(); } IpePgObject &IpePgObject::operator=(const IpePgObject &rhs) { if (this != &rhs) { delete iObject; iSelect = rhs.iSelect; iLayer = rhs.iLayer; iObject = rhs.iObject->Clone(); } return *this; } //! Destructor. IpePgObject::~IpePgObject() { delete iObject; } //! Replace the object. void IpePgObject::ReplaceObject(IpeObject *obj) { delete iObject; iObject = obj; InvalidateBBox(); } //! Return distance between object and \a v. /*! But may just return \a bound if the distance is larger. This function uses the cached bounded box, and is faster than calling IpeObject::Distance directly. */ double IpePgObject::Distance(const IpeVector &v, double bound) const { if (BBox().CertainClearance(v, bound)) return bound; return iObject->Distance(v, IpeMatrix(), bound); } //! Transform the object. /*! Use this function instead of calling IpeObject::SetMatrix directly, as the latter doesn't invalidate the cached bounding box. */ void IpePgObject::Transform(const IpeMatrix &m) { InvalidateBBox(); iObject->SetMatrix(m * iObject->Matrix()); } //! Invalidate the bounding box (the object is somehow changed). void IpePgObject::InvalidateBBox() const { iBBox = IpeRect(); } //! Return a bounding box for the object. /*! IpePgObject caches the box the first time it is computed. Make sure you call IpePgObject::Transform instead of IpeObject::SetMatrix, as the latter would not invalidate the bounding box. */ IpeRect IpePgObject::BBox() const { if (iBBox.IsEmpty()) iObject->AddToBBox(iBBox, IpeMatrix()); return iBBox; } //! Compute possible vertex snapping position. /*! Looks only for positions closer than \a bound. If successful, modifies \a pos and \a bound. */ void IpePgObject::SnapVtx(const IpeVector &mouse, IpeVector &pos, double &bound) const { if (BBox().CertainClearance(mouse, bound)) return; iObject->SnapVtx(mouse, IpeMatrix(), pos, bound); } //! Compute possible boundary snapping position. /*! Looks only for positions closer than \a bound. If successful, modifies \a pos and \a bound. */ void IpePgObject::SnapBnd(const IpeVector &mouse, IpeVector &pos, double &bound) const { if (BBox().CertainClearance(mouse, bound)) return; iObject->SnapBnd(mouse, IpeMatrix(), pos, bound); } // --------------------------------------------------------------------