Ipelets written in C++

As in Ipe 6, it is possible to write ipelets entirely in C++. Different from Ipe 6, however, the labels of the ipelet and its functions must now be specified in a short Lua wrapper with some boilerplate code. This Lua code will invoke your C++ methods.

# C++ ipelet framework

The C++ code is in a dynamically loaded library (DLL), that you place on Ipe's C++ ipelet path. The DLL has to be written in C++, and must export a function `newIpelet` that creates an object derived from the class `Ipelet` (defined in ipelet.h). Here is a minimal ipelet implementation:

    #include "ipelet.h"

    class MyIpelet : public ipe::Ipelet {
    public:
      virtual int ipelibVersion() const { return IPELIB_VERSION; }
      virtual bool run(int function, ipe::IpeletData *data, ipe::IpeletHelper *helper);
    };

    bool MyIpelet::run(int function, ipe::IpeletData *data, ipe::IpeletHelper *helper)
    {
      // this is where you do all the work
    }

    IPELET_DECLARE ipe::Ipelet *newIpelet()
    {
      return new MyIpelet;
    }

When the ipelet is executed, Ipe hands it a structure with some information about the document, in particular a pointer to the current page. The ipelet can examine the selected objects, and modify the page in any way it wishes. (It is not possible to modify the document outside the current page, as this would interfere with the undo stack). It can also request services from the Ipe application through the `IpeletHelper` object, for instance to display a message in the status bar, to pop up message boxes and to obtain input from the user.

The run method must return true if it modified the document page. This is used to create an item on the undo stack so that this change can be undone. If the run method returns false, then no undo stack item is created. In this case, the ipelet must not modify the page.

# The Lua wrapper

You need to provide a small Lua wrapper that declares the names of the ipelet and its methods, and that calls your C++ code when an ipelet method is invoked. This wrapper will look as follows:

    -- Lua wrapper for C++ ipelet "myipelet"

    label = "My Ipelet"

    about = "This ipelet is for explanation only"

    -- this variable will store the C++ ipelet when it has been loaded
    ipelet = false

    function run(ui, num)
      if not ipelet then ipelet = assert(loadIpelet("myipelet"))
      model:runIpelet(ipelet, num)
    end

    methods = { { label = "First function of my ipelet" },
                { label = "Second function of my ipelet" }
              }

If the ipelet contains only a single method, then the `methods` table is omitted.

The Lua wrapper needs to be placed in Ipe's ipelet directory. When Ipe starts up, it automatically loads all ipelets from this directory. Note that the wrapper above does not immediately load the C++ ipelet (using `loadIpelet`) when the Lua wrapper is loaded by Ipe, but only when the first method of the ipelet is called. This is considered good style.

# Passing parameters from Lua wrapper to the C++ code

Your Lua wrapper can include a table with key/value pairs that the C++ code can examine to set parameters or otherwise decide what to do without recompiling the C++ code.

The table is passed as an additional argument to model:runIpelet. You can retrieve the value for a given key using the ipe::IpeletHelper.getParameter method.

# An example ipelet

Kgon is a minimal ipelet that you can use as the basis for your own development. It defines only a single function, and makes no use of the function argument to `run`. It does show how to pass parameters from the Lua wrapper to the C++ code.

