[Previous] [Up]
Go backward to How Ipe calls a user macro
Go up to Ipe user macros

Writing Ipe user macros

Writing you own Ipe user macros is no witchcraft. It is, however, a question of proper tools. It is normally not useful to write your own code for reading and writing the interface file. (Well, if you only have to write the interface file, the situation is a bit different--actually, the Iums to import Postscript drawings or bitmaps write the interface file directly. The format is pretty simple, after all). Therefore, Ipe comes with a little library iumio to read and write the interface file. You can link your code with this library.

  Ipe itself is written in C++, using the PLAGEO library (by Geert-Jan Giezeman) for the geometric primitives. PLAGEO might be useful for you as well if you want to write a Ium. (You can get PLAGEO from ftp.cs.ruu.nl:pub/SGI/GEO). But you can certainly do without it--the iumio library will work even if you only have a C compiler.

This chapter describes how to use iumio to write your own Ipe user macros. You are also encouraged to look at the source code of the standard Iums that come with Ipe. These were deliberately written in different styles to give you some help in all possible situations. Look at the Makefile to see how to compile the files gridalign.c, spline.C, and goodies.C, and how to link with iumio.c.

The directory ftp://ftp.cs.ruu.nl/pub/X11/Ipe/IpeUserMacros/ contains these and many other sources.

gridalign.c, for instance, is written in C and uses the simple-most version of iumio. You have to link such a program with iumio.o.

spline.C is written in C++ and uses the PLAGEO version of iumio. As you can see in the Makefile, you have to link with pliumio.o and with libplageo.

goodies.C is also written in C++, uses the PLAGEO version of iumio, and also a C++ class Transform supplied with Ipe. Transform implements planar affine transformations, and is useful if you want to handle ellipses (which are not circles). You have to link with tpliumio.o and transform.o to use this version.

You will often want to do user interaction from your Ipe user macro, to display a message or to query for a parameter. You can use the stdout and stderr streams for output, but you will not be able to use stdin, since the Ium is run as a background process. If you need to get some user input, you would need to pop up a window. Fortunately there is an easier way: Ipe can be told that a certain Ipe user macro needs a parameter. Before starting the Ium, Ipe will then pop up a little dialog window, prompt the user for the necessary information, and pass the string to the Ium as an additional argument. If the necessary piece of information is a file name (a typical case for functions that import or export drawings in foreign formats), Ipe will even show a file selector and allow the user to browse through the file system.

Another way to pass flags or parameters to a Ium are through an environment variable, or even by passing a text object to the Ium.

  Sometimes you will wish that you could have persistent variables--information that is saved between successive invocations of a Ium (the rotation angle in precise rotate is a good example). For the time being, I suggest the following standard: All persistent parameters are kept in a file .iumrc in the user's home directory. Every line of this file will contain an alpha-numeric keyword terminated by a colon. White space after the colon is ignored, and the rest of the line is a string representation of the parameter. Ipe user macros can read this file, update parameter values, and add new parameter lines. I suggest that the keyword used for a parameter contains the name of the Ium executable, so don't use alpha, but thales.alpha. Some code to support this system is included in the iumio library and described below. Furthermore, the calling mechanism in Ipe can be told to make the information that has been obtained from the user persistent.

Using iumio -- the C version

This version of the iumio library does not need any supporting libraries, and compiles with a C compiler. (You should link with iumio.o, see the Makefile). Your Ium source should look like this.

/*
 * my_glorious_ium.c
 */
 
#include "ium.h"
 
void main(int argc, char **argv)
{
  int arg;
  ium_begin(argc, argv);
 
  switch (ium_argument) {
  case 0:
    /* this is the first function */
    ... do something with the objects ...
    break;
  case 1:
    /* this is the second function */
    ... do something else with the objects ...
    break;
  }   
 
  ium_mode = ium_select_mode;
  ium_message = "error_or_success_message";
  ium_end();
}
  ium_begin tests the number of parameters, decodes the arguments and reads in the interface file. It sets the variables ium_argument, ipe_environment, and ium_input. Before exiting, you should set the variables ium_output, ium_mode, and ium_message, and call ium_end. (All these variables are initialized to "empty", so nothing can go wrong if you call ium_end without setting them all.)

  If you do not need to read the interface file, you can call ium_init instead of ium_begin. Of course, you cannot use ium_input or ipe_environment in that case.

  ipe_environment contains the current state of Ipe. It is a struct, defined as follows:

