A small library of useful, common Cairo routines.
C C++ Python CMake
Latest commit 63046e3 Jun 8, 2016 @cubicool Apply major pre-1.1.0 features; see notes
This is a rather large, unwieldy patch that should have been avoided if
I had managed my changes better. Regardless, I'm going to stash
everything here now and proceed in a more managed fashion moving
forward. NOTE: the incoming changes for both 1.1.0 and 1.2.0 are
currently in--and will be kept up to date in--the README file.

README.rst

NOTICE!

This project/library was formerly known as Cairocks! If you are looking for it (for example, if your submodule just ... broke, somehow) then look no further; this is it!

Cairou

A small library of useful, common Cairo routines. It aims to be for Cairo what libGLU was for traditional OpenGL.

Overview

Cairou (Cairo Utility) was born from a common codebase I continually ended up copying from project to project. Instead of trying to keep these different versions in sync, I've decided to divorce them out into their own project and maintain them thusly. Cairou is written using a mix of C90-compatible C and C++03, and strictly maintains a C-linkable external API for maximum accessibility. C++ is usually only used (see text and named-path) to take advantage of the STL or interact with existing code that cannot be easily converted.

While Cairou does include an example build system using CMake, you are welcome to include the source wholesale into your own projects. Additionally, I have provided an example Python extension module demonstrating Cairou's usage when paired with the fantastic cairocffi project.

Enjoy! AND PLEASE, contribute your own little tidbits of useful stuff!

Special Thanks

  • Behdad Esfahbod
  • Mirco Mueller
  • John Schlag, "Graphics Gems IV"
  • Angus Johnson
  • ranma42, pippin, joonas (Andrea Canciani, Oyvind Kolas, M. Joonas Pilhaja) in IRC; so much fantastic help from a great community!

License

There is no single license accompanying Cairou. The code I have borrowed from others (that is, that code I did not write myself) was also released with no license of any kind. You are therefore free to do whatever you wish with this code, with the following exceptions:

  • Glyphicons

    The Glyphicons font comes in multiple varieties, free and otherwise. The license is generally very accomodating, and you can find more information here about the Pro version.

  • Clipper

    The Clipper library was created by Angus Johnson, and comes with a very permissable license.

Features

Version 1.0.0

  • Generate "distance field" surfaces, as popularized by Valve.
  • Emboss, gaussian blur, and surface inversion filters.
  • Support for loading image surfaces from GIF files (using GIFLIB 5.0 or higher), with access to each individual GIF frame.
  • Support for loading image surfaces from JPEG images using libjpeg-turbo.
  • A useful wrapper for loading PNG images from memory.
  • An API wrapping and simplifying using the Glyphicons font, with support for interchangable icon backends in the future.
  • Drastically simplified text functions wrapping the Cairo "toy" text API with support for multiple lines and advanced XY positioning based on the extents of the entire body of text.
  • Spline support, created with large groups of 2D points as control structures.
  • Map paths onto other paths; for example, rendering text along arcs, etc.
  • Introduces the concept of context-specific, persistent "named paths", which can be used to push and pop saved paths to and from the associated context.

TODO