// --------------------------------------------------------------------
// Ipelet for creating regular k-gons
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2021 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 3 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 "ipelet.h"
#include "ipepath.h"
#include "ipepage.h"
using namespace ipe;
// --------------------------------------------------------------------
class KGonIpelet : public Ipelet {
public:
virtual int ipelibVersion() const { return IPELIB_VERSION; }
virtual bool run(int, IpeletData *data, IpeletHelper *helper);
};
// --------------------------------------------------------------------
bool KGonIpelet::run(int, IpeletData *data, IpeletHelper *helper)
{
Page *page = data->iPage;
int sel = page->primarySelection();
if (sel < 0) {
helper->message("No selection");
return false;
}
const Path *p = page->object(sel)->asPath();
if (p == 0 || p->shape().countSubPaths() != 1 ||
p->shape().subPath(0)->type() != SubPath::EEllipse) {
helper->message("Primary selection is not a circle");
return false;
}
String str = helper->getParameter("n"); // get default value from Lua wrapper
if (!helper->getString("Enter k (number of corners)", str))
return false;
int k = Lex(str).getInt();
if (k < 3 || k > 1000)
return false;
const Ellipse *e = p->shape().subPath(0)->asEllipse();
Matrix m = p->matrix() * e->matrix();
Vector center = m.translation();
Vector v = m * Vector(1,0);
double radius = (v - center).len();
Curve *sp = new Curve;
double alpha = 2.0 * IpePi / k;
Vector v0 = center + radius * Vector(1,0);
for (int i = 1; i < k; ++i) {
Vector v1 = center + radius * Vector(Angle(i * alpha));
sp->appendSegment(v0, v1);
v0 = v1;
}
sp->setClosed(true);
Shape shape;
shape.appendSubPath(sp);
Path *obj = new Path(data->iAttributes, shape);
page->append(ESecondarySelected, data->iLayer, obj);
helper->message("Created regular k-gon");
return true;
}
// --------------------------------------------------------------------
IPELET_DECLARE Ipelet *newIpelet()
{
return new KGonIpelet;
}
// --------------------------------------------------------------------
ipe::Angle
A double that's an angle.
Definition ipegeo.h:76
ipe::Curve
Subpath consisting of a sequence of CurveSegment's.
Definition ipeshape.h:156
ipe::Curve::appendSegment
void appendSegment(const Vector &v0, const Vector &v1)
Append a straight segment to the subpath.
Definition ipeshape.cpp:322
ipe::Curve::setClosed
void setClosed(bool closed)
Set whether subpath is closed or not.
Definition ipeshape.cpp:426
ipe::Ellipse
An ellipse subpath.
Definition ipeshape.h:116
ipe::Ellipse::matrix
Matrix matrix() const
Return matrix that transforms unit circle to the ellipse.
Definition ipeshape.h:122
ipe::IpeletHelper
Service provider for Ipelets.
Definition ipelet.h:52
ipe::IpeletHelper::getString
virtual bool getString(const char *prompt, String &str)=0
ipe::IpeletHelper::message
virtual void message(const char *msg)=0
Show a message in the status bar.
ipe::IpeletHelper::getParameter
virtual String getParameter(const char *key)=0
ipe::Ipelet
Abstract base class for Ipelets.
Definition ipelet.h:91
ipe::Lex
Lexical analyser. Seeded with a string.
Definition ipebase.h:177
ipe::Lex::getInt
int getInt()
Extract integer token (skipping whitespace).
Definition ipebase.cpp:492
ipe::Matrix
Homogeneous transformation in the plane.
Definition ipegeo.h:284
ipe::Matrix::translation
Vector translation() const
Return translation component.
Definition ipegeo.h:609
ipe::Object::matrix
const Matrix & matrix() const
Return transformation matrix.
Definition ipeobject.h:84
ipe::Object::asPath
virtual Path * asPath()
Return pointer to this object if it is an Path, nullptr otherwise.
Definition ipeobject.cpp:261
ipe::Page
An Ipe document page.
Definition ipepage.h:45
ipe::Page::append
void append(TSelect sel, int layer, Object *obj)
Append a new object.
Definition ipepage.cpp:556
ipe::Page::primarySelection
int primarySelection() const
Return index of primary selection.
Definition ipepage.cpp:748
ipe::Page::object
Object * object(int i)
Return object at index i.
Definition ipepage.h:147
ipe::Path
The path object (polylines, polygons, and generalizations).
Definition ipepath.h:42
ipe::Path::shape
const Shape & shape() const
Return shape of the path object.
Definition ipepath.h:123
ipe::Shape
A geometric shape, consisting of several (open or closed) subpaths.
Definition ipeshape.h:226
ipe::Shape::appendSubPath
void appendSubPath(SubPath *sp)
Append a SubPath to shape.
Definition ipeshape.cpp:936
ipe::Shape::countSubPaths
int countSubPaths() const
Return number of subpaths.
Definition ipeshape.h:250
ipe::Shape::subPath
const SubPath * subPath(int i) const
Return subpath.
Definition ipeshape.h:252
ipe::String
Strings and buffers.
Definition ipebase.h:81
ipe::SubPath::type
virtual Type type() const =0
Return type of this subpath.
ipe::SubPath::asEllipse
virtual const Ellipse * asEllipse() const
Return this object as an Ellipse, or nullptr if it's not an ellipse.
Definition ipeshape.cpp:581
ipe::Vector
Two-dimensional vector.
Definition ipegeo.h:96
ipe
Definition ipeattributes.cpp:53
ipe::IpeletData
Information provided to an ipelet when it is run.
Definition ipelet.h:81
ipe::IpeletData::iLayer
int iLayer
Definition ipelet.h:84
ipe::IpeletData::iAttributes
AllAttributes iAttributes
Definition ipelet.h:85
ipe::IpeletData::iPage
Page * iPage
Definition ipelet.h:82

