CD - Canvas Draw

Internal Architecture


Apart from the several drivers, the CD library is composed of a few modules, the public header files cd.h and wd.h, those which implement the functions independently from drivers, cd.c and wd.c, and the header file cdprivat.h, apart from some other modules which implement non-exported specific functions. Such modules are totally independent from the implemented drivers, as well as every driver independs from one another, unless there is an intentional dependency.


Since the drivers independ from one another, we could create a library for each of them. For the drivers provided with CD it was easy to include them in their own library, thus simplifying the application's linking process. Note: Internally, the drivers are called "context".

In order to establish this dependency, when creating a canvas in a given driver the user must specify the driver to be used. This specification is done by means of a macro which is actually a function with no parameter, which passes the function table from that driver to the canvas creation function. For instance:

CD_PS => cdContext* cdContextPS();
cdCreateCanvas(CD_PS, ""); => novo_canvas->cdLine = context->cdLine

Since each primitive is called without the canvas as a parameter, an active canvas is assumed. That is, after the canvas is created it must be activated in order to be used. Therefore, we have to internally maintain an object containing the active canvas.

When there is a call to a function, the active canvas function is called, which is actually the driver function from where that canvas was created. For example:

cdLine => canvas_ativo->cdLine => context->cdLine


The control part has only 3 structures. Two of them are private structures which correspond to the cd.h public structures: cdPrivateContext and cdPrivateCanvas. The other one is only intern, used for the WD functions: wdCanvas. The cdPrivateContext structure is used only by the drivers; cdPrivateCanvas contains the function table and the values of all attributes which can be queried.

The drivers need not implement all functions from the function table, because those which are not implemented for not making sense in that driver will not run without generating an error for the  user.

Each driver has a private internal canvas structure. Apart from this structure, they have to define the cdContext structure to be returned by function cdContextXX() (where XX varies according to the driver, as described previously). Thus the drivers must also define a cdPrivateContext structure to be included into this driver's cdContext structure.

The table below illustrates this mechanism:

typedef struct _cdContext
  void *ctx;   =============>
} cdContext;
typedef struct _cdPrivateContext
  void* (*CreateCanvas)(cdCanvas* canvas, void *data);
  int  (*Play)(int xmin, int xmax, int ymin, int ymax, void *data);
  int  (*RegisterCallback)(int cb, cdCallback func);
} cdPrivateContext;
typedef struct _cdCanvas
  void *cnv;   =============>
} cdCanvas;
typedef struct _cdPrivateCanvas
  void (*Line)(int x1, int y1, int x2, int y2);
  void (*Rect)(int xmin, int xmax, int ymin, int ymax);
  void (*Box)(int xmin, int xmax, int ymin, int ymax);

  int mark_type, mark_size;
  int line_style, line_width;
  int interior_style, hatch_style;

  void* wd_canvas;       =============>  wdCanvas
  void* context_canvas;  =============>  cdCanvasXX
  void* context;         =============>  cdContext
} cdPrivateCanvas;


To simplify driver administration, the context structure's linking is done as follows:

/* In the header file */
#define CD_METAFILE cdContextMetafile()
cdContext* cdContextMetafile(void)

/* In the implementation file */
static cdPrivateContext private_context_metafile =

static cdContext context_metafile =

cdContext* cdContextMetafile(void)
  return &context_metafile;

In CDLua, in order to create a new driver, one must use the internal function cdluaAddContext. All predefined drivers are also included this way. A static structure cdContextLUA is created containing all necessary information, and the driver initialization function, as in cdluaiup_open, calls function cdluaAddContext and registers values, if necessary. See, for instance, CDLUAMF.C, which illustrates this process.


The query mechanism of an attribute is done still in the control part and does not reach the driver. That is, the drivers do not need to focus on the query mechanism and can use cdPrivateCanvas relative to the active canvas at any moment. Due to this fact, the attributes which are modified several times for the same value are not updated in the drivers, thus saving processing. Similarly, if an attribute modification in a driver was not successful, its value is not updated. Nevertheless, the fact that a driver does not implement the attribute's modification function does not mean that it rejects that attribute - the driver just does not need to do anything with this attribute on that moment and will query it later, before drawing the primitive.

The creation of customized attributes for each driver is made generically, using string-like attributes. A structure with the attribute's name and its set and get functions must be declared, as in the example below:

static void set_fill_attrib(char* data)
  CurrentCanvas->fill_attrib[0] = data[0];

static char* get_fill_attrib(void)
  return CurrentCanvas->fill_attrib;

static cdAttribute fill_attrib =

At createcanvas in the driver:

new_canvas->fill_attrib[0] = '1';
new_canvas->fill_attrib[1] = 0;

cdRegisterAttribute(private_canvas, &fill_attrib);

, for instance, must exist, thus initializing the attribute and registering it in the canvas' attribute list.


All directly or indirectly public functions, variables or numberings have the prefix "CD" ("cd" for functions and variables, and "CD_" for numberings). For drivers, one must add, after the prefix, a one- or two-letter identification relative to the driver (ex.: "cdps" for the Postscript driver). The same rule applies for the modules; modules from different platforms have more letters added to the prefix indicating the platform (ex.: "cdwiup", IUP driver in Winndows). This helps solving conflicts for RCS, the version control program.

There are no global variables in the control part, but some drivers, due to historical reasons, may contain some global variables, which attempt to follow this same nomenclature and should not cause any problem.


The first version of the library used macros to replace function names by pointers in a function pointer structure, that is, a function table which was indirectly visible to the user. This caused several problems, especially when using the dynamic library and due to the fact that it was not by any means possible to control the case of a non-active canvas. Indirectly, the users also depended on the header file cdprivat.h.

We have then decided to hide the function table, creating an intermediate level between the public functions and the function table. However, the drivers still knew the table's structure, therefore a change in the table meant the need to modify all drivers.

Thus, once again, the library's structure was modified so that the drivers would become independent from the function table. With this change we were able to create a method for replacing any primitive in a given driver by the simulation driver's primitive, as well as to allow the user to change any driver primitive to a specified primitive.

Together with these new changes, we approached a problem related to the WD functions, which were necessarily clients of the current drivers. This generated some precision losses and unnecessary conversions. Including the WD functions in the function table and using the already implemented WD functions as default functions for the drivers with no WD functions, we corrected this problem, because wherever it is possible to implement these functions the primitives are more precisely executed, and can be called from the cdPlay function when interpreting a metafile.