typedef struct _IpeEnvironment {
  IpeColor stroke, fill;        /* current stroke and fill colors  */
  unsigned short linestyle;     /* solid, dashed etc. as 16 bits   */
  float linewidth;              /* linewidth of stroke             */
  short arrow;                  /* two bits for two arrows         */
  float arsize;                 /* size of arrows                  */
  float marksize;               /* size of marks                   */
  float gridsize;               /* grid size                       */
  float snapangle;              /* snap angle                      */
  short marktype;               /* type of mark (1 .. 5)           */
  short font;                   /* font of text object             */
  float fontsize;               /* fontsize                        */
  bool  axisset;                /* is an axis system defined ?     */
  vertex origin;                /* if so, this is the origin       */
  float axisdir;                /*    and this the base direction  */
} IpeEnvironment;
  The entries reflect the settings of the various selectors in the Ipe window. IpeColor, and vertex are defined as follows.
typedef struct _IpeColor {
  float red, green, blue;
} IpeColor;
 
typedef int bool;
 
typedef struct { float x, y; } vertex;
  To specify the empty color, set the red component to -1.

There are predefined constants for the possible fonts, IPE_ROMAN, IPE_ITALIC, IPE_BOLD, and IPE_MATH.

ium_input is a pointer to a linked list of Ipe objects. The list order corresponds to the back to front order in Ipe. A list element looks like this.  

typedef struct _IpeObject {
  int type;                     /* type of this object             */
  bool primary;                 /* true if this is the primary sel.*/
  IpeColor stroke, fill;        /* stroke and fill color of object */
  unsigned short linestyle;     /* solid, dashed etc. as 16 bits   */
  float linewidth;              /* linewidth of stroke             */
  struct _IpeObject *next;      /* pointer to next object          */
 
  union W {
    Line   *line;
    Circle *circle;
    Mark   *mark;
    Text   *text;
    Arc    *arc;
    Bitmap *bitmap;
  } w ;
 
} IpeObject;
The object type is one of the predefined constants IPE_LINE, IPE_TEXT, IPE_CIRCLE, IPE_MARK, IPE_ARC, IPE_BITMAP, IPE_SPLINE, IPE_BEGINGROUP, and IPE_ENDGROUP. The meaning of the w field of the struct depends on the object type.

Here are the types for the different objects. If you have trouble understanding the specifications, refer to the Ipe input file description.

IPE_LINE and IPE_SPLINE describe polygonal and spline objects. Both use the w.line pointer:

typedef struct _Line {          /* also used for splines           */
  bool closed;                  /* true if closed curve (polygon)  */
  short arrow;                  /* two bits for two arrows         */
  float arsize;                 /* size of arrows                  */
  int n;                        /* number of vertices              */
  vertex *v;                    /* pointer to array of vertices    */
} Line;
IPE_TEXT describes text objects and minipages. It uses the w.text pointer:
typedef struct _Text {
  char *str;                    /* the string                      */
  short font;                   /* font                            */
  float fontsize;               /* LaTeX fontsize                  */
  vertex pos;                   /* position of text                */
  bool minipage;                /* true if text is minipage        */
  vertex ll, ur;                /* ll and ur vertex of bounding box*/
} Text;
IPE_MARK is for marks:
typedef struct _Mark {
  vertex pos;                   /* position                        */
  short type;                   /* type of mark (1 .. 5)           */
  float size;                   /* size of mark                    */
} Mark;
IPE_CIRCLE describes circles and ellipses.
typedef struct _Circle {
  vertex center;                /* center of circle                */
  float radius;                 /* radius of circle                */
  bool  ellipse;                /* is object an ellipse ?          */
  float tfm[4];                 /* tfm values from file            */
} Circle;
Here, ellipse is TRUE if the input object is not a circle. When creating an object, you can always set this to TRUE, even if the thing is circular, if you set tfm correctly. Ipe will understand what you mean. (You should not set it to FALSE if you want to write an ellipse, though!).

IPE_ARC is for circular arcs.

typedef struct _Arc {
  short arrow;                  /* two bits for two arrows         */
  float arsize;                 /* size of arrows                  */
  vertex center;                /* center of arc                   */
  float radius;                 /* radius of arc                   */
  float begangle, endangle;     /* two angles in radians           */
} Arc;
IPE_BITMAP is for bitmaps.
typedef struct _Bitmap {
  vertex ll, ur;                /* lower left, upper right corner  */
  short width, height;          /* no of bits in bitmap            */
  unsigned long *words;         /* pointer to width*height pixels  */
  bool in_color;                /* color bitmap ?                  */
} Bitmap;
Here, words points to width * height words containing the pixels in the format expected by lrectwrite. The section on bitmap input has more details.

Finally, IPE_BEGINGROUP and IPE_ENDGROUP indicate the beginning and end of a group object. They can be nested.