The Lua wrapper would look like this:

----------------------------------------------------------------------
-- kgon ipelet description
----------------------------------------------------------------------
--[[
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2021 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 3 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.
--]]
label = "Regular k-gon"
about = [[
Constructs a regular k-gon from a circle.
This ipelet is part of Ipe.
]]
-- this variable will store the C++ ipelet when it has been loaded
ipelet = false
-- parameters for the C++ code
parameters = { n = "7" }
function run(model)
if not ipelet then ipelet = assert(ipe.Ipelet(dllname)) end
model:runIpelet(label, ipelet, 1, parameters)
end
-- define a shortcut for this function
shortcuts.ipelet_1_kgon = "Alt+Ctrl+K"
----------------------------------------------------------------------

# Compiling ipelets on Unix

The ipelet must be compiled as a shared library and must be linked with the Ipe library libipe.so. C++ mandates that it must be compiled with the same compiler that was used to compile Ipe. Have a look at the ipelet sources in the Ipe source distribution, and their makefiles for details on compiling them.

# Compiling ipelets on Windows

The ipelet must be compiled as a DLL and must be linked with the Ipe library ipe.dll. C++ mandates that it must be compiled with the same compiler that was used to compile Ipe. If you use the binary Ipe distribution for Windows, that means you have to use the g++-mingw-w64-x86-64 toolchain. Place the resulting kgon.dll in the ipelets subdirectory, and restart Ipe.

# Linking with external libraries

If you write an ipelet in C++, you probably want to link with some existing C++ library or framework such as CGAL, and the loader needs to find this framework when it loads the ipelet.

On MacOS, you would for instance have a setting like this to tell the loader where to find shared libraries:

    $ export DYLD_LIBRARY_PATH=$CGAL_DIR/lib

Unfortunately, OSX integrity protection makes it impossible to specify such a setting inside Ipe, in ipe.conf, or in Ipe's Info.plist file.

You can set the environment variable when you call Ipe from the command line, but when starting Ipe from the Finder this does not work.

One clean solution is to make sure the path of the shared library is hard-coded in the ipelet, using otool and install_name_tool on OSX.

A simpler solution is to make a dynamic link like this:

    $ ln -s $HOME/CGAL/build/4.13R/lib $HOME/lib

Since $HOME/lib is searched by dlopen, this will work. You can check which paths are searched when Ipe loads an ipelet by setting this environment variable (and running Ipe from the command line):

    $ export DYLD_PRINT_LIBRARIES=1