1.1.0 Release

  1. Move routines out of map-path-onto and into cairou-math.c.
  2. Begin using the new cairo_rectangle_int_t, where appropriate.
  3. Remove the instances of cairo_bool_t being returned (where it doesn't make any sense to do so).
  4. Make documention punctuation consistent throughout the library.
  5. Make private naming conventions in the implementation source files consistent throughout the library; e.g, with the "_private" components always appearing at the END of the function/structure/etc. name.
  6. Consistently either check (or DO NOT check) for pointer validity throughout the API.
  7. Use cairo_public for all exported cairou_* routines.
  8. Determine the minimum official CAIRO_VERSION required for the Cairou API.
  9. Remove the public cairou_spline_t, replacing it with an array of doubles. The structure may still be necessary internally.
  10. (DONE) Rename each source file so that is begins with "cairou-".
  11. Remove "named path" routines; they suck(ed).
  12. Remove the ridiculous usage of printf() everywhere throughout the library; instead, set an error state on the Context/Surface and use cairou_status().

1.2.0 Release

  1. Wrap as many features as possible using some manner of conditional inclusion/evaluation, and improve the example CMake build system to demonstrate this functionality. Most features (especially the JPEG, GIF, Clipper, and Glyphicons components) should be optional. This may simply require Cairou to adandon its "only a single header file" constraint, and move the toggleable features into their own includes.
  2. Add support for the Clipper library, and break its functionality into different (but similar) APIs based on the methods provided by both the Clipper object and the ClipperOffset object.
  3. Consider supporting C++11 by default and, possibly, a newer specification of C. At the minimum, Cairou should use the spec of the newest, non-optional include dependency.

Future Enhancements

  1. BETTER TEST COVERAGE!

  2. Drastically improved documentation and web presence. Cairou is actually pretty great, but no one knows about it!

  3. Optional Harfbuzz layout support.

  4. Optional wrappers for simplifying CairoGL/SDL2 usage, potentially along with Glyphy.

  5. Spline constraints; i.e., point x0 must stay 5 units perpendicular to point x1 at all times.

  6. Add a pixel-aligned drawing API.

  7. Add cairou_object_path/cairou_show_object.

  8. Implement a cairou_state_t structure, as well as cairou_{save/restore} functions that are capable of optionally pushing/popping these states. Additionally, define a special syntax for quick and easy structure creation/allocation. For example:

    /* Allocate a new cairou_state_t object on the heap. */
    cairou_state_t* state = cairou_state_create(
       CAIROU_TRANSLATE, 1.0, 1.0,
       CAIROU_SCALE, 1.0, 0.0,
       CAIROU_LINE_WIDTH, 2.0
    );
    
    cairou_state_destroy(state);
    
    /* Alternatively, manage your cairou_state_t object locally and simply
    initialize it using the meta-syntax. */
    cairou_state_t state;
    
    cairou_state_init(
       &state,
       CAIROU_TRANSLATE, 1.0, 1.0,
       CAIROU_SCALE, 1.0, 0.0,
       CAIROU_LINE_WIDTH, 2.0
    );
    
    /* METHOD 1: Push/pop the custom state using the most verbose method. */
    cairo_save(); {
       cairou_push_state(state);
    
       /* ...draw... */
    
       cairou_pop_state();
    } cairo_restore();
    
    /* METHOD 2: Pass an existing state object to save/restore. */
    cairou_save(state); {
        /* ...draw... */
    } cairou_restore();
    
    /* METHOD 3: Allow the save/restore functions to manage their state
    themselves, without having to allocate memory on the heap. */
    cairou_save(
       CAIROU_ROTATE, M_PI,
       CAIROU_FG_RGBA, 1.0f, 1.0f, 0.5f, 1.0f,
       CAIROU_BG_RGB, 0.0f, 0.0f, 0.0f
    ); {
        /* ...draw... */
    } cairou_restore();

    Note

    It might make sense to support different kinds of dynamic state creation markups. A va_args-based version would certainly be the default, but we could also support CSS, JSON, etc.

  9. Develop a framework (and possibly adjust the named_path implmentation) that allows easy, consistent storing of Cairo/Cairou/etc. data as cairo_t "user_data."

  10. Develop a complimentary framework for creating cairo_t Context objects already set with a pre-defined group of user data. This will require the use of clever macros that _LOOK_ like typical Cairou functions, possibly needing to use the "argument-counting-trick" to give the impression the macros can be "overloaded" depending on how many arguments you pass them. For example:

    foo_t* foo = foo_create();
    bar_t* bar = bar_create();
    static baz_t baz;
    
    cairou_user_data_create(KEY_FOO, foo, foo_destroy);
    cairou_user_data_create(KEY_BAR, bar, bar_destroy);
    cairou_user_data_create(KEY_BAZ, &baz);
    
    cairo_t* cr0 = cairou_create(surface, KEY_FOO);
    cairo_t* cr1 = cairou_create(surface, KEY_FOO, KEY_BAR, KEY_BAZ);
    
    /* ...draw... */
  11. Introduce a system for creating an arbitrary number of "rendering objects" and having them called based on a timeout--and in some kind of dependent order--with the possibility of additional, pre-defined effects, etc. Something like:

    cairo_bool_t do_draw0(cairo_t* cr, cairou_state_t* state);
    cairo_bool_t do_draw1(cairo_t* cr, cairou_state_t* state);
    cairo_bool_t do_draw2(cairo_t* cr, cairou_state_t* state);
    
    typedef cairo_bool_t (*cairou_draw_cb_t)(cairo_t*, cairou_state_t*);
    
    typedef struct _cairou_draw_t {
        const char* name;
        cairou_draw_callback_t callback;
        double timeout;
        const char* draw_before;
        const char* draw_after;
    } cairou_draw_t;
    
    cairou_draw_t* draw0 = cairou_draw_create(do_draw0);
    cairou_draw_t* draw1 = cairou_draw_create(do_draw1);
    cairou_draw_t* draw2 = cairou_draw_create(do_draw2);
    
    cairou_state_t* state = cairo_state_create(...);
    
    cairou_draw(NULL, draw0);
    cairou_draw(state, draw1, draw2);
    
    cairo_state_destroy(state);
    
    cairou_draw_destroy(draw0);
    cairou_draw_destroy(draw1);
    cairou_draw_destroy(draw2);

    Warning

    A system like this might encroach too much on the user (as each developer will either have their own ideas about the best way to draw things or may be integrating with some existing rendering paradigm), and may be entirely worthless to implement. In fact, there are libraries like Clutter that do this quite well already.

  12. Create an API for performing drawing operations that are automatically "mirrored" along additional axes. One implementation could redraw the current path after rotating the canvas N number of times.

  13. Figure out some way to iplement variable stroke size. A possible implementation would be to create a stroke-worthy path of some sort (a box, for example) and calculate new points based on the "typical" stroke that would occur. While creating these new points, perturb them by some user-defined "weight", so that the old stroke becomes a new, complex fill.

  14. Provide "faux 3D" transforms, possibly by using something like Graphene to convert Cairo matrices into 3D matrices, and back.

  15. Create an optional cairou.hpp header file that implements C++11/14 API extensions to the core C-based Cairou routines.

  16. Implement a CAIRO_SAVE_RESTORE wrapper; macro in C, lambda in C++11?

  17. Wrap/shim the existing Cairo API with (conditional) prefixed calls to cairo_status(), invoking an error callback when the status is invalid.

  18. Add additional path->point iteration helpers to the Context. For example, cairou_foreach_point(cairo_t*, ...), etc.

  19. Introduce a group of aspect ratio routines that assist in calculating, placement, conversion, and maniuplation.

  20. Potentially create a binary-only file format.

  21. Add cairou_glyph_path_{rectangle,rect,constrained,...} that works similarly to the text API in that it allows you to path/show a single glyph in a user-defined rectangular region.

  22. Consider adding a cairou_glyph_atlas_t object that creates tightly-packed surfaces for use in OpenGL texturing, among other things.

  23. Add statically builtin "Lorem ipsum" text that easily be used for rendering. Since we only currently support L2R (and maybe R2L easily enough), we only need the standard text blob; not a full range of data covering Mandarain, Japanese, etc.

  24. Keep fleshing out this idea of a "contextless" cairou_object_t, potentially deprecating the functionality provided by the current named_path routines. A cairou_object_t structure would encompass multiple "layers" (cairou_layer_t?), with each layer managing both a single path AND some manner of "style" (cairou_style_t?) with which it will be drawn. Styles would encompass things like line width, dash settings, fill color, stroke color, etc.