[cairo-commit] cairo/doc/public language-bindings.xml,NONE,1.1
Owen Taylor
commit at pdx.freedesktop.org
Sat May 7 14:31:04 PDT 2005
Committed by: otaylor
Update of /cvs/cairo/cairo/doc/public
In directory gabe:/tmp/cvs-serv4782/doc/public
Added Files:
language-bindings.xml
Log Message:
2005-05-07 Owen Taylor <otaylor at redhat.com>
* doc/public/language-bindings.xml doc/public/cairo-doc.xml
doc/public/Makefile.am: Document suggested conventions and
techniques for many aspects of creating a language binding
for Cairo.
--- NEW FILE: language-bindings.xml ---
<appendix id="language-bindings">
<title>Creating a language binding for cairo</title>
<para>
While cairo is implemented and C, and has a C API, it is expected
that many users of cairo will be using it from languages other
than C. The glue that connects the core cairo library to another
language is known as a <firstterm>language
binding</firstterm>. This appendix attempts to collect together
issues that come up when creating a language bindings for cairo
and present standardized solutions to promote consistency among
the different language bindings.
</para>
<sect1 id="bindings-general">
<title>General considerations</title>
<para>
The naming of the central <link
linkend="cairo-t"><type>cairo_t</type></link> type is a
special exception. The object is âa cairo contextâ not âa
cairoâ, and names such as <type>cairo_t</type> rather than
<type>cairo_context_t</type> and
<function>cairo_set_source()</function> rather than
<function>cairo_context_set_source()</function> are simply
abbreviations to make the C API more palatable. In languages
which have object-oriented syntax, this abbreviation is much
less useful. In fact, if âCairoâ is used as a namespace, then
in many languages, you'd end up with a ridiculous type name
like âCairo.Cairoâ. For this reason, and for inter-language
consistency all object-oriented languages should name this
type as if it were <type>cairo_context_t</type>.
</para>
<para>
The punctuation and casing of the type names and
method names of cairo should be changed to match the general
convention of the language. In Java, where type names are written
in StudlyCaps and method names in javaCaps, cairo_font_extents_t
will become FontExtents and
<literal>cairo_set_source(cr,source)</literal>,
<literal>cr.setSource(source)</literal>.
As compared to changing the punctuation, and casing, much
more reluctance should be used in changing the method names
themselves. Even if get is usually omitted from getters in
your language, you shouldn't bind cairo_get_source() as
cr.source().
</para>
</sect1>
<sect1 id="bindings-memory">
<title>Memory Management</title>
<para>
The objects in cairo can roughly be divided into two types:
refcounted opaque types like
<link
linkend="cairo-surface-t"><type>cairo_surface_t</type></link>
and plain structures like
<link
linkend="cairo-glyph-t"><type>cairo_glyph_t</type></link>.
<link
linkend="cairo-path-t"><type>cairo_path_t</type></link>
and
<link
linkend="cairo-path-data-t"><type>cairo_path_data_t</type></link>
are special cases and are treated separately in this appendix.
</para>
<para>
Refcounted opaque types all have a
<function>..._reference()</function>
function to increase the refcount by one and a
<function>..._destroy()</function> to decrease the refcount
by one. These should not be exposed to the user of the language
binding, but rather used to implement memory management within
the language binding. The simplest way to do memory management
for a language binding is to treat the language binding object
as a simple handle to the cairo object. The language binding
object references the cairo object, and unreferences it when
finalized. This is the recommended method, though there are
a couple of caveats to be noted:
</para>
<itemizedlist>
<listitem>
<para>
Equality won't work as expected. You can have two language
objects for the same cairo and they won't necessarily
compare equal. If the language allows customizing the
equality operation, then this is fixable by comparing
the underlying pointers. It also can be fixed by creating
at most one language object per cairo object, and
uniquifying via a <firstterm>pin table</firstterm> (a hash
table that goes from cairo object to language object).
For <type>cairo_surface_t</type> you can use also
<link
linkend="cairo-surface-set-user-data"><function>cairo_surface_set_user_data()</function></link>
instead of a separate pin table.
</para>
</listitem>
<listitem>
<para>
Derivation from the language object doesn't work because
you can lose the language object while keeping the Cairo
object. Code like:
</para>
<programlisting>
public class MySurface (ImageSurface) {
public MySurface (width, height) {
super (Format.ARGB32, width, height);
}
public int get42 () {
return 42;
}
}
cr = Cairo(MySurface(width, height));
surface = cr.getTarget();
</programlisting>
<para>
Can result in <varname>surface</varname> containing an
<classname>ImageSurface</classname> not a <classname>MySurface</classname>.
This is not easily fixable without creating memory leaks,
and it's probably best to simply forbid deriving from the
language objects.
</para>
</listitem>
</itemizedlist>
<para>
When a plain structure is used as a return value from cairo,
this is done by passing it as a âout parameterâ.
</para>
<programlisting>
cairo_font_extents_t extents;
cairo_font_extents (cr, &extents);</programlisting>
<para>
In a language binding, this should typically be treated
as a return value:
</para>
<programlisting>
FontExtents extents = cr.fontExtents ();</programlisting>
<para>
A language binding has a choice in how it implements the
language objects for plain structures. It can use a pure
language object with fields corresponding to those of the C
structure, and convert from and to the C structure when calling
cairo functions or converting cairo return values. Or it
can keep a pointer to the C structure internally and wrap
it inside a language object much like occurs for refcounted
objects. The choice should be invisible to the user: they should
be able to imagine that it is implemented as a pure language
object.
</para>
</sect1>
<sect1 id="bindings-return-values">
<title>Multiple return values</title>
<para>
There are a number of functions in the cairo API that have
multiple <firstterm>out parameters</firstterm> or
<firstterm>in-out parameters</firstterm>. In some languages
these can be translated into multiple return values. In Python,
what is:
</para>
<programlisting>
cairo_user_to_device (cr, &x, &y);</programlisting>
<para>
can by mapped to:
</para>
<programlisting>
(x, y) = cr.user_to_device (cr, x, y);</programlisting>
<para>
but many languages don't have provisions for multiple return
values, so it is necessary to introduce auxiliary types.
Most of the functions that require the auxiliary types
require a type that would, in C, look like
</para>
<programlisting>
typedef struct _cairo_point cairo_point_t;
struct _cairo_point {
double x;
double y;
}</programlisting>
<para>
The same type should be used both for functions that use a pair
of coordinates as an absolute position, and functions that use
a pair of coordinates as a displacement. While an argument could
be made that having a separate âdistanceâ type is more correct,
it is more likely just to confuse users.
</para>
<programlisting>
void
cairo_user_to_device (cairo_t *cr, double *x, double *y);
void
cairo_user_to_device_distance (cairo_t *cr, double *dx, double *dy);
void
cairo_device_to_user (cairo_t *cr, double *x, double *y);
void
cairo_device_to_user_distance (cairo_t *cr, double *dx, double *dy);
void
cairo_matrix_transform_distance (cairo_matrix_t *matrix, double *dx, double *dy);
void
cairo_matrix_transform_point (cairo_matrix_t *matrix, double *x, double *y);
void
cairo_get_current_point (cairo_t *cr, double *x, double *y);
</programlisting>
<para>
There are also a couple of functions that return four values
representing a rectangle. These should be mapped to a
ârectangleâ type that looks like:
</para>
<programlisting>
typedef struct _cairo_rectangle cairo_rectangle_t;
struct _cairo_rectangle {
double x;
double y;
double width;
double height;
}</programlisting>
<para>
The C function returns the rectangle as a set of two points to
facilitate rounding to integral extents, but this isn't worth
adding a âboxâ type to go along with the more obvious
ârectangleâ representation.
</para>
<remark>
Q: Would it make sense here to define a standard
<function>cairo_rectangle_round()</function> method
that language bindings should map?
</remark>
<programlisting>
void
cairo_stroke_extents (cairo_t *cr,
double *x1, double *y1,
double *x2, double *y2);
void
cairo_fill_extents (cairo_t *cr,
double *x1, double *y1,
double *x2, double *y2);
</programlisting>
</sect1>
<sect1 id="bindings-overloading">
<title>Overloading and optional arguments</title>
<para>
Function overloading (having a several variants of a function
with the same name and different arguments) is a language
feature available in many languages but not in C.
</para>
<para>
In general, language binding authors should use restraint in
combining functions in the cairo API via function
overloading. What may seem like an obvious overload now may
turn out to be strange with future additions to cairo.
It might seem logical to make
<link
linkend="cairo-set-source-rgb"><function>cairo_set_source_rgb()</function></link>
an overload of <function>cairo_set_source()</function>, but future plans to add
<function>cairo_set_source_rgb_premultiplied()</function>,
which will also take three doubles make this a bad idea. For
this reason, only the following pairs of functions should
be combined via overloading
</para>
<programlisting>
void
cairo_set_source (cairo_t *cr, cairo_pattern_t *source);
void
cairo_set_source_surface (cairo_t *cr,
cairo_surface_t *source,
double surface_x,
double surface_y);
void
cairo_mask (cairo_t *cr,
cairo_pattern_t *pattern);
void
cairo_mask_surface (cairo_t *cr,
cairo_surface_t *surface,
double surface_x,
double surface_y);
cairo_surface_t *
cairo_image_surface_create (cairo_format_t format,
int width,
int height);
cairo_surface_t *
cairo_image_surface_create_for_data (unsigned char *data,
cairo_format_t format,
int width,
int height,
int stride);
cairo_status_t
cairo_surface_write_to_png (cairo_surface_t *surface,
const char *filename);
cairo_status_t
cairo_surface_write_to_png_stream (cairo_surface_t *surface,
cairo_write_func_t write_func,
void *closure);
cairo_surface_t *
cairo_image_surface_create_from_png (const char *filename);
cairo_surface_t *
cairo_image_surface_create_from_png_stream (cairo_read_func_t read_func,
void *closure);
</programlisting>
<para>
Note that there are cases where all constructors for a type
aren't overloaded together. For example
<link
linkend="cairo-image-surface-create-from-png"><function>cairo_image_surface_create_from_png()</function></link>
should <emphasis>not</emphasis> be overloaded together with
<link
linkend="cairo-image-surface-create"><function>cairo_image_surface_create()</function></link>.
In such cases, the remaining constructors will typically need to
be bound as static methods. In Java, for example, we might have:
</para>
<programlisting>
Surface surface1 = ImageSurface(Format.RGB24, 100, 100);
Surface surface2 = ImageSurface.createFromPNG("camera.png");</programlisting>
<para>
Some other overloads that add combinations not found in C may be
convenient for users for language bindings that provide
<type>cairo_point_t</type> and <type>cairo_rectangle_t</type>
types, for example:
</para>
<programlisting>
void
cairo_move_to (cairo_t *cr,
cairo_point_t *point);
void
cairo_rectangle (cairo_t *cr,
cairo_rectangle_t *rectangle);
</programlisting>
</sect1>
<sect1 id="bindings-streams">
<title>Streams and File I/O</title>
<para>
Various places in the cairo API deal with reading and writing
data, whether from and to files, or to other sources and
destinations. In these cases, what is typically provided in the
C API is a simple version that just takes a filename, and a
complex version that takes a callback function.
An example is the PNG handling functions:
</para>
<programlisting>
cairo_surface_t *
cairo_image_surface_create_from_png (const char *filename);
cairo_surface_t *
cairo_image_surface_create_from_png_stream (cairo_read_func_t read_func,
void *closure);
cairo_status_t
cairo_surface_write_to_png (cairo_surface_t *surface,
const char *filename);
cairo_status_t
cairo_surface_write_to_png_stream (cairo_surface_t *surface,
cairo_write_func_t write_func,
void *closure);</programlisting>
<para>
The expectation is that the filename version will be mapped
literally in the language binding, but the callback version
will be mapped to a version that takes a language stream
object. For example, in Java, the four functions above
might be mapped to:
</para>
<programlisting>
static public ImageSurface createFromPNG (String filename) throws IOException;
static public ImageSurface createFromPNG (InputStream stream) throws IOException;
public void writeToPNG (String filename) throws IOException;
public void writeToPNG (OutputStream stream) throws IOException;
</programlisting>
<para>
In many cases, it will be better to
implement the filename version internally
using the stream version, rather than building it on top of the
filename version in C. The reason for this is that will
naturally give a more standard handling of file errors for
the language, as seen in the above Java example, where
<methodname>createFromPNG()</methodname> is marked as raising
an exception. Propagating exceptions from inside the callback
function to the caller will pose a challenge to the language
binding implementor, since an exception must not propagate
through the Cairo code. A technique that will be useful in
some cases is to catch the exception in the callback,
store the exception object inside a structure pointed to by
<parameter>closure</parameter>, and then rethrow it once
the function returns.
</para>
<remark>
I'm not sure how to handle this for
<link
linkend="cairo-pdf-surface-create-for-callback"><function>cairo_pdf_surface_create_for_callback()</function></link>.
Other than keep a âexception to rethrowâ thread-specific
variable
that is checked after <emphasis>every</emphasis> call to a Cairo
function.
</remark>
</sect1>
<sect1 id="bindings-errors">
<title>Error handling</title>
<para>
The error handling approach in C for Cairo has multiple
elements:
</para>
<itemizedlist>
<listitem><para>
When a method on an object fails, the object is put into
an error state. Subsequent operations on the object do
nothing. The status of the object can be queried with
a function like <link
linkend="cairo-status"><function>status()</function></link>.
</para></listitem>
<listitem><para>
Constructors, rather than
returning<constant>NULL</constant> on out-of-memory failure,
return a special singleton object on which all
operations do nothing. Retrieving the status of the
singleton object returns <constant>CAIRO_STATUS_NO_MEMORY</constant>
</para>
<remark>
Is this going to apply to
<type>cairo_surface_t</type> as well?
</remark>
<remark>
What about cairo_copy_path_data()? It's probably going to
have to return <constant>NULL</constant>.
</remark>
</listitem>
<listitem><para>
Errors propagate from object to object. Setting a pattern
in an out-of-memory state as the source of a
<type>cairo_t</type> puts the type into an error state.
</para></listitem>
</itemizedlist>
<remark>Much of the above is not yet implemented at the time of
this writing</remark>
<para>
A language binding could copy the C approach, and for a
language without exceptions, this is likely the right thing
to do. However, for a language with exceptions, exposing
a completely different style of error handling for cairo
would be strange. So, instead, status should be checked
after every call to cairo, and exceptions thrown as necessary.
</para>
<para>
One problem that can arise with this, in languages
where handling exceptions is mandatory (like Java), is that almost
every cairo function can result in a status being set,
usually because of an out-of-memory condition. This could make
cairo hard to use. To resolve this problem, let's classify then
cairo status codes:
</para>
<programlisting>
/* Memory */
CAIRO_STATUS_NO_MEMORY,
/* Programmer error */
CAIRO_STATUS_INVALID_RESTORE
CAIRO_STATUS_INVALID_POP_GROUP
CAIRO_STATUS_NO_CURRENT_POINT
CAIRO_STATUS_INVALID_MATRIX
CAIRO_STATUS_NO_TARGET_SURFACE
CAIRO_STATUS_INVALID_STRING
CAIRO_STATUS_SURFACE_FINISHED
CAIRO_STATUS_BAD_NESTING
/* Language binding implementation */
CAIRO_STATUS_NULL_POINTER
CAIRO_STATUS_INVALID_PATH_DATA
CAIRO_STATUS_SURFACE_TYPE_MISMATCH
/* Other */
CAIRO_STATUS_READ_ERROR
CAIRO_STATUS_WRITE_ERROR
</programlisting>
<para>
If we look at these, the
<constant>CAIRO_STATUS_NO_MEMORY</constant>
should map to the native out-of-memory exception, which could
happen at any point in any case. Most of the others indicate
programmer error, and handling them in user code would be
silly. These should be mapped into whatever the language uses
for assertion failures, rather than errors that are normally
handled. (In Java, a subclass of Error rather than Exception,
perhaps.) And <constant>CAIRO_STATUS_READ_ERROR</constant>,
and <constant>CAIRO_STATUS_WRITE_ERROR</constant> can occur
only in very specific places. (In fact, as described
in <xref linkend="bindings-streams"/>, these errors may be
mapped into the language's native I/O error types.)
So, there really aren't exceptions that the programmer must
handle at most points in the Cairo API.
</para>
</sect1>
<sect1 id="bindings-patterns">
<title>Patterns</title>
<para>
The cairo C API allows for creating a number of different types
of patterns. All of these different types of patterns map to
<link
linkend="cairo-pattern-t"><type>cairo_pattern_t</type></link>
in C, but in an object oriented language, there should instead
be a hierarchy of types. (The functions that should map to
constructors for the various types are listed after the type,
methods on that type are listed below)
</para>
<programlisting>
cairo_pattern_t
<link linkend="cairo-pattern-set-matrix"><function>cairo_pattern_set_matrix()</function></link>
<link linkend="cairo-pattern-get-matrix"><function>cairo_pattern_get_matrix()</function></link>
cairo_solid_pattern_t
cairo_surface_pattern_t (<link linkend="cairo-pattern-create-for-surface"><function>cairo_pattern_create_for_surface()</function></link>)
<link linkend="cairo-pattern-set-extend"><function>cairo_pattern_set_extend()</function></link>
<link linkend="cairo-pattern-get-extend"><function>cairo_pattern_get_extend()</function></link>
<link linkend="cairo-pattern-set-filter"><function>cairo_pattern_set_filter()</function></link>
<link linkend="cairo-pattern-get-filter"><function>cairo_pattern_get_filter()</function></link>
cairo_gradient_t
<link linkend="cairo-pattern-add-color-stop-rgb"><function>cairo_pattern_add_color_stop_rgb()</function></link>
<link linkend="cairo-pattern-add-color-stop-rgba"><function>cairo_pattern_add_color_stop_rgba()</function></link>
cairo_linear_gradient_t (<link linkend="cairo-pattern-create-linear"><function>cairo_pattern_create_linear()</function></link>)
cairo_radial_gradient_t (<link linkend="cairo-pattern-create-radial"><function>cairo_pattern_create_radial()</function></link>)
</programlisting>
<para>
</para>
</sect1>
<sect1 id="bindings-surfaces">
<title>Surfaces</title>
<para>
Like patterns, surfaces, which use only the
<link
linkend="cairo-surface-t"><type>cairo_surface_t</type></link>
type in the C API should be broken up into a heirarchy of types
in a language binding.
</para>
<programlisting>
cairo_surface_t
cairo_image_surface_t
cairo_atsui_surface_t
cairo_win32_surface_t
cairo_xlib_surface_t
</programlisting>
<para>
Unlike patterns, the constructors and methods on these types are
clearly named, and can be trivially associated with the
appropriate subtype. Many language bindings will want to avoid
binding the platform-specific subtypes at all, since the
methods on these types are not useful without passing in native
C types. Unless there is a language binding for Xlib available,
there is no way to represent a XLib <type>Display</type> * in
that language.
</para>
<para>
This doesn't mean that platform-specific surface types can't
be used in a language binding that doesn't bind the constructor.
A very common situation is to use a cairo language binding in
combination with a binding for a higher level system like
the <ulink url="http://www.gtk.org/">GTK+</ulink> widget
toolkit. In such a situation, the higher level toolkit provides
ways to get references to platform specific surfaces.
</para>
<para>
The <link
linkend="cairo-surface-set-user-data"><function>cairo_surface_set_user_data()</function></link>,
and <link
linkend="cairo-surface-get-user-data"><function>cairo_surface_get_user_data()</function></link>
methods are provided for use in language bindings, and should
not be directly exposed to applications. One example of the use
of these functions in a language binding is creating a binding for:
</para>
<programlisting>
cairo_surface_t *
<link linkend="cairo-image-surface-create-for-data"><function>cairo_image_surface_create_for_data</function></link> (unsigned char *data,
cairo_format_t format,
int width,
int height,
int stride);
</programlisting>
<para>
The memory block passed in for <parameter>data</parameter> must be
kept around until the surface is destroyed, so the language
binding must have some way of determining when that happens. The
way to do this is to use the <parameter>destroy</parameter>
argument to <function>cairo_surface_set_user_data()</function>.
</para>
<remark>
Some languages may not have a suitable âpointer to a block of
dataâ type to pass in for <parameter>data</parameter>. And even
where a language does have such a type, the user will be
frequently able to cause the backing store to be reallocated
to a different location or truncated. Should we recommend a
standard type name and binding for a buffer object here?
</remark>
</sect1>
<sect1 id="bindings-fonts">
<title>Fonts</title>
<para>
Fonts are once more an area where there is a hierarchy of types:
</para>
<programlisting>
cairo_font_face_t
cairo_ft_font_face_t
cairo_win32_font_face_t
cairo_scaled_font_t
cairo_ft_scaled_font_t
cairo_win32_scaled_font_t
</programlisting>
<para>
The methods on the subtypes are, however, not useful without
bindings for fontconfig and FreeType or for the Win32 GDI,
so most language bindings will choose not to bind these
types.
</para>
<para>
The <link
linkend="cairo-font-face-set-user-data"><function>cairo_font_face_set_user_data()</function></link>,
and <link
linkend="cairo-font-face-get-user-data"><function>cairo_font_face_get_user_data()</function></link>
methods are provided for use in language bindings, and should
not be directly exposed to applications.
</para>
</sect1>
<sect1 id="bindings-path">
<title>cairo_path_t</title>
<para>
The <link linkend="cairo-path-t"><type>cairo_path_t</type></link> type is one
area in which most language bindings will differ significantly
from the C API. The C API for <type>cairo_path_t</type> is
designed for efficiency and to avoid auxiliary objects that
would be have to be manually memory managed by the
application. However,
a language binding should not present <type>cairo_path_t</type> as an
array, but rather as an opaque that can be iterated
over. Different languages have quite different conventions for
how iterators work, so it is impossible to give an exact
specification for how this API should work, but the type names
and methods should be similar to the language's mapping of the following:
</para>
<programlisting>
typedef struct cairo_path_iterator cairo_path_iterator_t;
typedef struct cairo_path_element cairo_path_element_t;
cairo_path_iterator_t *
cairo_path_get_iterator (cairo_path_t *path);
cairo_bool_t
cairo_path_iterator_has_next (cairo_path_iterator_t *iterator);
cairo_path_element_t *
cairo_path_iterator_next (cairo_path_iterator_t *iterator);
cairo_path_element_type_t
cairo_path_element_get_type (cairo_path_element_t *element);
void
cairo_path_element_get_point (cairo_path_element_t *element,
int index,
double *x,
double *y);
</programlisting>
<para>
The above is written using the Java conventions for
iterators. To illustrate how the API for PathIterator might
depend on the native iteration conventions of the API, examine
three versions of the loop, first written in a hypothetical Java
binding:
</para>
<programlisting>
PathIterator iter = cr.copyPath().iterator();
while (cr.hasNext()) {
PathElement element = iter.next();
if (element.getType() == PathElementType.MOVE_TO) {
Point p = element.getPoint(0);
doMoveTo (p.x, p.y);
}
}</programlisting>
<para>
And then in a hypothetical C++ binding:
</para>
<programlisting>
Path path = cr.copyPath();
for (PathIterator iter = path.begin(); iter != path.end(); iter++) {
PathElement element = *iter;
if (element.getType() == PathElementType.MOVE_TO) {
Point p = element.getPoint(0);
doMoveTo (p.x, p.y);
}
}</programlisting>
<para>
And then finally in a Python binding:
</para>
<programlisting>
for element in cr.copy_path():
if element.getType == cairo.PATH_ELEMENT_MOVE_TO:
(x, y) = element.getPoint(0)
doMoveTo (x, y);</programlisting>
<para>
While many of the API elements stay the same in the three
examples, the exact iteration mechanism is quite different, to
match how users of the language would expect to iterate over
a container.
</para>
<para>
You should not present an API for mutating or for creating new
<type>cairo_path_t</type> objects. In the future, these
guidelines may be extended to present an API for creating a
<type>cairo_path_t</type> from scratch for use with
<link
linkend="cairo-append-path"><function>cairo_append_path()</function></link>
but the current expectation is that <function>cairo_append_path()</function> will
mostly be used with paths from
<link
linkend="cairo-append-path"><function>cairo_copy_path()</function></link>.
</para>
</sect1>
</appendix>
<!--
Local variables:
mode: sgml
sgml-parent-document: ("cairo-docs.xml" "book" "book" "appendix")
End:
-->
More information about the cairo-commit
mailing list