After processing, you should set ium_output to NULL or to a linked list of IpeObjects. (You need not set the primary field in the objects). You can set ium_message to point to a message string that Ipe will print in the message field:

ium_message = "cannot compute weird diagram of bitmap";
Furthermore, you should set ium_mode to one of the values IUM_SELECT_OLD, IUM_SELECT_NEW, IUM_SELECT_BOTH, IUM_REPLACE, or IUM_DELETE_OLD. This indicates what you want Ipe to do with your output. IUM_REPLACE means that Ipe should replace the original selection with your ium_output. (If you use this, your output must have the same number of elements as the input! A nice way to do this is to walk through the list ium_input, making modifications in place, and, after successful completion, to simply set ium_output to ium_input. See gridalign.c for an example.) In the other cases, the output is added at the front of the drawing, and ium_mode determines whether to select the original or the new objects (or both). If ium_mode is IUM_DELETE_OLD, the selection is deleted befor adding and selecting the new objects.

If you encounter a really fatal error, you can simply exit with non-zero exit status. Ipe will not even try to read your output.

If you encounter a non-fatal error (like, for instance, objects of illegal type in the selection), here is a clean way to transmit an error message, but no objects to Ipe:

if (error_condition) {
  ium_output = NULL;
  ium_message = "error_message";
  ium_mode = IUM_SELECT_OLD;
  ium_end();
}
  As mentioned before, you may sometimes wish to use persistent parameters stored in your .iumrc file. You can call iumrc_get and iumrc_put to access those parameters. iumrc_get returns a pointer to the parameter string for the given keyword if it exists in the .iumrc file, NULL otherwise. The file is written back by ium_end if any parameter has been changed using iumrc_put. Here is a short piece of code using these two functions:
char *iumrc_value, *new_value, buf[64];
iumrc_value = iumrc_get("goodies.rotate");
strcpy(buf, (iumrc_value ? iumrc_value : "30.0"));
new_value = fl_show_input("Enter angle of rotation", buf);
iumrc_put("goodies.rotate", new_value);
For simple situations where you just have to prompt the user for a string it is easier to let Ipe handle this using the -input or -file flag in connection with the -persist flag.

Using iumio -- the plageo version

To use the PLAGEO version of the iumio library, you need a C++ compiler and, of course, the PLAGEO library. Here, we assume familiarity with PLAGEO. You proceed as for the C version of iumio, but before including ium.h, you have to define IUM_PLAGEO as follows:
#include <plageo.h>
#define IUM_PLAGEO
#include "ium.h"
After compiling with C++, you have to link with pliumio.o and with the -lplageo option (see the Makefile ).

Furthermore, the following things have changed with respect to the C version: First of all, the type vertex is now a synonym for the class pl_vec. The Line structure has changed:

typedef struct _Line {         /* also used for splines           */
  bool closed;                  /* true if closed curve (polygon)  */
  short arrow;                  /* two bits for two arrows         */
  float arsize;                 /* size of arrows                  */
  pl_vecreray v;                /* vertices of polyline            */
} Line;
Likewise, the Arc structure has changed to
typedef struct _Arc {
  short arrow;                  /* two bits for two arrows         */
  float arsize;                 /* size of arrows                  */
  pl_arc arc;                   /* center, radius, two angles      */
} Arc;

Using the Transform class

If you want to correctly handle ellipses in all their glory, you will need to correctly deal with the tfm field in the Circle structure. If you have C++, and are using the PLAGEO version of iumio, you can use the class Transform. Set IUM_TRANSFORM before including ium.h as follows:

#include <plageo.h>
#define IUM_PLAGEO
#define IUM_TRANSFORM
#include "ium.h"
After compilation, link with tpliumio.o and with transform.o (see the Makefile).

With this option, the definition of the Circle structure has changed to:

typedef struct _Circle {
  Transform tfm;
} Circle;
Here, tfm is the planar affine transformation that maps the unit circle into the ellipse you want to describe. Transform is a class defined in "transform.h" as follows.
class Transform {
public:
  s_coord A[6];                         // transformation matrix
 
  Transform(void);
  Transform(const s_coord *);
  void apply(pl_vec&);
  void lapply(pl_vec&);                 // apply without translation
  pl_vec transl(void);                  // return translational part
  void stretch(const s_coord, const s_coord); // apply stretch to tfm
  void transform(const pl_rotra&);      // apply pl_rotra to tfm
  Transform inverse(void);              // return inverse transformation
  int is_similar(void);                 // true if affinely similar
  void premult(const Transform&);       // premultiply second matrix
};
The first constructor makes an identity transformation, the second one takes a pointer to an array of six s_coords.

[Previous] [Up]