/* ctx git commit: e119e43 */
/* 
 * ctx.h is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * ctx.h 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with ctx; if not, see <https://www.gnu.org/licenses/>.
 *
 * 2012, 2015, 2019, 2020 Øyvind Kolås <pippin@gimp.org>
 *
 * ctx is a single header 2d vector graphics processing framework.
 *
 * To use ctx in a project, do the following:
 *
 * #define CTX_IMPLEMENTATION
 * #include "ctx.h"
 *
 * Ctx contains a minimal default fallback font with only ascii, so
 * you probably want to also include a font, and perhaps enable
 * the cairo or SDL2 optional renderers, a more complete example
 * could be:
 *
 * #include <cairo.h>
 * #include <SDL.h>
 * #include "ctx-font-regular.h"
 * #define CTX_IMPLEMENTATION
 * #include "ctx.h"
 *
 * The behavior of ctx can be tweaked, and features can be configured, enabled
 * or disabled with other #defines, see further down in the start of this file
 * for details.
 */

#ifndef CTX_H
#define CTX_H

#ifdef __cplusplus
extern "C" {
#endif

#if !__COSMOPOLITAN__
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#endif

typedef struct _Ctx            Ctx;

/* The pixel formats supported as render targets
 */
enum _CtxPixelFormat
{
  CTX_FORMAT_NONE=0,
  CTX_FORMAT_GRAY8,  // 1  - these enum values are not coincidence
  CTX_FORMAT_GRAYA8, // 2  -
  CTX_FORMAT_RGB8,   // 3  -
  CTX_FORMAT_RGBA8,  // 4  -
  CTX_FORMAT_BGRA8,  // 5
  CTX_FORMAT_RGB565, // 6
  CTX_FORMAT_RGB565_BYTESWAPPED, // 7
  CTX_FORMAT_RGB332, // 8
  CTX_FORMAT_RGBAF,  // 9
  CTX_FORMAT_GRAYF,  // 10
  CTX_FORMAT_GRAYAF, // 11
  CTX_FORMAT_GRAY1,  //12 MONO
  CTX_FORMAT_GRAY2,  //13 DUO
  CTX_FORMAT_GRAY4,  //14
  CTX_FORMAT_CMYK8,  //15
  CTX_FORMAT_CMYKA8, //16 
  CTX_FORMAT_CMYKAF, //17
  CTX_FORMAT_YUV420, //18
  CTX_FORMAT_RGBA8_SEPARATE_ALPHA, // 19
};
typedef enum   _CtxPixelFormat CtxPixelFormat;

typedef struct _CtxGlyph       CtxGlyph;

/**
 * ctx_new:
 *
 * Create a new drawing context, this context has no pixels but
 * accumulates commands and can be played back on other ctx
 * render contexts.
 */
Ctx *ctx_new (void);

/**
 * ctx_new_for_framebuffer:
 *
 * Create a new drawing context for a framebuffer, rendering happens
 * immediately.
 */
Ctx *ctx_new_for_framebuffer (void *data,
                              int   width,
                              int   height,
                              int   stride,
                              CtxPixelFormat pixel_format);
/**
 * ctx_new_ui:
 *
 * Create a new interactive ctx context, might depend on additional
 * integration.
 */
Ctx *ctx_new_ui (int width, int height);

/**
 * ctx_new_for_drawlist:
 *
 * Create a new drawing context for a pre-existing drawlist.
 */
Ctx *ctx_new_for_drawlist (void *data, size_t length);


/**
 * ctx_dirty_rect:
 *
 * Query the dirtied bounding box of drawing commands thus far.
 */
void  ctx_dirty_rect      (Ctx *ctx, int *x, int *y, int *width, int *height);

/**
 * ctx_free:
 * @ctx: a ctx context
 */
void ctx_free (Ctx *ctx);

/* clears and resets a context */
void ctx_reset          (Ctx *ctx);
void ctx_begin_path     (Ctx *ctx);
void ctx_save           (Ctx *ctx);
void ctx_restore        (Ctx *ctx);
void ctx_start_group    (Ctx *ctx);
void ctx_end_group      (Ctx *ctx);
void ctx_clip           (Ctx *ctx);
void ctx_identity       (Ctx *ctx);
void ctx_rotate         (Ctx *ctx, float x);

void ctx_image_smoothing     (Ctx *ctx, int enabled);
int  ctx_get_image_smoothing (Ctx *ctx);

#define CTX_LINE_WIDTH_HAIRLINE -1000.0
#define CTX_LINE_WIDTH_ALIASED  -1.0
#define CTX_LINE_WIDTH_FAST     -1.0  /* aliased 1px wide line */
void ctx_miter_limit (Ctx *ctx, float limit);
float ctx_get_miter_limit (Ctx *ctx);
void ctx_line_width       (Ctx *ctx, float x);
void ctx_line_dash_offset (Ctx *ctx, float line_dash);
float ctx_get_line_dash_offset (Ctx *ctx);
void ctx_apply_transform  (Ctx *ctx, float a,  float b,  // hscale, hskew
                                     float c,  float d,  // vskew,  vscale
                                     float e,  float f); // htran,  vtran
void ctx_set_transform    (Ctx *ctx, float a, float b, float c, float d, float e, float f);
void  ctx_line_dash       (Ctx *ctx, float *dashes, int count);
void  ctx_font_size       (Ctx *ctx, float x);
void  ctx_font            (Ctx *ctx, const char *font);
void  ctx_font_family     (Ctx *ctx, const char *font_family);
void  ctx_scale           (Ctx *ctx, float x, float y);
void  ctx_translate       (Ctx *ctx, float x, float y);
void  ctx_line_to         (Ctx *ctx, float x, float y);
void  ctx_move_to         (Ctx *ctx, float x, float y);
void  ctx_curve_to        (Ctx *ctx, float cx0, float cy0,
                           float cx1, float cy1,
                           float x, float y);
void  ctx_quad_to         (Ctx *ctx, float cx, float cy,
                           float x, float y);
void  ctx_arc             (Ctx  *ctx,
                           float x, float y,
                           float radius,
                           float angle1, float angle2,
                           int   direction);
void  ctx_arc_to          (Ctx *ctx, float x1, float y1,
                           float x2, float y2, float radius);
void  ctx_rel_arc_to      (Ctx *ctx, float x1, float y1,
                           float x2, float y2, float radius);
void  ctx_rectangle       (Ctx *ctx,
                           float x0, float y0,
                           float w, float h);
void  ctx_round_rectangle (Ctx *ctx,
                           float x0, float y0,
                           float w, float h,
                           float radius);
void  ctx_rel_line_to     (Ctx *ctx,
                           float x, float y);
void  ctx_rel_move_to     (Ctx *ctx,
                           float x, float y);
void  ctx_rel_curve_to    (Ctx *ctx,
                           float x0, float y0,
                           float x1, float y1,
                           float x2, float y2);
void  ctx_rel_quad_to     (Ctx *ctx,
                           float cx, float cy,
                           float x, float y);
void  ctx_close_path      (Ctx *ctx);
float ctx_get_font_size   (Ctx *ctx);
const char *ctx_get_font  (Ctx *ctx);
float ctx_get_line_width  (Ctx *ctx);
int   ctx_width           (Ctx *ctx);
int   ctx_height          (Ctx *ctx);
int   ctx_rev             (Ctx *ctx);
float ctx_x               (Ctx *ctx);
float ctx_y               (Ctx *ctx);
void  ctx_current_point   (Ctx *ctx, float *x, float *y);
void  ctx_get_transform   (Ctx *ctx, float *a, float *b,
                           float *c, float *d,
                           float *e, float *f);

CtxGlyph *ctx_glyph_allocate (int n_glyphs);

void gtx_glyph_free       (CtxGlyph *glyphs);

int  ctx_glyph            (Ctx *ctx, uint32_t unichar, int stroke);

void ctx_preserve         (Ctx *ctx);
void ctx_fill             (Ctx *ctx);
void ctx_stroke           (Ctx *ctx);

void ctx_parse            (Ctx *ctx, const char *string);

void ctx_shadow_rgba      (Ctx *ctx, float r, float g, float b, float a);
void ctx_shadow_blur      (Ctx *ctx, float x);
void ctx_shadow_offset_x  (Ctx *ctx, float x);
void ctx_shadow_offset_y  (Ctx *ctx, float y);
void ctx_view_box         (Ctx *ctx,
                           float x0, float y0,
                           float w, float h);
void
ctx_set_pixel_u8          (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

void  ctx_global_alpha     (Ctx *ctx, float global_alpha);
float ctx_get_global_alpha (Ctx *ctx);

void ctx_named_source (Ctx *ctx, const char *name);
// followed by a color, gradient or pattern definition

void ctx_stroke_source  (Ctx *ctx); // next source definition is for stroking

void ctx_rgba_stroke   (Ctx *ctx, float r, float g, float b, float a);
void ctx_rgb_stroke    (Ctx *ctx, float r, float g, float b);
void ctx_rgba8_stroke  (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

void ctx_gray_stroke   (Ctx *ctx, float gray);
void ctx_drgba_stroke  (Ctx *ctx, float r, float g, float b, float a);
void ctx_cmyka_stroke  (Ctx *ctx, float c, float m, float y, float k, float a);
void ctx_cmyk_stroke   (Ctx *ctx, float c, float m, float y, float k);
void ctx_dcmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a);
void ctx_dcmyk_stroke  (Ctx *ctx, float c, float m, float y, float k);



void ctx_rgba   (Ctx *ctx, float r, float g, float b, float a);
void ctx_rgb    (Ctx *ctx, float r, float g, float b);
void ctx_rgba8  (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

void ctx_gray   (Ctx *ctx, float gray);
void ctx_drgba  (Ctx *ctx, float r, float g, float b, float a);
void ctx_cmyka  (Ctx *ctx, float c, float m, float y, float k, float a);
void ctx_cmyk   (Ctx *ctx, float c, float m, float y, float k);
void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a);
void ctx_dcmyk  (Ctx *ctx, float c, float m, float y, float k);

/* there is also getters for colors, by first setting a color in one format and getting
 * it with another color conversions can be done
 */

void ctx_get_rgba   (Ctx *ctx, float *rgba);
void ctx_get_graya  (Ctx *ctx, float *ya);
void ctx_get_drgba  (Ctx *ctx, float *drgba);
void ctx_get_cmyka  (Ctx *ctx, float *cmyka);
void ctx_get_dcmyka (Ctx *ctx, float *dcmyka);
int  ctx_in_fill    (Ctx *ctx, float x, float y);
int  ctx_in_stroke  (Ctx *ctx, float x, float y);

void ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1);
void ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0,
                          float x1, float y1, float r1);
/* XXX should be ctx_gradient_add_stop_rgba */
void ctx_gradient_add_stop (Ctx *ctx, float pos, float r, float g, float b, float a);

void ctx_gradient_add_stop_u8 (Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a);


/*
 *
 */
void ctx_define_texture (Ctx *ctx,
                         const char *eid,
                         int         width,
                         int         height,
                         int         stride,
                         int         format,
                         void       *data,
                         char       *ret_eid);

void
ctx_source_transform (Ctx *ctx, float a, float b,  // hscale, hskew
                      float c, float d,  // vskew,  vscale
                      float e, float f);  // htran,  vtran
typedef struct _CtxMatrix     CtxMatrix;
void
ctx_source_transform_matrix (Ctx *ctx, CtxMatrix *matrix);

void
ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh,
                    CtxPixelFormat format, int dst_stride,
                    uint8_t *dst_data);

void
ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format,
                    uint8_t *data,
                    int ox, int oy,
                    int dirtyX, int dirtyY,
                    int dirtyWidth, int dirtyHeight);


/* loads an image file from disk into texture, returning pixel width, height
 * and eid, the eid is based on the path; not the contents - avoiding doing
 * sha1 checksum of contents. The width and height of the image is returned
 * along with the used eid, width height or eid can be NULL if we
 * do not care about their values.
 */
void ctx_texture_load (Ctx        *ctx,
                       const char *path,
                       int        *width,
                       int        *height,
                       char       *eid);

/* sets the paint source to be a texture by eid
 */
void ctx_texture              (Ctx *ctx, const char *eid, float x, float y);

void ctx_draw_texture         (Ctx *ctx, const char *eid, float x, float y, float w, float h);

void ctx_draw_texture_clipped (Ctx *ctx, const char *eid, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight);

void ctx_draw_image           (Ctx *ctx, const char *path, float x, float y, float w, float h);

void ctx_draw_image_clipped   (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight);

/* used by the render threads of fb and sdl backends.
 */
void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source);
/* used when sharing cache state of eids between clients
 */
void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache);

typedef struct _CtxDrawlist CtxDrawlist;
typedef void (*CtxFullCb) (CtxDrawlist *drawlist, void *data);

int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format); // bits per pixel
int ctx_pixel_format_get_stride (CtxPixelFormat format, int width);
int ctx_pixel_format_components (CtxPixelFormat format);

void _ctx_set_store_clear (Ctx *ctx);
void _ctx_set_transformation (Ctx *ctx, int transformation);

Ctx *ctx_hasher_new (int width, int height, int cols, int rows);
uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row);

int ctx_utf8_strlen (const char *s);

#ifdef _BABL_H
#define CTX_BABL 1
#else
#define CTX_BABL 0
#endif

/* If cairo.h is included before ctx.h add cairo integration code
 */
#ifdef CAIRO_H
#define CTX_CAIRO 1
#else
#define CTX_CAIRO 0
#endif

#ifdef SDL_h_
#define CTX_SDL 1
#else
#define CTX_SDL 0
#endif

#ifndef CTX_FB
#if CTX_SDL
#define CTX_FB 1
#else
#define CTX_FB 0
#endif
#endif

#if CTX_SDL
#define ctx_mutex_t            SDL_mutex
#define ctx_create_mutex()     SDL_CreateMutex()
#define ctx_lock_mutex(a)      SDL_LockMutex(a)
#define ctx_unlock_mutex(a)    SDL_UnlockMutex(a)
#else
#define ctx_mutex_t           int
#define ctx_create_mutex()    NULL
#define ctx_lock_mutex(a)   
#define ctx_unlock_mutex(a)  
#endif

#if CTX_CAIRO

/* render the deferred commands of a ctx context to a cairo
 * context
 */
void  ctx_render_cairo  (Ctx *ctx, cairo_t *cr);

/* create a ctx context that directly renders to the specified
 * cairo context
 */
Ctx * ctx_new_for_cairo (cairo_t *cr);
#endif

/* free with free() */
char *ctx_render_string (Ctx *ctx, int longform, int *retlen);

void ctx_render_stream  (Ctx *ctx, FILE *stream, int formatter);

void ctx_render_ctx     (Ctx *ctx, Ctx *d_ctx);
void ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx); /* cycles through all
                                                        used texture eids
                                                      */

void ctx_start_move     (Ctx *ctx);


int ctx_add_single      (Ctx *ctx, void *entry);

uint32_t ctx_utf8_to_unichar (const char *input);
int      ctx_unichar_to_utf8 (uint32_t  ch, uint8_t  *dest);


typedef enum
{
  CTX_FILL_RULE_WINDING,
  CTX_FILL_RULE_EVEN_ODD
} CtxFillRule;

typedef enum
{
#if 0
  CTX_COMPOSITE_SOURCE_OVER      = 0,
  CTX_COMPOSITE_COPY             = 32,
  CTX_COMPOSITE_SOURCE_IN        = 64,
  CTX_COMPOSITE_SOURCE_OUT       = 96,
  CTX_COMPOSITE_SOURCE_ATOP      = 128,
  CTX_COMPOSITE_CLEAR            = 160,

  CTX_COMPOSITE_DESTINATION_OVER = 192,
  CTX_COMPOSITE_DESTINATION      = 224,
  CTX_COMPOSITE_DESTINATION_IN   = 256,
  CTX_COMPOSITE_DESTINATION_OUT  = 288,
  CTX_COMPOSITE_DESTINATION_ATOP = 320,
  CTX_COMPOSITE_XOR              = 352,

  CTX_COMPOSITE_ALL              = (32+64+128+256)
#else
  CTX_COMPOSITE_SOURCE_OVER      =0,
  CTX_COMPOSITE_COPY             ,
  CTX_COMPOSITE_SOURCE_IN        ,
  CTX_COMPOSITE_SOURCE_OUT       ,
  CTX_COMPOSITE_SOURCE_ATOP      ,
  CTX_COMPOSITE_CLEAR            ,

  CTX_COMPOSITE_DESTINATION_OVER ,
  CTX_COMPOSITE_DESTINATION      ,
  CTX_COMPOSITE_DESTINATION_IN   ,
  CTX_COMPOSITE_DESTINATION_OUT  ,
  CTX_COMPOSITE_DESTINATION_ATOP ,
  CTX_COMPOSITE_XOR              ,
#endif
} CtxCompositingMode;

typedef enum
{
  CTX_BLEND_NORMAL,
  CTX_BLEND_MULTIPLY,
  CTX_BLEND_SCREEN,
  CTX_BLEND_OVERLAY,
  CTX_BLEND_DARKEN,
  CTX_BLEND_LIGHTEN,
  CTX_BLEND_COLOR_DODGE,
  CTX_BLEND_COLOR_BURN,
  CTX_BLEND_HARD_LIGHT,
  CTX_BLEND_SOFT_LIGHT,
  CTX_BLEND_DIFFERENCE,
  CTX_BLEND_EXCLUSION,
  CTX_BLEND_HUE, 
  CTX_BLEND_SATURATION, 
  CTX_BLEND_COLOR, 
  CTX_BLEND_LUMINOSITY,  // 15
  CTX_BLEND_DIVIDE,
  CTX_BLEND_ADDITION,
  CTX_BLEND_SUBTRACT,    // 18
} CtxBlend;

void ctx_blend_mode (Ctx *ctx, CtxBlend mode);

typedef enum
{
  CTX_JOIN_BEVEL = 0,
  CTX_JOIN_ROUND = 1,
  CTX_JOIN_MITER = 2
} CtxLineJoin;

typedef enum
{
  CTX_CAP_NONE   = 0,
  CTX_CAP_ROUND  = 1,
  CTX_CAP_SQUARE = 2
} CtxLineCap;

typedef enum
{
  CTX_TEXT_BASELINE_ALPHABETIC = 0,
  CTX_TEXT_BASELINE_TOP,
  CTX_TEXT_BASELINE_HANGING,
  CTX_TEXT_BASELINE_MIDDLE,
  CTX_TEXT_BASELINE_IDEOGRAPHIC,
  CTX_TEXT_BASELINE_BOTTOM
} CtxTextBaseline;

typedef enum
{
  CTX_TEXT_ALIGN_START = 0,
  CTX_TEXT_ALIGN_END,
  CTX_TEXT_ALIGN_CENTER,
  CTX_TEXT_ALIGN_LEFT,
  CTX_TEXT_ALIGN_RIGHT
} CtxTextAlign;

typedef enum
{
  CTX_TEXT_DIRECTION_INHERIT = 0,
  CTX_TEXT_DIRECTION_LTR,
  CTX_TEXT_DIRECTION_RTL
} CtxTextDirection;

struct
_CtxGlyph
{
  uint32_t index;
  float    x;
  float    y;
};

CtxTextAlign       ctx_get_text_align (Ctx *ctx);
CtxTextBaseline    ctx_get_text_baseline (Ctx *ctx);
CtxTextDirection   ctx_get_text_direction (Ctx *ctx);
CtxFillRule        ctx_get_fill_rule (Ctx *ctx);
CtxLineCap         ctx_get_line_cap (Ctx *ctx);
CtxLineJoin        ctx_get_line_join (Ctx *ctx);
CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx);
CtxBlend           ctx_get_blend_mode (Ctx *ctx);

void ctx_gradient_add_stop_string (Ctx *ctx, float pos, const char *color);

void ctx_text_align           (Ctx *ctx, CtxTextAlign      align);
void ctx_text_baseline        (Ctx *ctx, CtxTextBaseline   baseline);
void ctx_text_direction       (Ctx *ctx, CtxTextDirection  direction);
void ctx_fill_rule            (Ctx *ctx, CtxFillRule       fill_rule);
void ctx_line_cap             (Ctx *ctx, CtxLineCap        cap);
void ctx_line_join            (Ctx *ctx, CtxLineJoin       join);
void ctx_compositing_mode     (Ctx *ctx, CtxCompositingMode mode);
int  ctx_set_drawlist     (Ctx *ctx, void *data, int length);
typedef struct _CtxEntry CtxEntry;
/* we only care about the tight packing for this specific
 * struct as we do indexing across members in arrays of it,
 * to make sure its size becomes 9bytes -
 * the pack pragma is also sufficient on recent gcc versions
 */
#pragma pack(push,1)
struct
  _CtxEntry
{
  uint8_t code;
  union
  {
    float    f[2];
    uint8_t  u8[8];
    int8_t   s8[8];
    uint16_t u16[4];
    int16_t  s16[4];
    uint32_t u32[2];
    int32_t  s32[2];
    uint64_t u64[1]; // unused
  } data; // 9bytes long, we're favoring compactness and correctness
  // over performance. By sacrificing float precision, zeroing
  // first 8bit of f[0] would permit 8bytes long and better
  // aglinment and cacheline behavior.
};
#pragma pack(pop)
const CtxEntry *ctx_get_drawlist (Ctx *ctx);
int  ctx_append_drawlist  (Ctx *ctx, void *data, int length);

/* these are only needed for clients rendering text, as all text gets
 * converted to paths.
 */
void  ctx_glyphs        (Ctx        *ctx,
                         CtxGlyph   *glyphs,
                         int         n_glyphs);

void  ctx_glyphs_stroke (Ctx       *ctx,
                         CtxGlyph   *glyphs,
                         int         n_glyphs);

void  ctx_text          (Ctx        *ctx,
                         const char *string);
void  ctx_text_stroke   (Ctx        *ctx,
                         const char *string);

void  ctx_fill_text     (Ctx        *ctx,
                         const char *string,
                         float       x,
                         float       y);

void  ctx_stroke_text   (Ctx        *ctx,
                         const char *string,
                         float       x,
                         float       y);

/* returns the total horizontal advance if string had been rendered */
float ctx_text_width    (Ctx        *ctx,
                         const char *string);

float ctx_glyph_width   (Ctx *ctx, int unichar);

int   ctx_load_font_ttf (const char *name, const void *ttf_contents, int length);



enum _CtxModifierState
{
  CTX_MODIFIER_STATE_SHIFT   = (1<<0),
  CTX_MODIFIER_STATE_CONTROL = (1<<1),
  CTX_MODIFIER_STATE_ALT     = (1<<2),
  CTX_MODIFIER_STATE_BUTTON1 = (1<<3),
  CTX_MODIFIER_STATE_BUTTON2 = (1<<4),
  CTX_MODIFIER_STATE_BUTTON3 = (1<<5),
  CTX_MODIFIER_STATE_DRAG    = (1<<6), // pointer button is down (0 or any)
};
typedef enum _CtxModifierState CtxModifierState;

enum _CtxScrollDirection
{
  CTX_SCROLL_DIRECTION_UP,
  CTX_SCROLL_DIRECTION_DOWN,
  CTX_SCROLL_DIRECTION_LEFT,
  CTX_SCROLL_DIRECTION_RIGHT
};
typedef enum _CtxScrollDirection CtxScrollDirection;

typedef struct _CtxEvent CtxEvent;

void ctx_set_renderer (Ctx *ctx,
                       void *renderer);
void *ctx_get_renderer (Ctx *ctx);

int ctx_renderer_is_sdl (Ctx *ctx);
int ctx_renderer_is_fb (Ctx *ctx);
int ctx_renderer_is_ctx (Ctx *ctx);
int ctx_renderer_is_term (Ctx *ctx);

/* the following API is only available when CTX_EVENTS is defined to 1
 *
 * it provides the ability to register callbacks with the current path
 * that get delivered with transformed coordinates.
 */
int ctx_is_dirty (Ctx *ctx);
void ctx_set_dirty (Ctx *ctx, int dirty);
float ctx_get_float (Ctx *ctx, uint64_t hash);
void ctx_set_float (Ctx *ctx, uint64_t hash, float value);

unsigned long ctx_ticks (void);
void ctx_flush (Ctx *ctx);

void ctx_set_clipboard (Ctx *ctx, const char *text);
char *ctx_get_clipboard (Ctx *ctx);

void _ctx_events_init     (Ctx *ctx);
typedef struct _CtxIntRectangle CtxIntRectangle;
struct _CtxIntRectangle {
  int x;
  int y;
  int width;
  int height;
};

void ctx_quit (Ctx *ctx);
int  ctx_has_quit (Ctx *ctx);

typedef void (*CtxCb) (CtxEvent *event,
                       void     *data,
                       void     *data2);
typedef void (*CtxDestroyNotify) (void *data);

enum _CtxEventType {
  CTX_PRESS        = 1 << 0,
  CTX_MOTION       = 1 << 1,
  CTX_RELEASE      = 1 << 2,
  CTX_ENTER        = 1 << 3,
  CTX_LEAVE        = 1 << 4,
  CTX_TAP          = 1 << 5,
  CTX_TAP_AND_HOLD = 1 << 6,

  /* NYI: SWIPE, ZOOM ROT_ZOOM, */

  CTX_DRAG_PRESS   = 1 << 7,
  CTX_DRAG_MOTION  = 1 << 8,
  CTX_DRAG_RELEASE = 1 << 9,
  CTX_KEY_PRESS    = 1 << 10,
  CTX_KEY_DOWN     = 1 << 11,
  CTX_KEY_UP       = 1 << 12,
  CTX_SCROLL       = 1 << 13,
  CTX_MESSAGE      = 1 << 14,
  CTX_DROP         = 1 << 15,

  CTX_SET_CURSOR   = 1 << 16, // used internally

  /* client should store state - preparing
                                 * for restart
                                 */
  CTX_POINTER  = (CTX_PRESS | CTX_MOTION | CTX_RELEASE | CTX_DROP),
  CTX_TAPS     = (CTX_TAP | CTX_TAP_AND_HOLD),
  CTX_CROSSING = (CTX_ENTER | CTX_LEAVE),
  CTX_DRAG     = (CTX_DRAG_PRESS | CTX_DRAG_MOTION | CTX_DRAG_RELEASE),
  CTX_KEY      = (CTX_KEY_DOWN | CTX_KEY_UP | CTX_KEY_PRESS),
  CTX_MISC     = (CTX_MESSAGE),
  CTX_ANY      = (CTX_POINTER | CTX_DRAG | CTX_CROSSING | CTX_KEY | CTX_MISC | CTX_TAPS),
};
typedef enum _CtxEventType CtxEventType;

#define CTX_CLICK   CTX_PRESS   // SHOULD HAVE MORE LOGIC

struct _CtxEvent {
  CtxEventType  type;
  uint32_t time;
  Ctx     *ctx;
  int stop_propagate; /* when set - propagation is stopped */

  CtxModifierState state;

  int     device_no; /* 0 = left mouse button / virtual focus */
                     /* 1 = middle mouse button */
                     /* 2 = right mouse button */
                     /* 3 = first multi-touch .. (NYI) */

  float   device_x; /* untransformed (device) coordinates  */
  float   device_y;

  /* coordinates; and deltas for motion/drag events in user-coordinates: */
  float   x;
  float   y;
  float   start_x; /* start-coordinates (press) event for drag, */
  float   start_y; /*    untransformed coordinates */
  float   prev_x;  /* previous events coordinates */
  float   prev_y;
  float   delta_x; /* x - prev_x, redundant - but often useful */
  float   delta_y; /* y - prev_y, redundant - ..  */


  unsigned int unicode; /* only valid for key-events, re-use as keycode? */
  const char *string;   /* as key can be "up" "down" "space" "backspace" "a" "b" "ø" etc .. */
                        /* this is also where the message is delivered for
                         * MESSAGE events
                         *
                         * and the data for drop events are delivered
                         */
  CtxScrollDirection scroll_direction;


  // would be nice to add the bounding box of the hit-area causing
  // the event, making for instance scissored enter/leave repaint easier.
};

// layer-event "layer"  motion x y device_no 

void ctx_add_key_binding_full (Ctx *ctx,
                               const char *key,
                               const char *action,
                               const char *label,
                               CtxCb       cb,
                               void       *cb_data,
                               CtxDestroyNotify destroy_notify,
                               void       *destroy_data);
void ctx_add_key_binding (Ctx *ctx,
                          const char *key,
                          const char *action,
                          const char *label,
                          CtxCb cb,
                          void  *cb_data);
typedef struct CtxBinding {
  char *nick;
  char *command;
  char *label;
  CtxCb cb;
  void *cb_data;
  CtxDestroyNotify destroy_notify;
  void  *destroy_data;
} CtxBinding;
CtxBinding *ctx_get_bindings (Ctx *ctx);
void  ctx_clear_bindings     (Ctx *ctx);
void  ctx_remove_idle        (Ctx *ctx, int handle);
int   ctx_add_timeout_full   (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data,
                              void (*destroy_notify)(void *destroy_data), void *destroy_data);
int   ctx_add_timeout        (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data);
int   ctx_add_idle_full      (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data,
                              void (*destroy_notify)(void *destroy_data), void *destroy_data);
int   ctx_add_idle           (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data);


void ctx_add_hit_region (Ctx *ctx, const char *id);

void ctx_set_title (Ctx *ctx, const char *title);

void ctx_listen_full (Ctx     *ctx,
                      float    x,
                      float    y,
                      float    width,
                      float    height,
                      CtxEventType  types,
                      CtxCb    cb,
                      void    *data1,
                      void    *data2,
                      void   (*finalize)(void *listen_data, void *listen_data2,
                                         void *finalize_data),
                      void    *finalize_data);
void  ctx_event_stop_propagate (CtxEvent *event);
void  ctx_listen               (Ctx          *ctx,
                                CtxEventType  types,
                                CtxCb         cb,
                                void*         data1,
                                void*         data2);
void  ctx_listen_with_finalize (Ctx          *ctx,
                                CtxEventType  types,
                                CtxCb         cb,
                                void*         data1,
                                void*         data2,
                      void   (*finalize)(void *listen_data, void *listen_data2,
                                         void *finalize_data),
                      void    *finalize_data);

void ctx_init (int *argc, char ***argv); // is a no-op but could launch
                                         // terminal
CtxEvent *ctx_get_event (Ctx *ctx);
int       ctx_has_event (Ctx *ctx, int timeout);
void      ctx_get_event_fds (Ctx *ctx, int *fd, int *count);

int   ctx_pointer_is_down (Ctx *ctx, int no);
float ctx_pointer_x (Ctx *ctx);
float ctx_pointer_y (Ctx *ctx);
void  ctx_freeze (Ctx *ctx);
void  ctx_thaw   (Ctx *ctx);
int   ctx_events_frozen (Ctx *ctx);
void  ctx_events_clear_items (Ctx *ctx);
int   ctx_events_width (Ctx *ctx);
int   ctx_events_height (Ctx *ctx);

/* The following functions drive the event delivery, registered callbacks
 * are called in response to these being called.
 */

int ctx_key_down  (Ctx *ctx, unsigned int keyval,
                   const char *string, uint32_t time);
int ctx_key_up    (Ctx *ctx, unsigned int keyval,
                   const char *string, uint32_t time);
int ctx_key_press (Ctx *ctx, unsigned int keyval,
                   const char *string, uint32_t time);


int ctx_scrolled  (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time);
void ctx_incoming_message (Ctx *ctx, const char *message, long time);
int ctx_pointer_motion    (Ctx *ctx, float x, float y, int device_no, uint32_t time);
int ctx_pointer_release   (Ctx *ctx, float x, float y, int device_no, uint32_t time);
int ctx_pointer_press     (Ctx *ctx, float x, float y, int device_no, uint32_t time);
int ctx_pointer_drop      (Ctx *ctx, float x, float y, int device_no, uint32_t time,
                           char *string);

typedef enum
{
  CTX_CONT             = '\0', // - contains args from preceding entry
  CTX_NOP              = ' ', //
  CTX_DATA             = '(', // size size-in-entries - u32
  CTX_DATA_REV         = ')', // reverse traversal data marker
  CTX_SET_RGBA_U8      = '*', // r g b a - u8
  CTX_NEW_EDGE         = '+', // x0 y0 x1 y1 - s16
  // set pixel might want a shorter ascii form? or keep it an embedded
  // only option?
  CTX_SET_PIXEL        = '-', // 8bit "fast-path" r g b a x y - u8 for rgba, and u16 for x,y
  /* optimizations that reduce the number of entries used,
   * not visible outside the drawlist compression, thus
   * using entries that cannot be used directly as commands
   * since they would be interpreted as numbers - if values>127
   * then the embedded font data is harder to escape.
   */
  CTX_REL_LINE_TO_X4            = '0', // x1 y1 x2 y2 x3 y3 x4 y4   -- s8
  CTX_REL_LINE_TO_REL_CURVE_TO  = '1', // x1 y1 cx1 cy1 cx2 cy2 x y -- s8
  CTX_REL_CURVE_TO_REL_LINE_TO  = '2', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8
  CTX_REL_CURVE_TO_REL_MOVE_TO  = '3', // cx1 cy1 cx2 cy2 x y x1 y1 -- s8
  CTX_REL_LINE_TO_X2            = '4', // x1 y1 x2 y2 -- s16
  CTX_MOVE_TO_REL_LINE_TO       = '5', // x1 y1 x2 y2 -- s16
  CTX_REL_LINE_TO_REL_MOVE_TO   = '6', // x1 y1 x2 y2 -- s16
  CTX_FILL_MOVE_TO              = '7', // x y
  CTX_REL_QUAD_TO_REL_QUAD_TO   = '8', // cx1 x1 cy1 y1 cx1 x2 cy1 y1 -- s8
  CTX_REL_QUAD_TO_S16           = '9', // cx1 cy1 x y                 - s16
  // expand with: . : 
  CTX_FLUSH            = ';',

  CTX_DEFINE_GLYPH     = '@', // unichar width - u32
  CTX_ARC_TO           = 'A', // x1 y1 x2 y2 radius
  CTX_ARC              = 'B', // x y radius angle1 angle2 direction
  CTX_CURVE_TO         = 'C', // cx1 cy1 cx2 cy2 x y
  CTX_STROKE           = 'E', //
  CTX_FILL             = 'F', //
  CTX_RESTORE          = 'G', //
  CTX_HOR_LINE_TO      = 'H', // x
  CTX_DEFINE_TEXTURE   = 'I', // "eid" width height format "data"
  CTX_ROTATE           = 'J', // radians
  CTX_COLOR            = 'K', // model, c1 c2 c3 ca - has a variable set of
  // arguments.
  CTX_LINE_TO          = 'L', // x y
  CTX_MOVE_TO          = 'M', // x y
  CTX_BEGIN_PATH       = 'N', //
  CTX_SCALE            = 'O', // xscale yscale
  CTX_NEW_PAGE         = 'P', // - NYI - optional page-size
  CTX_QUAD_TO          = 'Q', // cx cy x y
  CTX_VIEW_BOX         = 'R', // x y width height
  CTX_SMOOTH_TO        = 'S', // cx cy x y
  CTX_SMOOTHQ_TO       = 'T', // x y
  CTX_RESET            = 'U', //
  CTX_VER_LINE_TO      = 'V', // y
  CTX_APPLY_TRANSFORM  = 'W', // a b c d e f - for set_transform combine with identity
  CTX_EXIT             = 'X', //
  CTX_ROUND_RECTANGLE  = 'Y', // x y width height radius

  CTX_CLOSE_PATH2      = 'Z', //
  CTX_STROKE_SOURCE    = '_', // next source definition applies to strokes
  CTX_KERNING_PAIR     = '[', // glA glB kerning, glA and glB in u16 kerning in s32
  CTX_COLOR_SPACE      = ']', // IccSlot  data  data_len,
                         //    data can be a string with a name,
                         //    icc data or perhaps our own serialization
                         //    of profile data
  CTX_REL_ARC_TO       = 'a', // x1 y1 x2 y2 radius
  CTX_CLIP             = 'b',
  CTX_REL_CURVE_TO     = 'c', // cx1 cy1 cx2 cy2 x y
  CTX_LINE_DASH        = 'd', // dashlen0 [dashlen1 ...]
  CTX_TRANSLATE        = 'e', // x y
  CTX_LINEAR_GRADIENT  = 'f', // x1 y1 x2 y2
  CTX_SAVE             = 'g',
  CTX_REL_HOR_LINE_TO  = 'h', // x
  CTX_TEXTURE          = 'i',
  CTX_PRESERVE         = 'j', // 
  CTX_SET_KEY          = 'k', // - used together with another char to identify
                              //   a key to set
  CTX_REL_LINE_TO      = 'l', // x y
  CTX_REL_MOVE_TO      = 'm', // x y
  CTX_FONT             = 'n', // as used by text parser
  CTX_RADIAL_GRADIENT  = 'o', // x1 y1 radius1 x2 y2 radius2
  CTX_GRADIENT_STOP    = 'p', // argument count depends on current color model
  CTX_REL_QUAD_TO      = 'q', // cx cy x y
  CTX_RECTANGLE        = 'r', // x y width height
  CTX_REL_SMOOTH_TO    = 's', // cx cy x y
  CTX_REL_SMOOTHQ_TO   = 't', // x y
  CTX_STROKE_TEXT      = 'u', // string - utf8 string
  CTX_REL_VER_LINE_TO  = 'v', // y
  CTX_GLYPH            = 'w', // unichar fontsize
  CTX_TEXT             = 'x', // string | kern - utf8 data to shape or horizontal kerning amount
  CTX_IDENTITY         = 'y', //
  CTX_CLOSE_PATH       = 'z', //
  CTX_START_GROUP      = '{',
  CTX_END_GROUP        = '}',
  CTX_SOURCE_TRANSFORM = '`',

  CTX_EDGE             = '&',                        // will not occur in commandstream
  CTX_EDGE_FLIPPED     = '^', // x0 y0 x1 y1 - s16   // thus these use reserved entries as code

  /* though expressed as two chars in serialization we have
   * dedicated byte commands for the setters to keep the dispatch
   * simpler. There is no need for these to be human readable thus we go >128
   *
   * unused:        !&<=>?:.=/\`,
   * reserved:      '"&   #  %^@
   */


  CTX_FILL_RULE        = 128, // kr rule - u8, default = CTX_FILLE_RULE_EVEN_ODD
  CTX_BLEND_MODE       = 129, // kB mode - u8 , default=0

  CTX_MITER_LIMIT      = 130, // km limit - float, default = 0.0

  CTX_LINE_JOIN        = 131, // kj join - u8 , default=0
  CTX_LINE_CAP         = 132, // kc cap - u8, default = 0
  CTX_LINE_WIDTH       = 133, // kw width, default = 2.0
  CTX_GLOBAL_ALPHA     = 134, // ka alpha - default=1.0
  CTX_COMPOSITING_MODE = 135, // kc mode - u8 , default=0

  CTX_FONT_SIZE        = 136, // kf size - float, default=?
  CTX_TEXT_ALIGN       = 137, // kt align - u8, default = CTX_TEXT_ALIGN_START
  CTX_TEXT_BASELINE    = 138, // kb baseline - u8, default = CTX_TEXT_ALIGN_ALPHABETIC
  CTX_TEXT_DIRECTION   = 139, // kd

  CTX_SHADOW_BLUR      = 140, // ks
  CTX_SHADOW_COLOR     = 141, // kC
  CTX_SHADOW_OFFSET_X  = 142, // kx
  CTX_SHADOW_OFFSET_Y  = 143, // ky
  CTX_IMAGE_SMOOTHING  = 144, // kS
  CTX_LINE_DASH_OFFSET = 145, // kD lineDashOffset

  // items marked with % are currently only for the parser
  // for instance for svg compatibility or simulated/converted color spaces
  // not the serialization/internal render stream
  //
  CTX_STROKE_RECT      = 200, // strokeRect - only exist in long form
  CTX_FILL_RECT        = 201, // fillRect   - only exist in long form
} CtxCode;


#pragma pack(push,1)

typedef struct _CtxCommand CtxCommand;
typedef struct _CtxIterator CtxIterator;

CtxIterator *
ctx_current_path (Ctx *ctx);
void
ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2);

#define CTX_ASSERT               0

#if CTX_ASSERT==1
#define ctx_assert(a)  if(!(a)){fprintf(stderr,"%s:%i assertion failed\n", __FUNCTION__, __LINE__);  }
#else
#define ctx_assert(a)
#endif

int ctx_get_drawlist_count (Ctx *ctx);

struct
  _CtxCommand
{
  union
  {
    uint8_t  code;
    CtxEntry entry;
    struct
    {
      uint8_t code;
      float scalex;
      float scaley;
    } scale;
    struct
    {
      uint8_t code;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t cont;
      uint8_t data[8]; /* ... and continues */
    } data;
    struct
    {
      uint8_t code;
      uint32_t stringlen;
      uint32_t blocklen;
    } data_rev;
    struct
    {
      uint8_t code;
      float pad;
      float pad2;
      uint8_t code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t code_cont;
      uint8_t utf8[8]; /* .. and continues */
    } text;
    struct
    {
      uint8_t  code;
      uint32_t key_hash;
      float    pad;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont;
      uint8_t  utf8[8]; /* .. and continues */
    } set;
    struct
    {
      uint8_t  code;
      uint32_t pad0;
      float    pad1;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont;
      uint8_t  utf8[8]; /* .. and continues */
    } get;
    struct {
      uint8_t  code;
      uint32_t count; /* better than byte_len in code, but needs to then be set   */
      float    pad1;
      uint8_t  code_data;
      uint32_t byte_len;
      uint32_t blocklen;
      uint8_t  code_cont;
      float    data[2]; /* .. and - possibly continues */
    } line_dash;
    struct {
      uint8_t  code;
      uint32_t space_slot;
      float    pad1;
      uint8_t  code_data;
      uint32_t data_len;
      uint32_t blocklen;
      uint8_t  code_cont;
      uint8_t  data[8]; /* .. and continues */
    } colorspace;
    struct
    {
      uint8_t  code;
      float    x;
      float    y;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont;
      char     eid[8]; /* .. and continues */
    } texture;
    struct
    {
      uint8_t  code;
      uint32_t width;
      uint32_t height;
      uint8_t  code_cont0;
      uint16_t format;
      uint16_t pad0;
      uint32_t pad1;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont1;
      char     eid[8]; /* .. and continues */
      // followed by - in variable offset code_Data, data_len, datablock_len, cont, pixeldata
    } define_texture;
    struct
    {
      uint8_t  code;
      float    pad;
      float    pad2;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont;
      uint8_t  utf8[8]; /* .. and continues */
    } text_stroke;
    struct
    {
      uint8_t  code;
      float    pad;
      float    pad2;
      uint8_t  code_data;
      uint32_t stringlen;
      uint32_t blocklen;
      uint8_t  code_cont;
      uint8_t  utf8[8]; /* .. and continues */
    } set_font;
    struct
    {
      uint8_t code;
      float model;
      float r;
      uint8_t pad1;
      float g;
      float b;
      uint8_t pad2;
      float a;
    } rgba;
    struct
    {
      uint8_t code;
      float model;
      float c;
      uint8_t pad1;
      float m;
      float y;
      uint8_t pad2;
      float k;
      float a;
    } cmyka;
    struct
    {
      uint8_t code;
      float model;
      float g;
      uint8_t pad1;
      float a;
    } graya;

    struct
    {
      uint8_t code;
      float model;
      float c0;
      uint8_t pad1;
      float c1;
      float c2;
      uint8_t pad2;
      float c3;
      float c4;
      uint8_t pad3;
      float c5;
      float c6;
      uint8_t pad4;
      float c7;
      float c8;
      uint8_t pad5;
      float c9;
      float c10;
    } set_color;
    struct
    {
      uint8_t code;
      float x;
      float y;
    } rel_move_to;
    struct
    {
      uint8_t code;
      float x;
      float y;
    } rel_line_to;
    struct
    {
      uint8_t code;
      float x;
      float y;
    } line_to;
    struct
    {
      uint8_t code;
      float cx1;
      float cy1;
      uint8_t pad0;
      float cx2;
      float cy2;
      uint8_t pad1;
      float x;
      float y;
    } rel_curve_to;
    struct
    {
      uint8_t code;
      float x;
      float y;
    } move_to;
    struct
    {
      uint8_t code;
      float cx1;
      float cy1;
      uint8_t pad0;
      float cx2;
      float cy2;
      uint8_t pad1;
      float x;
      float y;
    } curve_to;
    struct
    {
      uint8_t code;
      float x1;
      float y1;
      uint8_t pad0;
      float r1;
      float x2;
      uint8_t pad1;
      float y2;
      float r2;
    } radial_gradient;
    struct
    {
      uint8_t code;
      float x1;
      float y1;
      uint8_t pad0;
      float x2;
      float y2;
    } linear_gradient;
    struct
    {
      uint8_t code;
      float x;
      float y;
      uint8_t pad0;
      float width;
      float height;
      uint8_t pad1;
      float radius;
    } rectangle;
    struct {
      uint8_t code;
      float x;
      float y;
      uint8_t pad0;
      float width;
      float height;
    } view_box;

    struct
    {
      uint8_t code;
      uint16_t glyph_before;
      uint16_t glyph_after;
       int32_t amount;
    } kern;

    struct
    {
      uint8_t code;
      uint32_t glyph;
      uint32_t advance; // * 256
    } define_glyph;

    struct
    {
      uint8_t code;
      uint8_t rgba[4];
      uint16_t x;
      uint16_t y;
    } set_pixel;
    struct
    {
      uint8_t code;
      float cx;
      float cy;
      uint8_t pad0;
      float x;
      float y;
    } quad_to;
    struct
    {
      uint8_t code;
      float cx;
      float cy;
      uint8_t pad0;
      float x;
      float y;
    } rel_quad_to;
    struct
    {
      uint8_t code;
      float x;
      float y;
      uint8_t pad0;
      float radius;
      float angle1;
      uint8_t pad1;
      float angle2;
      float direction;
    }
    arc;
    struct
    {
      uint8_t code;
      float x1;
      float y1;
      uint8_t pad0;
      float x2;
      float y2;
      uint8_t pad1;
      float radius;
    }
    arc_to;
    /* some format specific generic accesors:  */
    struct
    {
      uint8_t code;
      float   x0;
      float   y0;
      uint8_t pad0;
      float   x1;
      float   y1;
      uint8_t pad1;
      float   x2;
      float   y2;
      uint8_t pad2;
      float   x3;
      float   y3;
      uint8_t pad3;
      float   x4;
      float   y4;
    } c;
    struct
    {
      uint8_t code;
      float   a0;
      float   a1;
      uint8_t pad0;
      float   a2;
      float   a3;
      uint8_t pad1;
      float   a4;
      float   a5;
      uint8_t pad2;
      float   a6;
      float   a7;
      uint8_t pad3;
      float   a8;
      float   a9;
    } f;
    struct
    {
      uint8_t  code;
      uint32_t a0;
      uint32_t a1;
      uint8_t  pad0;
      uint32_t a2;
      uint32_t a3;
      uint8_t  pad1;
      uint32_t a4;
      uint32_t a5;
      uint8_t  pad2;
      uint32_t a6;
      uint32_t a7;
      uint8_t  pad3;
      uint32_t a8;
      uint32_t a9;
    } u32;
    struct
    {
      uint8_t  code;
      uint64_t a0;
      uint8_t  pad0;
      uint64_t a1;
      uint8_t  pad1;
      uint64_t a2;
      uint8_t  pad2;
      uint64_t a3;
      uint8_t  pad3;
      uint64_t a4;
    } u64;
    struct
    {
      uint8_t code;
      int32_t a0;
      int32_t a1;
      uint8_t pad0;
      int32_t a2;
      int32_t a3;
      uint8_t pad1;
      int32_t a4;
      int32_t a5;
      uint8_t pad2;
      int32_t a6;
      int32_t a7;
      uint8_t pad3;
      int32_t a8;
      int32_t a9;
    } s32;
    struct
    {
      uint8_t code;
      int16_t a0;
      int16_t a1;
      int16_t a2;
      int16_t a3;
      uint8_t pad0;
      int16_t a4;
      int16_t a5;
      int16_t a6;
      int16_t a7;
      uint8_t pad1;
      int16_t a8;
      int16_t a9;
      int16_t a10;
      int16_t a11;
      uint8_t pad2;
      int16_t a12;
      int16_t a13;
      int16_t a14;
      int16_t a15;
      uint8_t pad3;
      int16_t a16;
      int16_t a17;
      int16_t a18;
      int16_t a19;
    } s16;
    struct
    {
      uint8_t code;
      uint16_t a0;
      uint16_t a1;
      uint16_t a2;
      uint16_t a3;
      uint8_t pad0;
      uint16_t a4;
      uint16_t a5;
      uint16_t a6;
      uint16_t a7;
      uint8_t pad1;
      uint16_t a8;
      uint16_t a9;
      uint16_t a10;
      uint16_t a11;
      uint8_t pad2;
      uint16_t a12;
      uint16_t a13;
      uint16_t a14;
      uint16_t a15;
      uint8_t pad3;
      uint16_t a16;
      uint16_t a17;
      uint16_t a18;
      uint16_t a19;
    } u16;
    struct
    {
      uint8_t code;
      uint8_t a0;
      uint8_t a1;
      uint8_t a2;
      uint8_t a3;
      uint8_t a4;
      uint8_t a5;
      uint8_t a6;
      uint8_t a7;
      uint8_t pad0;
      uint8_t a8;
      uint8_t a9;
      uint8_t a10;
      uint8_t a11;
      uint8_t a12;
      uint8_t a13;
      uint8_t a14;
      uint8_t a15;
      uint8_t pad1;
      uint8_t a16;
      uint8_t a17;
      uint8_t a18;
      uint8_t a19;
      uint8_t a20;
      uint8_t a21;
      uint8_t a22;
      uint8_t a23;
    } u8;
    struct
    {
      uint8_t code;
      int8_t a0;
      int8_t a1;
      int8_t a2;
      int8_t a3;
      int8_t a4;
      int8_t a5;
      int8_t a6;
      int8_t a7;
      uint8_t pad0;
      int8_t a8;
      int8_t a9;
      int8_t a10;
      int8_t a11;
      int8_t a12;
      int8_t a13;
      int8_t a14;
      int8_t a15;
      uint8_t pad1;
      int8_t a16;
      int8_t a17;
      int8_t a18;
      int8_t a19;
      int8_t a20;
      int8_t a21;
      int8_t a22;
      int8_t a23;
    } s8;
  };
  CtxEntry next_entry; // also pads size of CtxCommand slightly.
};

typedef struct _CtxImplementation CtxImplementation;
struct _CtxImplementation
{
  void (*process)        (void *renderer, CtxCommand *entry);
  void (*reset)          (void *renderer);
  void (*flush)          (void *renderer);
  char *(*get_clipboard) (void *ctxctx);
  void (*set_clipboard)  (void *ctxctx, const char *text);
  void (*free)           (void *renderer);
};

CtxCommand *ctx_iterator_next (CtxIterator *iterator);

#define ctx_arg_string()  ((char*)&entry[2].data.u8[0])


/* The above should be public API
 */

#pragma pack(pop)

/* access macros for nth argument of a given type when packed into
 * an CtxEntry pointer in current code context
 */
#define ctx_arg_float(no) entry[(no)>>1].data.f[(no)&1]
#define ctx_arg_u64(no)   entry[(no)].data.u64[0]
#define ctx_arg_u32(no)   entry[(no)>>1].data.u32[(no)&1]
#define ctx_arg_s32(no)   entry[(no)>>1].data.s32[(no)&1]
#define ctx_arg_u16(no)   entry[(no)>>2].data.u16[(no)&3]
#define ctx_arg_s16(no)   entry[(no)>>2].data.s16[(no)&3]
#define ctx_arg_u8(no)    entry[(no)>>3].data.u8[(no)&7]
#define ctx_arg_s8(no)    entry[(no)>>3].data.s8[(no)&7]
#define ctx_arg_string()  ((char*)&entry[2].data.u8[0])

typedef enum
{
  CTX_GRAY           = 1,
  CTX_RGB            = 3,
  CTX_DRGB           = 4,
  CTX_CMYK           = 5,
  CTX_DCMYK          = 6,
  CTX_LAB            = 7,
  CTX_LCH            = 8,
  CTX_GRAYA          = 101,
  CTX_RGBA           = 103,
  CTX_DRGBA          = 104,
  CTX_CMYKA          = 105,
  CTX_DCMYKA         = 106,
  CTX_LABA           = 107,
  CTX_LCHA           = 108,
  CTX_GRAYA_A        = 201,
  CTX_RGBA_A         = 203,
  CTX_RGBA_A_DEVICE  = 204,
  CTX_CMYKA_A        = 205,
  CTX_DCMYKA_A       = 206,
  // RGB  device and  RGB  ?
} CtxColorModel;

enum _CtxAntialias
{
  CTX_ANTIALIAS_DEFAULT, // fast - suitable for realtime UI
  CTX_ANTIALIAS_NONE, // non-antialiased
  CTX_ANTIALIAS_FAST, // aa 3    // deprected or is default equal to this now?
  CTX_ANTIALIAS_GOOD, // aa 5    // this should perhaps still be 5?
  CTX_ANTIALIAS_BEST  // aa 17   // accurate-suitable for saved assets
};
typedef enum _CtxAntialias CtxAntialias;

enum _CtxCursor
{
  CTX_CURSOR_UNSET,
  CTX_CURSOR_NONE,
  CTX_CURSOR_ARROW,
  CTX_CURSOR_IBEAM,
  CTX_CURSOR_WAIT,
  CTX_CURSOR_HAND,
  CTX_CURSOR_CROSSHAIR,
  CTX_CURSOR_RESIZE_ALL,
  CTX_CURSOR_RESIZE_N,
  CTX_CURSOR_RESIZE_S,
  CTX_CURSOR_RESIZE_E,
  CTX_CURSOR_RESIZE_NE,
  CTX_CURSOR_RESIZE_SE,
  CTX_CURSOR_RESIZE_W,
  CTX_CURSOR_RESIZE_NW,
  CTX_CURSOR_RESIZE_SW,
  CTX_CURSOR_MOVE
};
typedef enum _CtxCursor CtxCursor;

/* to be used immediately after a ctx_listen or ctx_listen_full causing the
 * cursor to change when hovering the listen area.
 */
void ctx_listen_set_cursor (Ctx      *ctx,
                            CtxCursor cursor);

/* lower level cursor setting that is independent of ctx event handling
 */
void         ctx_set_cursor (Ctx *ctx, CtxCursor cursor);
CtxCursor    ctx_get_cursor (Ctx *ctx);
void         ctx_set_antialias (Ctx *ctx, CtxAntialias antialias);
CtxAntialias ctx_get_antialias (Ctx *ctx);
void         ctx_set_render_threads   (Ctx *ctx, int n_threads);
int          ctx_get_render_threads   (Ctx *ctx);

void         ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache);
int          ctx_get_hash_cache (Ctx *ctx);


typedef struct _CtxParser CtxParser;
  CtxParser *ctx_parser_new (
  Ctx       *ctx,
  int        width,
  int        height,
  float      cell_width,
  float      cell_height,
  int        cursor_x,
  int        cursor_y,
  int   (*set_prop)(void *prop_data, uint64_t key, const char *data,  int len),
  int   (*get_prop)(void *prop_Data, const char *key, char **data, int *len),
  void  *prop_data,
  void (*exit) (void *exit_data),
  void *exit_data);


enum _CtxColorSpace
{
  CTX_COLOR_SPACE_DEVICE_RGB,
  CTX_COLOR_SPACE_DEVICE_CMYK,
  CTX_COLOR_SPACE_USER_RGB,
  CTX_COLOR_SPACE_USER_CMYK,
  CTX_COLOR_SPACE_TEXTURE
};
typedef enum _CtxColorSpace CtxColorSpace;

/* sets the color space for a slot, the space is either a string of
 * "sRGB" "rec2020" .. etc or an icc profile.
 *
 * The slots device_rgb and device_cmyk is mostly to be handled outside drawing 
 * code, and user_rgb and user_cmyk is to be used. With no user_cmyk set
 * user_cmyk == device_cmyk.
 *
 * The set profiles follows the graphics state.
 */
void ctx_colorspace (Ctx           *ctx,
                     CtxColorSpace  space_slot,
                     unsigned char *data,
                     int            data_length);

void
ctx_parser_set_size (CtxParser *parser,
                     int        width,
                     int        height,
                     float      cell_width,
                     float      cell_height);

void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count);

int
ctx_get_contents (const char     *path,
                   unsigned char **contents,
                   long           *length);

void ctx_parser_free (CtxParser *parser);
typedef struct _CtxSHA1 CtxSHA1;

void
ctx_bin2base64 (const void *bin,
                int         bin_length,
                char       *ascii);
int
ctx_base642bin (const char    *ascii,
                int           *length,
                unsigned char *bin);


struct
  _CtxMatrix
{
  float m[3][2];
};

void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix);
void ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y);
void ctx_matrix_invert (CtxMatrix *m);
void ctx_matrix_identity (CtxMatrix *matrix);
void ctx_matrix_scale (CtxMatrix *matrix, float x, float y);
void ctx_matrix_rotate (CtxMatrix *matrix, float angle);
void ctx_matrix_multiply (CtxMatrix       *result,
                          const CtxMatrix *t,
                          const CtxMatrix *s);




float ctx_term_get_cell_width (Ctx *ctx);
float ctx_term_get_cell_height (Ctx *ctx);





#if 1 // CTX_VT

typedef struct _VT VT;
void vt_feed_keystring  (VT *vt, CtxEvent *event, const char *str);
void vt_paste           (VT *vt, const char *str);
char *vt_get_selection  (VT *vt);
long vt_rev             (VT *vt);
int  vt_has_blink       (VT *vt);

int  ctx_clients_need_redraw   (Ctx *ctx);
void ctx_clients_handle_events (Ctx *ctx);


typedef struct _CtxBuffer CtxBuffer;
CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height,
                                    int stride,
                                    CtxPixelFormat pixel_format,
                                    void (*freefunc) (void *pixels, void *user_data),
                                    void *user_data);







#endif


#ifndef CTX_CODEC_CHAR
//#define CTX_CODEC_CHAR '\035'
//#define CTX_CODEC_CHAR 'a'
#define CTX_CODEC_CHAR '\020' // datalink escape
//#define CTX_CODEC_CHAR '^'
#endif

#ifndef assert
#define assert(a)
#endif

#ifdef __cplusplus
}
#endif
#endif
#ifndef __CTX_H__
#define __CTX_H__
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#ifndef CTX_STRING_H
#define CTX_STRING_H

typedef struct _CtxString CtxString;
struct _CtxString
{
  char *str;
  int   length;
  int   utf8_length;
  int   allocated_length;
  int   is_line;
};

CtxString   *ctx_string_new_with_size  (const char *initial, int initial_size);
CtxString   *ctx_string_new            (const char *initial);
char       *ctx_string_dissolve       (CtxString *string);
void        ctx_string_free           (CtxString *string, int freealloc);
const char *ctx_string_get            (CtxString *string);
uint32_t    ctx_string_get_unichar    (CtxString *string, int pos);
int         ctx_string_get_length     (CtxString *string);
int         ctx_string_get_utf8length (CtxString *string);
void        ctx_string_set            (CtxString *string, const char *new_string);
void        ctx_string_clear          (CtxString *string);
void        ctx_string_append_str     (CtxString *string, const char *str);
void        ctx_string_append_byte    (CtxString *string, char  val);
void        ctx_string_append_string  (CtxString *string, CtxString *string2);
void        ctx_string_append_unichar (CtxString *string, unsigned int unichar);
void        ctx_string_append_data    (CtxString *string, const char *data, int len);

void        ctx_string_append_utf8char (CtxString *string, const char *str);
void        ctx_string_append_printf  (CtxString *string, const char *format, ...);
void        ctx_string_replace_utf8   (CtxString *string, int pos, const char *new_glyph);
void        ctx_string_insert_utf8    (CtxString *string, int pos, const char *new_glyph);

void        ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar);
void        ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar);
void        ctx_string_remove         (CtxString *string, int pos);
char       *ctx_strdup_printf         (const char *format, ...);

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

#endif
#ifndef _CTX_INTERNAL_FONT_
#define _CTX_INTERNAL_FONT_

#ifndef CTX_FONT_ascii
/* this is a ctx encoded font based on DejaVuSans.ttf */
/* CTX_SUBDIV:8  CTX_BAKE_FONT_SIZE:160 */
/* glyphs covered: 

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
  jklmnopqrstuvwxyz{|}~  */
static const struct __attribute__ ((packed)) {uint8_t code; uint32_t a; uint32_t b;}
ctx_font_ascii[]={
{'@', 0x00000020, 0x00002bb0},/*                 x-advance: 43.687500 */
{'@', 0x00000021, 0x00003719},/*        !        x-advance: 55.097656 */
{'M', 0x41a5e7f2, 0xc1886037},
{'l', 0x4159fc90, 0x00000000},
{'4', 0x00880000, 0x0000ff94},
{'6', 0xff780000, 0xfd670000},
{'l', 0x4159fc90, 0x00000000},
{'l', 0x00000000, 0x422fd6c4},
{'l', 0xbfabcfe0, 0x41bfad86},
{'l', 0xc12df5b2, 0x00000000},
{'l', 0xbfb46710, 0xc1bfad86},
{'l', 0x00000000, 0xc22fd6c4},
{'@', 0x00000022, 0x00003f38},/*        "        x-advance: 63.218750 */
{'M', 0x41c50c07, 0xc2c86716},
{'l', 0x00000000, 0x4214fe48},
{'4', 0x0000ffa5, 0xfed70000},
{'6', 0x0000005b, 0x000000ca},
{'l', 0x00000000, 0x4214fe48},
{'l', 0xc1368ce4, 0x00000000},
{'l', 0x00000000, 0xc214fe48},
{'l', 0x41368ce4, 0x00000000},
{'@', 0x00000023, 0x0000732a},/*        #        x-advance: 115.164062 */
{'M', 0x428c8973, 0xc271e113},
{'l', 0xc19c3dda, 0x00000000},
{'4', 0x00b3ffd3, 0x0000009d},
{'6', 0xff4d002c, 0xfecfffb0},
{'l', 0xc0df5b10, 0x41ded19c},
{'l', 0x419cc74c, 0x00000000},
{'l', 0x40e180e0, 0xc1ded19c},
{'l', 0x412bcfe8, 0x00000000},
{'l', 0xc0dd3540, 0x41ded19c},
{'l', 0x41a78448, 0x00000000},
{'l', 0x00000000, 0x41255e7c},
{'l', 0xc1bc74d4, 0x00000000},
{'l', 0xc0b01b80, 0x41b35430},
{'l', 0x41aabd00, 0x00000000},
{'l', 0x00000000, 0x41244b9a},
{'l', 0xc1bfad88, 0x00000000},
{'l', 0xc0df5b10, 0x41de4829},
{'l', 0xc12bcfe4, 0x00000000},
{'l', 0x40dd3540, 0xc1de4829},
{'l', 0xc19d50c0, 0x00000000},
{'l', 0xc0dd3540, 0x41de4829},
{'l', 0xc12ce2ca, 0x00000000},
{'l', 0x40df5b10, 0xc1de4829},
{'l', 0xc1a920a5, 0x00000000},
{'l', 0x00000000, 0xc1244b9a},
{'l', 0x41bcfe48, 0x00000000},
{'l', 0x40b46718, 0xc1b35430},
{'l', 0xc1ace2cb, 0x00000000},
{'l', 0x00000000, 0xc1255e7c},
{'l', 0x41c1d353, 0x00000000},
{'l', 0x40db0f78, 0xc1ded19c},
{'l', 0x412df5b0, 0x00000000},
{'@', 0x00000024, 0x00005773},/*        $        x-advance: 87.449219 */
{'M', 0x4239c595, 0x41a19c59},
{'4', 0x0000ffcb, 0xff5f0000},
{'q', 0xc0e180d8, 0xbe09731d},
{0, 0xc16180dc, 0xbfce2cac},
{'9', 0xfff4ffc8, 0xffdcff8f},
{'l', 0x00000000, 0xc14149e1},
{'q', 0x40db0f76, 0x4089731e},
{0, 0x415d3543, 0x40d05278},
{'9', 0x00110038, 0x00110073},
{'l', 0x00000000, 0xc1f4d50c},
{'q', 0xc16d50c2, 0xc01aa180},
{0, 0xc1ace2cb, 0xc10301b8},
{'q', 0xc0d6c3de, 0xc0b8b2b0},
{0, 0xc0d6c3de, 0xc17d6c3c},
{'q', 0x00000000, 0xc12f0898},
{0, 0x40ea180e, 0xc189fc90},
{'9', 0xffce003a, 0xffc700a8},
{'4', 0xff820000, 0x00000035},
{'l', 0x00000000, 0x417920a8},
{'8', 0x0a600231, 0x165b082e},
{'l', 0x00000000, 0x413beb60},
{'8', 0xdea5ead4, 0xf2a0f4d2},
{'l', 0x00000000, 0x41e54302},
{'q', 0x4173c228, 0x401655f0},
{0, 0x41b35432, 0x41063a6c},
{'q', 0x40e5cc70, 0x40c149e0},
{0, 0x40e5cc70, 0x4184149e},
{'q', 0x00000000, 0x413579fc},
{0, 0xc0f4d510, 0x418f5b0f},
{'9', 0x0034ffc4, 0x003cff51},
{'6', 0x00a20000, 0xfdc1ffcb},
{'l', 0x00000000, 0xc1dc2258},
{'8', 0x23a106c2, 0x4be01ce0},
{'8', 0x471e2e00, 0x2561191e},
{'m', 0x40d6c3d8, 0x414e2cac},
{'l', 0x00000000, 0x41e87bb4},
{'8', 0xda66f744, 0xb322e322},
{'8', 0xb5dfd100, 0xd898e5e0},
{'@', 0x00000025, 0x0000829a},/*        %        x-advance: 130.601562 */
{'M', 0x42c7dda3, 0xc2306037},
{'8', 0x27b700d2, 0x6ee627e6},
{'8', 0x6e1a4500, 0x2749271a},
{'8', 0xd947002d, 0x921ad81a},
{'8', 0x92e6ba00, 0xd8b9d8e6},
{'m', 0x00000000, 0xc1086034},
{'q', 0x4129aa18, 0x00000000},
{0, 0x4186c3dc, 0x40ec3dd8},
{'q', 0x40c7bb50, 0x40ec3dd8},
{0, 0x40c7bb50, 0x419f768c},
{'q', 0x00000000, 0x4148ce2d},
{0, 0xc0c9e120, 0x419f768d},
{'q', 0xc0c7bb40, 0x40ea180d},
{0, 0xc1863a68, 0x40ea180d},
{'q', 0xc12bcfe8, 0x34000000},
{0, 0xc187d6c4, 0xc0ea180d},
{'q', 0xc0c7bb40, 0xc0ec3dda},
{0, 0xc0c7bb40, 0xc19f768d},
{'q', 0x00000000, 0xc149e114},
{0, 0x40c7bb40, 0xc19f768c},
{'9', 0xffc50032, 0xffc50087},
{'m', 0xc28a8603, 0xc2237d6c},
{'8', 0x28b700d2, 0x6de627e6},
{'8', 0x6e1a4600, 0x2749271a},
{'8', 0xd949002e, 0x921ad91a},
{'8', 0x93e6bb00, 0xd8b7d8e6},
{'m', 0x42726a86, 0xc1086038},
{'l', 0x412bcfe0, 0x00000000},
{'4', 0x033ffe0b, 0x0000ffab},
{'6', 0xfcc101f5, 0x0000fe1c},
{'q', 0x4129aa14, 0x00000000},
{0, 0x41874d50, 0x40ec3de0},
{'q', 0x40c9e110, 0x40ea1800},
{0, 0x40c9e110, 0x419eed18},
{'q', 0x00000000, 0x414af3f8},
{0, 0xc0c9e110, 0x41a00000},
{'q', 0xc0c7bb48, 0x40ea1808},
{0, 0xc1874d51, 0x40ea1808},
{'q', 0xc12abcfe, 0x00000000},
{0, 0xc1874d51, 0xc0ea1808},
{'q', 0xc0c59579, 0xc0ec3dd8},
{0, 0xc0c59579, 0xc1a00000},
{'q', 0x00000000, 0xc147bb48},
{0, 0x40c7bb47, 0xc19eed18},
{'q', 0x40c7bb46, 0xc0ec3de0},
{0, 0x4186c3de, 0xc0ec3de0},
{'@', 0x00000026, 0x00006b2e},/*        &        x-advance: 107.179688 */
{'M', 0x4205b0f7, 0xc257920a},
{'8', 0x56b92bd0, 0x5aea2aea},
{'q', 0x00000000, 0x411cc74e},
{0, 0x40e3a6a8, 0x41827845},
{'q', 0x40e3a6a8, 0x40d05278},
{0, 0x418ed19c, 0x40d05278},
{'8', 0xf05f0033, 0xcd53ef2c},
{'6', 0xfeddfee4, 0xffc4004b},
{'l', 0x42086037, 0x420b98e9},
{'q', 0x407d6c40, 0xc0bf2410},
{0, 0x40c59570, 0xc14c06e0},
{'9', 0xffca0011, 0xff8d0014},
{'l', 0x4147bb40, 0x00000000},
{'q', 0xbf4e2c80, 0x410dbeb8},
{0, 0xc0897310, 0x418c225c},
{'9', 0x0045ffe5, 0x0088ffb3},
{'4', 0x00990095, 0x0000ff79},
{'l', 0xc1198e98, 0xc11dda33},
{'q', 0xc0df5b10, 0x40bf2414},
{0, 0xc16a1810, 0x410ed19c},
{'q', 0xc0f4d510, 0x4038b2ae},
{0, 0xc1838b2c, 0x4038b2ae},
{'q', 0xc181655e, 0x34000000},
{0, 0xc1d38b2a, 0xc1131d35},
{'q', 0xc1244b99, 0xc114301c},
{0, 0xc1244b99, 0xc1bd87bb},
{'q', 0x00000000, 0xc109731e},
{0, 0x408fe482, 0xc180dbeb},
{'q', 0x408fe484, 0xc0f2af40},
{0, 0x4157d6c4, 0xc163a6a8},
{'8', 0xbdd9dfe7, 0xbef3dff3},
{'q', 0x00000000, 0xc12df5b0},
{0, 0x40ee63a4, 0xc18b98e8},
{'q', 0x40ee63a8, 0xc0d49e10},
{0, 0x419e63a6, 0xc0d49e10},
{'8', 0x0958002c, 0x1c5a092c},
{'l', 0x00000000, 0x41436fb0},
{'8', 0xdaa7e7d2, 0xf3b2f3d6},
{'8', 0x1ea500c8, 0x4cde1dde},
{'8', 0x370f1b00, 0x4d401b10},
{'@', 0x00000027, 0x000025c9},/*        '        x-advance: 37.785156 */
{'M', 0x41c50c07, 0xc2c86716},
{'l', 0x00000000, 0x4214fe48},
{'l', 0xc1368ce3, 0x00000000},
{'l', 0x00000000, 0xc214fe48},
{'l', 0x41368ce3, 0x00000000},
{'@', 0x00000028, 0x0000359f},/*        (        x-advance: 53.621094 */
{'M', 0x422a7844, 0xc2d09732},
{'q', 0xc10fe480, 0x4176fae0},
{0, 0xc155b0f6, 0x41f44b9c},
{'q', 0xc08b98e8, 0x41719c54},
{0, 0xc08b98e8, 0x41f4d50a},
{'q', 0x00000000, 0x41780dbe},
{0, 0x408b98e8, 0x41f5e7f2},
{'9', 0x00790023, 0x00f4006a},
{'l', 0xc12bcfe2, 0x00000000},
{'q', 0xc12112e6, 0xc17c5958},
{0, 0xc1719c5a, 0xc1f80dbf},
{'q', 0xc09eed18, 0xc173c224},
{0, 0xc09eed18, 0xc1f225cc},
{'q', 0x00000000, 0xc16f768c},
{0, 0x409eed18, 0xc1f112e6},
{'q', 0x409eed1c, 0xc172af40},
{0, 0x41719c5a, 0xc1f80dc0},
{'l', 0x412bcfe2, 0x00000000},
{'@', 0x00000029, 0x0000359f},/*        )        x-advance: 53.621094 */
{'M', 0x41301b7d, 0xc2d09732},
{'l', 0x412bcfe5, 0x00000000},
{'q', 0x412112e6, 0x417d6c40},
{0, 0x41708972, 0x41f80dc0},
{'q', 0x40a112e8, 0x4172af40},
{0, 0x40a112e8, 0x41f112e6},
{'q', 0x00000000, 0x41708974},
{0, 0xc0a112e8, 0x41f225cc},
{'9', 0x0079ffd9, 0x00f8ff88},
{'l', 0xc12bcfe5, 0x00000000},
{'q', 0x410ed19d, 0xc175e7f3},
{0, 0x41549e11, 0xc1f44b99},
{'q', 0x408dbeb4, 0xc173c226},
{0, 0x408dbeb4, 0xc1f5e7f2},
{'q', 0x00000000, 0xc1780dc0},
{0, 0xc08dbeb4, 0xc1f4d50a},
{'q', 0xc08b98e8, 0xc1719c58},
{0, 0xc1549e11, 0xc1f44b9c},
{'@', 0x0000002a, 0x000044b9},/*        *        x-advance: 68.722656 */
{'M', 0x42814302, 0xc2a761ef},
{'l', 0xc1c0c070, 0x41505278},
{'l', 0x41c0c070, 0x41516560},
{'l', 0xc07920b0, 0x40d27848},
{'l', 0xc1b46716, 0xc159fc94},
{'l', 0x00000000, 0x41ca6a88},
{'l', 0xc0f4d50c, 0x00000000},
{'l', 0x00000000, 0xc1ca6a88},
{'l', 0xc1b46716, 0x4159fc94},
{'l', 0xc07920a8, 0xc0d27848},
{'l', 0x41c0c06e, 0xc1516560},
{'l', 0xc1c0c06e, 0xc1505278},
{'l', 0x407920a4, 0xc0d49e10},
{'l', 0x41b46716, 0x4159fc90},
{'l', 0x36000000, 0xc1ca6a84},
{'l', 0x40f4d50c, 0x00000000},
{'l', 0x00000000, 0x41ca6a84},
{'l', 0x41b46716, 0xc159fc90},
{'l', 0x407920b0, 0x40d49e10},
{'@', 0x0000002b, 0x0000732a},/*        +        x-advance: 115.164062 */
{'M', 0x427ce2ca, 0xc2ac5957},
{'l', 0x00000000, 0x421587ba},
{'l', 0x421587bc, 0x00000000},
{'l', 0x00000000, 0x41368ce4},
{'l', 0xc21587bc, 0x00000000},
{'l', 0x00000000, 0x421587bb},
{'l', 0xc1346714, 0x00000000},
{'l', 0x00000000, 0xc21587bb},
{'l', 0xc21587bb, 0x00000000},
{'l', 0xb5800000, 0xc1368ce4},
{'l', 0x421587bb, 0x00000000},
{'l', 0x00000000, 0xc21587ba},
{'l', 0x41346714, 0x00000000},
{'@', 0x0000002c, 0x00002bb0},/*        ,        x-advance: 43.687500 */
{'M', 0x4180dbeb, 0xc1886037},
{'l', 0x416293c2, 0x00000000},
{'l', 0x00000000, 0x4138b2b0},
{'l', 0xc1301b7c, 0x41abcfe4},
{'l', 0xc10a8604, 0x00000000},
{'l', 0x40b01b7c, 0xc1abcfe4},
{'l', 0x00000000, 0xc138b2b0},
{'@', 0x0000002d, 0x00003198},/*        -        x-advance: 49.593750 */
{'M', 0x40d6c3dd, 0xc22c9e11},
{'l', 0x4210b2af, 0x00000000},
{'l', 0x00000000, 0x41301b7c},
{'l', 0xc210b2af, 0x00000000},
{'l', 0xb5c00000, 0xc1301b7c},
{'[', 0x0047002d, 0x00000508},
{'[', 0x004a002d, 0x000007a6},
{'[', 0x004f002d, 0x000003d3},
{'[', 0x0051002d, 0x00000508},
{'[', 0x006f002d, 0x0000028c},
{'@', 0x0000002e, 0x00002bb0},/*        .        x-advance: 43.687500 */
{'M', 0x416b2af4, 0xc1886037},
{'l', 0x416293c2, 0x00000000},
{'l', 0x00000000, 0x41886037},
{'l', 0xc16293c2, 0x00000000},
{'l', 0x00000000, 0xc1886037},
{'@', 0x0000002f, 0x00002e4f},/*        /        x-advance: 46.308594 */
{'M', 0x420b98e9, 0xc2c86716},
{'l', 0x41368ce4, 0x00000000},
{'l', 0xc20b98e9, 0x42e1e7f2},
{'l', 0xc1368ce4, 0xb5800000},
{'l', 0x420b98e9, 0xc2e1e7f2},
{'@', 0x00000030, 0x00005773},/*        0        x-advance: 87.449219 */
{'M', 0x422ec3dd, 0xc2b68ce3},
{'q', 0xc1278448, 0x00000000},
{0, 0xc17c5956, 0x41255e80},
{'q', 0xc0a7844c, 0x41244b98},
{0, 0xc0a7844c, 0x41f7844c},
{'q', 0x00000000, 0x41a4d50c},
{0, 0x40a7844c, 0x41f7844c},
{'q', 0x40a9aa1c, 0x41244b98},
{0, 0x417c5956, 0x41244b98},
{'q', 0x41289734, 0x00000000},
{0, 0x417c5958, 0xc1244b98},
{'q', 0x40a9aa18, 0xc1255e80},
{0, 0x40a9aa18, 0xc1f7844c},
{'q', 0x00000000, 0xc1a55e80},
{0, 0xc0a9aa18, 0xc1f7844c},
{'9', 0xffaeffd7, 0xffaeff82},
{'m', 0x00000000, 0xc12bcfe0},
{'q', 0x4186c3de, 0x00000000},
{0, 0x41cda33a, 0x4155b0f8},
{'q', 0x410ed198, 0x41549e10},
{0, 0x410ed198, 0x421aa180},
{'q', 0x00000000, 0x41ca6a86},
{0, 0xc10ed198, 0x421aa181},
{'q', 0xc10dbeb8, 0x41549e11},
{0, 0xc1cda33a, 0x41549e11},
{'q', 0xc186c3dd, 0xb4c00000},
{0, 0xc1ce2cab, 0xc1549e11},
{'q', 0xc10dbeb5, 0xc155b0f8},
{0, 0xc10dbeb5, 0xc21aa181},
{'q', 0x00000000, 0xc1caf3f8},
{0, 0x410dbeb5, 0xc21aa180},
{'q', 0x410ed19c, 0xc155b0f8},
{0, 0x41ce2cab, 0xc155b0f8},
{'@', 0x00000031, 0x00005773},/*        1        x-advance: 87.449219 */
{'M', 0x41886037, 0xc1368ce3},
{'l', 0x41b12e63, 0x00000000},
{'l', 0x00000000, 0xc298e2cb},
{'l', 0xc1c0c06e, 0x409aa180},
{'l', 0x35800000, 0xc1459578},
{'l', 0x41bfad88, 0xc09aa180},
{'l', 0x4158e9a8, 0x00000000},
{'l', 0x00000000, 0x42b1957a},
{'l', 0x41b12e64, 0xb6400000},
{'l', 0x00000000, 0x41368ce3},
{'l', 0xc266df5a, 0x00000000},
{'l', 0xb6000000, 0xc1368ce3},
{'@', 0x00000032, 0x00005773},/*        2        x-advance: 87.449219 */
{'M', 0x41d301b8, 0xc1368ce3},
{'l', 0x423d4302, 0x00000000},
{'4', 0x005b0000, 0x0000fe04},
{'l', 0xb6000000, 0xc1368ce3},
{'q', 0x40f6fad8, 0xc0ff920a},
{0, 0x41a80dbe, 0xc1ab4670},
{'q', 0x4155b0f6, 0xc157d6c4},
{0, 0x41863a6b, 0xc18b0f76},
{'8', 0x9e48c634, 0xb114d814},
{'q', 0x00000000, 0xc0ff9210},
{0, 0xc0b46718, 0xc1505278},
{'q', 0xc0b24148, 0xc0a112f0},
{0, 0xc1690528, 0xc0a112f0},
{'q', 0xc0cc06e0, 0x00000000},
{0, 0xc157d6c2, 0x400dbec0},
{'9', 0x0011ffc8, 0x0035ff88},
{'l', 0x00000000, 0xc15b0f78},
{'q', 0x410301b8, 0xc0527840},
{0, 0x4174d50c, 0xc09eed20},
{'q', 0x40e3a6a8, 0xbfd6c3c0},
{0, 0x41505278, 0xbfd6c3c0},
{'q', 0x417920a4, 0x00000000},
{0, 0x41c6a860, 0x40f920a0},
{'q', 0x41143018, 0x40f920b0},
{0, 0x41143018, 0x41a67168},
{'8', 0x5dee3100, 0x68bd2cee},
{'q', 0xbfd6c3e0, 0x3ff920c0},
{0, 0xc12abd00, 0x41346718},
{'q', 0xc10fe480, 0x4114301c},
{0, 0xc1caf3f8, 0x41cfc904},
{'@', 0x00000033, 0x00005773},/*        3        x-advance: 87.449219 */
{'M', 0x425f1656, 0xc2581b7d},
{'q', 0x411bb468, 0x40052780},
{0, 0x4172af40, 0x410a8604},
{'q', 0x40b01b80, 0x40d27840},
{0, 0x40b01b80, 0x4181eed1},
{'q', 0x00000000, 0x416d50c0},
{0, 0xc12338b8, 0x41b79fc8},
{'q', 0xc12338b0, 0x4101eed3},
{0, 0xc1e7f240, 0x4101eed3},
{'8', 0xf79800ce, 0xe292f6cb},
{'l', 0x00000000, 0xc151655e},
{'q', 0x40b46716, 0x40527844},
{0, 0x4145957b, 0x409eed18},
{'q', 0x40d6c3dc, 0x3fd6c3e0},
{0, 0x41606df6, 0x3fd6c3e0},
{'q', 0x414c06dc, 0x00000000},
{0, 0x419b2af2, 0xc0a112e6},
{'q', 0x40d6c3e0, 0xc0a112e6},
{0, 0x40d6c3e0, 0xc16a180d},
{'q', 0x00000000, 0xc10dbeb6},
{0, 0xc0c7bb48, 0xc15d3542},
{'9', 0xffd8ffcf, 0xffd8ff77},
{'4', 0x0000ffa3, 0xffa70000},
{'l', 0x41436fae, 0x00000000},
{'q', 0x41200000, 0x00000000},
{0, 0x4174d50c, 0xc07d6c40},
{'8', 0xa42ae02a, 0xa2d4c300},
{'q', 0xc0adf5b0, 0xc0852790},
{0, 0xc17a338c, 0xc0852790},
{'q', 0xc0b24148, 0x00000000},
{0, 0xc13f2414, 0x3f9aa180},
{'9', 0x0009ffcd, 0x001eff90},
{'l', 0x00000000, 0xc14149e0},
{'q', 0x40f6fad8, 0xc0097320},
{0, 0x4166df5a, 0xc04e2cc0},
{'q', 0x40d8e9ac, 0xbf897300},
{0, 0x414c06de, 0xbf897300},
{'q', 0x4176fad8, 0x00000000},
{0, 0x41c36fae, 0x40e180e0},
{'q', 0x410fe480, 0x40df5b10},
{0, 0x410fe480, 0x419768cc},
{'q', 0x00000000, 0x41052788},
{0, 0xc0987bb0, 0x416180dc},
{'q', 0xc0987bb0, 0x40b68ce8},
{0, 0xc158e9a8, 0x40fd6c40},
{'@', 0x00000034, 0x00005773},/*        4        x-advance: 87.449219 */
{'M', 0x424fc905, 0xc2b0c74d},
{'4', 0x01abfeef, 0x00000111},
{'6', 0xfe550000, 0xffa2ffe4},
{'l', 0x41886038, 0x00000000},
{'l', 0x00000000, 0x42829aa2},
{'l', 0x4164b990, 0xb6800000},
{'l', 0x00000000, 0x41346714},
{'l', 0xc164b990, 0x00000000},
{'l', 0x00000000, 0x41bcfe48},
{'l', 0xc157d6c4, 0x00000000},
{'l', 0x00000000, 0xc1bcfe48},
{'l', 0xc234f089, 0x00000000},
{'l', 0xb5c00000, 0xc151655c},
{'l', 0x4226b61e, 0xc27df5b1},
{'@', 0x00000035, 0x00005773},/*        5        x-advance: 87.449219 */
{'M', 0x416d50c0, 0xc2c86716},
{'l', 0x4254e2ca, 0x00000000},
{'4', 0x005b0000, 0x0000feba},
{'l', 0x00000000, 0x41c48294},
{'8', 0xf52ff817, 0xfc2ffc17},
{'q', 0x41863a6a, 0x00000000},
{0, 0x41d49e10, 0x41131d34},
{'q', 0x411cc750, 0x41131d34},
{0, 0x411cc750, 0x41c731d2},
{'q', 0x00000000, 0x4181655f},
{0, 0xc12112e8, 0x41c957a0},
{'q', 0xc12112e4, 0x410ed19d},
{0, 0xc1e31d34, 0x410ed19d},
{'8', 0xf89900ce, 0xe795f8cc},
{'l', 0x00000000, 0xc159fc90},
{'8', 0x27631a30, 0x0c6c0c33},
{'q', 0x4139c598, 0x00000000},
{0, 0x41931d36, 0xc0c36fae},
{'q', 0x40d8e9a8, 0xc0c36fae},
{0, 0x40d8e9a8, 0xc1849e12},
{'q', 0x00000000, 0xc1278448},
{0, 0xc0d8e9a8, 0xc1849e10},
{'q', 0xc0d8e9a8, 0xc0c36fb0},
{0, 0xc1931d36, 0xc0c36fb0},
{'8', 0x09aa00d5, 0x1ea809d6},
{'l', 0x00000000, 0xc249579f},
{'@', 0x00000036, 0x00005773},/*        6        x-advance: 87.449219 */
{'M', 0x423579fc, 0xc25e036f},
{'q', 0xc1120a4c, 0x00000000},
{0, 0xc167f240, 0x40c7bb40},
{'q', 0xc0a9aa18, 0x40c7bb48},
{0, 0xc0a9aa18, 0x4188e9aa},
{'q', 0x00000000, 0x412ce2cc},
{0, 0x40a9aa18, 0x4188e9aa},
{'q', 0x40abcfe8, 0x40c7bb48},
{0, 0x4167f240, 0x40c7bb48},
{'q', 0x41120a50, 0x00000000},
{0, 0x4166df5c, 0xc0c7bb46},
{'q', 0x40abcfe8, 0xc0c9e112},
{0, 0x40abcfe8, 0xc188e9aa},
{'q', 0x00000000, 0xc12df5b0},
{0, 0xc0abcfe8, 0xc188e9aa},
{'9', 0xffcfffd6, 0xffcfff8d},
{'m', 0x41d74d50, 0xc229eed1},
{'l', 0x00000000, 0x41459578},
{'8', 0xe3aeedd8, 0xf6aef6d7},
{'q', 0xc156c3e0, 0x00000000},
{0, 0xc1a44b99, 0x4110f768},
{'q', 0xc0e180dc, 0x4110f768},
{0, 0xc100dbec, 0x41db0f78},
{'8', 0xb94fd21f, 0xe769e72f},
{'q', 0x41719c58, 0x00000000},
{0, 0x41be9aa2, 0x41131d34},
{'q', 0x410cabd0, 0x41120a50},
{0, 0x410cabd0, 0x41c731d2},
{'q', 0x00000000, 0x4176fada},
{0, 0xc1120a50, 0x41c61eee},
{'q', 0xc1120a50, 0x41154301},
{0, 0xc1c25cc8, 0x41154301},
{'q', 0xc18b0f76, 0xb4c00000},
{0, 0xc1d49e10, 0xc1549e11},
{'q', 0xc1131d36, 0xc155b0f8},
{0, 0xc1131d36, 0xc21aa181},
{'q', 0x00000000, 0xc1be112c},
{0, 0x41346716, 0xc21768ce},
{'q', 0x41346718, 0xc16293c0},
{0, 0x41f225cc, 0xc16293c0},
{'8', 0x08520028, 0x18560829},
{'@', 0x00000037, 0x00005773},/*        7        x-advance: 87.449219 */
{'M', 0x41346716, 0xc2c86716},
{'l', 0x4280dbeb, 0x00000000},
{'l', 0x00000000, 0x40b8b2b0},
{'l', 0xc21180dc, 0x42bcdbeb},
{'l', 0xc16293c2, 0x00000000},
{'l', 0x4208e9aa, 0xc2b1957a},
{'l', 0xc2407bb4, 0x00000000},
{'l', 0xb6000000, 0xc1368ce0},
{'@', 0x00000038, 0x00005773},/*        8        x-advance: 87.449219 */
{'M', 0x422ec3dd, 0xc23e55e8},
{'q', 0xc11aa180, 0x00000000},
{0, 0xc173c224, 0x40a55e80},
{'q', 0xc0b01b7c, 0x40a55e80},
{0, 0xc0b01b7c, 0x4163a6a8},
{'q', 0x00000000, 0x4110f76a},
{0, 0x40b01b7c, 0x4163a6a9},
{'q', 0x40b24148, 0x40a55e7e},
{0, 0x4173c224, 0x40a55e7e},
{'q', 0x411aa184, 0x00000000},
{0, 0x4173c228, 0xc0a55e7e},
{'q', 0x40b24148, 0xc0a7844a},
{0, 0x40b24148, 0xc163a6a9},
{'q', 0x00000000, 0xc110f768},
{0, 0xc0b24148, 0xc163a6a8},
{'9', 0xffd7ffd4, 0xffd7ff87},
{'m', 0xc158e9a8, 0xc0b8b2b0},
{'q', 0xc10b98ea, 0xc0097310},
{0, 0xc159fc90, 0xc101eed0},
{'q', 0xc09aa182, 0xc0bf2410},
{0, 0xc09aa182, 0xc1690528},
{'q', 0x00000000, 0xc14036f8},
{0, 0x41086037, 0xc197f240},
{'q', 0x4109731e, 0xc0df5b10},
{0, 0x41bbeb61, 0xc0df5b10},
{'q', 0x416f7690, 0x00000000},
{0, 0x41bbeb62, 0x40df5b10},
{'q', 0x41086038, 0x40df5b10},
{0, 0x41086038, 0x4197f240},
{'q', 0x00000000, 0x41097320},
{0, 0xc09cc750, 0x41690528},
{'q', 0xc09aa180, 0x40bf2418},
{0, 0xc157d6c4, 0x4101eed0},
{'q', 0x411cc74c, 0x40120a50},
{0, 0x4173c224, 0x410ed1a0},
{'q', 0x40b01b80, 0x40d49e10},
{0, 0x40b01b80, 0x4181eed0},
{'q', 0x00000000, 0x41690528},
{0, 0xc10ed198, 0x41b2cabd},
{'q', 0xc10dbeb8, 0x40f920a5},
{0, 0xc1cb7d6e, 0x40f920a5},
{'q', 0xc1849e10, 0x34000000},
{0, 0xc1cc06de, 0xc0f920a4},
{'q', 0xc10dbeb7, 0xc0f920a5},
{0, 0xc10dbeb7, 0xc1b2cabd},
{'q', 0x00000000, 0xc1198e98},
{0, 0x40b01b7e, 0xc181eed0},
{'9', 0xffcb002c, 0xffb9007a},
{'m', 0xc09eed1c, 0xc1ab4670},
{'8', 0x61263e00, 0x226d2227},
{'8', 0xde6c0045, 0x9f27de27},
{'8', 0x9fd9c200, 0xde94ded9},
{'8', 0x229300ba, 0x61da22da},
{'@', 0x00000039, 0x00005773},/*        9        x-advance: 87.449219 */
{'M', 0x41719c59, 0xc0052784},
{'l', 0x00000000, 0xc145957a},
{'8', 0x1d521328, 0x0a520a29},
{'q', 0x4156c3dc, 0x00000000},
{0, 0x41a3c226, 0xc10fe482},
{'q', 0x40e3a6a8, 0xc110f768},
{0, 0x4101eed4, 0xc1db98ea},
{'8', 0x46b22ee1, 0x189718d1},
{'q', 0xc1708974, 0x00000000},
{0, 0xc1be9aa2, 0xc110f768},
{'q', 0xc10b98e9, 0xc1120a50},
{0, 0xc10b98e9, 0xc1c731d4},
{'q', 0x00000000, 0xc176fad8},
{0, 0x41120a4f, 0xc1c61eec},
{'q', 0x41120a4e, 0xc1154300},
{0, 0x41c25cc7, 0xc1154300},
{'q', 0x418b0f76, 0x00000000},
{0, 0x41d4149c, 0x4155b0f8},
{'q', 0x41131d38, 0x41549e10},
{0, 0x41131d38, 0x421aa180},
{'q', 0x00000000, 0x41bd87bc},
{0, 0xc1346718, 0x421768ce},
{'q', 0xc133542c, 0x416180dd},
{0, 0xc1f19c58, 0x416180dd},
{'8', 0xf8ae00d8, 0xe8aaf8d7},
{'m', 0x41d7d6c4, 0xc229eed2},
{'q', 0x41120a50, 0x00000000},
{0, 0x4166df5c, 0xc0c7bb40},
{'q', 0x40abcfe0, 0xc0c7bb48},
{0, 0x40abcfe0, 0xc188e9ac},
{'q', 0x00000000, 0xc12ce2c8},
{0, 0xc0abcfe0, 0xc1886034},
{'q', 0xc0a9aa18, 0xc0c9e120},
{0, 0xc166df5c, 0xc0c9e120},
{'q', 0xc1120a50, 0x00000000},
{0, 0xc167f240, 0x40c9e120},
{'q', 0xc0a9aa18, 0x40c7bb40},
{0, 0xc0a9aa18, 0x41886034},
{'q', 0x00000000, 0x412df5b4},
{0, 0x40a9aa18, 0x4188e9ac},
{'q', 0x40abcfe0, 0x40c7bb40},
{0, 0x4167f240, 0x40c7bb40},
{'@', 0x0000003a, 0x00002e4f},/*        :        x-advance: 46.308594 */
{'M', 0x4180dbeb, 0xc1886037},
{'l', 0x416293c2, 0x00000000},
{'4', 0x00880000, 0x0000ff8f},
{'6', 0xff780000, 0xfe500000},
{'l', 0x416293c2, 0x00000000},
{'l', 0x00000000, 0x41886036},
{'l', 0xc16293c2, 0x00000000},
{'l', 0x00000000, 0xc1886036},
{'@', 0x0000003b, 0x00002e4f},/*        ;        x-advance: 46.308594 */
{'M', 0x4180dbeb, 0xc28e25cc},
{'l', 0x416293c2, 0x00000000},
{'4', 0x00880000, 0x0000ff8f},
{'6', 0xff780000, 0x01b00000},
{'l', 0x416293c2, 0x36000000},
{'l', 0x00000000, 0x4138b2b0},
{'l', 0xc1301b7c, 0x41abcfe4},
{'l', 0xc10a8604, 0x00000000},
{'l', 0x40b01b7c, 0xc1abcfe4},
{'l', 0x00000000, 0xc138b2b0},
{'@', 0x0000003c, 0x0000732a},/*        <        x-advance: 115.164062 */
{'M', 0x42c93543, 0xc2874d51},
{'l', 0xc28a8604, 0x41c50c08},
{'l', 0x428a8604, 0x41c3f921},
{'l', 0x00000000, 0x41436fac},
{'l', 0xc2ac149e, 0xc1f9aa17},
{'l', 0xb5800000, 0xc132414c},
{'l', 0x42ac149e, 0xc1f9aa16},
{'l', 0x00000000, 0x41436fa8},
{'@', 0x0000003d, 0x0000732a},/*        =        x-advance: 115.164062 */
{'M', 0x41690527, 0xc279aa18},
{'l', 0x42ac149e, 0x00000000},
{'4', 0x005a0000, 0x0000fd50},
{'6', 0xffa60000, 0x00db0000},
{'l', 0x42ac149e, 0x00000000},
{'l', 0x00000000, 0x41368ce4},
{'l', 0xc2ac149e, 0x00000000},
{'l', 0xb5800000, 0xc1368ce4},
{'@', 0x0000003e, 0x0000732a},/*        >        x-advance: 115.164062 */
{'M', 0x41690527, 0xc2874d51},
{'l', 0x00000000, 0xc1436fa8},
{'l', 0x42ac149e, 0x41f9aa16},
{'l', 0x00000000, 0x4132414c},
{'l', 0xc2ac149e, 0x41f9aa17},
{'l', 0xb5800000, 0xc1436fac},
{'l', 0x428a414a, 0xc1c3f921},
{'l', 0xc28a414a, 0xc1c50c08},
{'@', 0x0000003f, 0x000048f3},/*        ?        x-advance: 72.949219 */
{'M', 0x41d1eed1, 0xc1886037},
{'l', 0x4159fc92, 0x00000000},
{'4', 0x00880000, 0x0000ff94},
{'6', 0xff780000, 0xffb20069},
{'4', 0x0000ff9a, 0xffae0000},
{'8', 0xa70fca00, 0xaf3fde0f},
{'l', 0x40c149e0, 0xc0bf2418},
{'8', 0xcb2ce41e, 0xcd0de70d},
{'8', 0xb3ddd100, 0xe3a4e3de},
{'8', 0x12a600d6, 0x369d12d1},
{'l', 0x00000000, 0xc149e110},
{'8', 0xd366e232, 0xf16bf134},
{'q', 0x41459578, 0x00000000},
{0, 0x419e63a6, 0x40d05270},
{'q', 0x40f08970, 0x40d05280},
{0, 0x40f08970, 0x41897320},
{'8', 0x4ded2800, 0x52bd24ed},
{'l', 0xc0bcfe48, 0x40b8b2b0},
{'8', 0x27dd19e7, 0x1bf20df6},
{'8', 0x1bfc0bfd, 0x2cff10ff},
{'l', 0x00000000, 0x410414a0},
{'@', 0x00000040, 0x00008973},/*        @        x-advance: 137.449219 */
{'M', 0x424c9052, 0xc210293c},
{'q', 0x00000000, 0x41198e9a},
{0, 0x40987bb8, 0x41719c5a},
{'8', 0x2b682b26, 0xd4670042},
{'q', 0x40987bc0, 0xc0b01b80},
{0, 0x40987bc0, 0xc1708974},
{'q', 0x00000000, 0xc11655e8},
{0, 0xc09aa180, 0xc16e63a4},
{'8', 0xd498d4da, 0x2c9900c0},
{'9', 0x002cffda, 0x0077ffda},
{'m', 0x42124f08, 0x41a08973},
{'8', 0x3db629e0, 0x13a013d7},
{'q', 0xc138b2b0, 0x00000000},
{0, 0xc19655e8, 0xc1052784},
{'q', 0xc0e5cc78, 0xc1063a6a},
{0, 0xc0e5cc78, 0xc1ae7f24},
{'q', 0x00000000, 0xc156c3dc},
{0, 0x40e7f240, 0xc1ae7f24},
{'q', 0x40e7f240, 0xc1063a68},
{0, 0x4195cc76, 0xc1063a68},
{'8', 0x14610037, 0x3c491329},
{'4', 0xffba0000, 0x0000004c},
{'l', 0x00000000, 0x4245957a},
{'q', 0x411cc748, 0xbfbcfe40},
{0, 0x4174d508, 0xc10ed19a},
{'q', 0x40b24150, 0xc0f08974},
{0, 0x40b24150, 0xc19b2af3},
{'8', 0x95efc700, 0xa3cdcef0},
{'q', 0xc0df5b10, 0xc10cabd0},
{0, 0xc1886038, 0xc156c3e0},
{'q', 0xc1200000, 0xc09655e0},
{0, 0xc1ae7f24, 0xc09655e0},
{'q', 0xc104149c, 0x00000000},
{0, 0xc17d6c3c, 0x400dbea0},
{'q', 0xc0f2af40, 0x40097320},
{0, 0xc1606df4, 0x40ce2cb0},
{'q', 0xc1289734, 0x40db0f80},
{0, 0xc184149f, 0x418fe482},
{'q', 0xc0bcfe48, 0x41312e64},
{0, 0xc0bcfe48, 0x41c036fc},
{'q', 0x00000000, 0x412abcfc},
{0, 0x4074d510, 0x419fffff},
{'q', 0x407920a0, 0x41154302},
{0, 0x4133542e, 0x41838b2b},
{'q', 0x40e180e0, 0x40df5b0f},
{0, 0x41827846, 0x412abcfe},
{'q', 0x41143018, 0x4067f240},
{0, 0x419e63a6, 0x4067f240},
{'q', 0x410a8600, 0x00000000},
{0, 0x4187d6c4, 0xc038b2ac},
{'9', 0xffe90043, 0xffbd007a},
{'l', 0x40c149e0, 0x40ee63a6},
{'q', 0xc1063a68, 0x40d0527a},
{0, 0xc19293c0, 0x41200001},
{'q', 0xc11dda38, 0x405b0f70},
{0, 0xc1a08974, 0x405b0f70},
{'q', 0xc146a860, 0x00000000},
{0, 0xc1bb61ee, 0xc08b98e8},
{'q', 0xc1301b80, 0xc08dbeb6},
{0, 0xc19cc74e, 0xc14d19c6},
{'q', 0xc109731e, 0xc1063a6a},
{0, 0xc151655e, 0xc19b2af4},
{'q', 0xc08fe482, 0xc1312e62},
{0, 0xc08fe482, 0xc1be112d},
{'q', 0x00000000, 0xc1436fb0},
{0, 0x40920a4e, 0xc1ba4f08},
{'q', 0x40920a50, 0xc1312e64},
{0, 0x41505278, 0xc19bb466},
{'q', 0x410a8604, 0xc1086038},
{0, 0x41a00000, 0xc1505278},
{'q', 0x413579fc, 0xc0920a50},
{0, 0x41c036fc, 0xc0920a50},
{'q', 0x4163a6a8, 0x00000000},
{0, 0x41d301b4, 0x40bad870},
{'q', 0x41436fb0, 0x40bad880},
{0, 0x41a3c228, 0x41849e14},
{'q', 0x40a112e0, 0x40d27840},
{0, 0x40f4d510, 0x4164b98c},
{'q', 0x402bcfe0, 0x40f6fad8},
{0, 0x402bcfe0, 0x417f9208},
{'q', 0x00000000, 0x418d3543},
{0, 0xc12abd00, 0x41ded19d},
{'q', 0xc12abd00, 0x412338b2},
{0, 0xc1ebb468, 0x4129aa17},
{'l', 0x00000000, 0xc1255e7f},
{'@', 0x00000041, 0x00005e06},/*        A        x-advance: 94.023438 */
{'M', 0x423beb62, 0xc2adb0f7},
{'4', 0x018eff6d, 0x00000126},
{'6', 0xfe72ff6d, 0xff96ffc3},
{'l', 0x4175e7f4, 0x00000000},
{'l', 0x4218c06d, 0x42c86716},
{'l', 0xc16180d8, 0x00000000},
{'l', 0xc1120a50, 0xc1cda338},
{'l', 0xc234abd0, 0x00000000},
{'l', 0xc1120a4e, 0x41cda338},
{'l', 0xc164b98e, 0x00000000},
{'l', 0x42190527, 0xc2c86716},
{'[', 0x00410041, 0x000003d3},
{'@', 0x00000042, 0x00005e4b},/*        B        x-advance: 94.292969 */
{'M', 0x41d86037, 0xc23f68ce},
{'4', 0x01250000, 0x000000ad},
{'q', 0x412f0894, 0x00000000},
{0, 0x4181655c, 0xc08fe482},
{'8', 0x912adc2a, 0x92d6b500},
{'9', 0xffddffd7, 0xffddff7f},
{'6', 0x0000ff53, 0xfeb70000},
{'4', 0x00f10000, 0x000000a0},
{'q', 0x411eed18, 0x00000000},
{0, 0x416c3dd8, 0xc06c3de0},
{'8', 0xa527e227, 0xa6d9c400},
{'9', 0xffe2ffda, 0xffe2ff8a},
{'6', 0x0000ff60, 0xffa7ff94},
{'l', 0x420a8603, 0x00000000},
{'q', 0x41780dc0, 0x00000000},
{0, 0x41bf2414, 0x40ce2cb0},
{'q', 0x41063a68, 0x40ce2ca0},
{0, 0x41063a68, 0x419293c0},
{'q', 0x00000000, 0x41131d38},
{0, 0xc0897310, 0x416a1810},
{'q', 0xc0897320, 0x40adf5b0},
{0, 0xc149e114, 0x40d8e9a8},
{'q', 0x411ffffc, 0x40097320},
{0, 0x41780dbc, 0x410fe480},
{'q', 0x40b24150, 0x40d8e9b0},
{0, 0x40b24150, 0x4187d6c5},
{'q', 0x00000000, 0x4156c3dd},
{0, 0xc1120a50, 0x41a5e7f2},
{'9', 0x003affb7, 0x003aff31},
{'l', 0xc20fe482, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x00000043, 0x00005ff9},/*        C        x-advance: 95.972656 */
{'M', 0x42b10c07, 0xc2b8f769},
{'l', 0x00000000, 0x4164b990},
{'q', 0xc0db0f80, 0xc0cc06e0},
{0, 0xc16a1810, 0xc1187bb0},
{'q', 0xc0f6fae0, 0xc049e120},
{0, 0xc1838b2c, 0xc049e120},
{'q', 0xc189731c, 0x00000000},
{0, 0xc1d27843, 0x41289730},
{'q', 0xc1120a50, 0x41278450},
{0, 0xc1120a50, 0x41f2af40},
{'q', 0x00000000, 0x419e63a7},
{0, 0x41120a50, 0x41f2af40},
{'q', 0x41120a4e, 0x4127844b},
{0, 0x41d27843, 0x4127844b},
{'q', 0x410b98e8, 0x00000000},
{0, 0x41838b2c, 0xc049e114},
{'9', 0xffe7003e, 0xffb40075},
{'l', 0x00000000, 0x416293c2},
{'q', 0xc0e3a6b0, 0x409aa180},
{0, 0xc1719c60, 0x40e7f241},
{'q', 0xc0fd6c30, 0x401aa180},
{0, 0xc1863a68, 0x401aa180},
{'q', 0xc1b60370, 0x34000000},
{0, 0xc20f5b10, 0xc15e4828},
{'q', 0xc151655c, 0xc15f5b10},
{0, 0xc151655c, 0xc21836fb},
{'q', 0x00000000, 0xc1c149e0},
{0, 0x4151655e, 0xc21836fa},
{'q', 0x4151655e, 0xc15f5b10},
{0, 0x420f5b10, 0xc15f5b10},
{'q', 0x410fe480, 0x00000000},
{0, 0x41874d50, 0x401aa180},
{'q', 0x40ff9210, 0x401655e0},
{0, 0x416f7690, 0x40e3a6a0},
{'@', 0x00000044, 0x000069d6},/*        D        x-advance: 105.835938 */
{'M', 0x41d86037, 0xc2b21eed},
{'4', 0x026f0000, 0x00000083},
{'q', 0x41a5e7f2, 0x00000000},
{0, 0x41f2af3e, 0xc11655e8},
{'q', 0x411aa180, 0xc11655e8},
{0, 0x411aa180, 0xc1ed50bf},
{'q', 0x00000000, 0xc1a112e8},
{0, 0xc11aa180, 0xc1ebb468},
{'9', 0xffb5ffb4, 0xffb5ff0e},
{'6', 0x0000ff7d, 0xffa7ff94},
{'l', 0x41ded19c, 0x00000000},
{'q', 0x41e90526, 0x00000000},
{0, 0x422b01b7, 0x41425cc8},
{'q', 0x4159fc90, 0x414149e0},
{0, 0x4159fc90, 0x421768ce},
{'q', 0x00000000, 0x41cf3f91},
{0, 0xc15b0f78, 0x421836fa},
{'9', 0x0061ff93, 0x0061feab},
{'l', 0xc1ded19c, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x00000045, 0x000056d8},/*        E        x-advance: 86.843750 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x427d6c3d, 0x00000000},
{'l', 0x00000000, 0x41368ce0},
{'l', 0xc24731d2, 0x00000000},
{'l', 0xb6000000, 0x41ed50c2},
{'l', 0x423edf5a, 0x00000000},
{'l', 0x00000000, 0x41368ce0},
{'l', 0xc23edf5a, 0x00000000},
{'l', 0xb6000000, 0x42113c22},
{'l', 0x424c06de, 0x35800000},
{'l', 0x00000000, 0x41368ce3},
{'l', 0xc28120a4, 0x00000000},
{'l', 0xb6800000, 0xc2c86716},
{'@', 0x00000046, 0x00004f0f},/*        F        x-advance: 79.058594 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x426655e7, 0x00000000},
{'l', 0x00000000, 0x41368ce0},
{'l', 0xc2301b7c, 0x00000000},
{'l', 0xb6000000, 0x41ec3dda},
{'l', 0x421eed18, 0x00000000},
{'l', 0x00000000, 0x41368ce4},
{'l', 0xc21eed18, 0x00000000},
{'l', 0xb6000000, 0x423f68ce},
{'l', 0xc158e9aa, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x00000047, 0x00006a82},/*        G        x-advance: 106.507812 */
{'M', 0x42a39fc9, 0xc164b98e},
{'l', 0x00000000, 0xc1d74d51},
{'l', 0xc1b12e64, 0x00000000},
{'4', 0xffa70000, 0x0000011c},
{'l', 0x00000000, 0x422c149e},
{'q', 0xc0fb4670, 0x40b24146},
{0, 0xc18a8600, 0x41074d4f},
{'q', 0xc11768d0, 0x40346716},
{0, 0xc1a19c5a, 0x40346716},
{'q', 0xc1bbeb62, 0x34000000},
{0, 0xc2131d36, 0xc15b0f76},
{'q', 0xc1538b28, 0xc15c225c},
{0, 0xc1538b28, 0xc2190528},
{'q', 0x00000000, 0xc1c48294},
{0, 0x41538b2a, 0xc2190526},
{'q', 0x41549e12, 0xc15c2260},
{0, 0x42131d36, 0xc15c2260},
{'q', 0x411cc748, 0x00000000},
{0, 0x4194b98c, 0x401aa180},
{'9', 0x00130046, 0x00380082},
{'l', 0x00000000, 0x4166df60},
{'q', 0xc0f08970, 0xc0cc06e0},
{0, 0xc17f9208, 0xc1198e98},
{'q', 0xc1074d50, 0xc04e2cc0},
{0, 0xc18e482a, 0xc04e2cc0},
{'q', 0xc1931d34, 0x00000000},
{0, 0xc1dd3542, 0x41244b98},
{'q', 0xc1131d36, 0x41244b98},
{0, 0xc1131d36, 0x41f4d50c},
{'q', 0x00000000, 0x41a225cd},
{0, 0x41131d36, 0x41f44b99},
{'q', 0x4114301c, 0x41244b99},
{0, 0x41dd3542, 0x41244b99},
{'8', 0xf7660039, 0xe151f62d},
{'@', 0x00000048, 0x0000675b},/*        H        x-advance: 103.355469 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x4158e9aa, 0x00000000},
{'l', 0x00000000, 0x42244b99},
{'l', 0x42450c06, 0x00000000},
{'l', 0x00000000, 0xc2244b99},
{'l', 0x4158e9a8, 0x00000000},
{'l', 0x00000000, 0x42c86716},
{'l', 0xc158e9a8, 0x00000000},
{'l', 0x00000000, 0xc23edf5b},
{'l', 0xc2450c06, 0x00000000},
{'l', 0xb6000000, 0x423edf5b},
{'l', 0xc158e9aa, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x00000049, 0x00002889},/*        I        x-advance: 40.535156 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x4158e9aa, 0x00000000},
{'l', 0x00000000, 0x42c86716},
{'l', 0xc158e9aa, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x0000004a, 0x00002889},/*        J        x-advance: 40.535156 */
{'M', 0x4157d6c4, 0xc2c86716},
{'4', 0x0000006c, 0x02e90000},
{'q', 0x00000000, 0x4190f769},
{0, 0xc0dd3544, 0x41d27845},
{'9', 0x0041ffca, 0x0041ff50},
{'4', 0x0000ffd7, 0xffa50000},
{'l', 0x40874d50, 0x00000000},
{'q', 0x410fe482, 0x00000000},
{0, 0x414af3f9, 0xc0a112e6},
{'q', 0x406c3ddc, 0xc0a112e5},
{0, 0x406c3ddc, 0xc1906df5},
{'l', 0x00000000, 0xc2ba7165},
{'@', 0x0000004b, 0x00005a22},/*        K        x-advance: 90.132812 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x4158e9aa, 0x00000000},
{'l', 0x00000000, 0x4229655e},
{'l', 0x4233dda2, 0xc229655e},
{'l', 0x418b98ec, 0x00000000},
{'l', 0xc246ed1a, 0x423ad87b},
{'l', 0x42552784, 0x4255f5b1},
{'l', 0xc18ed19c, 0x00000000},
{'l', 0xc2407bb4, 0xc2410527},
{'l', 0xb6000000, 0x42410527},
{'l', 0xc158e9aa, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x0000004c, 0x00004c93},/*        L        x-advance: 76.574219 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x4158e9aa, 0x00000000},
{'l', 0x00000000, 0x42b1957a},
{'l', 0x42432af4, 0xb6400000},
{'l', 0x00000000, 0x41368ce3},
{'l', 0xc279655f, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'[', 0x0041004c, 0x00000327},
{'@', 0x0000004d, 0x00007697},/*        M        x-advance: 118.589844 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x41a19c58, 0x00000000},
{'l', 0x41cc9054, 0x42886036},
{'l', 0x41cda336, 0xc2886036},
{'l', 0x41a19c5c, 0x00000000},
{'l', 0x00000000, 0x42c86716},
{'l', 0xc1538b30, 0x00000000},
{'l', 0x00000000, 0xc2aff920},
{'l', 0xc1ceb61c, 0x4289731c},
{'l', 0xc159fc94, 0x36800000},
{'l', 0xc1ceb61e, 0xc289731c},
{'l', 0x00000000, 0x42aff920},
{'l', 0xc1527844, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x0000004e, 0x000066d1},/*        N        x-advance: 102.816406 */
{'M', 0x4157d6c4, 0xc2c86716},
{'l', 0x41920a4f, 0x00000000},
{'l', 0x4231b7d6, 0x42a7a6a8},
{'l', 0x00000000, 0xc2a7a6a8},
{'l', 0x41527848, 0x00000000},
{'l', 0x00000000, 0x42c86716},
{'l', 0xc1920a50, 0x00000000},
{'l', 0xc231b7d6, 0xc2a7a6a8},
{'l', 0x00000000, 0x42a7a6a8},
{'l', 0xc1527844, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x0000004f, 0x00006c30},/*        O        x-advance: 108.187500 */
{'M', 0x4258a4f0, 0xc2b6036f},
{'q', 0xc16c3dd8, 0x00000000},
{0, 0xc1bbeb61, 0x41301b78},
{'q', 0xc10a8604, 0x41301b80},
{0, 0xc10a8604, 0x41f00000},
{'q', 0x00000000, 0x419768cf},
{0, 0x410a8604, 0x41ef768d},
{'q', 0x410b98ea, 0x41301b7d},
{0, 0x41bbeb61, 0x41301b7d},
{'q', 0x416c3dd8, 0x00000000},
{0, 0x41bad87c, 0xc1301b7d},
{'q', 0x410a8600, 0xc1301b7c},
{0, 0x410a8600, 0xc1ef768d},
{'q', 0x00000000, 0xc197f240},
{0, 0xc10a8600, 0xc1f00000},
{'9', 0xffa8ffbc, 0xffa8ff46},
{'m', 0x00000000, 0xc1301b80},
{'q', 0x41a89730, 0x00000000},
{0, 0x4206c3de, 0x416293c0},
{'q', 0x4149e110, 0x416180e0},
{0, 0x4149e110, 0x421768ce},
{'q', 0x00000000, 0x41bd87bc},
{0, 0xc149e110, 0x421768ce},
{'q', 0xc149e118, 0x416180dd},
{0, 0xc206c3de, 0x416180dd},
{'q', 0xc1a920a4, 0xb4c00000},
{0, 0xc2074d50, 0xc16180dc},
{'q', 0xc149e114, 0xc16180db},
{0, 0xc149e114, 0xc21768ce},
{'q', 0x00000000, 0xc1be112c},
{0, 0x4149e112, 0xc21768ce},
{'q', 0x414af3fa, 0xc16293c0},
{0, 0x42074d50, 0xc16293c0},
{'[', 0x002d004f, 0x000003d3},
{'@', 0x00000050, 0x000052e2},/*        P        x-advance: 82.882812 */
{'M', 0x41d86037, 0xc2b21eed},
{'4', 0x012d0000, 0x00000088},
{'q', 0x411768cc, 0x00000000},
{0, 0x416a180c, 0xc09cc750},
{'8', 0x9129d929, 0x91d7b900},
{'9', 0xffd9ffd7, 0xffd9ff8b},
{'6', 0x0000ff78, 0xffa7ff94},
{'l', 0x41f4d50c, 0x00000000},
{'q', 0x4186c3dc, 0x00000000},
{0, 0x41cb7d6a, 0x40f4d510},
{'q', 0x410a8608, 0x40f2af40},
{0, 0x410a8608, 0x41b24148},
{'q', 0x00000000, 0x416d50c0},
{0, 0xc10a8608, 0x41b35430},
{'9', 0x003cffbc, 0x003cff35},
{'l', 0xc1886037, 0x00000000},
{'l', 0x00000000, 0x422112e6},
{'l', 0xc158e9aa, 0x00000000},
{'l', 0x00000000, 0xc2c86716},
{'@', 0x00000051, 0x00006c30},/*        Q        x-advance: 108.187500 */
{'M', 0x4258a4f0, 0xc2b6036f},
{'q', 0xc16c3dd8, 0x00000000},
{0, 0xc1bbeb61, 0x41301b78},
{'q', 0xc10a8604, 0x41301b80},
{0, 0xc10a8604, 0x41f00000},
{'q', 0x00000000, 0x419768cf},
{0, 0x410a8604, 0x41ef768d},
{'q', 0x410b98ea, 0x41301b7d},
{0, 0x41bbeb61, 0x41301b7d},
{'q', 0x416c3dd8, 0x00000000},
{0, 0x41bad87c, 0xc1301b7d},
{'q', 0x410a8600, 0xc1301b7c},
{0, 0x410a8600, 0xc1ef768d},
{'q', 0x00000000, 0xc197f240},
{0, 0xc10a8600, 0xc1f00000},
{'9', 0xffa8ffbc, 0xffa8ff46},
{'m', 0x4197f240, 0x42b263a6},
{'4', 0x009c008e, 0x0000ff7d},
{'l', 0xc16d50bc, 0xc1805278},
{'8', 0x01e501ef, 0x00ef00f7},
{'q', 0xc1a920a4, 0x00000000},
{0, 0xc2074d50, 0xc16180dc},
{'q', 0xc149e114, 0xc16293c1},
{0, 0xc149e114, 0xc21768ce},
{'q', 0x00000000, 0xc1be112c},
{0, 0x4149e112, 0xc21768ce},
{'q', 0x414af3fa, 0xc16293c0},
{0, 0x42074d50, 0xc16293c0},
{'q', 0x41a89730, 0x00000000},
{0, 0x4206c3de, 0x416293c0},
{'q', 0x4149e110, 0x416180e0},
{0, 0x4149e110, 0x421768ce},
{'q', 0x00000000, 0x418b98ea},
{0, 0xc0e180e0, 0x41eeed1a},
{'q', 0xc0df5b10, 0x4146a860},
{0, 0xc1a225cc, 0x419293c2},
{'[', 0x002d0051, 0x000003d3},
{'@', 0x00000052, 0x00005f80},/*        R        x-advance: 95.500000 */
{'M', 0x427406df, 0xc23beb62},
{'8', 0x32430b22, 0x6a422621},
{'4', 0x00db006e, 0x0000ff8c},
{'l', 0xc14d19c8, 0xc1cda338},
{'8', 0x96b3b0d9, 0xe69be6db},
{'l', 0xc16c3dda, 0x00000000},
{'l', 0x00000000, 0x4229655e},
{'4', 0x0000ff94, 0xfcdf0000},
{'l', 0x41f4d50c, 0x00000000},
{'q', 0x4189731c, 0x00000000},
{0, 0x41cd19c6, 0x40e5cc70},
{'q', 0x41074d50, 0x40e5cc80},
{0, 0x41074d50, 0x41ad6c40},
{'q', 0x00000000, 0x411768cc},
{0, 0xc08dbec0, 0x417b4670},
{'9', 0x0031ffde, 0x0045ff9a},
{'m', 0xc207d6c4, 0xc2285278},
{'4', 0x011c0000, 0x00000088},
{'q', 0x411cc74c, 0x00000000},
{0, 0x416c3dd8, 0xc08fe480},
{'8', 0x9628dc28, 0x97d8ba00},
{'q', 0xc09eed18, 0xc08fe480},
{0, 0xc16c3dd8, 0xc08fe480},
{'l', 0xc1886037, 0x00000000},
{'@', 0x00000053, 0x0000573f},/*        S        x-advance: 87.246094 */
{'M', 0x42931d35, 0xc2c1d354},
{'l', 0x00000000, 0x41538b28},
{'q', 0xc0f6fad0, 0xc06c3dc0},
{0, 0xc1690528, 0xc0b01b70},
{'q', 0xc0db0f70, 0xbfe7f240},
{0, 0xc1538b28, 0xbfe7f240},
{'q', 0xc1312e64, 0x00000000},
{0, 0xc188e9ab, 0x40897310},
{'8', 0x61d122d1, 0x501f3500},
{'9', 0x001a0020, 0x002b0079},
{'l', 0x410301b8, 0x3fd6c3e0},
{'q', 0x4172af40, 0x4038b2b0},
{0, 0x41b2cabc, 0x412338b0},
{'q', 0x40e7f240, 0x40e7f240},
{0, 0x40e7f240, 0x419bb467},
{'q', 0x00000000, 0x41690528},
{0, 0xc11cc750, 0x41b0a4f0},
{'q', 0xc11bb464, 0x40f08975},
{0, 0xc1e4b98c, 0x40f08975},
{'q', 0xc0e3a6a8, 0x34000000},
{0, 0xc172af40, 0xbfce2cab},
{'9', 0xfff4ffc1, 0xffdaff7c},
{'l', 0x00000000, 0xc15f5b0f},
{'q', 0x4104149e, 0x4094301c},
{0, 0x4181655e, 0x40df5b0e},
{'q', 0x40fd6c3c, 0x401655e8},
{0, 0x417920a6, 0x401655e8},
{'q', 0x4139c594, 0x00000000},
{0, 0x418f5b0e, 0xc0920a4e},
{'8', 0x9832dc32, 0xa4dcc500},
{'9', 0xffdfffdd, 0xffcfff8a},
{'l', 0xc10414a0, 0xbfce2cc0},
{'q', 0xc172af3e, 0xc04149e0},
{0, 0xc1af920a, 0xc11768cc},
{'q', 0xc0d8e9a6, 0xc0ce2cb0},
{0, 0xc0d8e9a6, 0xc18f5b0e},
{'q', 0x00000000, 0xc1549e18},
{0, 0x41154301, 0xc1a7844c},
{'q', 0x411655e8, 0xc0f4d510},
{0, 0x41ceb61f, 0xc0f4d510},
{'q', 0x40e180d8, 0x00000000},
{0, 0x4165cc74, 0x3fa338c0},
{'q', 0x40ea1808, 0x3fa338c0},
{0, 0x416f768c, 0x4074d500},
{'[', 0x00410053, 0x0000028c},
{'@', 0x00000054, 0x000053f5},/*        T        x-advance: 83.957031 */
{'M', 0xbece2cac, 0xc2c86716},
{'l', 0x42a987bb, 0x00000000},
{'l', 0x00000000, 0x41368ce0},
{'l', 0xc20e4828, 0x00000000},
{'l', 0x00000000, 0x42b1957a},
{'l', 0xc159fc90, 0x00000000},
{'l', 0x00000000, 0xc2b1957a},
{'l', 0xc20e4829, 0x00000000},
{'l', 0xb5b00000, 0xc1368ce0},
{'@', 0x00000055, 0x0000649a},/*        U        x-advance: 100.601562 */
{'M', 0x413f2414, 0xc2c86716},
{'4', 0x0000006c, 0x01e60000},
{'q', 0x00000000, 0x4180dbeb},
{0, 0x40bad87c, 0x41b9c595},
{'q', 0x40bad87c, 0x40e180da},
{0, 0x419768cd, 0x40e180da},
{'q', 0x41505278, 0x00000000},
{0, 0x4196df5a, 0xc0e180da},
{'9', 0xffc8002e, 0xff47002e},
{'4', 0xfe1a0000, 0x0000006c},
{'l', 0x00000000, 0x427a338b},
{'q', 0x00000000, 0x419cc74d},
{0, 0xc11bb468, 0x41ecc74c},
{'q', 0xc11aa180, 0x41200001},
{0, 0xc1e4b98e, 0x41200001},
{'q', 0xc197f240, 0xb4c00000},
{0, 0xc1e5cc74, 0xc1200000},
{'q', 0xc11aa180, 0xc11fffff},
{0, 0xc11aa180, 0xc1ecc74c},
{'l', 0x00000000, 0xc27a338b},
{'@', 0x00000056, 0x00005e06},/*        V        x-advance: 94.023438 */
{'M', 0x421d50c0, 0x00000000},
{'l', 0xc2190527, 0xc2c86716},
{'l', 0x416293c1, 0x00000000},
{'l', 0x41fdf5b2, 0x42a8b98e},
{'l', 0x41fe7f24, 0xc2a8b98e},
{'l', 0x416180d8, 0x00000000},
{'l', 0xc218c06d, 0x42c86716},
{'l', 0xc175e7f4, 0x00000000},
{'@', 0x00000057, 0x000087e7},/*        W        x-advance: 135.902344 */
{'M', 0x40920a4f, 0xc2c86716},
{'l', 0x415b0f76, 0x00000000},
{'l', 0x41a89731, 0x42a9655e},
{'l', 0x41a80dbe, 0xc2a9655e},
{'l', 0x4173c224, 0x00000000},
{'l', 0x41a89734, 0x42a9655e},
{'l', 0x41a80dbc, 0xc2a9655e},
{'l', 0x415c2260, 0x00000000},
{'l', 0xc1c957a0, 0x42c86716},
{'l', 0xc1886038, 0x00000000},
{'l', 0xc1a920a4, 0xc2adf5b1},
{'l', 0xc1aabcfe, 0x42adf5b1},
{'l', 0xc1886036, 0x00000000},
{'l', 0xc1c8ce2c, 0xc2c86716},
{'@', 0x00000058, 0x00005e29},/*        X        x-advance: 94.160156 */
{'M', 0x410a8603, 0xc2c86716},
{'l', 0x41690527, 0x00000000},
{'l', 0x41c731d3, 0x4214fe48},
{'l', 0x41c844b8, 0xc214fe48},
{'l', 0x41690528, 0x00000000},
{'l', 0xc200dbeb, 0x42407bb4},
{'l', 0x4209731d, 0x42505278},
{'l', 0xc1690528, 0x00000000},
{'l', 0xc1e180da, 0xc22a7844},
{'l', 0xc1e31d35, 0x422a7844},
{'l', 0xc16a180e, 0x00000000},
{'l', 0x420f1656, 0xc255f5b1},
{'l', 0xc1f9aa18, 0xc23ad87b},
{'@', 0x00000059, 0x000053f5},/*        Y        x-advance: 83.957031 */
{'M', 0xbe89731d, 0xc2c86716},
{'l', 0x41690527, 0x00000000},
{'l', 0x41de4829, 0x4224d50c},
{'l', 0x41dcabd0, 0xc224d50c},
{'l', 0x41690528, 0x00000000},
{'l', 0xc20dbeb6, 0x4251eed1},
{'l', 0x00000000, 0x423edf5b},
{'l', 0xc159fc90, 0x00000000},
{'l', 0x00000000, 0xc23edf5b},
{'l', 0xc20dbeb6, 0xc251eed1},
{'@', 0x0000005a, 0x00005e29},/*        Z        x-advance: 94.160156 */
{'M', 0x40f6fad8, 0xc2c86716},
{'l', 0x429d731c, 0x00000000},
{'l', 0x00000000, 0x41255e80},
{'l', 0xc27d6c3c, 0x429ce9aa},
{'l', 0x4281cc74, 0xb6400000},
{'l', 0x00000000, 0x41368ce3},
{'l', 0xc2a39fc8, 0x00000000},
{'l', 0xb6400000, 0xc1255e7f},
{'l', 0x427d6c3d, 0xc29ce9aa},
{'l', 0xc2773f91, 0x00000000},
{'l', 0x00000000, 0xc1368ce0},
{'@', 0x0000005b, 0x0000359f},/*        [        x-advance: 53.621094 */
{'M', 0x413cfe48, 0xc2d0dbeb},
{'l', 0x41e3a6a8, 0x00000000},
{'l', 0x00000000, 0x41198e98},
{'l', 0xc180dbeb, 0x00000000},
{'l', 0x00000000, 0x42ceb61f},
{'l', 0x4180dbeb, 0xb5800000},
{'l', 0x00000000, 0x41198e9b},
{'l', 0xc1e3a6a8, 0x00000000},
{'l', 0x00000000, 0xc2f519c5},
{'@', 0x0000005c, 0x00002e4f},/*       \         x-advance: 46.308594 */
{'M', 0x41368ce3, 0xc2c86716},
{'l', 0x420b98e9, 0x42e1e7f2},
{'l', 0xc1368ce4, 0xb5800000},
{'l', 0xc20b98e9, 0xc2e1e7f2},
{'l', 0x41368ce3, 0x00000000},
{'@', 0x0000005d, 0x0000359f},/*        ]        x-advance: 53.621094 */
{'M', 0x42273f92, 0xc2d0dbeb},
{'l', 0x00000000, 0x42f519c5},
{'l', 0xc1e3a6a8, 0x36000000},
{'l', 0xb5800000, 0xc1198e9b},
{'l', 0x41805278, 0x00000000},
{'l', 0x00000000, 0xc2ceb61f},
{'l', 0xc1805278, 0x00000000},
{'l', 0xb5800000, 0xc1198e98},
{'l', 0x41e3a6a8, 0x00000000},
{'@', 0x0000005e, 0x0000732a},/*        ^        x-advance: 115.164062 */
{'M', 0x42805278, 0xc2c86716},
{'l', 0x4211c596, 0x421587bb},
{'l', 0xc157d6c8, 0x00000000},
{'l', 0xc1ec3dd8, 0xc1d4149e},
{'l', 0xc1ec3ddb, 0x41d4149e},
{'l', 0xc157d6c3, 0x00000000},
{'l', 0x4211c595, 0xc21587bb},
{'l', 0x41527844, 0x00000000},
{'@', 0x0000005f, 0x000044b9},/*        _        x-advance: 68.722656 */
{'M', 0x428c225d, 0x41b68ce3},
{'l', 0x00000000, 0x41198e9a},
{'l', 0xc28ed19d, 0x00000000},
{'l', 0x36600000, 0xc1198e9a},
{'l', 0x428ed19d, 0x00000000},
{'@', 0x00000060, 0x000044b9},/*        `        x-advance: 68.722656 */
{'M', 0x41c50c07, 0xc2dbdda3},
{'l', 0x419768cd, 0x41c8ce2c},
{'l', 0xc1244b98, 0x00000000},
{'l', 0xc1af0896, 0xc1c8ce2c},
{'l', 0x41538b2a, 0x00000000},
{'@', 0x00000061, 0x0000543a},/*        a        x-advance: 84.226562 */
{'M', 0x423c74d5, 0xc2172414},
{'q', 0xc16f768c, 0x00000000},
{0, 0xc1a5e7f2, 0x405b0f70},
{'8', 0x5dd21bd2, 0x53223400},
{'q', 0x408b98e8, 0x4074d50c},
{0, 0x413cfe46, 0x4074d50c},
{'q', 0x41244b9c, 0x00000000},
{0, 0x41838b2c, 0xc0e7f242},
{'9', 0xffc60031, 0xff650031},
{'4', 0xffea0000, 0x0000ff9e},
{'m', 0x41c50c06, 0xc0a338b8},
{'4', 0x01570000, 0x0000ff9e},
{'l', 0x00000000, 0xc1368ce3},
{'q', 0xc0874d50, 0x40db0f77},
{0, 0xc1289734, 0x412225cd},
{'q', 0xc0c9e110, 0x404e2caa},
{0, 0xc176fad8, 0x404e2caa},
{'q', 0xc138b2ae, 0x34000000},
{0, 0xc1931d34, 0xc0ce2cab},
{'q', 0xc0d8e9ac, 0xc0d05278},
{0, 0xc0d8e9ac, 0xc18b0f76},
{'q', 0x00000000, 0xc14af3fc},
{0, 0x41074d50, 0xc1990528},
{'9', 0xffcd0044, 0xffcd00ca},
{'4', 0x0000008a, 0xfff70000},
{'q', 0x00000000, 0xc1086034},
{0, 0xc0b46718, 0xc1527844},
{'q', 0xc0b24148, 0xc09655e0},
{0, 0xc17b4670, 0xc09655e0},
{'8', 0x0c9c00cd, 0x25a30cd0},
{'l', 0x00000000, 0xc1368ce4},
{'8', 0xe169ec36, 0xf663f633},
{'q', 0x41827844, 0x00000000},
{0, 0x41c2e63a, 0x41074d50},
{'q', 0x4100dbec, 0x41074d54},
{0, 0x4100dbec, 0x41cd19c6},
{'@', 0x00000062, 0x0000573f},/*        b        x-advance: 87.246094 */
{'M', 0x4285d354, 0xc216112e},
{'q', 0x00000000, 0xc159fc90},
{0, 0xc0b46718, 0xc1aabcfe},
{'q', 0xc0b24148, 0xc0f920a8},
{0, 0xc175e7f0, 0xc0f920a8},
{'q', 0xc11cc750, 0x00000000},
{0, 0xc176fada, 0x40f920a8},
{'q', 0xc0b24148, 0x40f6fad8},
{0, 0xc0b24148, 0x41aabcfe},
{'q', 0x00000000, 0x4159fc90},
{0, 0x40b24148, 0x41ab4671},
{'q', 0x40b46714, 0x40f6fad8},
{0, 0x4176fada, 0x40f6fad8},
{'q', 0x411cc74c, 0x00000000},
{0, 0x4175e7f0, 0xc0f6fad8},
{'9', 0xffc2002d, 0xff55002d},
{'m', 0xc2280dbe, 0xc1d1eed2},
{'q', 0x407920a0, 0xc0d6c3d8},
{0, 0x411cc74c, 0xc11eed1c},
{'q', 0x40bf2410, 0xc0527840},
{0, 0x4163a6a8, 0xc0527840},
{'q', 0x415b0f74, 0x00000000},
{0, 0x41b1b7d6, 0x412df5b0},
{'q', 0x41097320, 0x412df5b4},
{0, 0x41097320, 0x41e4b990},
{'q', 0x00000000, 0x418dbeb6},
{0, 0xc1097320, 0x41e4b98e},
{'q', 0xc1086038, 0x412df5b1},
{0, 0xc1b1b7d6, 0x412df5b1},
{'q', 0xc10414a0, 0xb4c00000},
{0, 0xc163a6a8, 0xc04e2cad},
{'9', 0xffe6ffd1, 0xffb0ffb2},
{'l', 0x00000000, 0x41346716},
{'l', 0xc146a860, 0x00000000},
{'l', 0x00000000, 0xc2d0dbeb},
{'l', 0x4146a860, 0x00000000},
{'l', 0x00000000, 0x4222af3f},
{'@', 0x00000063, 0x00004b92},/*        c        x-advance: 75.570312 */
{'M', 0x4286180e, 0xc2909052},
{'l', 0x00000000, 0x4138b2ac},
{'8', 0xdeace9d7, 0xf5acf5d7},
{'q', 0xc14036fc, 0x00000000},
{0, 0xc1954302, 0x40f4d508},
{'q', 0xc0d49e10, 0x40f2af40},
{0, 0xc0d49e10, 0x41aabcfe},
{'q', 0x00000000, 0x415c225c},
{0, 0x40d49e10, 0x41ab4671},
{'q', 0x40d49e10, 0x40f2af3e},
{0, 0x41954302, 0x40f2af3e},
{'8', 0xf554002a, 0xde54f52a},
{'l', 0x00000000, 0x41368ce2},
{'8', 0x1cab13d7, 0x09a309d4},
{'q', 0xc187d6c4, 0x00000000},
{0, 0xc1d7d6c4, 0xc12abcfe},
{'q', 0xc1200000, 0xc12abcff},
{0, 0xc1200000, 0xc1e655e8},
{'q', 0xb5000000, 0xc1931d36},
{0, 0x412112e6, 0xc1e768d0},
{'q', 0x412225cc, 0xc1289730},
{0, 0x41ddbeb5, 0xc1289730},
{'8', 0x0959002d, 0x1b54092b},
{'@', 0x00000064, 0x0000573f},/*        d        x-advance: 87.246094 */
{'M', 0x4279aa18, 0xc27f0897},
{'l', 0x00000000, 0xc222af3f},
{'l', 0x41459578, 0x00000000},
{'4', 0x03430000, 0x0000ff9e},
{'l', 0x00000000, 0xc1346716},
{'q', 0xc07920b0, 0x40d6c3dd},
{0, 0xc11dda34, 0x41200000},
{'q', 0xc0bcfe48, 0x404e2caa},
{0, 0xc163a6a8, 0x404e2caa},
{'q', 0xc159fc90, 0x34000000},
{0, 0xc1b1b7d7, 0xc12df5b0},
{'q', 0xc1086036, 0xc12df5b0},
{0, 0xc1086036, 0xc1e4b98e},
{'q', 0xb5000000, 0xc18dbeb6},
{0, 0x41086036, 0xc1e4b990},
{'q', 0x4109731e, 0xc12df5b0},
{0, 0x41b1b7d7, 0xc12df5b0},
{'q', 0x41052784, 0x00000000},
{0, 0x4163a6a8, 0x40527840},
{'9', 0x0019002f, 0x004f004e},
{'m', 0xc2285278, 0x41d1eed2},
{'q', 0xb6000000, 0x4159fc90},
{0, 0x40b24148, 0x41ab4671},
{'q', 0x40b46714, 0x40f6fad8},
{0, 0x4176fad8, 0x40f6fad8},
{'q', 0x411cc74c, 0x00000000},
{0, 0x4176fad8, 0xc0f6fad8},
{'q', 0x40b46718, 0xc0f920a4},
{0, 0x40b46718, 0xc1ab4671},
{'q', 0x00000000, 0xc159fc90},
{0, 0xc0b46718, 0xc1aabcfe},
{'q', 0xc0b46718, 0xc0f920a8},
{0, 0xc176fad8, 0xc0f920a8},
{'q', 0xc11cc74e, 0x00000000},
{0, 0xc176fad8, 0x40f920a8},
{'q', 0xc0b2414c, 0x40f6fad8},
{0, 0xc0b2414c, 0x41aabcfe},
{'@', 0x00000065, 0x00005490},/*        e        x-advance: 84.562500 */
{'M', 0x429a7f24, 0xc222af3f},
{'4', 0x00300000, 0x0000fe3a},
{'q', 0x3f4e2ca0, 0x414c06de},
{0, 0x40f4d508, 0x419bb466},
{'q', 0x40dd3548, 0x40d49e12},
{0, 0x41998e9a, 0x40d49e12},
{'8', 0xf36e0038, 0xd76af335},
{'l', 0x00000000, 0x413ad87b},
{'q', 0xc0d49e10, 0x40346716},
{0, 0xc159fc8c, 0x4089731d},
{'q', 0xc0df5b10, 0x3fbcfe49},
{0, 0xc16293c4, 0x3fbcfe49},
{'q', 0xc18fe482, 0x00000000},
{0, 0xc1e4301b, 0xc127844c},
{'q', 0xc127844a, 0xc127844b},
{0, 0xc127844a, 0xc1e293c2},
{'q', 0xb5000000, 0xc193a6a8},
{0, 0x411eed1a, 0xc1ea180e},
{'q', 0x411ffffe, 0xc12df5b0},
{0, 0x41d74d4f, 0xc12df5b0},
{'q', 0x4172af40, 0x00000000},
{0, 0x41bfad88, 0x411cc750},
{'9', 0x004d0046, 0x00d40046},
{'m', 0xc1459578, 0xc067f240},
{'q', 0xbe097400, 0xc12225cc},
{0, 0xc0b68ce8, 0xc1816560},
{'q', 0xc0b01b80, 0xc0c149d8},
{0, 0xc16a180c, 0xc0c149d8},
{'q', 0xc1255e80, 0x00000000},
{0, 0xc1849e12, 0x40bad878},
{'q', 0xc0c59578, 0x40bad878},
{0, 0xc0e3a6a8, 0x41838b2a},
{'l', 0x42301b7e, 0xbd897200},
{'@', 0x00000066, 0x00003063},/*        f        x-advance: 48.386719 */
{'M', 0x424c06df, 0xc2d0dbeb},
{'4', 0x00520000, 0x0000ffa2},
{'8', 0x15b600cb, 0x4dec15ec},
{'l', 0x00000000, 0x40d49e10},
{'l', 0x41a2af40, 0x00000000},
{'l', 0x00000000, 0x41198ea0},
{'l', 0xc1a2af40, 0x00000000},
{'l', 0x00000000, 0x42832414},
{'l', 0xc146a85f, 0x00000000},
{'l', 0x00000000, 0xc2832414},
{'0', 0xb40000a2, 0xd700005e},
{'q', 0x00000000, 0xc148ce30},
{0, 0x40bad87a, 0xc1920a50},
{'q', 0x40bad87c, 0xc0b8b2b0},
{0, 0x4194301b, 0xc0b8b2b0},
{'l', 0x413ad87c, 0x00000000},
{'@', 0x00000067, 0x0000573f},/*        g        x-advance: 87.246094 */
{'M', 0x4279aa18, 0xc219d354},
{'q', 0x00000000, 0xc156c3dc},
{0, 0xc0b24150, 0xc1a67166},
{'q', 0xc0b01b78, 0xc0ec3dd8},
{0, 0xc1780dbc, 0xc0ec3dd8},
{'q', 0xc11eed1a, 0x00000000},
{0, 0xc1780dbe, 0x40ec3dd8},
{'q', 0xc0b01b80, 0x40ec3de0},
{0, 0xc0b01b80, 0x41a67166},
{'q', 0x00000000, 0x4155b0f8},
{0, 0x40b01b80, 0x41a5e7f2},
{'q', 0x40b24148, 0x40ec3dda},
{0, 0x41780dbe, 0x40ec3dda},
{'q', 0x41200000, 0x00000000},
{0, 0x41780dbc, 0xc0ec3dda},
{'9', 0xffc5002c, 0xff5b002c},
{'m', 0x41459578, 0x41e90528},
{'q', 0x00000000, 0x41998e9a},
{0, 0xc1086038, 0x41e4b98e},
{'q', 0xc1086034, 0x41154300},
{0, 0xc1d0dbea, 0x41154300},
{'8', 0xf99e00cc, 0xe8a7f8d2},
{'l', 0x00000000, 0xc14036fb},
{'8', 0x2255172b, 0x0b560b2a},
{'q', 0x41425cc4, 0x00000000},
{0, 0x419180dc, 0xc0c9e112},
{'9', 0xffcd0030, 0xff670030},
{'l', 0x00000000, 0xc0c36fb0},
{'q', 0xc074d510, 0x40d49e12},
{0, 0xc11cc750, 0x411eed1a},
{'q', 0xc0bf2410, 0x40527844},
{0, 0xc164b98c, 0x40527844},
{'q', 0xc15d3544, 0x00000000},
{0, 0xc1b2414a, 0xc1289732},
{'q', 0xc1074d50, 0xc1289732},
{0, 0xc1074d50, 0xc1df5b0f},
{'q', 0xb5000000, 0xc18b98ea},
{0, 0x41074d50, 0xc1dfe484},
{'q', 0x41074d50, 0xc1289730},
{0, 0x41b2414a, 0xc1289730},
{'q', 0x41052784, 0x00000000},
{0, 0x4164b98c, 0x40527840},
{'9', 0x001a002f, 0x004f004e},
{'l', 0x00000000, 0xc1368ce4},
{'l', 0x41459578, 0x00000000},
{'l', 0x00000000, 0x4283ad88},
{'@', 0x00000068, 0x0000571d},/*        h        x-advance: 87.113281 */
{'M', 0x4296df5b, 0xc23579fc},
{'4', 0x016a0000, 0x0000ff9e},
{'l', 0x00000000, 0xc233dda3},
{'q', 0x00000000, 0xc12abcfc},
{0, 0xc0852780, 0xc17f9208},
{'q', 0xc0852788, 0xc0a9aa18},
{0, 0xc147bb48, 0xc0a9aa18},
{'q', 0xc1200000, 0x00000000},
{0, 0xc17c5956, 0x40cc06d8},
{'9', 0x0033ffd2, 0x008bffd2},
{'l', 0x00000000, 0x4229eed1},
{'l', 0xc146a860, 0x00000000},
{'4', 0xfcbd0000, 0x00000063},
{'l', 0x00000000, 0x4223c225},
{'8', 0xaf53ca23, 0xe66fe630},
{'q', 0x414f3f90, 0x00000000},
{0, 0x419cc74e, 0x4100dbf0},
{'q', 0x40d49e10, 0x40ff9208},
{0, 0x40d49e10, 0x41bc74d4},
{'@', 0x00000069, 0x00002630},/*        i        x-advance: 38.187500 */
{'M', 0x414f3f92, 0xc29655e8},
{'l', 0x4145957a, 0x00000000},
{'4', 0x02590000, 0x0000ff9e},
{'6', 0xfda70000, 0xff160000},
{'l', 0x4145957a, 0x00000000},
{'l', 0x00000000, 0x417a3388},
{'l', 0xc145957a, 0x00000000},
{'l', 0x00000000, 0xc17a3388},
{'@', 0x0000006a, 0x00002630},/*        j        x-advance: 38.187500 */
{'M', 0x414f3f92, 0xc29655e8},
{'4', 0x00000062, 0x02640000},
{'q', 0x00000000, 0x4165cc71},
{0, 0xc0b01b80, 0x41a67163},
{'9', 0x0033ffd5, 0x0033ff74},
{'4', 0x0000ffdb, 0xffad0000},
{'l', 0x40527845, 0x00000000},
{'8', 0xe74c0038, 0x9414e614},
{'6', 0xfd9c0000, 0xff160000},
{'l', 0x4145957a, 0x00000000},
{'l', 0x00000000, 0x417a3388},
{'l', 0xc145957a, 0x00000000},
{'l', 0x00000000, 0xc17a3388},
{'@', 0x0000006b, 0x00004f98},/*        k        x-advance: 79.593750 */
{'M', 0x4147bb46, 0xc2d0dbeb},
{'l', 0x4146a860, 0x00000000},
{'l', 0x00000000, 0x4276b61e},
{'l', 0x421361ee, 0xc201aa18},
{'l', 0x417c5958, 0x00000000},
{'l', 0xc21f768d, 0x420cabd0},
{'l', 0x42262cab, 0x42200000},
{'l', 0xc180dbea, 0x00000000},
{'l', 0xc218c06e, 0xc212d87b},
{'l', 0x36000000, 0x4212d87b},
{'l', 0xc146a860, 0x00000000},
{'l', 0x00000000, 0xc2d0dbeb},
{'@', 0x0000006c, 0x00002630},/*        l        x-advance: 38.187500 */
{'M', 0x414f3f92, 0xc2d0dbeb},
{'l', 0x4145957a, 0x00000000},
{'l', 0x00000000, 0x42d0dbeb},
{'l', 0xc145957a, 0x00000000},
{'l', 0x00000000, 0xc2d0dbeb},
{'@', 0x0000006d, 0x000085e4},/*        m        x-advance: 133.890625 */
{'M', 0x428ef3f9, 0xc272f3f9},
{'q', 0x40943020, 0xc1052784},
{0, 0x41312e60, 0xc1448294},
{'q', 0x40ce2cb0, 0xc07d6c40},
{0, 0x4172af40, 0xc07d6c40},
{'q', 0x413beb60, 0x00000000},
{0, 0x4190f768, 0x410414a0},
{'9', 0x00410033, 0x00ba0033},
{'4', 0x016a0000, 0x0000ff9d},
{'l', 0x00000000, 0xc233dda3},
{'q', 0x00000000, 0xc12ce2cc},
{0, 0xc074d500, 0xc1805278},
{'q', 0xc074d500, 0xc0a78448},
{0, 0xc13ad878, 0xc0a78448},
{'q', 0xc1198ea0, 0x00000000},
{0, 0xc172af40, 0x40cc06d8},
{'9', 0x0033ffd4, 0x008bffd4},
{'4', 0x01530000, 0x0000ff9d},
{'l', 0x00000000, 0xc233dda3},
{'q', 0x00000000, 0xc12df5b0},
{0, 0xc074d510, 0xc1805278},
{'q', 0xc074d500, 0xc0a78448},
{0, 0xc13cfe48, 0xc0a78448},
{'q', 0xc11768cc, 0x00000000},
{0, 0xc1708972, 0x40ce2ca8},
{'9', 0x0033ffd4, 0x008affd4},
{'l', 0x00000000, 0x4229eed1},
{'l', 0xc146a860, 0x00000000},
{'4', 0xfda70000, 0x00000063},
{'l', 0x00000000, 0x413ad87c},
{'q', 0x40874d50, 0xc0dd3538},
{0, 0x412225ce, 0xc12338b4},
{'q', 0x40bcfe48, 0xc0527840},
{0, 0x41606df4, 0xc0527840},
{'8', 0x216f0041, 0x6044212e},
{'@', 0x0000006e, 0x0000571d},/*        n        x-advance: 87.113281 */
{'M', 0x4296df5b, 0xc23579fc},
{'4', 0x016a0000, 0x0000ff9e},
{'l', 0x00000000, 0xc233dda3},
{'q', 0x00000000, 0xc12abcfc},
{0, 0xc0852780, 0xc17f9208},
{'q', 0xc0852788, 0xc0a9aa18},
{0, 0xc147bb48, 0xc0a9aa18},
{'q', 0xc1200000, 0x00000000},
{0, 0xc17c5956, 0x40cc06d8},
{'9', 0x0033ffd2, 0x008bffd2},
{'l', 0x00000000, 0x4229eed1},
{'l', 0xc146a860, 0x00000000},
{'4', 0xfda70000, 0x00000063},
{'l', 0x00000000, 0x413ad87c},
{'8', 0xaf53ca23, 0xe66fe630},
{'q', 0x414f3f90, 0x00000000},
{0, 0x419cc74e, 0x4100dbf0},
{'q', 0x40d49e10, 0x40ff9208},
{0, 0x40d49e10, 0x41bc74d4},
{'@', 0x0000006f, 0x00005418},/*        o        x-advance: 84.093750 */
{'M', 0x42285278, 0xc2850527},
{'q', 0xc11eed18, 0x00000000},
{0, 0xc17b4670, 0x40f920a0},
{'q', 0xc0b8b2b0, 0x40f6fad8},
{0, 0xc0b8b2b0, 0x41a9aa18},
{'q', 0x00000000, 0x4157d6c2},
{0, 0x40b68ce0, 0x41aa338b},
{'q', 0x40b8b2b0, 0x40f6fad6},
{0, 0x417c5958, 0x40f6fad6},
{'q', 0x411dda34, 0x00000000},
{0, 0x417a338c, 0xc0f920a6},
{'q', 0x40b8b2b0, 0xc0f920a4},
{0, 0x40b8b2b0, 0xc1a9aa17},
{'q', 0x00000000, 0xc155b0f8},
{0, 0xc0b8b2b0, 0xc1a920a6},
{'9', 0xffc2ffd2, 0xffc2ff83},
{'m', 0x00000000, 0xc1278450},
{'q', 0x4180dbec, 0x00000000},
{0, 0x41ca6a84, 0x41278450},
{'q', 0x41131d38, 0x41278448},
{0, 0x41131d38, 0x41e7f240},
{'q', 0x00000000, 0x4193a6a8},
{0, 0xc1131d38, 0x41e7f240},
{'q', 0xc1131d30, 0x4127844d},
{0, 0xc1ca6a84, 0x4127844d},
{'q', 0xc181655e, 0xb4c00000},
{0, 0xc1caf3f9, 0xc127844c},
{'q', 0xc1120a4e, 0xc1289731},
{0, 0xc1120a4e, 0xc1e7f240},
{'q', 0xb5000000, 0xc194301c},
{0, 0x41120a4e, 0xc1e7f240},
{'q', 0x41131d36, 0xc1278450},
{0, 0x41caf3f9, 0xc1278450},
{'[', 0x002d006f, 0x0000028c},
{'@', 0x00000070, 0x0000573f},/*        p        x-advance: 87.246094 */
{'M', 0x41c731d3, 0xc1346716},
{'l', 0x00000000, 0x421f768c},
{'l', 0xc146a860, 0x36000000},
{'4', 0xfcc20000, 0x00000063},
{'l', 0x00000000, 0x41368ce4},
{'q', 0x407920a8, 0xc0d6c3d8},
{0, 0x411cc74e, 0xc11eed1c},
{'q', 0x40bf2410, 0xc0527840},
{0, 0x4163a6a8, 0xc0527840},
{'q', 0x415b0f74, 0x00000000},
{0, 0x41b1b7d6, 0x412df5b0},
{'q', 0x41097320, 0x412df5b4},
{0, 0x41097320, 0x41e4b990},
{'q', 0x00000000, 0x418dbeb6},
{0, 0xc1097320, 0x41e4b98e},
{'q', 0xc1086038, 0x412df5b1},
{0, 0xc1b1b7d6, 0x412df5b1},
{'q', 0xc10414a0, 0xb4c00000},
{0, 0xc163a6a8, 0xc04e2cad},
{'9', 0xffe6ffd1, 0xffb0ffb2},
{'m', 0x42280dbe, 0xc1d1eed1},
{'q', 0x00000000, 0xc159fc90},
{0, 0xc0b46718, 0xc1aabcfe},
{'q', 0xc0b24148, 0xc0f920a8},
{0, 0xc175e7f0, 0xc0f920a8},
{'q', 0xc11cc750, 0x00000000},
{0, 0xc176fada, 0x40f920a8},
{'q', 0xc0b24148, 0x40f6fad8},
{0, 0xc0b24148, 0x41aabcfe},
{'q', 0x00000000, 0x4159fc90},
{0, 0x40b24148, 0x41ab4671},
{'q', 0x40b46714, 0x40f6fad8},
{0, 0x4176fada, 0x40f6fad8},
{'q', 0x411cc74c, 0x00000000},
{0, 0x4175e7f0, 0xc0f6fad8},
{'q', 0x40b46718, 0xc0f920a4},
{0, 0x40b46718, 0xc1ab4671},
{'@', 0x00000071, 0x0000573f},/*        q        x-advance: 87.246094 */
{'M', 0x41a2af3f, 0xc216112e},
{'q', 0x00000000, 0x4159fc90},
{0, 0x40b2414c, 0x41ab4671},
{'q', 0x40b46714, 0x40f6fad8},
{0, 0x4176fad8, 0x40f6fad8},
{'q', 0x411cc74c, 0x00000000},
{0, 0x4176fad8, 0xc0f6fad8},
{'q', 0x40b46718, 0xc0f920a4},
{0, 0x40b46718, 0xc1ab4671},
{'q', 0x00000000, 0xc159fc90},
{0, 0xc0b46718, 0xc1aabcfe},
{'q', 0xc0b46718, 0xc0f920a8},
{0, 0xc176fad8, 0xc0f920a8},
{'q', 0xc11cc74e, 0x00000000},
{0, 0xc176fad8, 0x40f920a8},
{'9', 0x003dffd4, 0x00aaffd4},
{'m', 0x42285278, 0x41d1eed1},
{'q', 0xc07920b0, 0x40d6c3dd},
{0, 0xc11dda34, 0x41200000},
{'q', 0xc0bcfe48, 0x404e2caa},
{0, 0xc163a6a8, 0x404e2caa},
{'q', 0xc159fc90, 0x34000000},
{0, 0xc1b1b7d7, 0xc12df5b0},
{'q', 0xc1086036, 0xc12df5b0},
{0, 0xc1086036, 0xc1e4b98e},
{'q', 0xb5000000, 0xc18dbeb6},
{0, 0x41086036, 0xc1e4b990},
{'q', 0x4109731e, 0xc12df5b0},
{0, 0x41b1b7d7, 0xc12df5b0},
{'q', 0x41052784, 0x00000000},
{0, 0x4163a6a8, 0x40527840},
{'9', 0x0019002f, 0x004f004e},
{'l', 0x00000000, 0xc1368ce4},
{'l', 0x41459578, 0x00000000},
{'l', 0x00000000, 0x42cf844c},
{'l', 0xc1459578, 0xb6800000},
{'l', 0x00000000, 0xc21f768c},
{'@', 0x00000072, 0x00003882},/*        r        x-advance: 56.507812 */
{'M', 0x42620a4f, 0xc27e7f24},
{'8', 0xf3dcf7f0, 0xfcd6fced},
{'q', 0xc127844c, 0x00000000},
{0, 0xc180dbeb, 0x40db0f78},
{'9', 0x0036ffd4, 0x009cffd4},
{'l', 0x00000000, 0x421e63a6},
{'l', 0xc146a860, 0x00000000},
{'4', 0xfda70000, 0x00000063},
{'l', 0x00000000, 0x413ad87c},
{'q', 0x407920a8, 0xc0db0f78},
{0, 0x412225ce, 0xc12225cc},
{'q', 0x40c7bb40, 0xc056c3e0},
{0, 0x4172af3c, 0xc056c3e0},
{'8', 0x0116000a, 0x031b010c},
{'l', 0x3d897400, 0x414af3f8},
{'@', 0x00000073, 0x0000479c},/*        s        x-advance: 71.609375 */
{'M', 0x42737d6c, 0xc291e7f2},
{'l', 0x00000000, 0x413ad87c},
{'8', 0xe0aaebd7, 0xf6a3f6d3},
{'8', 0x169200b7, 0x43dc16dc},
{'8', 0x361a2200, 0x2569131a},
{'l', 0x40874d50, 0x3f708980},
{'q', 0x41527844, 0x40346720},
{0, 0x41954302, 0x40ff9208},
{'q', 0x40b24150, 0x40a338b4},
{0, 0x40b24150, 0x4164b990},
{'q', 0x00000000, 0x4127844b},
{0, 0xc1052788, 0x41849e11},
{'q', 0xc104149c, 0x40c36fad},
{0, 0xc1b6036e, 0x40c36fad},
{'8', 0xf79c00d0, 0xe492f7cc},
{'l', 0x00000000, 0xc14c06df},
{'8', 0x2a6b1c36, 0x0d690d35},
{'8', 0xe96b0045, 0xbd25e825},
{'q', 0x00000000, 0xc0a112e8},
{0, 0xc05b0f70, 0xc0f6fad8},
{'9', 0xffebffe6, 0xffd7ff8a},
{'l', 0xc0897320, 0xbf80dbe0},
{'q', 0xc1379fc8, 0xc01aa180},
{0, 0xc1849e11, 0xc0ec3de0},
{'q', 0xc0a338b2, 0xc0a112e0},
{0, 0xc0a338b2, 0xc15c225c},
{'q', 0x00000000, 0xc129aa18},
{0, 0x40f08972, 0xc18301b8},
{'q', 0x40f08974, 0xc0b8b2b0},
{0, 0x41aabcff, 0xc0b8b2b0},
{'8', 0x08670036, 0x18590830},
{'@', 0x00000074, 0x000035e4},/*        t        x-advance: 53.890625 */
{'M', 0x41c9579f, 0xc2c10527},
{'l', 0x00000000, 0x41aabcfc},
{'l', 0x41cb7d6d, 0x00000000},
{'4', 0x004c0000, 0x0000ff35},
{'l', 0x00000000, 0x422338b2},
{'8', 0x5e134900, 0x14521414},
{'4', 0x00000065, 0x00520000},
{'l', 0xc14af3f8, 0x00000000},
{'q', 0xc164b990, 0x00000000},
{0, 0xc19dda34, 0xc0a9aa18},
{'9', 0xffd6ffd5, 0xff65ffd5},
{'l', 0x00000000, 0xc22338b2},
{'l', 0xc110f768, 0x00000000},
{'l', 0xb5000000, 0xc1198ea0},
{'l', 0x4110f768, 0x00000000},
{'l', 0x35800000, 0xc1aabcfc},
{'l', 0x4146a85f, 0x00000000},
{'@', 0x00000075, 0x0000571d},/*        u        x-advance: 87.113281 */
{'M', 0x413ad87b, 0xc1ed50c0},
{'4', 0xfe940000, 0x00000062},
{'l', 0x00000000, 0x4234225d},
{'q', 0x00000000, 0x412abcfe},
{0, 0x40852784, 0x41805278},
{'q', 0x40852780, 0x40a9aa18},
{0, 0x4147bb44, 0x40a9aa18},
{'q', 0x41200000, 0x00000000},
{0, 0x417c5958, 0xc0cc06de},
{'9', 0xffcd002e, 0xff75002e},
{'l', 0x00000000, 0xc22a7845},
{'l', 0x41459574, 0x00000000},
{'4', 0x02590000, 0x0000ff9e},
{'l', 0x00000000, 0xc138b2af},
{'8', 0x51ad36dd, 0x1a921ad1},
{'q', 0xc14f3f94, 0x34000000},
{0, 0xc19d50c1, 0xc100dbeb},
{'9', 0xffc0ffcb, 0xff44ffcb},
{'m', 0x41f89732, 0xc23d4302},
{'l', 0x00000000, 0x00000000},
{'@', 0x00000076, 0x00005157},/*        v        x-advance: 81.339844 */
{'M', 0x408301b8, 0xc29655e8},
{'l', 0x4151655e, 0x00000000},
{'l', 0x41bbeb61, 0x427c5958},
{'l', 0x41bbeb62, 0xc27c5958},
{'l', 0x41516560, 0x00000000},
{'l', 0xc1e180dc, 0x429655e8},
{'l', 0xc1863a6a, 0x00000000},
{'l', 0xc1e180dc, 0xc29655e8},
{'@', 0x00000077, 0x0000706a},/*        w        x-advance: 112.414062 */
{'M', 0x40b8b2af, 0xc29655e8},
{'l', 0x4145957a, 0x00000000},
{'l', 0x4176fad6, 0x426aa181},
{'l', 0x4175e7f4, 0xc26aa181},
{'l', 0x41690528, 0x00000000},
{'l', 0x4176fad4, 0x426aa181},
{'l', 0x4175e7f8, 0xc26aa181},
{'l', 0x41459578, 0x00000000},
{'l', 0xc19d50c0, 0x429655e8},
{'l', 0xc1690528, 0x00000000},
{'l', 0xc1816560, 0xc2767165},
{'l', 0xc181eed0, 0x42767165},
{'l', 0xc1690528, 0x00000000},
{'l', 0xc19d50c0, 0xc29655e8},
{'@', 0x00000078, 0x00005157},/*        x        x-advance: 81.339844 */
{'M', 0x4296df5b, 0xc29655e8},
{'l', 0xc1d9731e, 0x42124f09},
{'l', 0x41e4b98e, 0x421a5cc7},
{'l', 0xc1690524, 0x00000000},
{'l', 0xc1af0898, 0xc1ec3dda},
{'l', 0xc1af0897, 0x41ec3dda},
{'l', 0xc1690527, 0x00000000},
{'l', 0x41e98e9a, 0xc21d50c0},
{'l', 0xc1d5b0f7, 0xc20f5b10},
{'l', 0x41690526, 0x00000000},
{'l', 0x419f768e, 0x41d63a6c},
{'l', 0x419f768c, 0xc1d63a6c},
{'l', 0x41690528, 0x00000000},
{'@', 0x00000079, 0x00005157},/*        y        x-advance: 81.339844 */
{'M', 0x4230e9aa, 0x40df5b0f},
{'q', 0xc0a78450, 0x4156c3dc},
{0, 0xc12338b4, 0x418c225c},
{'9', 0x0020ffd9, 0x0020ff96},
{'4', 0x0000ffb2, 0xffae0000},
{'l', 0x40e7f242, 0x00000000},
{'8', 0xed3f0028, 0xa531ed16},
{'l', 0x400dbeb0, 0xc0b46716},
{'l', 0xc1f338b2, 0xc293eb62},
{'l', 0x4151655e, 0x00000000},
{'l', 0x41bbeb61, 0x426b2af4},
{'l', 0x41bbeb62, 0xc26b2af4},
{'l', 0x41516560, 0x00000000},
{'l', 0xc204149e, 0x42a44b99},
{'@', 0x0000007a, 0x00004825},/*        z        x-advance: 72.144531 */
{'M', 0x40f2af3f, 0xc29655e8},
{'l', 0x426aa180, 0x00000000},
{'l', 0x00000000, 0x41346718},
{'l', 0xc239c595, 0x42581b7d},
{'l', 0x4239c595, 0x35800000},
{'l', 0x00000000, 0x411dda33},
{'l', 0xc271579f, 0x00000000},
{'l', 0x00000000, 0xc1346716},
{'l', 0x4239c595, 0xc2581b7c},
{'l', 0xc2330f76, 0x00000000},
{'l', 0xb5000000, 0xc11dda38},
{'@', 0x0000007b, 0x00005773},/*        {        x-advance: 87.449219 */
{'M', 0x428c8973, 0x414c06df},
{'4', 0x004d0000, 0x0000ffdf},
{'q', 0xc185b0f8, 0x00000000},
{0, 0xc1b35432, 0xc09eed1c},
{'9', 0xffd9ffd3, 0xff62ffd3},
{'l', 0x00000000, 0xc1805278},
{'q', 0x00000000, 0xc12225cc},
{0, 0xc067f240, 0xc1606df6},
{'9', 0xffe1ffe4, 0xffe1ff97},
{'4', 0x0000ffe0, 0xffb40000},
{'l', 0x408301b8, 0x00000000},
{'8', 0xe269004c, 0x911ce11c},
{'l', 0x00000000, 0xc180dbec},
{'q', 0x00000000, 0xc16d50c0},
{0, 0x40b46710, 0xc19dda30},
{'9', 0xffd9002d, 0xffd900b3},
{'4', 0x00000021, 0x004c0000},
{'l', 0xc0920a50, 0x00000000},
{'8', 0x179e00b5, 0x63e917e9},
{'l', 0x00000000, 0x41852786},
{'q', 0x00000000, 0x41289730},
{0, 0xc0459580, 0x4174d50c},
{'8', 0x33ad26e8, 0x34530e3b},
{'9', 0x00260018, 0x00790018},
{'l', 0x00000000, 0x41852785},
{'8', 0x63174b00, 0x17621717},
{'l', 0x40920a50, 0x00000000},
{'@', 0x0000007c, 0x00002e4f},/*        |        x-advance: 46.308594 */
{'M', 0x41e6df5b, 0xc2d2112e},
{'l', 0x00000000, 0x4309731d},
{'l', 0xc1368ce4, 0x00000000},
{'l', 0x00000000, 0xc309731d},
{'l', 0x41368ce4, 0x00000000},
{'@', 0x0000007d, 0x00005773},/*        }        x-advance: 87.449219 */
{'M', 0x4189731d, 0x414c06df},
{'l', 0x409655e8, 0x00000000},
{'8', 0xe961004b, 0x9d17e917},
{'l', 0x00000000, 0xc1852784},
{'q', 0x00000000, 0xc127844a},
{0, 0x404149e0, 0xc173c224},
{'8', 0xcc53da18, 0xcdadf3c5},
{'9', 0xffdaffe8, 0xff86ffe8},
{'l', 0x00000000, 0xc1852786},
{'8', 0x9de9b400, 0xe99fe9ea},
{'4', 0x0000ffdb, 0xffb40000},
{'l', 0x40874d50, 0x00000000},
{'q', 0x4185b0f7, 0x00000000},
{0, 0x41b24149, 0x409eed20},
{'9', 0x0027002d, 0x009d002d},
{'l', 0x00000000, 0x4180dbec},
{'8', 0x6f1c5000, 0x1e691e1c},
{'4', 0x00000021, 0x004c0000},
{'l', 0xc0852780, 0x00000000},
{'q', 0xc1187bb8, 0x00000000},
{0, 0xc1527848, 0x407920a0},
{'9', 0x001fffe4, 0x0070ffe4},
{'l', 0x00000000, 0x41805278},
{'q', 0x00000000, 0x416d50c0},
{0, 0xc0b46718, 0x419e63a6},
{'9', 0x0027ffd4, 0x0027ff4e},
{'l', 0xc0874d50, 0x00000000},
{'l', 0x00000000, 0xc11aa181},
{'@', 0x0000007e, 0x0000732a},/*        ~        x-advance: 115.164062 */
{'M', 0x42c93543, 0xc25b5430},
{'l', 0x00000000, 0x413f2414},
{'8', 0x3c982ac8, 0x129d12d1},
{'q', 0xc0ec3dd0, 0x00000000},
{0, 0xc189731c, 0xc07d6c40},
{'8', 0xfdf8fefb, 0xfcf5fffd},
{'q', 0xc1267168, 0xc0852780},
{0, 0xc185b0f8, 0xc0852780},
{'8', 0x14a300d1, 0x409e14d2},
{'l', 0x00000000, 0xc13f2414},
{'8', 0xc468d638, 0xee64ee30},
{'q', 0x40ec3dd8, 0x00000000},
{0, 0x4189fc90, 0x4080dbe8},
{'8', 0x03080205, 0x040b0104},
{'q', 0x41267164, 0x40852780},
{0, 0x4185b0f6, 0x40852780},
{'8', 0xec5b002e, 0xbf64ec2d},
};
#define CTX_FONT_ascii 1
#endif
#endif //_CTX_INTERNAL_FONT_
#ifndef __CTX_LIST__
#define __CTX_LIST__

#if !__COSMOPOLITAN__
#include <stdlib.h>
#endif

/* The whole ctx_list implementation is in the header and will be inlined
 * wherever it is used.
 */

static inline void *ctx_calloc (size_t size, size_t count)
{
  size_t byte_size = size * count;
  char *ret = (char*)malloc (byte_size);
  for (size_t i = 0; i < byte_size; i++)
     ret[i] = 0;
  return ret;
}

typedef struct _CtxList CtxList;
struct _CtxList {
  void *data;
  CtxList *next;
  void (*freefunc)(void *data, void *freefunc_data);
  void *freefunc_data;
};

static inline void ctx_list_prepend_full (CtxList **list, void *data,
    void (*freefunc)(void *data, void *freefunc_data),
    void *freefunc_data)
{
  CtxList *new_= (CtxList*)ctx_calloc (sizeof (CtxList), 1);
  new_->next = *list;
  new_->data=data;
  new_->freefunc=freefunc;
  new_->freefunc_data = freefunc_data;
  *list = new_;
}

static inline int ctx_list_length (CtxList *list)
{
  int length = 0;
  CtxList *l;
  for (l = list; l; l = l->next, length++);
  return length;
}

static inline void ctx_list_prepend (CtxList **list, void *data)
{
  CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1);
  new_->next= *list;
  new_->data=data;
  *list = new_;
}

static inline CtxList *ctx_list_nth (CtxList *list, int no)
{
  while (no-- && list)
    { list = list->next; }
  return list;
}

static inline void *ctx_list_nth_data (CtxList *list, int no)
{
  CtxList *l = ctx_list_nth (list, no);
  if (l)
    return l->data;
  return NULL;
}


static inline void
ctx_list_insert_before (CtxList **list, CtxList *sibling,
                       void *data)
{
  if (*list == NULL || *list == sibling)
    {
      ctx_list_prepend (list, data);
    }
  else
    {
      CtxList *prev = NULL;
      for (CtxList *l = *list; l; l=l->next)
        {
          if (l == sibling)
            { break; }
          prev = l;
        }
      if (prev)
        {
          CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1);
          new_->next = sibling;
          new_->data = data;
          prev->next=new_;
        }
    }
}

static inline void ctx_list_remove_link (CtxList **list, CtxList *link)
{
  CtxList *iter, *prev = NULL;
  if ((*list) == link)
    {
      prev = (*list)->next;
      *list = prev;
      link->next = NULL;
      return;
    }
  for (iter = *list; iter; iter = iter->next)
    if (iter == link)
      {
        if (prev)
          prev->next = iter->next;
        link->next = NULL;
        return;
      }
    else
      prev = iter;
}

static inline void ctx_list_remove (CtxList **list, void *data)
{
  CtxList *iter, *prev = NULL;
  if ((*list)->data == data)
    {
      if ((*list)->freefunc)
        (*list)->freefunc ((*list)->data, (*list)->freefunc_data);
      prev = (*list)->next;
      free (*list);
      *list = prev;
      return;
    }
  for (iter = *list; iter; iter = iter->next)
    if (iter->data == data)
      {
        if (iter->freefunc)
          iter->freefunc (iter->data, iter->freefunc_data);
        prev->next = iter->next;
        free (iter);
        break;
      }
    else
      prev = iter;
}

static inline void ctx_list_free (CtxList **list)
{
  while (*list)
    ctx_list_remove (list, (*list)->data);
}

static inline void
ctx_list_reverse (CtxList **list)
{
  CtxList *new_ = NULL;
  CtxList *l;
  for (l = *list; l; l=l->next)
    ctx_list_prepend (&new_, l->data);
  ctx_list_free (list);
  *list = new_;
}

static inline void *ctx_list_last (CtxList *list)
{
  if (list)
    {
      CtxList *last;
      for (last = list; last->next; last=last->next);
      return last->data;
    }
  return NULL;
}

static inline void ctx_list_concat (CtxList **list, CtxList *list_b)
{
  if (*list)
    {
      CtxList *last;
      for (last = *list; last->next; last=last->next);
      last->next = list_b;
      return;
    }
  *list = list_b;
}

static inline void ctx_list_append_full (CtxList **list, void *data,
    void (*freefunc)(void *data, void *freefunc_data),
    void *freefunc_data)
{
  CtxList *new_ = (CtxList*) ctx_calloc (sizeof (CtxList), 1);
  new_->data=data;
  new_->freefunc = freefunc;
  new_->freefunc_data = freefunc_data;
  ctx_list_concat (list, new_);
}

static inline void ctx_list_append (CtxList **list, void *data)
{
  ctx_list_append_full (list, data, NULL, NULL);
}

static inline void
ctx_list_insert_at (CtxList **list,
                    int       no,
                    void     *data)
{
  if (*list == NULL || no == 0)
    {
      ctx_list_prepend (list, data);
    }
  else
    {
      int pos = 0;
      CtxList *prev = NULL;
      CtxList *sibling = NULL;
      for (CtxList *l = *list; l && pos < no; l=l->next)
        {
          prev = sibling;
          sibling = l;
          pos ++;
        }
      if (prev)
        {
          CtxList *new_ = (CtxList*)ctx_calloc (sizeof (CtxList), 1);
          new_->next = sibling;
          new_->data = data;
          prev->next=new_;
          return;
        }
      ctx_list_append (list, data);
    }
}

static CtxList*
ctx_list_merge_sorted (CtxList* list1,
                       CtxList* list2,
    int(*compare)(const void *a, const void *b, void *userdata), void *userdata
)
{
  if (list1 == NULL)
     return(list2);
  else if (list2==NULL)
     return(list1);

  if (compare (list1->data, list2->data, userdata) >= 0)
  {
    list1->next = ctx_list_merge_sorted (list1->next,list2, compare, userdata);
    /*list1->next->prev = list1;
      list1->prev = NULL;*/
    return list1;
  }
  else
  {
    list2->next = ctx_list_merge_sorted (list1,list2->next, compare, userdata);
    /*list2->next->prev = list2;
      list2->prev = NULL;*/
    return list2;
  }
}

static void
ctx_list_split_half (CtxList*  head,
                     CtxList** list1,
                     CtxList** list2)
{
  CtxList* fast;
  CtxList* slow;
  if (head==NULL || head->next==NULL)
  {
    *list1 = head;
    *list2 = NULL;
  }
  else
  {
    slow = head;
    fast = head->next;

    while (fast != NULL)
    {
      fast = fast->next;
      if (fast != NULL)
      {
        slow = slow->next;
        fast = fast->next;
      }
    }

    *list1 = head;
    *list2 = slow->next;
    slow->next = NULL;
  }
}

static inline void ctx_list_sort (CtxList **head,
    int(*compare)(const void *a, const void *b, void *userdata),
    void *userdata)
{
  CtxList* list1;
  CtxList* list2;

  /* Base case -- length 0 or 1 */
  if ((*head == NULL) || ((*head)->next == NULL))
  {
    return;
  }

  ctx_list_split_half (*head, &list1, &list2);
  ctx_list_sort (&list1, compare, userdata);
  ctx_list_sort (&list2, compare, userdata);
  *head = ctx_list_merge_sorted (list1, list2, compare, userdata);
}

static inline void ctx_list_insert_sorted (CtxList **list,
                                           void     *item,
    int(*compare)(const void *a, const void *b, void *userdata),
                                           void     *userdata)
{
  ctx_list_prepend (list, item);
  ctx_list_sort (list, compare, userdata);
}


static inline CtxList *ctx_list_find_custom (CtxList *list,
                                         void    *needle,
                                         int(*compare)(const void *a, const void *b),
                                         void *userdata)
{
  CtxList *l;
  for (l = list; l; l = l->next)
  {
    if (compare (l->data, needle) == 0)
      return l;
  }
  return NULL;
}

#endif

/* definitions that determine which features are included and their settings,
 * for particular platforms - in particular microcontrollers ctx might need
 * tuning for different quality/performance/resource constraints.
 *
 * the way to configure ctx is to set these defines, before both including it
 * as a header and in the file where CTX_IMPLEMENTATION is set to include the
 * implementation for different featureset and runtime settings.
 *
 */

/* whether the font rendering happens in backend or front-end of API, the
 * option is used set to 0 by the tool that converts ttf fonts to ctx internal
 * representation - both should be possible so that this tool can be made
 * into a TTF/OTF font import at runtime (perhaps even with live subsetting).
 */
#ifndef CTX_BACKEND_TEXT
#define CTX_BACKEND_TEXT 1
#endif


#define CTX_RASTERIZER_AA_SLOPE_LIMIT3          (65536/CTX_SUBDIV/15)
#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA  (65536/CTX_SUBDIV/15)
//#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (120536/CTX_SUBDIV/15)
//#define CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA (105000/CTX_SUBDIV/15)
#define CTX_RASTERIZER_AA_SLOPE_LIMIT5         (140425/CTX_SUBDIV/15)
#define CTX_RASTERIZER_AA_SLOPE_LIMIT15        (260425/CTX_SUBDIV/15)

/* subpixel-aa coordinates used in BITPACKing of drawlist
 *
 * powers of 2 is faster
 */
#ifndef CTX_SUBDIV
#define CTX_SUBDIV   8  //  max framebufer width 4095
//#define CTX_SUBDIV  10  //  max framebufer width 3250
//#define CTX_SUBDIV  16  //  max framebufer width 2047
//#define CTX_SUBDIV  24  //  max framebufer width 1350
//#define CTX_SUBDIV  32  //  max framebufer width 1023
#endif


// 8    12 68 40 24
// 16   12 68 40 24
/* scale-factor for font outlines prior to bit quantization by CTX_SUBDIV
 *
 * changing this also changes font file format - the value should be baked
 * into the ctxf files making them less dependent on the ctx used to
 * generate them
 */
#define CTX_BAKE_FONT_SIZE    160

/* pack some linetos/curvetos/movetos into denser drawlist instructions,
 * permitting more vectors to be stored in the same space, experimental
 * feature with added overhead.
 */
#ifndef CTX_BITPACK
#define CTX_BITPACK           1
#endif

/* whether we have a shape-cache where we keep pre-rasterized bitmaps of
 * commonly occuring small shapes, disabled by default since it has some
 * glitches (and potential hangs with multi threading).
 */
#ifndef CTX_SHAPE_CACHE
#define CTX_SHAPE_CACHE        0
#endif

/* size (in pixels, w*h) that we cache rasterization for
 */
#ifndef CTX_SHAPE_CACHE_DIM
#define CTX_SHAPE_CACHE_DIM      (16*8)
#endif

#ifndef CTX_SHAPE_CACHE_MAX_DIM
#define CTX_SHAPE_CACHE_MAX_DIM  20
#endif

/* maximum number of entries in shape cache
 */
#ifndef CTX_SHAPE_CACHE_ENTRIES
#define CTX_SHAPE_CACHE_ENTRIES  160
#endif


#ifndef CTX_PARSER_FIXED_TEMP
#define CTX_PARSER_FIXED_TEMP 0
         // when 1  CTX_PARSER_MAXLEN is the fixed max stringlen
#endif   // and no allocations happens beyond creating the parser,
         // when 0 the scratchbuf for parsing is a separate dynamically
         // growing buffer, that maxes out at CTX_PARSER_MAXLEN
         //
#ifndef CTX_PARSER_MAXLEN
#if CTX_PARSER_FIXED_TEMP
#define CTX_PARSER_MAXLEN  1024*128        // This is the maximum texture/string size supported
#else
#define CTX_PARSER_MAXLEN  1024*1024*16    // 16mb
#endif
#endif

#ifndef CTX_COMPOSITING_GROUPS
#define CTX_COMPOSITING_GROUPS   1
#endif

/* maximum nesting level of compositing groups
 */
#ifndef CTX_GROUP_MAX
#define CTX_GROUP_MAX             8
#endif

#ifndef CTX_ENABLE_CLIP
#define CTX_ENABLE_CLIP           1
#endif

/* use a 1bit clip buffer, saving RAM on microcontrollers, other rendering
 * will still be antialiased.
 */
#ifndef CTX_1BIT_CLIP
#define CTX_1BIT_CLIP             0
#endif


#ifndef CTX_ENABLE_SHADOW_BLUR
#define CTX_ENABLE_SHADOW_BLUR    1
#endif

#ifndef CTX_GRADIENTS
#define CTX_GRADIENTS             1
#endif

#ifndef CTX_ALIGNED_STRUCTS
#define CTX_ALIGNED_STRUCTS       1
#endif

#ifndef CTX_GRADIENT_CACHE
#define CTX_GRADIENT_CACHE        1
#endif

#ifndef CTX_FONTS_FROM_FILE
#define CTX_FONTS_FROM_FILE  0
#endif

#ifndef CTX_GET_CONTENTS
#if CTX_FONTS_FROM_FILE
#define CTX_GET_CONTENTS    1
#else
#define CTX_GET_CONTENTS    0
#endif
#endif

#ifndef CTX_FORMATTER
#define CTX_FORMATTER       1
#endif

#ifndef CTX_PARSER
#define CTX_PARSER          1
#endif

#ifndef CTX_CURRENT_PATH
#define CTX_CURRENT_PATH    1
#endif

#ifndef CTX_XML
#define CTX_XML             1
#endif

#ifndef CTX_VT
#define CTX_VT              0
#endif

/* when ctx_math is defined, which it is by default, we use ctx' own
 * implementations of math functions, instead of relying on math.h
 * the possible inlining gives us a slight speed-gain, and on
 * embedded platforms guarantees that we do not do double precision
 * math.
 */
#ifndef CTX_MATH
#define CTX_MATH           1  // use internal fast math for sqrt,sin,cos,atan2f etc.
#endif

#define ctx_log(fmt, ...)
//#define ctx_log(str, a...) fprintf(stderr, str, ##a)

/* the initial journal size - for both rasterizer
 * edgelist and drawlist.
 */
#ifndef CTX_MIN_JOURNAL_SIZE
#define CTX_MIN_JOURNAL_SIZE      512
#endif

/* The maximum size we permit the drawlist to grow to,
 * the memory used is this number * 9, where 9 is sizeof(CtxEntry)
 */
#ifndef CTX_MAX_JOURNAL_SIZE
//#define CTX_MAX_JOURNAL_SIZE   CTX_MIN_JOURNAL_SIZE
#define CTX_MAX_JOURNAL_SIZE 1024*1024*16
#endif

#ifndef CTX_DRAWLIST_STATIC
#define CTX_DRAWLIST_STATIC  0
#endif

#ifndef CTX_MIN_EDGE_LIST_SIZE
#define CTX_MIN_EDGE_LIST_SIZE   1024
#endif

#ifndef CTX_RASTERIZER_AA
#define CTX_RASTERIZER_AA 15   // vertical-AA of CTX_ANTIALIAS_DEFAULT
#endif

/* The maximum complexity of a single path
 */
#ifndef CTX_MAX_EDGE_LIST_SIZE
#define CTX_MAX_EDGE_LIST_SIZE  CTX_MIN_EDGE_LIST_SIZE
#endif

#ifndef CTX_STRINGPOOL_SIZE
  // XXX should be possible to make zero and disappear when codepaths not in use
  //     to save size, for card10 this is defined as a low number (some text
  //     properties still make use of it)
  //     
  //     for desktop-use this should be fully dynamic, possibly
  //     with chained pools, gradients are stored here.
#define CTX_STRINGPOOL_SIZE     1000 //
#endif

/* whether we dither or not for gradients
 */
#ifndef CTX_DITHER
#define CTX_DITHER 0
#endif

/*  only source-over clear and copy will work, the API still
 *  through - but the renderer is limited, for use to measure
 *  size and possibly in severely constrained ROMs.
 */
#ifndef CTX_BLENDING_AND_COMPOSITING
#define CTX_BLENDING_AND_COMPOSITING 1
#endif

/*  this forces the inlining of some performance
 *  critical paths.
 */
#ifndef CTX_FORCE_INLINES
#define CTX_FORCE_INLINES               1
#endif

/* create one-off inlined inner loop for normal blend mode (for floating point,
 * for RGBA8 manual loops overrrides
 */
#ifndef CTX_INLINED_NORMAL     
#define CTX_INLINED_NORMAL      1
#endif

#ifndef CTX_INLINED_GRADIENTS
#define CTX_INLINED_GRADIENTS   1
#endif

#ifndef CTX_BRAILLE_TEXT
#define CTX_BRAILLE_TEXT        0
#endif

/* Build code paths for grayscale rasterization, this makes clipping
 * faster.
 */
#ifndef CTX_NATIVE_GRAYA8
#define CTX_NATIVE_GRAYA8       1
#endif

/* enable CMYK rasterization targets
 */
#ifndef CTX_ENABLE_CMYK
#define CTX_ENABLE_CMYK         1
#endif

/* enable color management, slightly increases CtxColor struct size, can
 * be disabled for microcontrollers.
 */
#ifndef CTX_ENABLE_CM
#define CTX_ENABLE_CM           1
#endif

#ifndef CTX_EVENTS
#define CTX_EVENTS              1
#endif

#ifndef CTX_LIMIT_FORMATS
#define CTX_LIMIT_FORMATS       0
#endif

#ifndef CTX_ENABLE_FLOAT
#define CTX_ENABLE_FLOAT        0
#endif

/* by default ctx includes all pixel formats, on microcontrollers
 * it can be useful to slim down code and runtime size by only
 * defining the used formats, set CTX_LIMIT_FORMATS to 1, and
 * manually add CTX_ENABLE_ flags for each of them.
 */
#if CTX_LIMIT_FORMATS
#if CTX_NATIVE_GRAYA8
#define CTX_ENABLE_GRAYA8               1
#define CTX_ENABLE_GRAY8                1
#endif
#else

#define CTX_ENABLE_GRAY1                1
#define CTX_ENABLE_GRAY2                1
#define CTX_ENABLE_GRAY4                1
#define CTX_ENABLE_GRAY8                1
#define CTX_ENABLE_GRAYA8               1
#define CTX_ENABLE_GRAYF                1
#define CTX_ENABLE_GRAYAF               1

#define CTX_ENABLE_RGB8                 1
#define CTX_ENABLE_RGBA8                1
#define CTX_ENABLE_BGRA8                1
#define CTX_ENABLE_RGB332               1
#define CTX_ENABLE_RGB565               1
#define CTX_ENABLE_RGB565_BYTESWAPPED   1
#define CTX_ENABLE_RGBAF                1
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT                1
#define CTX_ENABLE_YUV420               1

#if CTX_ENABLE_CMYK
#define CTX_ENABLE_CMYK8                1
#define CTX_ENABLE_CMYKA8               1
#define CTX_ENABLE_CMYKAF               1
#endif
#endif

#ifndef CTX_RGB565_ALPHA
#define CTX_RGB565_ALPHA                0   // when enabled pure purple is transparent,
                                            // for a ~15% overall performance hit
#endif

#ifndef CTX_RGB332_ALPHA
#define CTX_RGB332_ALPHA                0   // when enabled pure purple is transparent,
                                            // for a ~15% overall performance hit
#endif

/* by including ctx-font-regular.h, or ctx-font-mono.h the
 * built-in fonts using ctx drawlist encoding is enabled
 */
#if CTX_FONT_regular || CTX_FONT_mono || CTX_FONT_bold \
  || CTX_FONT_italic || CTX_FONT_sans || CTX_FONT_serif \
  || CTX_FONT_ascii
#ifndef CTX_FONT_ENGINE_CTX
#define CTX_FONT_ENGINE_CTX        1
#endif
#endif

#ifndef CTX_FONT_ENGINE_CTX_FS
#define CTX_FONT_ENGINE_CTX_FS 0
#endif

/* If stb_strutype.h is included before ctx.h add integration code for runtime loading
 * of opentype fonts.
 */
#ifdef __STB_INCLUDE_STB_TRUETYPE_H__
#ifndef CTX_FONT_ENGINE_STB
#define CTX_FONT_ENGINE_STB        1
#endif
#else
#define CTX_FONT_ENGINE_STB        0
#endif

#ifdef _BABL_H
#define CTX_BABL 1
#else
#define CTX_BABL 0
#endif

#ifndef CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
#define CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 1
#endif

/* force add format if we have shape cache */
#if CTX_SHAPE_CACHE
#ifdef CTX_ENABLE_GRAY8
#undef CTX_ENABLE_GRAY8
#endif
#define CTX_ENABLE_GRAY8  1
#endif

/* include the bitpack packer, can be opted out of to decrease code size
 */
#ifndef CTX_BITPACK_PACKER
#define CTX_BITPACK_PACKER 0
#endif

/* enable RGBA8 intermediate format for
 *the indirectly implemented pixel-formats.
 */
#if CTX_ENABLE_GRAY1 | CTX_ENABLE_GRAY2 | CTX_ENABLE_GRAY4 | CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED | CTX_ENABLE_RGB8 | CTX_ENABLE_RGB332

  #ifdef CTX_ENABLE_RGBA8
    #undef CTX_ENABLE_RGBA8
  #endif
  #define CTX_ENABLE_RGBA8  1
#endif

#ifdef CTX_ENABLE_CMYKF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif

#ifdef CTX_ENABLE_GRAYF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif

#ifdef CTX_ENABLE_GRAYAF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif

#ifdef CTX_ENABLE_RGBAF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif

#ifdef CTX_ENABLE_CMYKAF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif

#ifdef CTX_ENABLE_CMYKF
#ifdef CTX_ENABLE_FLOAT
#undef CTX_ENABLE_FLOAT
#endif
#define CTX_ENABLE_FLOAT 1
#endif


/* enable cmykf which is cmyk intermediate format
 */
#ifdef CTX_ENABLE_CMYK8
#ifdef CTX_ENABLE_CMYKF
#undef CTX_ENABLE_CMYKF
#endif
#define CTX_ENABLE_CMYKF  1
#endif
#ifdef CTX_ENABLE_CMYKA8
#ifdef CTX_ENABLE_CMYKF
#undef CTX_ENABLE_CMYKF
#endif
#define CTX_ENABLE_CMYKF  1
#endif

#ifdef CTX_ENABLE_CMYKF8
#ifdef CTX_ENABLE_CMYK
#undef CTX_ENABLE_CMYK
#endif
#define CTX_ENABLE_CMYK   1
#endif

#define CTX_PI                              3.141592653589793f
#ifndef CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS
#define CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS  400
#endif

#ifndef CTX_MAX_FRAMEBUFFER_WIDTH
#define CTX_MAX_FRAMEBUFFER_WIDTH 2560
#endif

#ifndef CTX_MAX_FONTS
#define CTX_MAX_FONTS            3
#endif

#ifndef CTX_MAX_STATES
#define CTX_MAX_STATES           10
#endif

#ifndef CTX_MAX_EDGES
#define CTX_MAX_EDGES            257
#endif

#ifndef CTX_MAX_LINGERING_EDGES
#define CTX_MAX_LINGERING_EDGES  64
#endif


#ifndef CTX_MAX_PENDING
#define CTX_MAX_PENDING          128
#endif

#ifndef CTX_MAX_TEXTURES
#define CTX_MAX_TEXTURES         16
#endif

#ifndef CTX_HASH_ROWS
#define CTX_HASH_ROWS            8
#endif
#ifndef CTX_HASH_COLS
#define CTX_HASH_COLS            8
#endif

#ifndef CTX_MAX_THREADS
#define CTX_MAX_THREADS          8 // runtime is max of cores/2 and this
#endif



#define CTX_RASTERIZER_EDGE_MULTIPLIER  1024
                                        // increasing this to 2048
                                        // removes artifacts in top half of res-diagram -
                                        // but reduces maximum available buffer width
#ifndef CTX_IMPLEMENTATION
#define CTX_IMPLEMENTATION 0
#else
#undef CTX_IMPLEMENTATION
#define CTX_IMPLEMENTATION 1
#endif


#ifdef CTX_RASTERIZER
#if CTX_RASTERIZER==0
#if CTX_SDL || CTX_FB
#undef CTX_RASTERIZER
#define CTX_RASTERIZER 1
#endif
#else
#undef CTX_RASTERIZER
#define CTX_RASTERIZER 1
#endif
#endif

#if CTX_RASTERIZER
#ifndef CTX_COMPOSITE
#define CTX_COMPOSITE 1
#endif
#else
#ifndef CTX_COMPOSITE
#define CTX_COMPOSITE 0
#endif
#endif

#ifndef CTX_GRADIENT_CACHE_ELEMENTS
#define CTX_GRADIENT_CACHE_ELEMENTS 256
#endif

#ifndef CTX_PARSER_MAX_ARGS
#define CTX_PARSER_MAX_ARGS 20
#endif


#ifndef CTX_SCREENSHOT
#define CTX_SCREENSHOT 0
#endif

#ifndef CTX_ALSA_AUDIO
#define CTX_ALSA_AUDIO 0
#endif

#if NO_ALSA
#undef CTX_ALSA_AUDIO
#define CTX_ALSA_AUDIO 0
#endif

#ifndef CTX_AUDIO
#define CTX_AUDIO 0
#endif

#ifndef CTX_TILED
#if CTX_SDL || CTX_FB
#define CTX_TILED 1
#else
#define CTX_TILED 0
#endif
#endif

#ifndef CTX_THREADS
#if CTX_FB
#define CTX_THREADS 1
#else
#define CTX_THREADS 0
#endif
#endif

#if CTX_THREADS
#include <threads.h>
#endif
 /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org>
 */

#if CTX_FORMATTER

/* returns the maximum string length including terminating \0 */
int ctx_a85enc_len (int input_length);
int ctx_a85enc (const void *srcp, char *dst, int count);

#if CTX_PARSER

int ctx_a85dec (const char *src, char *dst, int count);
int ctx_a85len (const char *src, int count);
#endif

#endif
#ifndef __CTX_EXTRA_H
#define __CTX_EXTRA_H


#define CTX_CLAMP(val,min,max) ((val)<(min)?(min):(val)>(max)?(max):(val))
static inline int   ctx_mini (int a, int b)     { return (a < b) * a + (a >= b) * b; }
static inline float ctx_minf (float a, float b) { return (a < b) * a + (a >= b) * b; }
static inline int   ctx_maxi (int a, int b)     { return (a > b) * a + (a <= b) * b; }
static inline float ctx_maxf (float a, float b) { return (a > b) * a + (a <= b) * b; }


typedef enum CtxOutputmode
{
  CTX_OUTPUT_MODE_QUARTER,
  CTX_OUTPUT_MODE_BRAILLE,
  CTX_OUTPUT_MODE_SIXELS,
  CTX_OUTPUT_MODE_GRAYS,
  CTX_OUTPUT_MODE_CTX,
  CTX_OUTPUT_MODE_CTX_COMPACT,
  CTX_OUTPUT_MODE_UI
} CtxOutputmode;





#if CTX_FORCE_INLINES
#define CTX_INLINE inline __attribute__((always_inline))
#else
#define CTX_INLINE inline
#endif

static inline float ctx_pow2 (float a) { return a * a; }
#if CTX_MATH

static inline float
ctx_fabsf (float x)
{
  union
  {
    float f;
    uint32_t i;
  } u = { x };
  u.i &= 0x7fffffff;
  return u.f;
}

static inline float
ctx_invsqrtf (float x)
{
  void *foo = &x;
  float xhalf = 0.5f * x;
  int i=* (int *) foo;
  void *bar = &i;
  i = 0x5f3759df - (i >> 1);
  x = * (float *) bar;
  x *= (1.5f - xhalf * x * x);
  x *= (1.5f - xhalf * x * x); //repeating Newton-Raphson step for higher precision
  return x;
}

static inline float
ctx_invsqrtf_fast (float x)
{
  void *foo = &x;
//float xhalf = 0.5f * x;
  int i=* (int *) foo;
  void *bar = &i;
  i = 0x5f3759df - (i >> 1);
  x = * (float *) bar;
//x *= (1.5f - xhalf * x * x);
  return x;
}

CTX_INLINE static float ctx_sqrtf (float a)
{
  return 1.0f/ctx_invsqrtf (a);
}

CTX_INLINE static float ctx_sqrtf_fast (float a)
{
  return 1.0f/ctx_invsqrtf_fast (a);
}

CTX_INLINE static float ctx_hypotf (float a, float b)
{
  return ctx_sqrtf (ctx_pow2 (a)+ctx_pow2 (b) );
}

CTX_INLINE static float ctx_hypotf_fast (float a, float b)
{
  return ctx_sqrtf_fast (ctx_pow2 (a)+ctx_pow2 (b) );
}

CTX_INLINE static float
ctx_sinf (float x)
{
  if (x < -CTX_PI * 2)
    {
      x = -x;
      long ix = x / (CTX_PI * 2);
      x = x - ix * CTX_PI * 2;
      x = -x;
    }
  if (x < -CTX_PI * 1000)
  {
    x = -0.5;
  }
  if (x > CTX_PI * 1000)
  {
          // really large numbers tend to cause practically inifinite
          // loops since the > CTX_PI * 2 seemingly fails
    x = 0.5;
  }
  if (x > CTX_PI * 2)
    { 
      long ix = x / (CTX_PI * 2);
      x = x - (ix * CTX_PI * 2);
    }
  while (x < -CTX_PI)
    { x += CTX_PI * 2; }
  while (x > CTX_PI)
    { x -= CTX_PI * 2; }

  /* source : http://mooooo.ooo/chebyshev-sine-approximation/ */
  float coeffs[]=
  {
    -0.10132118f,           // x
      0.0066208798f,         // x^3
      -0.00017350505f,        // x^5
      0.0000025222919f,      // x^7
      -0.000000023317787f,    // x^9
      0.00000000013291342f
    }; // x^11
  float x2 = x*x;
  float p11 = coeffs[5];
  float p9  = p11*x2 + coeffs[4];
  float p7  = p9*x2  + coeffs[3];
  float p5  = p7*x2  + coeffs[2];
  float p3  = p5*x2  + coeffs[1];
  float p1  = p3*x2  + coeffs[0];
  return (x - CTX_PI + 0.00000008742278f) *
         (x + CTX_PI - 0.00000008742278f) * p1 * x;
}

static inline float ctx_atan2f (float y, float x)
{
  float atan, z;
  if ( x == 0.0f )
    {
      if ( y > 0.0f )
        { return CTX_PI/2; }
      if ( y == 0.0f )
        { return 0.0f; }
      return -CTX_PI/2;
    }
  z = y/x;
  if ( ctx_fabsf ( z ) < 1.0f )
    {
      atan = z/ (1.0f + 0.28f*z*z);
      if (x < 0.0f)
        {
          if ( y < 0.0f )
            { return atan - CTX_PI; }
          return atan + CTX_PI;
        }
    }
  else
    {
      atan = CTX_PI/2 - z/ (z*z + 0.28f);
      if ( y < 0.0f ) { return atan - CTX_PI; }
    }
  return atan;
}


static inline float ctx_atanf (float a)
{
  return ctx_atan2f ( (a), 1.0f);
}

static inline float ctx_asinf (float x)
{
  return ctx_atanf ( (x) * (ctx_invsqrtf (1.0f-ctx_pow2 (x) ) ) );
}

static inline float ctx_acosf (float x)
{
  return ctx_atanf ( (ctx_sqrtf (1.0f-ctx_pow2 (x) ) / (x) ) );
}

CTX_INLINE static float ctx_cosf (float a)
{
  return ctx_sinf ( (a) + CTX_PI/2.0f);
}

static inline float ctx_tanf (float a)
{
  return (ctx_cosf (a) /ctx_sinf (a) );
}
static inline float
ctx_floorf (float x)
{
  return (int)x; // XXX
}
static inline float
ctx_expf (float x)
{
  union { uint32_t i; float f; } v =
    {  (uint32_t)( (1 << 23) * (x + 183.1395965f)) };
  return v.f;
}

/* define more trig based on having sqrt, sin and atan2 */

#else
#if !__COSMOPOLITAN__
#include <math.h>
#endif
static inline float ctx_fabsf (float x)           { return fabsf (x); }
static inline float ctx_floorf (float x)          { return floorf (x); }
static inline float ctx_sinf (float x)            { return sinf (x); }
static inline float ctx_atan2f (float y, float x) { return atan2f (y, x); }
static inline float ctx_hypotf (float a, float b) { return hypotf (a, b); }
static inline float ctx_acosf (float a)           { return acosf (a); }
static inline float ctx_cosf (float a)            { return cosf (a); }
static inline float ctx_tanf (float a)            { return tanf (a); }
static inline float ctx_expf (float p)            { return expf (p); }
static inline float ctx_sqrtf (float a)           { return sqrtf (a); }
#endif

static inline float _ctx_parse_float (const char *str, char **endptr)
{
  return strtod (str, endptr); /* XXX: , vs . problem in some locales */
}

const char *ctx_get_string (Ctx *ctx, uint64_t hash);
void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value);
typedef struct _CtxColor CtxColor;

void
ctx_matrix_translate (CtxMatrix *matrix, float x, float y);


void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix);
void ctx_set_matrix (Ctx *ctx, CtxMatrix *matrix);
int _ctx_is_rasterizer (Ctx *ctx);

int ctx_color (Ctx *ctx, const char *string);
typedef struct _CtxState CtxState;
CtxColor *ctx_color_new ();
CtxState *ctx_get_state (Ctx *ctx);
void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out);
void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a);
void ctx_color_free (CtxColor *color);
void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color);
int  ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color);
int  ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string);

int ctx_color_is_transparent (CtxColor *color);
int ctx_utf8_len (const unsigned char first_byte);

void ctx_user_to_device          (Ctx *ctx, float *x, float *y);
void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y);
const char *ctx_utf8_skip (const char *s, int utf8_length);
int ctx_is_set_now (Ctx *ctx, uint64_t hash);
void ctx_set_size (Ctx *ctx, int width, int height);

static inline float ctx_matrix_get_scale (CtxMatrix *matrix)
{
   return ctx_maxf (ctx_maxf (ctx_fabsf (matrix->m[0][0]),
                         ctx_fabsf (matrix->m[0][1]) ),
               ctx_maxf (ctx_fabsf (matrix->m[1][0]),
                         ctx_fabsf (matrix->m[1][1]) ) );
}

#if CTX_GET_CONTENTS
int
_ctx_file_get_contents (const char     *path,
                        unsigned char **contents,
                        long           *length);
#endif

#if CTX_FONTS_FROM_FILE
int   ctx_load_font_ttf_file (const char *name, const char *path);
#endif

#if CTX_BABL
void ctx_rasterizer_colorspace_babl (CtxState      *state,
                                     CtxColorSpace  space_slot,
                                     const Babl    *space);
#endif
void ctx_rasterizer_colorspace_icc (CtxState      *state,
                                    CtxColorSpace  space_slot,
                                    char          *icc_data,
                                    int            icc_length);


CtxBuffer *ctx_buffer_new_bare (void);

void ctx_buffer_set_data (CtxBuffer *buffer,
                          void *data, int width, int height,
                          int stride,
                          CtxPixelFormat pixel_format,
                          void (*freefunc) (void *pixels, void *user_data),
                          void *user_data);

int _ctx_set_frame (Ctx *ctx, int frame);
int _ctx_frame (Ctx *ctx);


void ctx_exit (Ctx *ctx);
void ctx_list_backends(void);
int ctx_pixel_format_ebpp (CtxPixelFormat format);

#endif
#ifndef __CTX_CONSTANTS
#define __CTX_CONSTANTS

#define TOKENHASH(a)    ((uint64_t)a)

#define CTX_strokeSource TOKENHASH(3061861651908008)
#define CTX_add_stop 	TOKENHASH(1274978316678)
#define CTX_addStop 	TOKENHASH(40799943078278)
#define CTX_alphabetic 	TOKENHASH(2629359926678406)
#define CTX_arc 	TOKENHASH(11526)
#define CTX_arc_to 	TOKENHASH(1187065094)
#define CTX_arcTo 	TOKENHASH(38558051590)
#define CTX_begin_path 	TOKENHASH(3004110681622984)
#define CTX_beginPath 	TOKENHASH(8437143659599196)
#define CTX_bevel 	TOKENHASH(29868488)
#define CTX_bottom 	TOKENHASH(1043772488)
#define CTX_cap 	TOKENHASH(37066)
#define CTX_center 	TOKENHASH(1358332362)
#define CTX_clear 	TOKENHASH(42154890)
#define CTX_color 	TOKENHASH(43086922)
#define CTX_copy 	TOKENHASH(1807434)
#define CTX_clip 	TOKENHASH(1203082)
#define CTX_close_path 	TOKENHASH(3004110663420810)
#define CTX_closePath 	TOKENHASH(8437144279135038)
#define CTX_cmyka 	TOKENHASH(7199690)
#define CTX_cmyk 	TOKENHASH(908234)
#define CTX_cmykaS 	TOKENHASH(36313095114)
#define CTX_cmykS 	TOKENHASH(1135467466)
#define CTX_color 	TOKENHASH(43086922)
#define CTX_blending 	TOKENHASH(653586873224)
#define CTX_blend 	TOKENHASH(13646728)
#define CTX_blending_mode 	TOKENHASH(8147360531130856)
#define CTX_blendingMode 	TOKENHASH(7483585768187540)
#define CTX_blend_mode 	TOKENHASH(2758775686577032)
#define CTX_blendMode 	TOKENHASH(7773213171090182)
#define CTX_composite 	TOKENHASH(16930059746378)
#define CTX_compositing_mode 	TOKENHASH(2417816728103524)
#define CTX_compositingMode 	TOKENHASH(2807194446992106)
#define CTX_curve_to 	TOKENHASH(1215559149002)
#define CTX_curveTo 	TOKENHASH(39483449320906)
#define CTX_darken 	TOKENHASH(1089315020)
#define CTX_defineGlyph 	TOKENHASH(2497926167421194)
#define CTX_defineTexture 	TOKENHASH(2623744577477404)
#define CTX_kerningPair 	TOKENHASH(6964644556489058)
#define CTX_destinationIn 	TOKENHASH(8153299143600102)
#define CTX_destination_in 	TOKENHASH(3824201982576824)
#define CTX_destinationAtop 	TOKENHASH(8185118415574560)
#define CTX_destination_atop 	TOKENHASH(7742210324901698)
#define CTX_destinationOver 	TOKENHASH(3261713333438500)
#define CTX_destination_over 	TOKENHASH(7742210324728474)
#define CTX_destinationOut 	TOKENHASH(7742210322269456)
#define CTX_destination_out 	TOKENHASH(8153299143489102)
#define CTX_difference 	TOKENHASH(2756492040618700)
#define CTX_done 	TOKENHASH(492620)
#define CTX_drgba 	TOKENHASH(6573324)
#define CTX_drgb 	TOKENHASH(281868)
#define CTX_drgbaS 	TOKENHASH(36312468748)
#define CTX_drgbS 	TOKENHASH(1134841100)
#define CTX_end 	TOKENHASH(13326)
#define CTX_endfun 	TOKENHASH(1122513934)
#define CTX_end_group 	TOKENHASH(41200834917390)
#define CTX_endGroup 	TOKENHASH(3570227948106766)
#define CTX_even_odd 	TOKENHASH(426345774606)
#define CTX_evenOdd 	TOKENHASH(13671748091406)
#define CTX_exit 	TOKENHASH(1465998)
#define CTX_fill 	TOKENHASH(946896)
#define CTX_fill_rule 	TOKENHASH(16405972808400)
#define CTX_fillRule 	TOKENHASH(2776813389378256)
#define CTX_flush 	TOKENHASH(22395792)
#define CTX_font 	TOKENHASH(1475664)
#define CTX_font_size 	TOKENHASH(17342343316560)
#define CTX_setFontSize 	TOKENHASH(8657699789799734)
#define CTX_fontSize 	TOKENHASH(2806775148872784)
#define CTX_function 	TOKENHASH(1136803546576)
#define CTX_getkey 	TOKENHASH(1827516882)
#define CTX_global_alpha 	TOKENHASH(6945103263242432)
#define CTX_globalAlpha 	TOKENHASH(2684560928159160)
#define CTX_glyph 	TOKENHASH(22207378)
#define CTX_gradient_add_stop 	TOKENHASH(7829524561074416)
#define CTX_gradientAddStop 	TOKENHASH(8126442749593072)
#define CTX_graya 	TOKENHASH(8068370)
#define CTX_gray 	TOKENHASH(1776914)
#define CTX_grayaS 	TOKENHASH(36313963794)
#define CTX_grayS 	TOKENHASH(1136336146)
#define CTX_hanging 	TOKENHASH(20424786132)
#define CTX_height 	TOKENHASH(1497979348)
#define CTX_hor_line_to 	TOKENHASH(8345271542735158)
#define CTX_horLineTo 	TOKENHASH(3629696407754856)
#define CTX_hue 	TOKENHASH(15828)
#define CTX_identity 	TOKENHASH(1903455910294)
#define CTX_ideographic 	TOKENHASH(4370819675496700)
#define CTX_imageSmoothing 	TOKENHASH(4268778175825416)
#define CTX_join 	TOKENHASH(1072216)
#define CTX_laba 	TOKENHASH(205020)
#define CTX_lab 	TOKENHASH(8412)
#define CTX_lcha 	TOKENHASH(217436)
#define CTX_lch 	TOKENHASH(20828)
#define CTX_labaS 	TOKENHASH(1134764252)
#define CTX_labS 	TOKENHASH(35463388)
#define CTX_lchaS 	TOKENHASH(1134776668)
#define CTX_lchS 	TOKENHASH(35475804)
#define CTX_left 	TOKENHASH(1458652)
#define CTX_lighter 	TOKENHASH(43466246876)
#define CTX_lighten 	TOKENHASH(34876312284)
#define CTX_linear_gradient 	TOKENHASH(7801595375834212)
#define CTX_linearGradient 	TOKENHASH(4439260636789186)
#define CTX_line_cap 	TOKENHASH(1243731165916)
#define CTX_lineCap 	TOKENHASH(3436510399409980)
#define CTX_setLineCap 	TOKENHASH(7897176123029482)
#define CTX_line_height 	TOKENHASH(3300223516389168)
#define CTX_line_join 	TOKENHASH(35977601450716)
#define CTX_lineJoin 	TOKENHASH(3403122163024604)
#define CTX_setLineJoin 	TOKENHASH(2768281536656332)
#define CTX_line_spacing 	TOKENHASH(2519451230887150)
#define CTX_line_to 	TOKENHASH(37986206428)
#define CTX_lineTo 	TOKENHASH(1233857774300)
#define CTX_lineDash 	TOKENHASH(3001937455186652)
#define CTX_lineDashOffset 	TOKENHASH(3704120356324362)
#define CTX_line_width 	TOKENHASH(3004303386575580)
#define CTX_lineWidth 	TOKENHASH(8241159254028040)
#define CTX_setLineWidth 	TOKENHASH(8037913618228476)
#define CTX_view_box 	TOKENHASH(1823485803248)
#define CTX_viewBox 	TOKENHASH(3915860941641152)
#define CTX_middle 	TOKENHASH(499528414)
#define CTX_miter 	TOKENHASH(42447582)
#define CTX_miter_limit 	TOKENHASH(4281255327472850)
#define CTX_miterLimit 	TOKENHASH(7937453649653124)
#define CTX_move_to 	TOKENHASH(37986223198)
#define CTX_moveTo 	TOKENHASH(1233857791070)
#define CTX_multiply 	TOKENHASH(1886723143134)
#define CTX_new_page 	TOKENHASH(500602882528)
#define CTX_newPage 	TOKENHASH(16020123011552)
#define CTX_new_path 	TOKENHASH(734678600160)
#define CTX_newPath 	TOKENHASH(23510545975776)
#define CTX_new_state 	TOKENHASH(16912954280416)
#define CTX_none 	TOKENHASH(492640)
#define CTX_nonzero     TOKENHASH(37865948256)
#define CTX_non_zero    TOKENHASH(1211709359200)
#define CTX_normal 	TOKENHASH(946840672)
#define CTX_quad_to 	TOKENHASH(37986115046)
#define CTX_quadTo 	TOKENHASH(1233857682918)
#define CTX_radial_gradient 	TOKENHASH(8267515704460560)
#define CTX_radialGradient 	TOKENHASH(4399889250822134)
#define CTX_rectangle 	TOKENHASH(16375644301800)
#define CTX_rect 	TOKENHASH(1452520)
#define CTX_rel_arc_to 	TOKENHASH(3496527781786088)
#define CTX_relArcTo 	TOKENHASH(3209152175601038)
#define CTX_rel_curve_to 	TOKENHASH(4439651822639910)
#define CTX_relCurveTo 	TOKENHASH(7294415873689320)
#define CTX_rel_hor_line_to 	TOKENHASH(7051067105640810)
#define CTX_relHorLineTo 	TOKENHASH(8737419863647946)
#define CTX_relVerLineTo 	TOKENHASH(8737441317512906)
#define CTX_rel_line_to 	TOKENHASH(8345271542378314)
#define CTX_relLineTo 	TOKENHASH(3629696197927444)
#define CTX_rel_move_to 	TOKENHASH(8344984486309706)
#define CTX_relMoveTo 	TOKENHASH(3571677202293268)
#define CTX_rel_quad_to 	TOKENHASH(8343627754794826)
#define CTX_relQuadTo 	TOKENHASH(7894357900599828)
#define CTX_rel_smoothq_to 	TOKENHASH(7340038162167138)
#define CTX_relSmoothqTo 	TOKENHASH(3188040406230844)
#define CTX_rel_smooth_to 	TOKENHASH(8144941131301668)
#define CTX_relSmoothTo 	TOKENHASH(8947422784198618)
#define CTX_rel_ver_line_to 	TOKENHASH(8148126344839530)
#define CTX_restore 	TOKENHASH(16411699688)
#define CTX_reset 	TOKENHASH(46639592)
#define CTX_rgba 	TOKENHASH(205416)
#define CTX_rgb 	TOKENHASH(8808)
#define CTX_rgbaS 	TOKENHASH(1134764648)
#define CTX_rgbS 	TOKENHASH(35463784)
#define CTX_right 	TOKENHASH(46811880)
#define CTX_rotate 	TOKENHASH(516142184)
#define CTX_round 	TOKENHASH(13679720)
#define CTX_round_rectangle 	TOKENHASH(4332080966833870)
#define CTX_roundRectangle 	TOKENHASH(8317255488676642)
#define CTX_save 	TOKENHASH(508138)
#define CTX_scale 	TOKENHASH(15604074)
#define CTX_screen 	TOKENHASH(1088921962)
#define CTX_setkey 	TOKENHASH(1827516906)
#define CTX_shadowBlur 	TOKENHASH(2924056626980284)
#define CTX_shadowColor 	TOKENHASH(3509599043947446)
#define CTX_shadowOffsetX 	TOKENHASH(8499312693589794)
#define CTX_shadowOffsetY 	TOKENHASH(8499312693589796)
#define CTX_smooth_quad_to 	TOKENHASH(6832232668547050)
#define CTX_smoothQuadTo 	TOKENHASH(8278352345012646)
#define CTX_smooth_to 	TOKENHASH(38898089692138)
#define CTX_smoothTo 	TOKENHASH(3515270388878314)
#define CTX_sourceIn 	TOKENHASH(3444145493687402)
#define CTX_source_in 	TOKENHASH(35942915423338)
#define CTX_sourceAtop 	TOKENHASH(2920281959978332)
#define CTX_source_atop 	TOKENHASH(3007410591464110)
#define CTX_sourceOut 	TOKENHASH(7371294932695718)
#define CTX_source_out 	TOKENHASH(3851660580666474)
#define CTX_sourceOver 	TOKENHASH(7584784067385004)
#define CTX_sourceTransform TOKENHASH(7515321363744130)
#define CTX_source_over 	TOKENHASH(8690648756484770)
#define CTX_square 	TOKENHASH(511950058)
#define CTX_start 	TOKENHASH(47455658)
#define CTX_start_move 	TOKENHASH(2798358138985898)
#define CTX_start_group 	TOKENHASH(7836274863228782)
#define CTX_startGroup 	TOKENHASH(3812645199786240)
#define CTX_stroke 	TOKENHASH(498181546)
#define CTX_text_align 	TOKENHASH(3398277113762284)
#define CTX_textAlign 	TOKENHASH(3063795447820748)
#define CTX_texture 	TOKENHASH(16424292844)
#define CTX_text_baseline 	TOKENHASH(2589194334827348)
#define CTX_text_baseline 	TOKENHASH(2589194334827348)
#define CTX_textBaseline 	TOKENHASH(8381669925369340)
#define CTX_fillRect 	TOKENHASH(3811453831115472)
#define CTX_text 	TOKENHASH(1495532)
#define CTX_text_direction 	TOKENHASH(3614589880641524)
#define CTX_textDirection 	TOKENHASH(6790122975782654)
#define CTX_text_indent 	TOKENHASH(3633795456290560)
#define CTX_text_stroke 	TOKENHASH(8259523149811490)
#define CTX_strokeText 	TOKENHASH(8131451867629426)
#define CTX_strokeRect 	TOKENHASH(8165399289138988)
#define CTX_top 	TOKENHASH(37996)
#define CTX_transform 	TOKENHASH(34396827557164)
#define CTX_translate 	TOKENHASH(16912418348332)
#define CTX_verLineTo 	TOKENHASH(3629696413166220)
#define CTX_ver_line_to 	TOKENHASH(8345271542726354)
#define CTX_width 	TOKENHASH(22426354)
#define CTX_winding 	TOKENHASH(20424590066)
#define CTX_x 	TOKENHASH(52)
#define CTX_xor 	TOKENHASH(42100)
#define CTX_y 	TOKENHASH(54)
#define CTX_colorSpace 	TOKENHASH(3674150843793134)
#define CTX_userRGB 	TOKENHASH(59177128181102)
#define CTX_userCMYK 	TOKENHASH(3354734206905240)
#define CTX_deviceRGB 	TOKENHASH(7818727413767480)
#define CTX_deviceCMYK 	TOKENHASH(8943291245184210)
#define CTX_silver 	TOKENHASH(1358459626)
#define CTX_fuchsia 	TOKENHASH(7225355728)
#define CTX_gray 	TOKENHASH(1776914)
#define CTX_yellow 	TOKENHASH(1714319862)
#define CTX_white 	TOKENHASH(16145074)
#define CTX_maroon 	TOKENHASH(1110548702)
#define CTX_magenta 	TOKENHASH(7952877790)
#define CTX_blue 	TOKENHASH(506760)
#define CTX_green 	TOKENHASH(34028818)
#define CTX_red 	TOKENHASH(12776)
#define CTX_purple 	TOKENHASH(500344292)
#define CTX_olive 	TOKENHASH(16276386)
#define CTX_teal 	TOKENHASH(924140)
#define CTX_black 	TOKENHASH(27597704)
#define CTX_cyan 	TOKENHASH(1056458)
#define CTX_navy 	TOKENHASH(1818848)
#define CTX_lime 	TOKENHASH(490204)
#define CTX_aqua 	TOKENHASH(244934)
#define CTX_transparent 	TOKENHASH(3654078210101184)
#define CTX_currentColor 	TOKENHASH(7501877057638746)

#endif
#ifndef __CTX_LIBC_H
#define __CTX_LIBC_H

#if !__COSMOPOLITAN__
#include <stddef.h>
#endif

#if 0
static inline void
ctx_memset (void *ptr, uint8_t val, int length)
{
  uint8_t *p = (uint8_t *) ptr;
  for (int i = 0; i < length; i ++)
    { p[i] = val; }
}
#else
#define ctx_memset memset
#endif


static inline void ctx_strcpy (char *dst, const char *src)
{
  int i = 0;
  for (i = 0; src[i]; i++)
    { dst[i] = src[i]; }
  dst[i] = 0;
}

static inline char *_ctx_strchr (const char *haystack, char needle)
{
  const char *p = haystack;
  while (*p && *p != needle)
    {
      p++;
    }
  if (*p == needle)
    { return (char *) p; }
  return NULL;
}
static inline char *ctx_strchr (const char *haystack, char needle)
{
  return _ctx_strchr (haystack, needle);
}

static inline int ctx_strcmp (const char *a, const char *b)
{
  int i;
  for (i = 0; a[i] && b[i]; a++, b++)
    if (a[0] != b[0])
      { return 1; }
  if (a[0] == 0 && b[0] == 0) { return 0; }
  return 1;
}

static inline int ctx_strncmp (const char *a, const char *b, size_t n)
{
  size_t i;
  for (i = 0; a[i] && b[i] && i < n; a++, b++)
    if (a[0] != b[0])
      { return 1; }
  return 0;
}

static inline int ctx_strlen (const char *s)
{
  int len = 0;
  for (; *s; s++) { len++; }
  return len;
}

static inline char *ctx_strstr (const char *h, const char *n)
{
  int needle_len = ctx_strlen (n);
  if (n[0]==0)
    { return (char *) h; }
  while (h)
    {
      h = ctx_strchr (h, n[0]);
      if (!h)
        { return NULL; }
      if (!ctx_strncmp (h, n, needle_len) )
        { return (char *) h; }
      h++;
    }
  return NULL;
}

#endif

#if CTX_IMPLEMENTATION|CTX_COMPOSITE

#ifndef __CTX_INTERNAL_H
#define __CTX_INTERNAL_H

#if !__COSMOPOLITAN__
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#endif

#define CTX_BRANCH_HINTS  1

#if CTX_BRANCH_HINTS
#define CTX_LIKELY(x)      __builtin_expect(!!(x), 1)
#define CTX_UNLIKELY(x)    __builtin_expect(!!(x), 0)
#else
#define CTX_LIKELY(x)      (x)
#define CTX_UNLIKELY(x)    (x)
#endif

typedef struct _CtxRasterizer CtxRasterizer;
typedef struct _CtxGState     CtxGState;
typedef struct _CtxState      CtxState;

typedef struct _CtxSource CtxSource;


#define CTX_VALID_RGBA_U8     (1<<0)
#define CTX_VALID_RGBA_DEVICE (1<<1)
#if CTX_ENABLE_CM
#define CTX_VALID_RGBA        (1<<2)
#endif
#if CTX_ENABLE_CMYK
#define CTX_VALID_CMYKA       (1<<3)
#define CTX_VALID_DCMYKA      (1<<4)
#endif
#define CTX_VALID_GRAYA       (1<<5)
#define CTX_VALID_GRAYA_U8    (1<<6)
#define CTX_VALID_LABA        ((1<<7) | CTX_VALID_GRAYA)

struct _CtxColor
{
  uint8_t magic; // for colors used in keydb, set to a non valid start of
                 // string value.
  uint8_t rgba[4];
  uint8_t l_u8;
  uint8_t original; // the bitmask of the originally set color
  uint8_t valid;    // bitmask of which members contain valid
  // values, gets denser populated as more
  // formats are requested from a set color.
  float   device_red;
  float   device_green;
  float   device_blue;
  float   alpha;
  float   l;        // luminance and gray
#if CTX_ENABLE_LAB  // NYI
  float   a;
  float   b;
#endif
#if CTX_ENABLE_CMYK
  float   device_cyan;
  float   device_magenta;
  float   device_yellow;
  float   device_key;
  float   cyan;
  float   magenta;
  float   yellow;
  float   key;
#endif

#if CTX_ENABLE_CM
#if CTX_BABL
  const Babl *space; // gets copied from state when color is declared
#else
  void   *space; // gets copied from state when color is declared, 
#endif
  float   red;
  float   green;
  float   blue;
#endif
};

typedef struct _CtxGradientStop CtxGradientStop;

struct _CtxGradientStop
{
  float   pos;
  CtxColor color;
};


enum _CtxSourceType
{
  CTX_SOURCE_COLOR = 0,
  CTX_SOURCE_TEXTURE,
  CTX_SOURCE_LINEAR_GRADIENT,
  CTX_SOURCE_RADIAL_GRADIENT,
  CTX_SOURCE_INHERIT_FILL
};

typedef enum _CtxSourceType CtxSourceType;

typedef struct _CtxPixelFormatInfo CtxPixelFormatInfo;


struct _CtxBuffer
{
  void               *data;
  int                 width;
  int                 height;
  int                 stride;
  char               *eid;        // might be NULL, when not - should be unique for pixel contents
  int                 frame;      // last frame used in, everything > 3 can be removed,
                                  // as clients wont rely on it.
  CtxPixelFormatInfo *format;
  void (*free_func) (void *pixels, void *user_data);
  void               *user_data;

#if CTX_ENABLE_CM
#if CTX_BABL
  const Babl *space;
#else
  void       *space; 
#endif
#endif
#if 1
  CtxBuffer          *color_managed; /* only valid for one render target, cache
                                        for a specific space
                                        */
#endif
};

//void _ctx_user_to_device          (CtxState *state, float *x, float *y);
//void _ctx_user_to_device_distance (CtxState *state, float *x, float *y);

typedef struct _CtxGradient CtxGradient;
struct _CtxGradient
{
  CtxGradientStop stops[16];
  int n_stops;
};

struct _CtxSource
{
  int type;
  CtxMatrix  set_transform;
  CtxMatrix  transform;
  union
  {
    CtxColor color;
    struct
    {
      uint8_t rgba[4]; // shares data with set color
      uint8_t pad;
      CtxBuffer *buffer;
    } texture;
    struct
    {
      float x0;
      float y0;
      float x1;
      float y1;
      float dx;
      float dy;
      float start;
      float end;
      float length;
      float rdelta;
    } linear_gradient;
    struct
    {
      float x0;
      float y0;
      float r0;
      float x1;
      float y1;
      float r1;
      float rdelta;
    } radial_gradient;
  };
};

struct _CtxGState
{
  int           keydb_pos;
  int           stringpool_pos;

  CtxMatrix     transform;
  CtxSource     source_stroke;
  CtxSource     source_fill;
  float         global_alpha_f;
  uint8_t       global_alpha_u8;

  float         line_width;
  float         line_dash_offset;
  float         miter_limit;
  float         font_size;
#if CTX_ENABLE_SHADOW_BLUR
  float         shadow_blur;
  float         shadow_offset_x;
  float         shadow_offset_y;
#endif
  int           clipped:1;

  int16_t       clip_min_x;
  int16_t       clip_min_y;
  int16_t       clip_max_x;
  int16_t       clip_max_y;

#if CTX_ENABLE_CM
#if CTX_BABL
  const Babl   *device_space;
  const Babl   *texture_space;
  const Babl   *rgb_space;       
  const Babl   *cmyk_space;

  const Babl   *fish_rgbaf_user_to_device;
  const Babl   *fish_rgbaf_texture_to_device;
  const Babl   *fish_rgbaf_device_to_user;

#else
  void         *device_space;
  void         *texture_space;
  void         *rgb_space;       
  void         *cmyk_space;
  void         *fish_rgbaf_user_to_device; // dummy padding
  void         *fish_rgbaf_texture_to_device; // dummy padding
  void         *fish_rgbaf_device_to_user; // dummy padding
#endif
#endif
  CtxCompositingMode  compositing_mode; // bitfield refs lead to
  CtxBlend                  blend_mode; // non-vectorization

  float dashes[CTX_PARSER_MAX_ARGS];
  int n_dashes;

  CtxColorModel    color_model;
  /* bitfield-pack small state-parts */
  CtxLineCap          line_cap:2;
  CtxLineJoin        line_join:2;
  CtxFillRule        fill_rule:1;
  unsigned int image_smoothing:1;
  unsigned int            font:6;
  unsigned int            bold:1;
  unsigned int          italic:1;
};

typedef enum
{
  CTX_TRANSFORMATION_NONE         = 0,
  CTX_TRANSFORMATION_SCREEN_SPACE = 1,
  CTX_TRANSFORMATION_RELATIVE     = 2,
#if CTX_BITPACK
  CTX_TRANSFORMATION_BITPACK      = 4,
#endif
  CTX_TRANSFORMATION_STORE_CLEAR  = 16,
} CtxTransformation;

#define CTX_DRAWLIST_DOESNT_OWN_ENTRIES   64
#define CTX_DRAWLIST_EDGE_LIST            128
#define CTX_DRAWLIST_CURRENT_PATH         512
// BITPACK

struct _CtxDrawlist
{
  CtxEntry *entries;
  int       count;
  int       size;
  uint32_t  flags;
  int       bitpack_pos;  // stream is bitpacked up to this offset
};

#define CTX_MAX_KEYDB 64 // number of entries in keydb
                         // entries are "copy-on-change" between states

// the keydb consists of keys set to floating point values,
// that might also be interpreted as integers for enums.
//
// the hash
typedef struct _CtxKeyDbEntry CtxKeyDbEntry;
struct _CtxKeyDbEntry
{
  uint64_t key;
  float value;
  //union { float f[1]; uint8_t u8[4]; }value;
};

struct _CtxState
{
  int           has_moved:1;
  int           has_clipped:1;
  float         x;
  float         y;
  int           min_x;
  int           min_y;
  int           max_x;
  int           max_y;
  int16_t       gstate_no;
  CtxGState     gstate;
  CtxGState     gstate_stack[CTX_MAX_STATES];//at end, so can be made dynamic
#if CTX_GRADIENTS
  CtxGradient   gradient; /* we keep only one gradient,
                             this goes icky with multiple
                             restores - it should really be part of
                             graphics state..
                             XXX, with the stringpool gradients
                             can be stored there.
                           */
#endif
  CtxKeyDbEntry keydb[CTX_MAX_KEYDB];
  char          stringpool[CTX_STRINGPOOL_SIZE];
  int8_t        source; // used for the single-shifting to stroking
                // 0  = fill
                // 1  = start_stroke
                // 2  = in_stroke
                //
                //   if we're at in_stroke at start of a source definition
                //   we do filling
};


typedef struct _CtxFont       CtxFont;
typedef struct _CtxFontEngine CtxFontEngine;

struct _CtxFontEngine
{
#if CTX_FONTS_FROM_FILE
  int   (*load_file)   (const char *name, const char *path);
#endif
  int   (*load_memory) (const char *name, const void *data, int length);
  int   (*glyph)       (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke);
  float (*glyph_width) (CtxFont *font, Ctx *ctx, uint32_t unichar);
  float (*glyph_kern)  (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB);
};

struct _CtxFont
{
  CtxFontEngine *engine;
  const char *name;
  int type; // 0 ctx    1 stb    2 monobitmap
  union
  {
    struct
    {
      CtxEntry *data;
      int length;
      /* we've got ~110 bytes to fill to cover as
         much data as stbtt_fontinfo */
      //int16_t glyph_pos[26]; // for a..z
      int       glyphs; // number of glyphs
      uint32_t *index;
    } ctx;
    struct
    {
      char *path;
    } ctx_fs;
#if CTX_FONT_ENGINE_STB
    struct
    {
      stbtt_fontinfo ttf_info;
      int cache_index;
      uint32_t cache_unichar;
    } stb;
#endif
    struct { int start; int end; int gw; int gh; const uint8_t *data;} monobitmap;
  };
};


enum _CtxIteratorFlag
{
  CTX_ITERATOR_FLAT           = 0,
  CTX_ITERATOR_EXPAND_BITPACK = 2,
  CTX_ITERATOR_DEFAULTS       = CTX_ITERATOR_EXPAND_BITPACK
};
typedef enum _CtxIteratorFlag CtxIteratorFlag;


struct
  _CtxIterator
{
  int              pos;
  int              first_run;
  CtxDrawlist *drawlist;
  int              end_pos;
  int              flags;

  int              bitpack_pos;
  int              bitpack_length;     // if non 0 bitpack is active
  CtxEntry         bitpack_command[6]; // the command returned to the
  // user if unpacking is needed.
};
#define CTX_MAX_DEVICES 16
#define CTX_MAX_KEYBINDINGS         256

#if CTX_EVENTS 

// include list implementation - since it already is a header+inline online
// implementation?

typedef struct CtxItemCb {
  CtxEventType types;
  CtxCb        cb;
  void*        data1;
  void*        data2;

  void (*finalize) (void *data1, void *data2, void *finalize_data);
  void  *finalize_data;

} CtxItemCb;


#define CTX_MAX_CBS              128

typedef struct CtxItem {
  CtxMatrix inv_matrix;  /* for event coordinate transforms */

  /* bounding box */
  float          x0;
  float          y0;
  float          x1;
  float          y1;

  void *path;
  double          path_hash;

  CtxCursor       cursor; /* if 0 then UNSET and no cursor change is requested
                           */

  CtxEventType   types;   /* all cb's ored together */
  CtxItemCb cb[CTX_MAX_CBS];
  int       cb_count;
  int       ref_count;
} CtxItem;


typedef struct _CtxEvents CtxEvents;
struct _CtxEvents
{
  int             frozen;
  int             fullscreen;
  CtxList        *grabs; /* could split the grabs per device in the same way,
                            to make dispatch overhead smaller,. probably
                            not much to win though. */
  CtxItem         *prev[CTX_MAX_DEVICES];
  float            pointer_x[CTX_MAX_DEVICES];
  float            pointer_y[CTX_MAX_DEVICES];
  unsigned char    pointer_down[CTX_MAX_DEVICES];
  CtxEvent         drag_event[CTX_MAX_DEVICES];
  CtxList         *idles;
  CtxList         *events; // for ctx_get_event
  int              ctx_get_event_enabled;
  int              idle_id;
  CtxBinding       bindings[CTX_MAX_KEYBINDINGS]; /*< better as list, uses no mem if unused */
  int              n_bindings;
  int              width;
  int              height;
  CtxList         *items;
  CtxItem         *last_item;
  CtxModifierState modifier_state;
  int              tap_delay_min;
  int              tap_delay_max;
  int              tap_delay_hold;
  double           tap_hysteresis;
};


#endif

typedef struct _CtxEidInfo
{
  char *eid;
  int   frame;
  int   width;
  int   height;
} CtxEidInfo;

struct _Ctx
{
  CtxImplementation *renderer;
  CtxDrawlist        drawlist;
  int                transformation;
  CtxBuffer          texture[CTX_MAX_TEXTURES];
  Ctx               *texture_cache;
  CtxList           *eid_db;
  int                rev;
  void              *backend;
  CtxState           state;        /**/
  int                frame; /* used for texture lifetime */
#if CTX_EVENTS 
  CtxCursor          cursor;
  int                quit;
  int                dirty;
  CtxEvents          events;
  int                mouse_fd;
  int                mouse_x;
  int                mouse_y;
#endif
#if CTX_CURRENT_PATH
  CtxDrawlist    current_path; // possibly transformed coordinates !
  CtxIterator        current_path_iterator;
#endif
};


static void ctx_process (Ctx *ctx, CtxEntry *entry);
CtxBuffer *ctx_buffer_new (int width, int height,
                           CtxPixelFormat pixel_format);
void ctx_buffer_free (CtxBuffer *buffer);

void
ctx_state_gradient_clear_stops (CtxState *state);

static inline void ctx_interpret_style         (CtxState *state, CtxEntry *entry, void *data);
static inline void ctx_interpret_transforms    (CtxState *state, CtxEntry *entry, void *data);
static inline void ctx_interpret_pos           (CtxState *state, CtxEntry *entry, void *data);
static inline void ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data);

struct _CtxInternalFsEntry
{
  char *path;
  int   length;
  char *data;
};

struct _CtxPixelFormatInfo
{
  CtxPixelFormat pixel_format;
  uint8_t        components:4; /* number of components */
  uint8_t        bpp; /* bits  per pixel - for doing offset computations
                         along with rowstride found elsewhere, if 0 it indicates
                         1/8  */
  uint8_t        ebpp; /*effective bytes per pixel - for doing offset
                         computations, for formats that get converted, the
                         ebpp of the working space applied */
  uint8_t        dither_red_blue;
  uint8_t        dither_green;
  CtxPixelFormat composite_format;

  void         (*to_comp) (CtxRasterizer *r,
                           int x, const void * __restrict__ src, uint8_t * __restrict__ comp, int count);
  void         (*from_comp) (CtxRasterizer *r,
                             int x, const uint8_t * __restrict__ comp, void *__restrict__ dst, int count);
  void         (*apply_coverage) (CtxRasterizer *r, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x, uint8_t *coverage,
                          int count);
  void         (*setup) (CtxRasterizer *r);
};


static inline void
_ctx_user_to_device (CtxState *state, float *x, float *y);
static void
_ctx_user_to_device_distance (CtxState *state, float *x, float *y);
static void ctx_state_init (CtxState *state);
static inline void
ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data);
static inline void
ctx_drawlist_deinit (CtxDrawlist *drawlist);

CtxPixelFormatInfo *
ctx_pixel_format_info (CtxPixelFormat format);


int ctx_utf8_len (const unsigned char first_byte);
const char *ctx_utf8_skip (const char *s, int utf8_length);
int ctx_utf8_strlen (const char *s);
int
ctx_unichar_to_utf8 (uint32_t  ch,
                     uint8_t  *dest);

uint32_t
ctx_utf8_to_unichar (const char *input);


typedef struct _CtxHasher CtxHasher;

typedef void (*CtxFragment) (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy);

#define CTX_MAX_GAUSSIAN_KERNEL_DIM    512

struct _CtxShapeEntry
{
  uint32_t hash;
  uint16_t width;
  uint16_t height;
  int      last_frame; // xxx
  uint32_t uses;  // instrumented for longer keep-alive
  uint8_t  data[];
};

typedef struct _CtxShapeEntry CtxShapeEntry;


struct _CtxShapeCache
{
  CtxShapeEntry *entries[CTX_SHAPE_CACHE_ENTRIES];
  long size;
};

typedef struct _CtxShapeCache CtxShapeCache;

typedef enum {
   CTX_COV_PATH_FALLBACK =0,
   CTX_COV_PATH_OVER,
   CTX_COV_PATH_COPY,
   CTX_COV_PATH_COPY_FRAGMENT,
   CTX_COV_PATH_OVER_FRAGMENT
} CtxCovPath;

struct _CtxRasterizer
{
  CtxImplementation vfuncs;
  /* these should be initialized and used as the bounds for rendering into the
     buffer as well XXX: not yet in use, and when in use will only be
     correct for axis aligned clips - proper rasterization of a clipping path
     would be yet another refinement on top.
   */


#define CTX_COMPOSITE_ARGUMENTS CtxRasterizer *rasterizer, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x0, uint8_t * __restrict__ coverage, int count
  void (*comp_op)(CTX_COMPOSITE_ARGUMENTS);
  CtxFragment fragment;
  Ctx       *ctx;
  CtxState  *state;
  void      *buf;
  int fast_aa;
  CtxCovPath  comp;
  float      x;  // < redundant? use state instead?
  float      y;

  unsigned int aa;          // level of vertical aa
  int prev_active_edges;
  int active_edges;
  int pending_edges;
  int ending_edges;
  int edge_pos;         // where we're at in iterating all edges
  unsigned int needs_aa3; // count of how many edges implies antialiasing
  unsigned int needs_aa5; // count of how many edges implies antialiasing
  unsigned int needs_aa15; // count of how many edges implies antialiasing
  int        horizontal_edges;

  int scanline;
  int        scan_min;
  int        scan_max;
  int        col_min;
  int        col_max;


  int        inner_x;
  int        inner_y;

  float      first_x;
  float      first_y;
  int        uses_transforms;
  int        has_shape:2;
  int        has_prev:2;
  int        preserve:1;

  int16_t    blit_x;
  int16_t    blit_y;
  int16_t    blit_width;
  int16_t    blit_height;
  int16_t    blit_stride;

  CtxPixelFormatInfo *format;
  Ctx       *texture_source; /* normally same as ctx */

  int edges[CTX_MAX_EDGES]; // integer position in edge array
  CtxDrawlist edge_list;





#if CTX_ENABLE_SHADOW_BLUR
  int in_shadow;
#endif
  int in_text;
  int shadow_x;
  int shadow_y;

  int swap_red_green;
  uint8_t             color[4*5];

  int clip_rectangle;

#if CTX_ENABLE_CLIP
  CtxBuffer *clip_buffer;
#endif


#if CTX_COMPOSITING_GROUPS
  void      *saved_buf; // when group redirected
  CtxBuffer *group[CTX_GROUP_MAX];
#endif
#if CTX_ENABLE_SHADOW_BLUR
  float      kernel[CTX_MAX_GAUSSIAN_KERNEL_DIM];
#endif

#if CTX_SHAPE_CACHE
  CtxShapeCache shape_cache;
#endif
#if CTX_BRAILLE_TEXT
  int        term_glyphs:1; // store appropriate glyphs for redisplay
  CtxList   *glyphs;
#endif
};

struct _CtxSHA1 {
    uint64_t length;
    uint32_t state[5], curlen;
    unsigned char buf[64];
};


struct _CtxHasher
{
  CtxRasterizer rasterizer;
  int           cols;
  int           rows;
  uint8_t      *hashes;
  CtxSHA1       sha1_fill; 
  CtxSHA1       sha1_stroke;
};

#if CTX_RASTERIZER
void ctx_rasterizer_deinit (CtxRasterizer *rasterizer);
#endif

#if CTX_EVENTS
extern int ctx_native_events;

#if CTX_SDL
extern int ctx_sdl_events;
int ctx_sdl_consume_events (Ctx *ctx);
#endif

#if CTX_FB
extern int ctx_fb_events;
int ctx_fb_consume_events (Ctx *ctx);
#endif


int ctx_nct_consume_events (Ctx *ctx);
int ctx_ctx_consume_events (Ctx *ctx);

#endif

enum {
  NC_MOUSE_NONE  = 0,
  NC_MOUSE_PRESS = 1,  /* "mouse-pressed", "mouse-released" */
  NC_MOUSE_DRAG  = 2,  /* + "mouse-drag"   (motion with pressed button) */
  NC_MOUSE_ALL   = 3   /* + "mouse-motion" (also delivered for release) */
};
void _ctx_mouse (Ctx *term, int mode);
void nc_at_exit (void);

int ctx_terminal_width  (void);
int ctx_terminal_height (void);
int ctx_terminal_cols   (void);
int ctx_terminal_rows   (void);
extern int ctx_frame_ack;

int ctx_nct_consume_events (Ctx *ctx);

typedef struct _CtxCtx CtxCtx;
struct _CtxCtx
{
   void (*render) (void *ctxctx, CtxCommand *command);
   void (*reset)  (void *ctxvtx);
   void (*flush)  (void *ctxctx);
   char *(*get_clipboard) (void *ctxctx);
   void (*set_clipboard) (void *ctxctx, const char *text);
   void (*free)   (void *ctxctx);
   Ctx *ctx;
   int  width;
   int  height;
   int  cols;
   int  rows;
   int  was_down;
};


extern int _ctx_max_threads;
extern int _ctx_enable_hash_cache;
void
ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len);
const char *
ctx_get (Ctx *ctx, const char *key);

int ctx_renderer_is_term (Ctx *ctx);
Ctx *ctx_new_ctx (int width, int height);
Ctx *ctx_new_fb (int width, int height, int drm);
Ctx *ctx_new_sdl (int width, int height);
Ctx *ctx_new_term (int width, int height);
Ctx *ctx_new_termimg (int width, int height);

int ctx_resolve_font (const char *name);
extern float ctx_u8_float[256];
#define ctx_u8_to_float(val_u8) ctx_u8_float[((uint8_t)(val_u8))]
//#define ctx_u8_to_float(val_u8) (val_u8/255.0f)
//
//


static uint8_t ctx_float_to_u8 (float val_f)
{
  return val_f < 0.0f ? 0 : val_f > 1.0f ? 0xff : 0xff * val_f +  0.5f;
#if 0
  int val_i = val_f * 255.999f;
  if (val_i < 0) { return 0; }
  else if (val_i > 255) { return 255; }
  return val_i;
#endif
}


#define CTX_CSS_LUMINANCE_RED   0.3f
#define CTX_CSS_LUMINANCE_GREEN 0.59f
#define CTX_CSS_LUMINANCE_BLUE  0.11f

/* works on both float and uint8_t */
#define CTX_CSS_RGB_TO_LUMINANCE(rgb)  (\
  (rgb[0]) * CTX_CSS_LUMINANCE_RED + \
  (rgb[1]) * CTX_CSS_LUMINANCE_GREEN +\
  (rgb[2]) * CTX_CSS_LUMINANCE_BLUE)

const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y);
const char *ctx_native_get_event (Ctx *n, int timeoutms);
void
ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out);
void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out);
float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb);
void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out);
void ctx_rgb_to_cmyk (float r, float g, float b,
              float *c_out, float *m_out, float *y_out, float *k_out);
uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb);
#if CTX_ENABLE_CMYK
void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out);
#endif
static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a);
static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a);
void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out);
static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a);
static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a);
static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha);

int ctx_color_model_get_components (CtxColorModel model);

static void ctx_state_set (CtxState *state, uint64_t key, float value);
static void
ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f);
static void ctx_font_setup ();
static float ctx_state_get (CtxState *state, uint64_t hash);

#if CTX_RASTERIZER

static void
ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y);
static void
ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y);

static void
ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y);
static void
ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y);
static void
ctx_rasterizer_curve_to (CtxRasterizer *rasterizer,
                         float x0, float y0,
                         float x1, float y1,
                         float x2, float y2);
static void
ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer,
                         float x0, float y0,
                         float x1, float y1,
                         float x2, float y2);

static void
ctx_rasterizer_reset (CtxRasterizer *rasterizer);
static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer);
static void
ctx_rasterizer_arc (CtxRasterizer *rasterizer,
                    float        x,
                    float        y,
                    float        radius,
                    float        start_angle,
                    float        end_angle,
                    int          anticlockwise);

static void
ctx_rasterizer_quad_to (CtxRasterizer *rasterizer,
                        float        cx,
                        float        cy,
                        float        x,
                        float        y);

static void
ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer,
                        float        cx,
                        float        cy,
                        float        x,
                        float        y);

static void
ctx_rasterizer_rectangle (CtxRasterizer *rasterizer,
                          float x,
                          float y,
                          float width,
                          float height);

static void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer);
static void ctx_rasterizer_clip (CtxRasterizer *rasterizer);
static void
ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name);

static void
ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba);
static void
ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer,
                          uint16_t x,
                          uint16_t y,
                          uint8_t r,
                          uint8_t g,
                          uint8_t b,
                          uint8_t a);
static void
ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius);

#endif

#if CTX_ENABLE_CM // XXX to be moved to ctx.h
void
ctx_set_drgb_space (Ctx *ctx, int device_space);
void
ctx_set_dcmyk_space (Ctx *ctx, int device_space);
void
ctx_rgb_space (Ctx *ctx, int device_space);
void
ctx_set_cmyk_space (Ctx *ctx, int device_space);
#endif

#endif

CtxRasterizer *
ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias);


CTX_INLINE static uint8_t ctx_lerp_u8 (uint8_t v0, uint8_t v1, uint8_t dx)
{
#if 0
  return v0 + ((v1-v0) * dx)/255;
#else
  return ( ( ( ( (v0) <<8) + (dx) * ( (v1) - (v0) ) ) ) >>8);
#endif
}

CTX_INLINE static uint32_t ctx_lerp_RGBA8 (const uint32_t v0, const uint32_t v1, const uint8_t dx)
{
#if 0
  char bv0[4];
  char bv1[4];
  char res[4];
  memcpy (&bv0[0], &v0, 4);
  memcpy (&bv1[0], &v1, 4);
  for (int c = 0; c < 4; c++)
    res [c] = ctx_lerp_u8 (bv0[c], bv1[c], dx);
  return ((uint32_t*)(&res[0]))[0];
#else
  const uint32_t cov = dx;
  const uint32_t si_ga = (v1 & 0xff00ff00);
  const uint32_t si_rb = v1 & 0x00ff00ff;
  const uint32_t di_rb = v0 & 0x00ff00ff;
  const uint32_t d_rb = si_rb - di_rb;
  const uint32_t di_ga = v0 & 0xff00ff00;
  const uint32_t d_ga = (si_ga >>8) - (di_ga>>8);
  return
     (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) |
     (((di_ga + (0xff00ff + d_ga * cov))      & 0xff00ff00));

#endif
}

CTX_INLINE static void ctx_lerp_RGBA8_split (const uint32_t v0, const uint32_t v1, const uint8_t dx,
                                             uint32_t *dest_ga, uint32_t *dest_rb)
{
  const uint32_t cov = dx;
  const uint32_t si_ga = (v1 & 0xff00ff00);
  const uint32_t si_rb = v1 & 0x00ff00ff;
  const uint32_t di_rb = v0 & 0x00ff00ff;
  const uint32_t di_ga = v0 & 0xff00ff00;
  const uint32_t d_rb = si_rb - di_rb;
  const uint32_t d_ga = (si_ga >>8) - (di_ga >> 8);
  *dest_rb = (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff));
  *dest_ga = (((di_ga + (0xff00ff + d_ga * cov))      & 0xff00ff00));
}

CTX_INLINE static uint32_t ctx_lerp_RGBA8_merge (uint32_t di_ga, uint32_t di_rb, uint32_t si_ga, uint32_t si_rb, const uint8_t dx)
{
  const uint32_t cov = dx;
  const uint32_t d_rb = si_rb - di_rb;
  const uint32_t d_ga = (si_ga - di_ga) >> 8;
  return
     (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff))  |
      ((di_ga + ((0xff00ff + d_ga * cov)      & 0xff00ff00)));
}

CTX_INLINE static uint32_t ctx_lerp_RGBA8_2 (const uint32_t v0, uint32_t si_ga, uint32_t si_rb, const uint8_t dx)
{
  const uint32_t cov = dx;
  const uint32_t di_ga = ( v0 & 0xff00ff00);
  const uint32_t di_rb = v0 & 0x00ff00ff;
  const uint32_t d_rb = si_rb - di_rb;
  const uint32_t d_ga = si_ga - (di_ga>>8);
  return
     (((di_rb + ((0xff00ff + d_rb * cov)>>8)) & 0x00ff00ff)) |
     (((di_ga + (0xff00ff + d_ga * cov))      & 0xff00ff00));
}



CTX_INLINE static float
ctx_lerpf (float v0, float v1, float dx)
{
  return v0 + (v1-v0) * dx;
}


#ifndef CTX_MIN
#define CTX_MIN(a,b)  (((a)<(b))?(a):(b))
#endif
#ifndef CTX_MAX
#define CTX_MAX(a,b)  (((a)>(b))?(a):(b))
#endif

static inline void *ctx_calloc (size_t size, size_t count);

void ctx_screenshot (Ctx *ctx, const char *output_path);


CtxSHA1 *ctx_sha1_new (void);
void ctx_sha1_free (CtxSHA1 *sha1);
int ctx_sha1_process(CtxSHA1 *sha1, const unsigned char * msg, unsigned long len);
int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out);

void _ctx_texture_lock (void);
void _ctx_texture_unlock (void);
uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry);
void ctx_buffer_pixels_free (void *pixels, void *userdata);

/*ctx_texture_init:
 * return value: eid, as passed in or if NULL generated by hashing pixels and width/height
 * XXX  this is low-level and not to be used directly use define_texture instead.  XXX
 */
const char *ctx_texture_init (
                      Ctx        *ctx,
                      const char *eid,
                      int         width,
                      int         height,
                      int         stride,
                      CtxPixelFormat format,
                      void       *space,
                      uint8_t    *pixels,
                      void (*freefunc) (void *pixels, void *user_data),
                      void *user_data);

#if CTX_TILED
#if !__COSMOPOLITAN__
#include <threads.h>
#endif
#endif
typedef struct _CtxTiled CtxTiled;

struct _CtxTiled
{
   void (*render)    (void *term, CtxCommand *command);
   void (*reset)     (void *term);
   void (*flush)     (void *term);
   char *(*get_clipboard) (void *ctxctx);
   void (*set_clipboard) (void *ctxctx, const char *text);
   void (*free)      (void *term);
   Ctx          *ctx;
   int           width;
   int           height;
   int           cols;
   int           rows;
   int           was_down;
   uint8_t      *pixels;
   Ctx          *ctx_copy;
   Ctx          *host[CTX_MAX_THREADS];
   CtxAntialias  antialias;
   int           quit;
#if CTX_TILED
   _Atomic int   thread_quit;
#endif
   int           shown_frame;
   int           render_frame;
   int           rendered_frame[CTX_MAX_THREADS];
   int           frame;
   int       min_col; // hasher cols and rows
   int       min_row;
   int       max_col;
   int       max_row;
   uint8_t  hashes[CTX_HASH_ROWS * CTX_HASH_COLS *  20];
   int8_t    tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is
                                                           // responsible for a tile
                                                           //

   int           pointer_down[3];

   CtxCursor     shown_cursor;
#if CTX_TILED
   cnd_t  cond;
   mtx_t  mtx;
#endif
};

static void
_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer,
                                      CtxBuffer     *buffer);

#endif


uint64_t    thash              (const char *utf8);
const char *thash_decode       (uint64_t hash);
uint64_t    ctx_strhash        (const char *str);
CtxColor   *ctx_color_new      (void);
int         ctx_get_int        (Ctx *ctx, uint64_t hash);
int         ctx_get_is_set     (Ctx *ctx, uint64_t hash);
Ctx        *ctx_new_for_buffer (CtxBuffer *buffer);

#if CTX_IMPLEMENTATION

#define SHA1_IMPLEMENTATION
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
 *
 * LibTomCrypt is a library that provides various cryptographic
 * algorithms in a highly modular and flexible manner.
 *
 * The library is free for all purposes without any express
 * guarantee it works.
 *
 * Tom St Denis, tomstdenis@gmail.com, http://libtom.org
 *
 * The plain ANSIC sha1 functionality has been extracted from libtomcrypt,
 * and is included directly in the sources. /Øyvind K. - since libtomcrypt
 * is public domain the adaptations done here to make the sha1 self contained
 * also is public domain.
 */
#ifndef __SHA1_H
#define __SHA1_H
#if !__COSMOPOLITAN__
#include <inttypes.h>
#endif


int ctx_sha1_init(CtxSHA1 * sha1);
CtxSHA1 *ctx_sha1_new (void)
{
  CtxSHA1 *state = (CtxSHA1*)calloc (sizeof (CtxSHA1), 1);
  ctx_sha1_init (state);
  return state;
}
void ctx_sha1_free (CtxSHA1 *sha1)
{
  free (sha1);
}

#if 0
          CtxSHA1 sha1;
          ctx_sha1_init (&sha1);
          ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
#endif

#ifdef SHA1_FF0
#undef SHA1_FF0
#endif
#ifdef SHA1_FF1
#undef SHA1_FF1
#endif

#ifdef SHA1_IMPLEMENTATION
#if !__COSMOPOLITAN__
#include <stdlib.h>
#include <string.h>
#endif

#define STORE64H(x,                                                             y)                                                                     \
   { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned                char)(((x)>>48)&255);     \
     (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned                char)(((x)>>32)&255);     \
     (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned                char)(((x)>>16)&255);     \
     (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); }

#define STORE32H(x,                                                             y)                                                                     \
     { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned              char)(((x)>>16)&255);   \
       (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned               char)((x)&255); }

#define LOAD32H(x, y)                            \
     { x = ((unsigned long)((y)[0] & 255)<<24) | \
           ((unsigned long)((y)[1] & 255)<<16) | \
           ((unsigned long)((y)[2] & 255)<<8)  | \
           ((unsigned long)((y)[3] & 255)); }

/* rotates the hard way */
#define ROL(x, y)  ((((unsigned long)(x)<<(unsigned long)((y)&31)) | (((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL)
#define ROLc(x, y) ROL(x,y)

#define CRYPT_OK     0
#define CRYPT_ERROR  1
#define CRYPT_NOP    2

#ifndef MAX
   #define MAX(x, y) ( ((x)>(y))?(x):(y) )
#endif
#ifndef MIN
   #define MIN(x, y) ( ((x)<(y))?(x):(y) )
#endif

/* a simple macro for making hash "process" functions */
#define HASH_PROCESS(func_name, compress_name, state_var, block_size)               \
int func_name (CtxSHA1 *sha1, const unsigned char *in, unsigned long inlen)      \
{                                                                                   \
    unsigned long n;                                                                \
    int           err;                                                              \
    assert (sha1 != NULL);                                                          \
    assert (in != NULL);                                                            \
    if (sha1->curlen > sizeof(sha1->buf)) {                                         \
       return -1;                                                                   \
    }                                                                               \
    while (inlen > 0) {                                                             \
        if (sha1->curlen == 0 && inlen >= block_size) {                             \
           if ((err = compress_name (sha1, (unsigned char *)in)) != CRYPT_OK) {     \
              return err;                                                           \
           }                                                                        \
           sha1->length += block_size * 8;                                          \
           in             += block_size;                                            \
           inlen          -= block_size;                                            \
        } else {                                                                    \
           n = MIN(inlen, (block_size - sha1->curlen));                             \
           memcpy(sha1->buf + sha1->curlen, in, (size_t)n);                         \
           sha1->curlen += n;                                                       \
           in             += n;                                                     \
           inlen          -= n;                                                     \
           if (sha1->curlen == block_size) {                                        \
              if ((err = compress_name (sha1, sha1->buf)) != CRYPT_OK) {            \
                 return err;                                                        \
              }                                                                     \
              sha1->length += 8*block_size;                                         \
              sha1->curlen = 0;                                                     \
           }                                                                        \
       }                                                                            \
    }                                                                               \
    return CRYPT_OK;                                                                \
}

/**********************/

#define F0(x,y,z)  (z ^ (x & (y ^ z)))
#define F1(x,y,z)  (x ^ y ^ z)
#define F2(x,y,z)  ((x & y) | (z & (x | y)))
#define F3(x,y,z)  (x ^ y ^ z)

static int  ctx_sha1_compress(CtxSHA1 *sha1, unsigned char *buf)
{
    uint32_t a,b,c,d,e,W[80],i;

    /* copy the state into 512-bits into W[0..15] */
    for (i = 0; i < 16; i++) {
        LOAD32H(W[i], buf + (4*i));
    }

    /* copy state */
    a = sha1->state[0];
    b = sha1->state[1];
    c = sha1->state[2];
    d = sha1->state[3];
    e = sha1->state[4];

    /* expand it */
    for (i = 16; i < 80; i++) {
        W[i] = ROL(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1); 
    }

    /* compress */
    /* round one */
    #define SHA1_FF0(a,b,c,d,e,i) e = (ROLc(a, 5) + F0(b,c,d) + e + W[i] + 0x5a827999UL); b = ROLc(b, 30);
    #define SHA1_FF1(a,b,c,d,e,i) e = (ROLc(a, 5) + F1(b,c,d) + e + W[i] + 0x6ed9eba1UL); b = ROLc(b, 30);
    #define SHA1_FF2(a,b,c,d,e,i) e = (ROLc(a, 5) + F2(b,c,d) + e + W[i] + 0x8f1bbcdcUL); b = ROLc(b, 30);
    #define SHA1_FF3(a,b,c,d,e,i) e = (ROLc(a, 5) + F3(b,c,d) + e + W[i] + 0xca62c1d6UL); b = ROLc(b, 30);
 
    for (i = 0; i < 20; ) {
       SHA1_FF0(a,b,c,d,e,i++);
       SHA1_FF0(e,a,b,c,d,i++);
       SHA1_FF0(d,e,a,b,c,i++);
       SHA1_FF0(c,d,e,a,b,i++);
       SHA1_FF0(b,c,d,e,a,i++);
    }

    /* round two */
    for (; i < 40; )  { 
       SHA1_FF1(a,b,c,d,e,i++);
       SHA1_FF1(e,a,b,c,d,i++);
       SHA1_FF1(d,e,a,b,c,i++);
       SHA1_FF1(c,d,e,a,b,i++);
       SHA1_FF1(b,c,d,e,a,i++);
    }

    /* round three */
    for (; i < 60; )  { 
       SHA1_FF2(a,b,c,d,e,i++);
       SHA1_FF2(e,a,b,c,d,i++);
       SHA1_FF2(d,e,a,b,c,i++);
       SHA1_FF2(c,d,e,a,b,i++);
       SHA1_FF2(b,c,d,e,a,i++);
    }

    /* round four */
    for (; i < 80; )  { 
       SHA1_FF3(a,b,c,d,e,i++);
       SHA1_FF3(e,a,b,c,d,i++);
       SHA1_FF3(d,e,a,b,c,i++);
       SHA1_FF3(c,d,e,a,b,i++);
       SHA1_FF3(b,c,d,e,a,i++);
    }

    #undef SHA1_FF0
    #undef SHA1_FF1
    #undef SHA1_FF2
    #undef SHA1_FF3

    /* store */
    sha1->state[0] = sha1->state[0] + a;
    sha1->state[1] = sha1->state[1] + b;
    sha1->state[2] = sha1->state[2] + c;
    sha1->state[3] = sha1->state[3] + d;
    sha1->state[4] = sha1->state[4] + e;

    return CRYPT_OK;
}

/**
   Initialize the hash state
   @param md   The hash state you wish to initialize
   @return CRYPT_OK if successful
*/
int ctx_sha1_init(CtxSHA1 * sha1)
{
   assert(sha1 != NULL);
   sha1->state[0] = 0x67452301UL;
   sha1->state[1] = 0xefcdab89UL;
   sha1->state[2] = 0x98badcfeUL;
   sha1->state[3] = 0x10325476UL;
   sha1->state[4] = 0xc3d2e1f0UL;
   sha1->curlen = 0;
   sha1->length = 0;
   return CRYPT_OK;
}

/**
   Process a block of memory though the hash
   @param md     The hash state
   @param in     The data to hash
   @param inlen  The length of the data (octets)
   @return CRYPT_OK if successful
*/
HASH_PROCESS(ctx_sha1_process, ctx_sha1_compress, sha1, 64)

/**
   Terminate the hash to get the digest
   @param md  The hash state
   @param out [out] The destination of the hash (20 bytes)
   @return CRYPT_OK if successful
*/
int ctx_sha1_done(CtxSHA1 * sha1, unsigned char *out)
{
    int i;

    assert(sha1 != NULL);
    assert(out != NULL);

    if (sha1->curlen >= sizeof(sha1->buf)) {
       return -1;
    }

    /* increase the length of the message */
    sha1->length += sha1->curlen * 8;

    /* append the '1' bit */
    sha1->buf[sha1->curlen++] = (unsigned char)0x80;

    /* if the length is currently above 56 bytes we append zeros
     * then compress.  Then we can fall back to padding zeros and length
     * encoding like normal.
     */
    if (sha1->curlen > 56) {
        while (sha1->curlen < 64) {
            sha1->buf[sha1->curlen++] = (unsigned char)0;
        }
        ctx_sha1_compress(sha1, sha1->buf);
        sha1->curlen = 0;
    }

    /* pad upto 56 bytes of zeroes */
    while (sha1->curlen < 56) {
        sha1->buf[sha1->curlen++] = (unsigned char)0;
    }

    /* store length */
    STORE64H(sha1->length, sha1->buf+56);
    ctx_sha1_compress(sha1, sha1->buf);

    /* copy output */
    for (i = 0; i < 5; i++) {
        STORE32H(sha1->state[i], out+(4*i));
    }
    return CRYPT_OK;
}
#endif

#endif
#endif
#ifndef CTX_AUDIO_H
#define CTX_AUDIO_H

#if !__COSMOPOLITAN__
#include <stdint.h>
#endif

/* This enum should be kept in sync with the corresponding mmm enum.
 */
typedef enum {
  CTX_f32,
  CTX_f32S,
  CTX_s16,
  CTX_s16S
} CtxPCM;

void   ctx_pcm_set_format        (Ctx *ctx, CtxPCM format);
CtxPCM ctx_pcm_get_format        (Ctx *ctx);
int    ctx_pcm_get_sample_rate   (Ctx *ctx);
void   ctx_pcm_set_sample_rate   (Ctx *ctx, int sample_rate);
int    ctx_pcm_get_frame_chunk   (Ctx *ctx);
int    ctx_pcm_get_queued        (Ctx *ctx);
float  ctx_pcm_get_queued_length (Ctx *ctx);
int    ctx_pcm_queue             (Ctx *ctx, const int8_t *data, int frames);

#endif

#if CTX_IMPLEMENTATION
#if CTX_AUDIO

//#include <string.h>
//#include "ctx-internal.h"
//#include "mmm.h"

#if !__COSMOPOLITAN__

#include <pthread.h>
#if CTX_ALSA_AUDIO
#include <alsa/asoundlib.h>
#endif
#include <alloca.h>

#endif

#define DESIRED_PERIOD_SIZE 1000

int ctx_pcm_bytes_per_frame (CtxPCM format)
{
  switch (format)
  {
    case CTX_f32:  return 4;
    case CTX_f32S: return 8;
    case CTX_s16:  return 2;
    case CTX_s16S: return 4;
    default: return 1;
  }
}

static float    ctx_host_freq     = 48000;
static CtxPCM   ctx_host_format   = CTX_s16S;
static float    client_freq   = 48000;
static CtxPCM   ctx_client_format = CTX_s16S;
static int      ctx_pcm_queued    = 0;
static int      ctx_pcm_cur_left  = 0;
static CtxList *ctx_pcm_list;                 /* data is a blob a 32bit uint first, followed by pcm-data */


//static long int ctx_pcm_queued_ticks = 0;  /*  the number of ticks into the future
  //                                      *  we've queued audio for
                                       


int
ctx_pcm_channels (CtxPCM format)
{
  switch (format)
  {
    case CTX_s16:
    case CTX_f32:
      return 1;
    case CTX_s16S:
    case CTX_f32S:
      return 2;
  }
  return 0;
}

/* todo: only start audio thread on first write - enabling dynamic choice
 * of sample-rate? or is it better to keep to opening 48000 as a standard
 * and do better internal resampling for others?
 */

#if CTX_ALSA_AUDIO
static snd_pcm_t *alsa_open (char *dev, int rate, int channels)
{
   snd_pcm_hw_params_t *hwp;
   snd_pcm_sw_params_t *swp;
   snd_pcm_t *h;
   int r;
   int dir;
   snd_pcm_uframes_t period_size_min;
   snd_pcm_uframes_t period_size_max;
   snd_pcm_uframes_t period_size;
   snd_pcm_uframes_t buffer_size;

   if ((r = snd_pcm_open(&h, dev, SND_PCM_STREAM_PLAYBACK, 0) < 0))
           return NULL;

   hwp = alloca(snd_pcm_hw_params_sizeof());
   memset(hwp, 0, snd_pcm_hw_params_sizeof());
   snd_pcm_hw_params_any(h, hwp);

   snd_pcm_hw_params_set_access(h, hwp, SND_PCM_ACCESS_RW_INTERLEAVED);
   snd_pcm_hw_params_set_format(h, hwp, SND_PCM_FORMAT_S16_LE);
   snd_pcm_hw_params_set_rate(h, hwp, rate, 0);
   snd_pcm_hw_params_set_channels(h, hwp, channels);
   dir = 0;
   snd_pcm_hw_params_get_period_size_min(hwp, &period_size_min, &dir);
   dir = 0;
   snd_pcm_hw_params_get_period_size_max(hwp, &period_size_max, &dir);

   period_size = DESIRED_PERIOD_SIZE;

   dir = 0;
   r = snd_pcm_hw_params_set_period_size_near(h, hwp, &period_size, &dir);
   r = snd_pcm_hw_params_get_period_size(hwp, &period_size, &dir);
   buffer_size = period_size * 4;
   r = snd_pcm_hw_params_set_buffer_size_near(h, hwp, &buffer_size);
   r = snd_pcm_hw_params(h, hwp);
   swp = alloca(snd_pcm_sw_params_sizeof());
   memset(hwp, 0, snd_pcm_sw_params_sizeof());
   snd_pcm_sw_params_current(h, swp);
   r = snd_pcm_sw_params_set_avail_min(h, swp, period_size);
   snd_pcm_sw_params_set_start_threshold(h, swp, 0);
   r = snd_pcm_sw_params(h, swp);
   r = snd_pcm_prepare(h);

   return h;
}

static  snd_pcm_t *h = NULL;
static void *ctx_alsa_audio_start(Ctx *ctx)
{
//  Lyd *lyd = aux;
  int c;

  /* The audio handler is implemented as a mixer that adds data on top
   * of 0s, XXX: it should be ensured that minimal work is there is
   * no data available.
   */
  for (;;)
  {
    int client_channels = ctx_pcm_channels (ctx_client_format);
    int is_float = 0;
    int16_t data[81920*8]={0,};

    if (ctx_client_format == CTX_f32 ||
        ctx_client_format == CTX_f32S)
      is_float = 1;

    c = snd_pcm_wait(h, 1000);

    if (c >= 0)
       c = snd_pcm_avail_update(h);

    if (c > 1000) c = 1000; // should use max mmm buffer sizes

    if (c == -EPIPE)
      snd_pcm_prepare(h);

    if (c > 0)
    {
      int i;
      for (i = 0; i < c && ctx_pcm_cur_left; i ++)
      {
        if (ctx_pcm_cur_left)  //  XXX  this line can be removed
        {
          uint32_t *packet_sizep = (ctx_pcm_list->data);
          uint32_t packet_size = *packet_sizep;
          uint16_t left = 0, right = 0;

          if (is_float)
          {
            float *packet = (ctx_pcm_list->data);
            packet += 4;
            packet += (packet_size - ctx_pcm_cur_left) * client_channels;
            left = right = packet[0] * (1<<15);
            if (client_channels > 1)
              right = packet[0] * (1<<15);
          }
          else // s16
          {
            uint16_t *packet = (ctx_pcm_list->data);
            packet += 8;
            packet += (packet_size - ctx_pcm_cur_left) * client_channels;

            left = right = packet[0];
            if (client_channels > 1)
              right = packet[1];
          }
          data[i * 2 + 0] = left;
          data[i * 2 + 1] = right;

          ctx_pcm_cur_left--;
          ctx_pcm_queued --;
          if (ctx_pcm_cur_left == 0)
          {
            void *old = ctx_pcm_list->data;
            ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data);
            free (old);
            ctx_pcm_cur_left = 0;
            if (ctx_pcm_list)
            {
              uint32_t *packet_sizep = (ctx_pcm_list->data);
              uint32_t packet_size = *packet_sizep;
              ctx_pcm_cur_left = packet_size;
            }
          }
        }
      }

    c = snd_pcm_writei(h, data, c);
    if (c < 0)
      c = snd_pcm_recover (h, c, 0);
     }else{
      if (getenv("LYD_FATAL_UNDERRUNS"))
        {
          printf ("dying XXxx need to add API for this debug\n");
          //printf ("%i", lyd->active);
          exit(0);
        }
      fprintf (stderr, "ctx alsa underun\n");
      //exit(0);
    }
  }
}
#endif

static char MuLawCompressTable[256] =
{
   0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
   4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

static unsigned char LinearToMuLawSample(int16_t sample)
{
  const int cBias = 0x84;
  const int cClip = 32635;
  int sign = (sample >> 8) & 0x80;

  if (sign)
    sample = (int16_t)-sample;

  if (sample > cClip)
    sample = cClip;

  sample = (int16_t)(sample + cBias);

  int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF];
  int mantissa = (sample >> (exponent+3)) & 0x0F;

  int compressedByte = ~ (sign | (exponent << 4) | mantissa);

  return (unsigned char)compressedByte;
}

void ctx_ctx_pcm (Ctx *ctx)
{
    int client_channels = ctx_pcm_channels (ctx_client_format);
    int is_float = 0;
    uint8_t data[81920*8]={0,};
    int c;

    if (ctx_client_format == CTX_f32 ||
        ctx_client_format == CTX_f32S)
      is_float = 1;

    c = 2000;

    if (c > 0)
    {
      int i;
      for (i = 0; i < c && ctx_pcm_cur_left; i ++)
      {
        if (ctx_pcm_cur_left)  //  XXX  this line can be removed
        {
          uint32_t *packet_sizep = (ctx_pcm_list->data);
          uint32_t packet_size = *packet_sizep;
          int left = 0, right = 0;

          if (is_float)
          {
            float *packet = (ctx_pcm_list->data);
            packet += 4;
            packet += (packet_size - ctx_pcm_cur_left) * client_channels;
            left = right = packet[0] * (1<<15);
            if (client_channels > 1)
              right = packet[1] * (1<<15);
          }
          else // s16
          {
            uint16_t *packet = (ctx_pcm_list->data);
            packet += 8;
            packet += (packet_size - ctx_pcm_cur_left) * client_channels;

            left = right = packet[0];
            if (client_channels > 1)
              right = packet[1];
          }
          data[i] = LinearToMuLawSample((left+right)/2);

          ctx_pcm_cur_left--;
          ctx_pcm_queued --;
          if (ctx_pcm_cur_left == 0)
          {
            void *old = ctx_pcm_list->data;
            ctx_list_remove (&ctx_pcm_list, ctx_pcm_list->data);
            free (old);
            ctx_pcm_cur_left = 0;
            if (ctx_pcm_list)
            {
              uint32_t *packet_sizep = (ctx_pcm_list->data);
              uint32_t packet_size = *packet_sizep;
              ctx_pcm_cur_left = packet_size;
            }
          }
        }
      }

    char encoded[81920*8]="";

    int encoded_len = ctx_a85enc (data, encoded, i);
    fprintf (stdout, "\033_Af=%i;", i);
    fwrite (encoded, 1, encoded_len, stdout);
    fwrite ("\e\\", 1, 2, stdout);
    fflush (stdout);
    }
}

int ctx_pcm_init (Ctx *ctx)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return 0;
  }
  else
#endif
  if (ctx_renderer_is_ctx (ctx))
  {
     ctx_host_freq = 8000;
     ctx_host_format = CTX_s16;
#if 0
     pthread_t tid;
     pthread_create(&tid, NULL, (void*)ctx_audio_start, ctx);
#endif
  }
  else
  {
#if CTX_ALSA_AUDIO
     pthread_t tid;
     h = alsa_open("default", ctx_host_freq, ctx_pcm_channels (ctx_host_format));
  if (!h) {
    fprintf(stderr, "ctx unable to open ALSA device (%d channels, %f Hz), dying\n",
            ctx_pcm_channels (ctx_host_format), ctx_host_freq);
    return -1;
  }
  pthread_create(&tid, NULL, (void*)ctx_alsa_audio_start, ctx);
#endif
  }
  return 0;
}

int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames)
{
  static int inited = 0;
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return mmm_pcm_queue (ctx->backend_data, data, frames);
  }
  else
#endif
  {
    if (!inited)
    {
      ctx_pcm_init (ctx);
      inited = 1;
    }
    float factor = client_freq * 1.0 / ctx_host_freq;
    int   scaled_frames = frames / factor;
    int   bpf = ctx_pcm_bytes_per_frame (ctx_client_format);

    uint8_t *packet = malloc (scaled_frames * ctx_pcm_bytes_per_frame (ctx_client_format) + 16);
    *((uint32_t *)packet) = scaled_frames;

    if (factor > 0.999 && factor < 1.0001)
    {
       memcpy (packet + 16, data, frames * bpf);
    }
    else
    {
      /* a crude nearest / sample-and hold resampler */
      int i;
      for (i = 0; i < scaled_frames; i++)
      {
        int source_frame = i * factor;
        memcpy (packet + 16 + bpf * i, data + source_frame * bpf, bpf);
      }
    }
    if (ctx_pcm_list == NULL)     // otherwise it is another frame at front
      ctx_pcm_cur_left = scaled_frames;  // and current cur_left is valid

    ctx_list_append (&ctx_pcm_list, packet);
    ctx_pcm_queued += scaled_frames;

    return frames;
  }
  return 0;
}

static int ctx_pcm_get_queued_frames (Ctx *ctx)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return mmm_pcm_get_queued_frames (ctx->backend_data);
  }
#endif
  return ctx_pcm_queued;
}

int ctx_pcm_get_queued (Ctx *ctx)
{
  return ctx_pcm_get_queued_frames (ctx);
}

float ctx_pcm_get_queued_length (Ctx *ctx)
{
  return 1.0 * ctx_pcm_get_queued_frames (ctx) / ctx_host_freq;
}

int ctx_pcm_get_frame_chunk (Ctx *ctx)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return mmm_pcm_get_frame_chunk (ctx->backend_data);
  }
#endif
  if (ctx_renderer_is_ctx (ctx))
  {
    // 300 stuttering
    // 350 nothing
    // 380 slight buzz
    // 390  buzzing
    // 400 ok - but sometimes falling out
    // 410 buzzing
    // 420 ok - but odd latency
    // 450 buzzing

    if (ctx_pcm_get_queued_frames (ctx) > 400)
      return 0;
    else
      return 400 - ctx_pcm_get_queued_frames (ctx);

  }

  if (ctx_pcm_get_queued_frames (ctx) > 1000)
    return 0;
  else
    return 1000 - ctx_pcm_get_queued_frames (ctx);
}

void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    mmm_pcm_set_sample_rate (ctx->backend_data, sample_rate);
  }
  else
#endif
    client_freq = sample_rate;
}

void ctx_pcm_set_format (Ctx *ctx, CtxPCM format)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    mmm_pcm_set_format (ctx->backend_data, format);
  }
  else
#endif
    ctx_client_format = format;
}

CtxPCM ctx_pcm_get_format (Ctx *ctx)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return mmm_pcm_get_format (ctx->backend_data);
  }
#endif
  return ctx_client_format;
}

int ctx_pcm_get_sample_rate (Ctx *ctx)
{
#if 0
  if (!strcmp (ctx->backend->name, "mmm") ||
      !strcmp (ctx->backend->name, "mmm-client"))
  {
    return mmm_pcm_get_sample_rate (ctx->backend_data);
  }
#endif
  return client_freq;
}

#endif
 /* Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org>
 */

#if CTX_FORMATTER

/* returns the maximum string length including terminating \0 */
int ctx_a85enc_len (int input_length)
{
  return (input_length / 4 + 1) * 5;
}

int ctx_a85enc (const void *srcp, char *dst, int count)
{
  const uint8_t *src = (uint8_t*)srcp;
  int out_len = 0;

  int padding = 4-(count % 4);
  if (padding == 4) padding = 0;

  for (int i = 0; i < (count+3)/4; i ++)
  {
    uint32_t input = 0;
    for (int j = 0; j < 4; j++)
    {
      input = (input << 8);
      if (i*4+j<=count)
        input += src[i*4+j];
    }

    int divisor = 85 * 85 * 85 * 85;
#if 0
    if (input == 0)
    {
        dst[out_len++] = 'z';
    }
    /* todo: encode 4 spaces as 'y' */
    else
#endif
    {
      for (int j = 0; j < 5; j++)
      {
        dst[out_len++] = ((input / divisor) % 85) + '!';
        divisor /= 85;
      }
    }
  }
  out_len -= padding;
  dst[out_len]=0;
  return out_len;
}
#endif

#if CTX_PARSER

int ctx_a85dec (const char *src, char *dst, int count)
{
  int out_len = 0;
  uint32_t val = 0;
  int k = 0;
  int i = 0;
  int p = 0;
  for (i = 0; i < count; i ++)
  {
    p = src[i];
    val *= 85;
    if (CTX_UNLIKELY(p == '~'))
    {
      break;
    }
#if 0
    else if (p == 'z')
    {
      for (int j = 0; j < 4; j++)
        dst[out_len++] = 0;
      k = 0;
    }
    else if (p == 'y') /* lets support this extension */
    {
      for (int j = 0; j < 4; j++)
        dst[out_len++] = 32;
      k = 0;
    }
#endif
    else if (CTX_LIKELY(p >= '!' && p <= 'u'))
    {
      val += p-'!';
      if (CTX_UNLIKELY (k % 5 == 4))
      {
         for (int j = 0; j < 4; j++)
         {
           dst[out_len++] = (val & (0xff << 24)) >> 24;
           val <<= 8;
         }
         val = 0;
      }
      k++;
    }
    // we treat all other chars as whitespace
  }
  if (CTX_LIKELY (p != '~'))
  { 
    val *= 85;
  }
  k = k % 5;
  if (k)
  {
    val += 84;
    for (int j = k; j < 4; j++)
    {
      val *= 85;
      val += 84;
    }

    for (int j = 0; j < k-1; j++)
    {
      dst[out_len++] = (val & (0xff << 24)) >> 24;
      val <<= 8;
    }
    val = 0;
  }
  dst[out_len] = 0;
  return out_len;
}

#if 1
int ctx_a85len (const char *src, int count)
{
  int out_len = 0;
  int k = 0;
  for (int i = 0; i < count; i ++)
  {
    if (src[i] == '~')
      break;
    else if (src[i] == 'z')
    {
      for (int j = 0; j < 4; j++)
        out_len++;
      k = 0;
    }
    else if (src[i] >= '!' && src[i] <= 'u')
    {
      if (k % 5 == 4)
        out_len += 4;
      k++;
    }
    // we treat all other chars as whitespace
  }
  k = k % 5;
  if (k)
    out_len += k-1;
  return out_len;
}
#endif

#endif
#ifndef THASH_H
#define THASH_H

#if !__COSMOPOLITAN__
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#endif

#define THASH_NO_INTERNING   // ctx doesn't make use of thash_decode

#define THASH_ENTER_DIRECT     16

#define THASH_SPACE            0
#define THASH_DEC_OFFSET       29
#define THASH_INC_OFFSET       30
#define THASH_ENTER_UTF5       31
#define THASH_START_OFFSET     'l'
#define THASH_JUMP_OFFSET      27
#define THASH_MAXLEN           10

// todo: better whitespace handling for double version


static inline int thash_new_offset (uint32_t unichar)
{
   int offset = unichar % 32;
   return unichar - offset + 14; // this gives ~85% compression on test corpus
   return unichar; // this gives 88% compression on test corpus
}

static int thash_is_in_range (uint32_t offset, uint32_t unichar)
{
  if (unichar == 32)
    return 1;
  if (offset - unichar <= 13 ||
      unichar - offset <= 14)
      return 1;
  return 0;
}

static int thash_is_in_jump_range_dec (uint32_t offset, uint32_t unichar)
{
  return thash_is_in_range (offset - THASH_JUMP_OFFSET, unichar);
}

static int thash_is_in_jump_range_inc (uint32_t offset, uint32_t unichar)
{
  return thash_is_in_range (offset + THASH_JUMP_OFFSET, unichar);
}

//uint32_t ctx_utf8_to_unichar (const char *input);
//int ctx_unichar_to_utf8      (uint32_t ch, uint8_t  *dest);
//int ctx_utf8_len             (const unsigned char first_byte);

static int thash_utf5_length (uint32_t unichar)
{
  int octets = 0;
  if (unichar == 0) return 1;
  while (unichar)
  {  octets ++;
     unichar /= 16;
  }
  return octets;
}

typedef struct EncodeUtf5 {
  int      is_utf5;
  int      offset;
  int      length;
  void    *write_data;
  uint32_t current;
} EncodeUtf5;

static void thash_encode_utf5 (const char *input, int inlen,
                               char *output, int *r_outlen)
{
  uint32_t offset = THASH_START_OFFSET;

  int is_utf5 = 1;
  int len = 0;

  for (int i = 0; i < inlen; i+= ctx_utf8_len (input[i]))
  {
    int val = ctx_utf8_to_unichar(&input[i]);
    int next_val = ' '; // always in range
    int next_next_val = ' ';
    int first_len = ctx_utf8_len (input[i]);
    if (i + first_len < inlen)
    {
        int next_len = ctx_utf8_to_unichar (&input[i + first_len]);
        if (i + first_len + next_len < inlen)
        {
          next_next_val = ctx_utf8_to_unichar (&input[i + first_len + next_len]);
        }
    }

    if (is_utf5)
    {
      int in_range = 
          thash_is_in_range (offset, val) +
          thash_is_in_range (offset, next_val) +
          thash_is_in_range (offset, next_next_val);
      int change_cost = 4;
      int no_change_cost = thash_utf5_length (val) + thash_utf5_length (next_val)
                                                   + thash_utf5_length (next_next_val);

      if (in_range > 2 && change_cost < no_change_cost)
      {
        output[len++] = THASH_ENTER_DIRECT;
        is_utf5 = 0;
      }
    } else
    {
      if (!thash_is_in_range(offset, val))
      {
        if (thash_is_in_jump_range_dec (offset, val))
        {
            output[len++] = THASH_DEC_OFFSET;
            offset -= THASH_JUMP_OFFSET;
        }
        else if (thash_is_in_jump_range_inc (offset, val))
        {
          output[len++] = THASH_INC_OFFSET;
          offset += THASH_JUMP_OFFSET;
        }
        else
        {
          output[len++] = THASH_ENTER_UTF5;
          is_utf5 = 1;
        }
      }
    }

    if (is_utf5)
    {
          int octets = 0;
          offset = thash_new_offset (val);
          while (val)
          {
            int oval = val % 16;
            int last = 0;
            if (val / 32 == 0) last = 16;
            output[len+ (octets++)] = oval + last;
            val /= 16;
          }
          for (int j = 0; j < octets/2; j++) // mirror in-place
          {
            int tmp = output[len+j];
            output[len+j] = output[len+octets-1-j];
            output[len+octets-1-j] = tmp;
          }
          len += octets;
        }
        else 
      {
        if (val == 32)
        {
          output[len++] = THASH_SPACE;
        }
        else
        {
          output[len++]= val-offset+14;
        }
      }
  }
  if (len && output[len-1]==0)
    output[len++] = 16;
  output[len]=0;
  *r_outlen = len;
}

static inline uint64_t _thash (const char *utf8)
{
  char encoded[4096]="";
  int  encoded_len=0;
  int  wordlen = 0;
  thash_encode_utf5 (utf8, strlen (utf8), encoded, &encoded_len);
#if 0
  Word word = {0};
  word.utf5 = (encoded[0] != THASH_ENTER_DIRECT);
  for (int i = !word.utf5; i < encoded_len; i++)
    word_set_val (&word, wordlen++, encoded[i]);
  return word.hash;
#else
  uint64_t hash = 0;
  int  utf5 = (encoded[0] != THASH_ENTER_DIRECT);
  for (int i = !utf5; i < encoded_len; i++)
  {
    uint64_t val = encoded[i];

    if (wordlen < THASH_MAXLEN)
    {
      hash = hash | (val << (5*wordlen));
      hash &= (((uint64_t)1<<52)-1);
    }
    else
    {
      hash = hash ^ ((hash << 4) + val);
      hash &= (((uint64_t)1<<52)-1);
    }
    wordlen++;
  }
  hash <<= 1;
  if (wordlen >= THASH_MAXLEN) 
    hash |= ((uint64_t)1<<51); // overflowed
  return hash |  utf5;
#endif
}

typedef struct _Interned Interned;

struct _Interned {
    uint64_t   hash;
    char      *string;
};

static Interned *interned = NULL;
static int n_interned = 0;
static int s_interned = 0;
static int interned_sorted = 1;

static int interned_compare (const void *a, const void *b)
{
  const Interned *ia = (Interned*)a;
  const Interned *ib = (Interned*)b;
  if (ia->hash < ib->hash ) return -1;
  else if (ia->hash > ib->hash ) return 1;
  return 0;
}

uint64_t thash (const char *utf8)
{
  uint64_t hash = _thash (utf8);
#ifdef THASH_NO_INTERNING
  return hash;
#endif
  if (hash & ((uint64_t)1<<51)) /* overflowed */
  {
    int i;
    for (i = 0; i < n_interned; i++)
    {
      Interned *entry = &interned[i];
      if (entry->hash == hash)
        return hash;
    }

    if (n_interned + 1 >= s_interned)
    {
       s_interned = (s_interned + 128)*1.5;
       //fprintf (stderr, "\r%p %i ", interned, s_interned);
       interned = (Interned*)realloc (interned, s_interned * sizeof (Interned));
    }

    {
      Interned *entry = &interned[n_interned];
      entry->hash = hash;
      entry->string = strdup (utf8);
    }
    n_interned++;
    interned_sorted = 0;
  }
  return hash;
}
uint64_t ctx_strhash(const char *str) {
  return thash (str);
}

typedef struct ThashUtf5Dec {
  int      is_utf5;
  int      offset;
  void    *write_data;
  uint32_t current;
  void   (*append_unichar) (uint32_t unichar, void *write_data);
} ThashUtf5Dec;

typedef struct ThashUtf5DecDefaultData {
   uint8_t *buf;
   int     length;
} ThashUtf5DecDefaultData;

static void thash_decode_utf5_append_unichar_as_utf8 (uint32_t unichar, void *write_data)
{
  ThashUtf5DecDefaultData *data = (ThashUtf5DecDefaultData*)write_data;
  unsigned char utf8[8]="";
  utf8[ctx_unichar_to_utf8 (unichar, utf8)]=0;
  for (int j = 0; utf8[j]; j++)
    data->buf[data->length++]=utf8[j];
  data->buf[data->length]=0;
}

static void thash_decode_utf5 (ThashUtf5Dec *dec, uint8_t in)
{
  if (dec->is_utf5)
  {
      if (in > 16)
      {
        if (dec->current)
        {
          dec->offset = thash_new_offset (dec->current);
          dec->append_unichar (dec->current, dec->write_data);
          dec->current = 0;
        }
      }
      if (in == THASH_ENTER_DIRECT)
      {
        if (dec->current)
        {
          dec->offset = thash_new_offset (dec->current);
          dec->append_unichar (dec->current, dec->write_data);
          dec->current = 0;
        }
        dec->is_utf5 = 0;
      }
      else
      {
        dec->current = dec->current * 16 + (in % 16);
      }
  }
  else
  {
      switch (in)
      {
        case THASH_ENTER_UTF5: dec->is_utf5 = 1;  break;
        case THASH_SPACE:      dec->append_unichar (' ', dec->write_data); break;
        case THASH_DEC_OFFSET: dec->offset -= THASH_JUMP_OFFSET; break;
        case THASH_INC_OFFSET: dec->offset += THASH_JUMP_OFFSET; break;
        default:
          dec->append_unichar (dec->offset + in - 14, dec->write_data);
      }
  }
}

static void thash_decode_utf5_bytes (int is_utf5, 
                        const unsigned char *input, int inlen,
                        char *output, int *r_outlen)
{
  ThashUtf5DecDefaultData append_data= {(unsigned char*)output, };
  ThashUtf5Dec dec = {is_utf5,
                    THASH_START_OFFSET,
                    &append_data,
  0, thash_decode_utf5_append_unichar_as_utf8
  };
  for (int i = 0; i < inlen; i++)
  {
    thash_decode_utf5 (&dec, input[i]);
  }
  if (dec.current)
    dec.append_unichar (dec.current, dec.write_data);
  if (r_outlen)*r_outlen = append_data.length;
}

const char *thash_decode (uint64_t hash)
{
  if (!interned_sorted && interned)
  {
    qsort (interned, n_interned, sizeof (Interned), interned_compare);
    interned_sorted = 1;
  }
  if (hash &  ((uint64_t)1<<51))
  {

    for (int i = 0; i < n_interned; i++)
    {
      Interned *entry = &interned[i];
      if (entry->hash == hash)
        return entry->string;
    }
    return "[missing string]";
  }

  static char ret[4096]="";
  uint8_t utf5[40]="";
  uint64_t tmp = hash & (((uint64_t)1<<51)-1);
  int len = 0;
  int is_utf5 = tmp & 1;
  tmp /= 2;
  int in_utf5 = is_utf5;
  while (tmp > 0)
  {
    uint64_t remnant = tmp % 32;
    uint64_t val = remnant;

    if      ( in_utf5 && val == THASH_ENTER_DIRECT) in_utf5 = 0;
    else if (!in_utf5 && val == THASH_ENTER_UTF5) in_utf5 = 1;

    utf5[len++] = val;
    tmp -= remnant;
    tmp /= 32;
  }
  if (in_utf5 && len && utf5[len-1] > 'G')
  {
    utf5[len++] = 0;//utf5_alphabet[0]; 
  }
  utf5[len]=0;
  int retlen = sizeof (ret);
  thash_decode_utf5_bytes (is_utf5, utf5, len, ret, &retlen);
  ret[len]=0;
  return ret;
}

#if 0

#include <assert.h>
#pragma pack(push,1)
typedef union Word
{
  uint64_t hash;
  struct {
    unsigned int utf5:1;
    unsigned int c0:5;
    unsigned int c1:5;
    unsigned int c2:5;
    unsigned int c3:5;
    unsigned int c4:5;
    unsigned int c5:5;
    unsigned int c6:5;
    unsigned int c7:5;
    unsigned int c8:5;
    unsigned int c9:5;
    unsigned int overflowed:1;
  };
} Word;

static inline void word_set_val (Word *word, int no, int val)
{
#if 0
  switch(no)
  {
     case 0: word->c0 = val; break;
     case 1: word->c1 = val; break;
     case 2: word->c2 = val; break;
     case 3: word->c3 = val; break;
     case 4: word->c4 = val; break;
     case 5: word->c5 = val; break;
     case 6: word->c6 = val; break;
     case 7: word->c7 = val; break;
     case 8: word->c8 = val; break;
     case 9: word->c9 = val; break;
     default:
       // for overflow only works when setting all in sequence
       word->hash = word->hash + ((uint64_t)(val) << (5*no+1));
       word->overflowed = 1;
       break;
  }
#else
  word->hash = word->hash | ((uint64_t)(val) << (5*no+1));
  if (no >= 9) 
    word->hash |= ((uint64_t)1<<51);
  word->hash &= (((uint64_t)1<<52)-1);
#endif
}

static inline int word_get_val (Word *word, int no)
{
  switch(no)
  {
     case 0: return word->c0;break;
     case 1: return word->c1;break;
     case 2: return word->c2;break;
     case 3: return word->c3;break;
     case 4: return word->c4;break;
     case 5: return word->c5;break;
     case 6: return word->c6;break;
     case 7: return word->c7;break;
     case 8: return word->c8;break;
     case 9: return word->c9;break;
  }
}

static inline int word_get_length (Word *word)
{
   int len = 0;
   if (word->c0) len ++; else return len;
   if (word->c1) len ++; else return len;
   if (word->c2) len ++; else return len;
   if (word->c3) len ++; else return len;
   if (word->c4) len ++; else return len;
   if (word->c5) len ++; else return len;
   if (word->c6) len ++; else return len;
   if (word->c7) len ++; else return len;
   if (word->c8) len ++; else return len;
   if (word->c9) len ++; else return len;
   return len;
}


static Word *word_append_unichar (Word *word, uint32_t unichar)
{
  //word_set_char (word, word_get_length (word), unichar);
  // append unichar - possibly advancing.
  return word;
}
#endif

#endif
/* atty - audio interface and driver for terminals
 * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 
 */

static const char *base64_map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
static void bin2base64_group (const unsigned char *in, int remaining, char *out)
{
  unsigned char digit[4] = {0,0,64,64};
  int i;
  digit[0] = in[0] >> 2;
  digit[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4);
  if (remaining > 1)
    {
      digit[2] = ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6);
      if (remaining > 2)
        digit[3] = ((in[2] & 0x3f));
    }
  for (i = 0; i < 4; i++)
    out[i] = base64_map[digit[i]];
}

void
ctx_bin2base64 (const void *bin,
                int         bin_length,
                char       *ascii)
{
  /* this allocation is a hack to ensure we always produce the same result,
   * regardless of padding data accidentally taken into account.
   */
  unsigned char *bin2 = (unsigned char*)calloc (bin_length + 4, 1);
  unsigned const char *p = bin2;
  int i;
  memcpy (bin2, bin, bin_length);
  for (i=0; i*3 < bin_length; i++)
   {
     int remaining = bin_length - i*3;
     bin2base64_group (&p[i*3], remaining, &ascii[i*4]);
   }
  free (bin2);
  ascii[i*4]=0;
}

static unsigned char base64_revmap[255];
static void base64_revmap_init (void)
{
  static int done = 0;
  if (done)
    return;

  for (int i = 0; i < 255; i ++)
    base64_revmap[i]=255;
  for (int i = 0; i < 64; i ++)
    base64_revmap[((const unsigned char*)base64_map)[i]]=i;
  /* include variants used in URI encodings for decoder,
   * even if that is not how we encode
  */
  base64_revmap['-']=62;
  base64_revmap['_']=63;
  base64_revmap['+']=62;
  base64_revmap['/']=63;

  done = 1;
}


int
ctx_base642bin (const char    *ascii,
                int           *length,
                unsigned char *bin)
{
  int i;
  int charno = 0;
  int outputno = 0;
  int carry = 0;
  base64_revmap_init ();
  for (i = 0; ascii[i]; i++)
    {
      int bits = base64_revmap[((const unsigned char*)ascii)[i]];
      if (length && outputno > *length)
        {
          *length = -1;
          return -1;
        }
      if (bits != 255)
        {
          switch (charno % 4)
            {
              case 0:
                carry = bits;
                break;
              case 1:
                bin[outputno] = (carry << 2) | (bits >> 4);
                outputno++;
                carry = bits & 15;
                break;
              case 2:
                bin[outputno] = (carry << 4) | (bits >> 2);
                outputno++;
                carry = bits & 3;
                break;
              case 3:
                bin[outputno] = (carry << 6) | bits;
                outputno++;
                carry = 0;
                break;
            }
          charno++;
        }
    }
  bin[outputno]=0;
  if (length)
    *length= outputno;
  return outputno;
}
#include <stdio.h>
#include <string.h>

static int ctx_yenc (const char *src, char *dst, int count)
{
  int out_len = 0;
  for (int i = 0; i < count; i ++)
  {
    int o = (src[i] + 42) % 256;
    switch (o)
    {
      case 0x00: //null
      case 0x20: //space// but better safe
      case 0x0A: //lf   // than sorry
      case 0x0D: //cr
      case 0x09: //tab  // not really needed
      case 0x10: //datalink escape (used by ctx)
      case 0x11: //xoff
      case 0x13: //xon
      case 0x1b: //
      case 0xff: //
      case 0x3D: //=
        dst[out_len++] = '=';
        o = (o + 64) % 256;
        /* FALLTHROUGH */
      default:
        dst[out_len++] = o;
        break;
    }
  }
  dst[out_len]=0;
  return out_len;
}

static int ctx_ydec (const char *tmp_src, char *dst, int count)
{
  const char *src = tmp_src;
#if 0
  if (tmp_src == dst)
  {
    src = malloc (count);
    memcpy (src, tmp_src, count);
  }
#endif
  int out_len = 0;
  for (int i = 0; i < count; i ++)
  {
    int o = src[i];
    switch (o)
    {
      case '=':
        i++;
        o = src[i];
        if (o == 'y')
        {
          dst[out_len]=0;
#if 0
          if (tmp_src == dst) free (src);
#endif
          return out_len;
        }
        o = (o-42-64) % 256;
        dst[out_len++] = o;
        break;
      case '\n':
      case '\e':
      case '\r':
      case '\0':
        break;
      default:
        o = (o-42) % 256;
        dst[out_len++] = o;
        break;
    }
  }
  dst[out_len]=0;
#if 0
  if (tmp_src == dst) free (src);
#endif
  return out_len;
}

#if 0
int main (){
  char *input="this is a testæøåÅØ'''\"!:_asdac\n\r";
  char  encoded[256]="";
  char  decoded[256]="";
  int   in_len = strlen (input);
  int   out_len;
  int   dec_len;

  printf ("input: %s\n", input);

  out_len = ctx_yenc (input, encoded, in_len);
  printf ("encoded: %s\n", encoded);

  dec_len = ydec (encoded, encoded, out_len);

  printf ("decoded: %s\n", encoded);

  return 0;
}
#endif
#ifndef CTX_DRAWLIST_H
#define CTX_DRAWLIST_H

static int
ctx_conts_for_entry (CtxEntry *entry);
static void
ctx_iterator_init (CtxIterator      *iterator,
                   CtxDrawlist  *drawlist,
                   int               start_pos,
                   int               flags);

int ctx_iterator_pos (CtxIterator *iterator);

static void
ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size);
static int
ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry);
static int ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry);
int
ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry);
int
ctx_add_data (Ctx *ctx, void *data, int length);

int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2]);
int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length);

static CtxEntry
ctx_void (CtxCode code);
static inline CtxEntry
ctx_f (CtxCode code, float x, float y);
static CtxEntry
ctx_u32 (CtxCode code, uint32_t x, uint32_t y);
#if 0
static CtxEntry
ctx_s32 (CtxCode code, int32_t x, int32_t y);
#endif

static inline CtxEntry
ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1);
static CtxEntry
ctx_u8 (CtxCode code,
        uint8_t a, uint8_t b, uint8_t c, uint8_t d,
        uint8_t e, uint8_t f, uint8_t g, uint8_t h);

#define CTX_PROCESS_VOID(cmd) do {\
  CtxEntry command = {cmd};\
  ctx_process (ctx, &command);}while(0) \

#define CTX_PROCESS_F(cmd, x, y) do {\
  CtxEntry command = ctx_f(cmd, x, y);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_F1(cmd, x) do {\
  CtxEntry command = ctx_f(cmd, x, 0);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_U32(cmd, x, y) do {\
  CtxEntry command = ctx_u32(cmd, x, y);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_U8(cmd, x) do {\
  CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\
  ctx_process (ctx, &command);}while(0)


#if CTX_BITPACK_PACKER
static int
ctx_last_history (CtxDrawlist *drawlist);
#endif

#if CTX_BITPACK_PACKER
static void
ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos);

static void
ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos);
#endif

static void
ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1);
static void
ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1);
static void
ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len);

#pragma pack(push,1)
typedef struct 
CtxSegment {
  uint16_t code;
  union {
   int16_t s16[4];
   uint32_t u32[2];
  } data;
  int32_t val;
  int32_t delta;
} CtxSegment;
#pragma pack(pop)

#endif

#ifndef __CTX_UTIL_H
#define __CTX_UTIL_H

inline static float ctx_fast_hypotf (float x, float y)
{
  if (x < 0) { x = -x; }
  if (y < 0) { y = -y; }
  if (x < y)
    { return 0.96f * y + 0.4f * x; }
  else
    { return 0.96f * x + 0.4f * y; }
}

static int ctx_str_is_number (const char *str)
{
  int got_digit = 0;
  for (int i = 0; str[i]; i++)
  {
    if (str[i] >= '0' && str[i] <= '9')
    {
       got_digit ++;
    }
    else if (str[i] == '.')
    {
    }
    else
      return 0;
  }
  if (got_digit)
    return 1;
  return 0;
}

#if CTX_GET_CONTENTS

typedef struct CtxFileContent
{
  char *path;
  unsigned char *contents;
  long  length;
  int   free_data;
} CtxFileContent;

CtxList *registered_contents = NULL;

void
ctx_register_contents (const char *path,
                       const unsigned char *contents,
                       long length,
                       int  free_data)
{
  // if (path[0] != '/') && strchr(path, ':')) 
  //   with this check regular use is faster, but we lose
  //   generic filesystem overrides..
  for (CtxList *l = registered_contents; l; l = l->next)
  {
    CtxFileContent *c = (CtxFileContent*)l->data;
    if (!strcmp (c->path, path))
    {
       if (c->free_data)
       {
         free (c->contents);
       }
       c->free_data = free_data;
       c->contents = (unsigned char*)contents;
       c->length = length;
       return;
    }
  }
  CtxFileContent *c = (CtxFileContent*)calloc (sizeof (CtxFileContent), 1);
  c->free_data = free_data;
  c->contents = (unsigned char*)contents;
  c->length    = length;
  ctx_list_append (&registered_contents, c);
}

void
_ctx_file_set_contents (const char     *path,
                        const unsigned char  *contents,
                        long            length)
{
  FILE *file;
  file = fopen (path, "wb");
  if (!file)
    { return; }
  if (length < 0) length = strlen ((const char*)contents);
  fwrite (contents, 1, length, file);
  fclose (file);
}

static int
__ctx_file_get_contents (const char     *path,
                        unsigned char **contents,
                        long           *length)
{
  FILE *file;
  long  size;
  long  remaining;
  char *buffer;
  file = fopen (path, "rb");
  if (!file)
    { return -1; }
  fseek (file, 0, SEEK_END);
  size = remaining = ftell (file);
  if (length)
    { *length =size; }
  rewind (file);
  buffer = (char*)malloc (size + 8);
  if (!buffer)
    {
      fclose (file);
      return -1;
    }
  remaining -= fread (buffer, 1, remaining, file);
  if (remaining)
    {
      fclose (file);
      free (buffer);
      return -1;
    }
  fclose (file);
  *contents = (unsigned char*) buffer;
  buffer[size] = 0;
  return 0;
}

#if !__COSMOPOLITAN__
#include <limits.h>
#endif




#endif


#endif



static inline int
ctx_conts_for_entry (CtxEntry *entry)
{
    switch (entry->code)
    {
      case CTX_DATA:
        return entry->data.u32[1];
      case CTX_LINEAR_GRADIENT:
      //case CTX_DEFINE_TEXTURE:
        return 1;
      case CTX_RADIAL_GRADIENT:
      case CTX_ARC:
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
      case CTX_CURVE_TO:
      case CTX_REL_CURVE_TO:
      case CTX_APPLY_TRANSFORM:
      case CTX_SOURCE_TRANSFORM:
      case CTX_COLOR:
      case CTX_ROUND_RECTANGLE:
      case CTX_SHADOW_COLOR:
        return 2;
      case CTX_FILL_RECT:
      case CTX_STROKE_RECT:
      case CTX_RECTANGLE:
      case CTX_VIEW_BOX:
      case CTX_REL_QUAD_TO:
      case CTX_QUAD_TO:
        return 1;

      case CTX_TEXT:
      case CTX_LINE_DASH:
      case CTX_COLOR_SPACE:
      case CTX_STROKE_TEXT:
      case CTX_FONT:
      case CTX_TEXTURE:
        {
          int eid_len = entry[1].data.u32[1];
          return eid_len + 1;
        }
      case CTX_DEFINE_TEXTURE:
        {
          int eid_len = entry[2].data.u32[1];
          int pix_len = entry[2 + eid_len + 1].data.u32[1];
          return eid_len + pix_len + 2 + 1;
        }
      default:
        return 0;
    }
}

// expanding arc_to to arc can be the job
// of a layer in front of renderer?
//   doing:
//     rectangle
//     arc
//     ... etc reduction to beziers
//     or even do the reduction to
//     polylines directly here...
//     making the rasterizer able to
//     only do poly-lines? will that be faster?

/* the iterator - should decode bitpacked data as well -
 * making the rasterizers simpler, possibly do unpacking
 * all the way to absolute coordinates.. unless mixed
 * relative/not are wanted.
 */


static void
ctx_iterator_init (CtxIterator      *iterator,
                   CtxDrawlist  *drawlist,
                   int               start_pos,
                   int               flags)
{
  iterator->drawlist   = drawlist;
  iterator->flags          = flags;
  iterator->bitpack_pos    = 0;
  iterator->bitpack_length = 0;
  iterator->pos            = start_pos;
  iterator->end_pos        = drawlist->count;
  iterator->first_run      = 1; // -1 is a marker used for first run
  ctx_memset (iterator->bitpack_command, 0, sizeof (iterator->bitpack_command) );
}

int ctx_iterator_pos (CtxIterator *iterator)
{
  return iterator->pos;
}

static CtxEntry *_ctx_iterator_next (CtxIterator *iterator)
{
  int ret = iterator->pos;
  CtxEntry *entry = &iterator->drawlist->entries[ret];
  if (CTX_UNLIKELY(ret >= iterator->end_pos))
    { return NULL; }

  if (CTX_UNLIKELY(iterator->first_run))
      iterator->first_run = 0;
  else
     iterator->pos += (ctx_conts_for_entry (entry) + 1);

  if (CTX_UNLIKELY(iterator->pos >= iterator->end_pos))
    { return NULL; }
  return &iterator->drawlist->entries[iterator->pos];
}

// 6024x4008
#if CTX_BITPACK
static void
ctx_iterator_expand_s8_args (CtxIterator *iterator, CtxEntry *entry)
{
  int no = 0;
  for (int cno = 0; cno < 4; cno++)
    for (int d = 0; d < 2; d++, no++)
      iterator->bitpack_command[cno].data.f[d] =
        entry->data.s8[no] * 1.0f / CTX_SUBDIV;
  iterator->bitpack_command[0].code =
    iterator->bitpack_command[1].code =
      iterator->bitpack_command[2].code =
        iterator->bitpack_command[3].code = CTX_CONT;
  iterator->bitpack_length = 4;
  iterator->bitpack_pos = 0;
}

static void
ctx_iterator_expand_s16_args (CtxIterator *iterator, CtxEntry *entry)
{
  int no = 0;
  for (int cno = 0; cno < 2; cno++)
    for (int d = 0; d < 2; d++, no++)
      iterator->bitpack_command[cno].data.f[d] = entry->data.s16[no] * 1.0f /
          CTX_SUBDIV;
  iterator->bitpack_command[0].code =
    iterator->bitpack_command[1].code = CTX_CONT;
  iterator->bitpack_length = 2;
  iterator->bitpack_pos    = 0;
}
#endif

CtxCommand *
ctx_iterator_next (CtxIterator *iterator)
{
  CtxEntry *ret;
#if CTX_BITPACK
  int expand_bitpack = iterator->flags & CTX_ITERATOR_EXPAND_BITPACK;
again:
  if (CTX_UNLIKELY(iterator->bitpack_length))
    {
      ret = &iterator->bitpack_command[iterator->bitpack_pos];
      iterator->bitpack_pos += (ctx_conts_for_entry (ret) + 1);
      if (iterator->bitpack_pos >= iterator->bitpack_length)
        {
          iterator->bitpack_length = 0;
        }
      return (CtxCommand *) ret;
    }
#endif
  ret = _ctx_iterator_next (iterator);
#if CTX_BITPACK
  if (CTX_UNLIKELY(ret && expand_bitpack))
    switch ((CtxCode)(ret->code))
      {
        case CTX_REL_CURVE_TO_REL_LINE_TO:
          ctx_iterator_expand_s8_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_REL_CURVE_TO;
          iterator->bitpack_command[1].code =
          iterator->bitpack_command[2].code = CTX_CONT;
          iterator->bitpack_command[3].code = CTX_REL_LINE_TO;
          // 0.0 here is a common optimization - so check for it
          if (ret->data.s8[6]== 0 && ret->data.s8[7] == 0)
            { iterator->bitpack_length = 3; }
          else
            iterator->bitpack_length          = 4;
          goto again;
        case CTX_REL_LINE_TO_REL_CURVE_TO:
          ctx_iterator_expand_s8_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_REL_LINE_TO;
          iterator->bitpack_command[1].code = CTX_REL_CURVE_TO;
          iterator->bitpack_length          = 2;
          goto again;
        case CTX_REL_CURVE_TO_REL_MOVE_TO:
          ctx_iterator_expand_s8_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_REL_CURVE_TO;
          iterator->bitpack_command[3].code = CTX_REL_MOVE_TO;
          iterator->bitpack_length          = 4;
          goto again;
        case CTX_REL_LINE_TO_X4:
          ctx_iterator_expand_s8_args (iterator, ret);
          iterator->bitpack_command[0].code =
          iterator->bitpack_command[1].code =
          iterator->bitpack_command[2].code =
          iterator->bitpack_command[3].code = CTX_REL_LINE_TO;
          iterator->bitpack_length          = 4;
          goto again;
        case CTX_REL_QUAD_TO_S16:
          ctx_iterator_expand_s16_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_REL_QUAD_TO;
          iterator->bitpack_length          = 1;
          goto again;
        case CTX_REL_QUAD_TO_REL_QUAD_TO:
          ctx_iterator_expand_s8_args (iterator, ret);
          iterator->bitpack_command[0].code =
          iterator->bitpack_command[2].code = CTX_REL_QUAD_TO;
          iterator->bitpack_length          = 3;
          goto again;
        case CTX_REL_LINE_TO_X2:
          ctx_iterator_expand_s16_args (iterator, ret);
          iterator->bitpack_command[0].code =
          iterator->bitpack_command[1].code = CTX_REL_LINE_TO;
          iterator->bitpack_length          = 2;
          goto again;
        case CTX_REL_LINE_TO_REL_MOVE_TO:
          ctx_iterator_expand_s16_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_REL_LINE_TO;
          iterator->bitpack_command[1].code = CTX_REL_MOVE_TO;
          iterator->bitpack_length          = 2;
          goto again;
        case CTX_MOVE_TO_REL_LINE_TO:
          ctx_iterator_expand_s16_args (iterator, ret);
          iterator->bitpack_command[0].code = CTX_MOVE_TO;
          iterator->bitpack_command[1].code = CTX_REL_MOVE_TO;
          iterator->bitpack_length          = 2;
          goto again;
        case CTX_FILL_MOVE_TO:
          iterator->bitpack_command[1]      = *ret;
          iterator->bitpack_command[0].code = CTX_FILL;
          iterator->bitpack_command[1].code = CTX_MOVE_TO;
          iterator->bitpack_pos             = 0;
          iterator->bitpack_length          = 2;
          goto again;
        case CTX_LINEAR_GRADIENT:
        case CTX_QUAD_TO:
        case CTX_REL_QUAD_TO:
        case CTX_TEXTURE:
        case CTX_RECTANGLE:
        case CTX_VIEW_BOX:
        case CTX_ARC:
        case CTX_ARC_TO:
        case CTX_REL_ARC_TO:
        case CTX_COLOR:
        case CTX_SHADOW_COLOR:
        case CTX_RADIAL_GRADIENT:
        case CTX_CURVE_TO:
        case CTX_REL_CURVE_TO:
        case CTX_APPLY_TRANSFORM:
        case CTX_SOURCE_TRANSFORM:
        case CTX_ROUND_RECTANGLE:
        case CTX_TEXT:
        case CTX_STROKE_TEXT:
        case CTX_FONT:
        case CTX_LINE_DASH:
        case CTX_FILL:
        case CTX_NOP:
        case CTX_MOVE_TO:
        case CTX_LINE_TO:
        case CTX_REL_MOVE_TO:
        case CTX_REL_LINE_TO:
        case CTX_VER_LINE_TO:
        case CTX_REL_VER_LINE_TO:
        case CTX_HOR_LINE_TO:
        case CTX_REL_HOR_LINE_TO:
        case CTX_ROTATE:
        case CTX_FLUSH:
        case CTX_TEXT_ALIGN:
        case CTX_TEXT_BASELINE:
        case CTX_TEXT_DIRECTION:
        case CTX_MITER_LIMIT:
        case CTX_GLOBAL_ALPHA:
        case CTX_COMPOSITING_MODE:
        case CTX_BLEND_MODE:
        case CTX_SHADOW_BLUR:
        case CTX_SHADOW_OFFSET_X:
        case CTX_SHADOW_OFFSET_Y:
        case CTX_RESET:
        case CTX_EXIT:
        case CTX_BEGIN_PATH:
        case CTX_CLOSE_PATH:
        case CTX_SAVE:
        case CTX_CLIP:
        case CTX_PRESERVE:
        case CTX_DEFINE_GLYPH:
        case CTX_IDENTITY:
        case CTX_FONT_SIZE:
        case CTX_START_GROUP:
        case CTX_END_GROUP:
        case CTX_RESTORE:
        case CTX_LINE_WIDTH:
        case CTX_LINE_DASH_OFFSET:
        case CTX_STROKE:
        case CTX_KERNING_PAIR:
        case CTX_SCALE:
        case CTX_GLYPH:
        case CTX_SET_PIXEL:
        case CTX_FILL_RULE:
        case CTX_LINE_CAP:
        case CTX_LINE_JOIN:
        case CTX_NEW_PAGE:
        case CTX_SET_KEY:
        case CTX_TRANSLATE:
        case CTX_DEFINE_TEXTURE:
        case CTX_GRADIENT_STOP:
        case CTX_DATA: // XXX : would be better if we hide the DATAs
        case CTX_CONT: // shouldnt happen
        default:
          iterator->bitpack_length = 0;
          return (CtxCommand *) ret;
#if 0
        default: // XXX remove - and get better warnings
          iterator->bitpack_command[0] = ret[0];
          iterator->bitpack_command[1] = ret[1];
          iterator->bitpack_command[2] = ret[2];
          iterator->bitpack_command[3] = ret[3];
          iterator->bitpack_command[4] = ret[4];
          iterator->bitpack_pos = 0;
          iterator->bitpack_length = 1;
          goto again;
#endif
      }
#endif
  return (CtxCommand *) ret;
}

static void ctx_drawlist_compact (CtxDrawlist *drawlist);
static void
ctx_drawlist_resize (CtxDrawlist *drawlist, int desired_size)
{
  int flags=drawlist->flags;
#if CTX_DRAWLIST_STATIC
  if (flags & CTX_DRAWLIST_EDGE_LIST)
    {
      static CtxSegment sbuf[CTX_MAX_EDGE_LIST_SIZE];
      drawlist->entries = (CtxEntry*)&sbuf[0];
      drawlist->size = CTX_MAX_EDGE_LIST_SIZE;
    }
  else if (flags & CTX_DRAWLIST_CURRENT_PATH)
    {
      static CtxEntry sbuf[CTX_MAX_EDGE_LIST_SIZE];
      drawlist->entries = &sbuf[0];
      drawlist->size = CTX_MAX_EDGE_LIST_SIZE;
    }
  else
    {
      static CtxEntry sbuf[CTX_MAX_JOURNAL_SIZE];
      drawlist->entries = &sbuf[0];
      drawlist->size = CTX_MAX_JOURNAL_SIZE;
      ctx_drawlist_compact (drawlist);
    }
#else
  int new_size = desired_size;
  int min_size = CTX_MIN_JOURNAL_SIZE;
  int max_size = CTX_MAX_JOURNAL_SIZE;
  if ((flags & CTX_DRAWLIST_EDGE_LIST))
    {
      min_size = CTX_MIN_EDGE_LIST_SIZE;
      max_size = CTX_MAX_EDGE_LIST_SIZE;
    }
  else if (flags & CTX_DRAWLIST_CURRENT_PATH)
    {
      min_size = CTX_MIN_EDGE_LIST_SIZE;
      max_size = CTX_MAX_EDGE_LIST_SIZE;
    }
  else
    {
#if 0
      ctx_drawlist_compact (drawlist);
#endif
    }

  if (CTX_UNLIKELY(new_size < drawlist->size))
    { return; }
  if (CTX_UNLIKELY(drawlist->size == max_size))
    { return; }
  new_size = ctx_maxi (new_size, min_size);
  //if (new_size < drawlist->count)
  //  { new_size = drawlist->count + 4; }
  new_size = ctx_mini (new_size, max_size);
  if (new_size != drawlist->size)
    {
      int item_size = sizeof (CtxEntry);
      if (flags & CTX_DRAWLIST_EDGE_LIST) item_size = sizeof (CtxSegment);
      //fprintf (stderr, "growing drawlist %p %i to %d from %d\n", drawlist, flags, new_size, drawlist->size);
  if (drawlist->entries)
    {
      //printf ("grow %p to %d from %d\n", drawlist, new_size, drawlist->size);
      CtxEntry *ne =  (CtxEntry *) malloc (item_size * new_size);
      memcpy (ne, drawlist->entries, drawlist->size * item_size );
      free (drawlist->entries);
      drawlist->entries = ne;
      //drawlist->entries = (CtxEntry*)malloc (drawlist->entries, item_size * new_size);
    }
  else
    {
      //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size);
      drawlist->entries = (CtxEntry *) malloc (item_size * new_size);
    }
  drawlist->size = new_size;
    }
  //fprintf (stderr, "drawlist %p is %d\n", drawlist, drawlist->size);
#endif
}

static void
ctx_edgelist_resize (CtxDrawlist *drawlist, int desired_size)
{
#if CTX_DRAWLIST_STATIC
    {
      static CtxSegment sbuf[CTX_MAX_EDGE_LIST_SIZE];
      drawlist->entries = (CtxEntry*)&sbuf[0];
      drawlist->size = CTX_MAX_EDGE_LIST_SIZE;
    }
#else
  int new_size = desired_size;
  int min_size = CTX_MIN_JOURNAL_SIZE;
  int max_size = CTX_MAX_JOURNAL_SIZE;
    {
      min_size = CTX_MIN_EDGE_LIST_SIZE;
      max_size = CTX_MAX_EDGE_LIST_SIZE;
    }

  if (CTX_UNLIKELY(drawlist->size == max_size))
    { return; }
  new_size = ctx_maxi (new_size, min_size);
  //if (new_size < drawlist->count)
  //  { new_size = drawlist->count + 4; }
  new_size = ctx_mini (new_size, max_size);
  if (new_size != drawlist->size)
    {
      int item_size = item_size = sizeof (CtxSegment);
      //fprintf (stderr, "growing drawlist %p %i to %d from %d\n", drawlist, flags, new_size, drawlist->size);
  if (drawlist->entries)
    {
      //printf ("grow %p to %d from %d\n", drawlist, new_size, drawlist->size);
      CtxEntry *ne =  (CtxEntry *) malloc (item_size * new_size);
      memcpy (ne, drawlist->entries, drawlist->size * item_size );
      free (drawlist->entries);
      drawlist->entries = ne;
      //drawlist->entries = (CtxEntry*)malloc (drawlist->entries, item_size * new_size);
    }
  else
    {
      //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size);
      drawlist->entries = (CtxEntry *) malloc (item_size * new_size);
    }
  drawlist->size = new_size;
    }
  //fprintf (stderr, "drawlist %p is %d\n", drawlist, drawlist->size);
#endif
}


static inline int
ctx_drawlist_add_single (CtxDrawlist *drawlist, CtxEntry *entry)
{
  int max_size = CTX_MAX_JOURNAL_SIZE;
  int ret = drawlist->count;
  int flags = drawlist->flags;
  if (CTX_LIKELY((flags & CTX_DRAWLIST_EDGE_LIST ||
       flags & CTX_DRAWLIST_CURRENT_PATH)))
    {
      max_size = CTX_MAX_EDGE_LIST_SIZE;
    }
  if (CTX_UNLIKELY(flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES))
    {
      return ret;
    }
  if (CTX_UNLIKELY(ret + 64 >= drawlist->size - 40))
    {
      int new_ = CTX_MAX (drawlist->size * 2, ret + 1024);
      ctx_drawlist_resize (drawlist, new_);
    }

  if (CTX_UNLIKELY(drawlist->count >= max_size - 20))
    {
      return 0;
    }
  if ((flags & CTX_DRAWLIST_EDGE_LIST))
    ((CtxSegment*)(drawlist->entries))[drawlist->count] = *(CtxSegment*)entry;
  else
    drawlist->entries[drawlist->count] = *entry;
  ret = drawlist->count;
  drawlist->count++;
  return ret;
}

static inline int
ctx_edgelist_add_single (CtxDrawlist *drawlist, CtxEntry *entry)
{
  int max_size = CTX_MAX_EDGE_LIST_SIZE;
  int ret = drawlist->count;
  if ((ret + 64 >= drawlist->size - 40))
    {
      int new_ = CTX_MAX (drawlist->size * 2, ret + 1024);
      ctx_edgelist_resize (drawlist, new_);
    }

  if (CTX_UNLIKELY(drawlist->count >= max_size - 20))
    {
      return 0;
    }
  ((CtxSegment*)(drawlist->entries))[drawlist->count] = *(CtxSegment*)entry;
  ret = drawlist->count;
  drawlist->count++;
  return ret;
}

int
ctx_add_single (Ctx *ctx, void *entry)
{
  return ctx_drawlist_add_single (&ctx->drawlist, (CtxEntry *) entry);
}

static inline int
ctx_drawlist_add_entry (CtxDrawlist *drawlist, CtxEntry *entry)
{
  int length = ctx_conts_for_entry (entry) + 1;
  int ret = 0;
  for (int i = 0; i < length; i ++)
    {
      ret = ctx_drawlist_add_single (drawlist, &entry[i]);
    }
  return ret;
}

#if 0
int
ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry)
{
  int length = ctx_conts_for_entry (entry) + 1;
  int tmp_pos = ctx_drawlist_add_entry (drawlist, entry);
  for (int i = 0; i < length; i++)
  {
    for (int j = pos + i + 1; j < tmp_pos; j++)
      drawlist->entries[j] = entry[j-1];
    drawlist->entries[pos + i] = entry[i];
  }
  return pos;
}
#endif
int
ctx_drawlist_insert_entry (CtxDrawlist *drawlist, int pos, CtxEntry *entry)
{
  int length = ctx_conts_for_entry (entry) + 1;
  int tmp_pos = ctx_drawlist_add_entry (drawlist, entry);
#if 1
  for (int i = 0; i < length; i++)
  {
    for (int j = tmp_pos; j > pos + i; j--)
      drawlist->entries[j] = drawlist->entries[j-1];
    drawlist->entries[pos + i] = entry[i];
  }
  return pos;
#endif
  return tmp_pos;
}

int ctx_append_drawlist (Ctx *ctx, void *data, int length)
{
  CtxEntry *entries = (CtxEntry *) data;
  if (length % sizeof (CtxEntry) )
    {
      ctx_log("drawlist not multiple of 9\n");
      return -1;
    }
  for (unsigned int i = 0; i < length / sizeof (CtxEntry); i++)
    {
      ctx_drawlist_add_single (&ctx->drawlist, &entries[i]);
    }
  return 0;
}

int ctx_set_drawlist (Ctx *ctx, void *data, int length)
{
  CtxDrawlist *drawlist = &ctx->drawlist;
  ctx->drawlist.count = 0;
  if (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES)
    {
      return -1;
    }
  if (CTX_UNLIKELY(length % 9)) return -1;
  ctx_drawlist_resize (drawlist, length/9);
  memcpy (drawlist->entries, data, length);
  drawlist->count = length / 9;
  return length;
}

int ctx_get_drawlist_count (Ctx *ctx)
{
  return ctx->drawlist.count;
}

const CtxEntry *ctx_get_drawlist (Ctx *ctx)
{
  return ctx->drawlist.entries;
}

int
ctx_add_data (Ctx *ctx, void *data, int length)
{
  if (CTX_UNLIKELY(length % sizeof (CtxEntry) ))
    {
      //ctx_log("err\n");
      return -1;
    }
  /* some more input verification might be in order.. like
   * verify that it is well-formed up to length?
   *
   * also - it would be very useful to stop processing
   * upon flush - and do drawlist resizing.
   */
  return ctx_drawlist_add_entry (&ctx->drawlist, (CtxEntry *) data);
}

int ctx_drawlist_add_u32 (CtxDrawlist *drawlist, CtxCode code, uint32_t u32[2])
{
  CtxEntry entry = {code, {{0},}};
  entry.data.u32[0] = u32[0];
  entry.data.u32[1] = u32[1];
  return ctx_drawlist_add_single (drawlist, &entry);
}

int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length)
{
  CtxEntry entry = {CTX_DATA, {{0},}};
  entry.data.u32[0] = 0;
  entry.data.u32[1] = 0;
  int ret = ctx_drawlist_add_single (drawlist, &entry);
  if (CTX_UNLIKELY(!data)) { return -1; }
  int length_in_blocks;
  if (length <= 0) { length = strlen ( (char *) data) + 1; }
  length_in_blocks = length / sizeof (CtxEntry);
  length_in_blocks += (length % sizeof (CtxEntry) ) ?1:0;
  if (drawlist->count + length_in_blocks + 4 > drawlist->size)
    { ctx_drawlist_resize (drawlist, drawlist->count * 1.2 + length_in_blocks + 32); }
  if (CTX_UNLIKELY(drawlist->count >= drawlist->size))
    { return -1; }
  drawlist->count += length_in_blocks;
  drawlist->entries[ret].data.u32[0] = length;
  drawlist->entries[ret].data.u32[1] = length_in_blocks;
  memcpy (&drawlist->entries[ret+1], data, length);
  {
    //int reverse = ctx_drawlist_add (drawlist, CTX_DATA_REV);
    CtxEntry entry = {CTX_DATA_REV, {{0},}};
    entry.data.u32[0] = length;
    entry.data.u32[1] = length_in_blocks;
    ctx_drawlist_add_single (drawlist, &entry);
    /* this reverse marker exist to enable more efficient
       front to back traversal, can be ignored in other
       direction, is this needed after string setters as well?
     */
  }
  return ret;
}

static inline CtxEntry
ctx_void (CtxCode code)
{
  CtxEntry command;
  command.code = code;
  return command;
}

static inline CtxEntry
ctx_f (CtxCode code, float x, float y)
{
  CtxEntry command;
  command.code = code;
  command.data.f[0] = x;
  command.data.f[1] = y;
  return command;
}

static CtxEntry
ctx_u32 (CtxCode code, uint32_t x, uint32_t y)
{
  CtxEntry command = ctx_void (code);
  command.data.u32[0] = x;
  command.data.u32[1] = y;
  return command;
}

#if 0
static CtxEntry
ctx_s32 (CtxCode code, int32_t x, int32_t y)
{
  CtxEntry command = ctx_void (code);
  command.data.s32[0] = x;
  command.data.s32[1] = y;
  return command;
}
#endif

static inline CtxEntry
ctx_s16 (CtxCode code, int x0, int y0, int x1, int y1)
{
  CtxEntry command;
  command.code = code;
  command.data.s16[0] = x0;
  command.data.s16[1] = y0;
  command.data.s16[2] = x1;
  command.data.s16[3] = y1;
  return command;
}

static inline CtxSegment
ctx_segment_s16 (CtxCode code, int x0, int y0, int x1, int y1)
{
  CtxSegment command;
  command.code = code;
  command.data.s16[0] = x0;
  command.data.s16[1] = y0;
  command.data.s16[2] = x1;
  command.data.s16[3] = y1;
  return command;
}

static CtxEntry
ctx_u8 (CtxCode code,
        uint8_t a, uint8_t b, uint8_t c, uint8_t d,
        uint8_t e, uint8_t f, uint8_t g, uint8_t h)
{
  CtxEntry command;
  command.code = code;
  command.data.u8[0] = a;
  command.data.u8[1] = b;
  command.data.u8[2] = c;
  command.data.u8[3] = d;
  command.data.u8[4] = e;
  command.data.u8[5] = f;
  command.data.u8[6] = g;
  command.data.u8[7] = h;
  return command;
}

#define CTX_PROCESS_VOID(cmd) do {\
  CtxEntry command = {cmd};\
  ctx_process (ctx, &command);}while(0) \

#define CTX_PROCESS_F(cmd, x, y) do {\
  CtxEntry command = ctx_f(cmd, x, y);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_F1(cmd, x) do {\
  CtxEntry command = ctx_f(cmd, x, 0);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_U32(cmd, x, y) do {\
  CtxEntry command = ctx_u32(cmd, x, y);\
  ctx_process (ctx, &command);}while(0)

#define CTX_PROCESS_U8(cmd, x) do {\
  CtxEntry command = ctx_u8(cmd, x,0,0,0,0,0,0,0);\
  ctx_process (ctx, &command);}while(0)


static void
ctx_process_cmd_str_with_len (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1, int len)
{
  CtxEntry commands[1 + 2 + (len+1+1)/9];
  ctx_memset (commands, 0, sizeof (commands) );
  commands[0] = ctx_u32 (code, arg0, arg1);
  commands[1].code = CTX_DATA;
  commands[1].data.u32[0] = len;
  commands[1].data.u32[1] = (len+1+1)/9 + 1;
  memcpy( (char *) &commands[2].data.u8[0], string, len);
  ( (char *) (&commands[2].data.u8[0]) ) [len]=0;
  ctx_process (ctx, commands);
}

static void
ctx_process_cmd_str (Ctx *ctx, CtxCode code, const char *string, uint32_t arg0, uint32_t arg1)
{
  ctx_process_cmd_str_with_len (ctx, code, string, arg0, arg1, strlen (string));
}

static void
ctx_process_cmd_str_float (Ctx *ctx, CtxCode code, const char *string, float arg0, float arg1)
{
  uint32_t iarg0;
  uint32_t iarg1;
  memcpy (&iarg0, &arg0, sizeof (iarg0));
  memcpy (&iarg1, &arg1, sizeof (iarg1));
  ctx_process_cmd_str_with_len (ctx, code, string, iarg0, iarg1, strlen (string));
}

#if CTX_BITPACK_PACKER
static int
ctx_last_history (CtxDrawlist *drawlist)
{
  int last_history = 0;
  int i = 0;
  while (i < drawlist->count)
    {
      CtxEntry *entry = &drawlist->entries[i];
      i += (ctx_conts_for_entry (entry) + 1);
    }
  return last_history;
}
#endif

#if CTX_BITPACK_PACKER

static float
find_max_dev (CtxEntry *entry, int nentrys)
{
  float max_dev = 0.0;
  for (int c = 0; c < nentrys; c++)
    {
      for (int d = 0; d < 2; d++)
        {
          if (entry[c].data.f[d] > max_dev)
            { max_dev = entry[c].data.f[d]; }
          if (entry[c].data.f[d] < -max_dev)
            { max_dev = -entry[c].data.f[d]; }
        }
    }
  return max_dev;
}

static void
pack_s8_args (CtxEntry *entry, int npairs)
{
  for (int c = 0; c < npairs; c++)
    for (int d = 0; d < 2; d++)
      { entry[0].data.s8[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; }
}

static void
pack_s16_args (CtxEntry *entry, int npairs)
{
  for (int c = 0; c < npairs; c++)
    for (int d = 0; d < 2; d++)
      { entry[0].data.s16[c*2+d]=entry[c].data.f[d] * CTX_SUBDIV; }
}
#endif

#if CTX_BITPACK_PACKER
static void
ctx_drawlist_remove_tiny_curves (CtxDrawlist *drawlist, int start_pos)
{
  CtxIterator iterator;
  if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0)
    { return; }
  ctx_iterator_init (&iterator, drawlist, start_pos, CTX_ITERATOR_FLAT);
  iterator.end_pos = drawlist->count - 5;
  CtxCommand *command = NULL;
  while ( (command = ctx_iterator_next (&iterator) ) )
    {
      CtxEntry *entry = &command->entry;
      /* things smaller than this have probably been scaled down
         beyond recognition, bailing for both better packing and less rasterization work
       */
      if (command[0].code == CTX_REL_CURVE_TO)
        {
          float max_dev = find_max_dev (entry, 3);
          if (max_dev < 1.0)
            {
              entry[0].code = CTX_REL_LINE_TO;
              entry[0].data.f[0] = entry[2].data.f[0];
              entry[0].data.f[1] = entry[2].data.f[1];
              entry[1].code = CTX_NOP;
              entry[2].code = CTX_NOP;
            }
        }
    }
}
#endif

#if CTX_BITPACK_PACKER
static void
ctx_drawlist_bitpack (CtxDrawlist *drawlist, int start_pos)
{
#if CTX_BITPACK
  int i = 0;
  if ( (drawlist->flags & CTX_TRANSFORMATION_BITPACK) == 0)
    { return; }
  ctx_drawlist_remove_tiny_curves (drawlist, drawlist->bitpack_pos);
  i = drawlist->bitpack_pos;
  if (start_pos > i)
    { i = start_pos; }
  while (i < drawlist->count - 4) /* the -4 is to avoid looking past
                                    initialized data we're not ready
                                    to bitpack yet*/
    {
      CtxEntry *entry = &drawlist->entries[i];
      if (entry[0].code == CTX_SET_RGBA_U8 &&
          entry[1].code == CTX_MOVE_TO &&
          entry[2].code == CTX_REL_LINE_TO &&
          entry[3].code == CTX_REL_LINE_TO &&
          entry[4].code == CTX_REL_LINE_TO &&
          entry[5].code == CTX_REL_LINE_TO &&
          entry[6].code == CTX_FILL &&
          ctx_fabsf (entry[2].data.f[0] - 1.0f) < 0.02f &&
          ctx_fabsf (entry[3].data.f[1] - 1.0f) < 0.02f)
        {
          entry[0].code = CTX_SET_PIXEL;
          entry[0].data.u16[2] = entry[1].data.f[0];
          entry[0].data.u16[3] = entry[1].data.f[1];
          entry[1].code = CTX_NOP;
          entry[2].code = CTX_NOP;
          entry[3].code = CTX_NOP;
          entry[4].code = CTX_NOP;
          entry[5].code = CTX_NOP;
          entry[6].code = CTX_NOP;
        }
#if 1
      else if (entry[0].code == CTX_REL_LINE_TO)
        {
          if (entry[1].code == CTX_REL_LINE_TO &&
              entry[2].code == CTX_REL_LINE_TO &&
              entry[3].code == CTX_REL_LINE_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_LINE_TO_X4;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else if (entry[1].code == CTX_REL_CURVE_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_LINE_TO_REL_CURVE_TO;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else if (entry[1].code == CTX_REL_LINE_TO &&
                   entry[2].code == CTX_REL_LINE_TO &&
                   entry[3].code == CTX_REL_LINE_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_LINE_TO_X4;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else if (entry[1].code == CTX_REL_MOVE_TO)
            {
              float max_dev = find_max_dev (entry, 2);
              if (max_dev < 31000 / CTX_SUBDIV)
                {
                  pack_s16_args (entry, 2);
                  entry[0].code = CTX_REL_LINE_TO_REL_MOVE_TO;
                  entry[1].code = CTX_NOP;
                }
            }
          else if (entry[1].code == CTX_REL_LINE_TO)
            {
              float max_dev = find_max_dev (entry, 2);
              if (max_dev < 31000 / CTX_SUBDIV)
                {
                  pack_s16_args (entry, 2);
                  entry[0].code = CTX_REL_LINE_TO_X2;
                  entry[1].code = CTX_NOP;
                }
            }
        }
#endif
#if 1
      else if (entry[0].code == CTX_REL_CURVE_TO)
        {
          if (entry[3].code == CTX_REL_LINE_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else if (entry[3].code == CTX_REL_MOVE_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_CURVE_TO_REL_MOVE_TO;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else
            {
              float max_dev = find_max_dev (entry, 3);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 3);
                  ctx_arg_s8 (6) =
                    ctx_arg_s8 (7) = 0;
                  entry[0].code = CTX_REL_CURVE_TO_REL_LINE_TO;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                }
            }
        }
#endif
#if 1
      else if (entry[0].code == CTX_REL_QUAD_TO)
        {
          if (entry[2].code == CTX_REL_QUAD_TO)
            {
              float max_dev = find_max_dev (entry, 4);
              if (max_dev < 114 / CTX_SUBDIV)
                {
                  pack_s8_args (entry, 4);
                  entry[0].code = CTX_REL_QUAD_TO_REL_QUAD_TO;
                  entry[1].code = CTX_NOP;
                  entry[2].code = CTX_NOP;
                  entry[3].code = CTX_NOP;
                }
            }
          else
            {
              float max_dev = find_max_dev (entry, 2);
              if (max_dev < 3100 / CTX_SUBDIV)
                {
                  pack_s16_args (entry, 2);
                  entry[0].code = CTX_REL_QUAD_TO_S16;
                  entry[1].code = CTX_NOP;
                }
            }
        }
#endif
#if 1
      else if (entry[0].code == CTX_FILL &&
               entry[1].code == CTX_MOVE_TO)
        {
          entry[0] = entry[1];
          entry[0].code = CTX_FILL_MOVE_TO;
          entry[1].code = CTX_NOP;
        }
#endif
#if 1
      else if (entry[0].code == CTX_MOVE_TO &&
               entry[1].code == CTX_MOVE_TO &&
               entry[2].code == CTX_MOVE_TO)
        {
          entry[0]      = entry[2];
          entry[0].code = CTX_MOVE_TO;
          entry[1].code = CTX_NOP;
          entry[2].code = CTX_NOP;
        }
#endif
#if 1
      else if ( (entry[0].code == CTX_MOVE_TO &&
                 entry[1].code == CTX_MOVE_TO) ||
                (entry[0].code == CTX_REL_MOVE_TO &&
                 entry[1].code == CTX_MOVE_TO) )
        {
          entry[0]      = entry[1];
          entry[0].code = CTX_MOVE_TO;
          entry[1].code = CTX_NOP;
        }
#endif
      i += (ctx_conts_for_entry (entry) + 1);
    }
  int source = drawlist->bitpack_pos;
  int target = drawlist->bitpack_pos;
  int removed = 0;
  /* remove nops that have been inserted as part of shortenings
   */
  while (source < drawlist->count)
    {
      CtxEntry *sentry = &drawlist->entries[source];
      CtxEntry *tentry = &drawlist->entries[target];
      while (sentry->code == CTX_NOP && source < drawlist->count)
        {
          source++;
          sentry = &drawlist->entries[source];
          removed++;
        }
      if (sentry != tentry)
        { *tentry = *sentry; }
      source ++;
      target ++;
    }
  drawlist->count -= removed;
  drawlist->bitpack_pos = drawlist->count;
#endif
}

#endif

static void
ctx_drawlist_compact (CtxDrawlist *drawlist)
{
#if CTX_BITPACK_PACKER
  int last_history;
  last_history = ctx_last_history (drawlist);
#else
  if (drawlist) {};
#endif
#if CTX_BITPACK_PACKER
  ctx_drawlist_bitpack (drawlist, last_history);
#endif
}

uint8_t *ctx_define_texture_pixel_data (CtxEntry *entry)
{
  return &entry[2 + 1 + 1 + ctx_conts_for_entry (&entry[2])].data.u8[0];
}

#ifndef __CTX_TRANSFORM
#define __CTX_TRANSFORM

static inline void
_ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y)
{
  float x_in = *x;
  float y_in = *y;
  *x = ( (x_in * m->m[0][0]) + (y_in * m->m[1][0]) + m->m[2][0]);
  *y = ( (y_in * m->m[1][1]) + (x_in * m->m[0][1]) + m->m[2][1]);
}

void
ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y)
{
  _ctx_matrix_apply_transform (m, x, y);
}

static inline void
_ctx_user_to_device (CtxState *state, float *x, float *y)
{
  _ctx_matrix_apply_transform (&state->gstate.transform, x, y);
}

static void
_ctx_user_to_device_distance (CtxState *state, float *x, float *y)
{
  const CtxMatrix *m = &state->gstate.transform;
  _ctx_matrix_apply_transform (m, x, y);
  *x -= m->m[2][0];
  *y -= m->m[2][1];
}

void ctx_user_to_device          (Ctx *ctx, float *x, float *y)
{
  _ctx_user_to_device (&ctx->state, x, y);
}
void ctx_user_to_device_distance (Ctx *ctx, float *x, float *y)
{
  _ctx_user_to_device_distance (&ctx->state, x, y);
}

static void
ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f)
{
  matrix->m[0][0] = a;
  matrix->m[0][1] = b;
  matrix->m[1][0] = c;
  matrix->m[1][1] = d;
  matrix->m[2][0] = e;
  matrix->m[2][1] = f;
}

static inline void
_ctx_matrix_identity (CtxMatrix *matrix)
{
  matrix->m[0][0] = 1.0f;
  matrix->m[0][1] = 0.0f;
  matrix->m[1][0] = 0.0f;
  matrix->m[1][1] = 1.0f;
  matrix->m[2][0] = 0.0f;
  matrix->m[2][1] = 0.0f;
}

void
ctx_matrix_identity (CtxMatrix *matrix)
{
  _ctx_matrix_identity (matrix);
}

static void
_ctx_matrix_multiply (CtxMatrix       *result,
                      const CtxMatrix *t,
                      const CtxMatrix *s)
{
  CtxMatrix r;
  r.m[0][0] = t->m[0][0] * s->m[0][0] + t->m[0][1] * s->m[1][0];
  r.m[0][1] = t->m[0][0] * s->m[0][1] + t->m[0][1] * s->m[1][1];
  r.m[1][0] = t->m[1][0] * s->m[0][0] + t->m[1][1] * s->m[1][0];
  r.m[1][1] = t->m[1][0] * s->m[0][1] + t->m[1][1] * s->m[1][1];
  r.m[2][0] = t->m[2][0] * s->m[0][0] + t->m[2][1] * s->m[1][0] + s->m[2][0];
  r.m[2][1] = t->m[2][0] * s->m[0][1] + t->m[2][1] * s->m[1][1] + s->m[2][1];
  *result = r;
}

void
ctx_matrix_multiply (CtxMatrix       *result,
                     const CtxMatrix *t,
                     const CtxMatrix *s)
{
  _ctx_matrix_multiply (result, t, s);
}

void
ctx_matrix_translate (CtxMatrix *matrix, float x, float y)
{
  CtxMatrix transform;
  transform.m[0][0] = 1.0f;
  transform.m[0][1] = 0.0f;
  transform.m[1][0] = 0.0f;
  transform.m[1][1] = 1.0f;
  transform.m[2][0] = x;
  transform.m[2][1] = y;
  _ctx_matrix_multiply (matrix, &transform, matrix);
}

void
ctx_matrix_scale (CtxMatrix *matrix, float x, float y)
{
  CtxMatrix transform;
  transform.m[0][0] = x;
  transform.m[0][1] = 0.0f;
  transform.m[1][0] = 0.0f;
  transform.m[1][1] = y;
  transform.m[2][0] = 0.0f;
  transform.m[2][1] = 0.0f;
  _ctx_matrix_multiply (matrix, &transform, matrix);
}

void
ctx_matrix_rotate (CtxMatrix *matrix, float angle)
{
  CtxMatrix transform;
  float val_sin = ctx_sinf (angle);
  float val_cos = ctx_cosf (angle);
  transform.m[0][0] =  val_cos;
  transform.m[0][1] = val_sin;
  transform.m[1][0] = -val_sin;
  transform.m[1][1] = val_cos;
  transform.m[2][0] =     0.0f;
  transform.m[2][1] = 0.0f;
  _ctx_matrix_multiply (matrix, &transform, matrix);
}

#if 0
static void
ctx_matrix_skew_x (CtxMatrix *matrix, float angle)
{
  CtxMatrix transform;
  float val_tan = ctx_tanf (angle);
  transform.m[0][0] =    1.0f;
  transform.m[0][1] = 0.0f;
  transform.m[1][0] = val_tan;
  transform.m[1][1] = 1.0f;
  transform.m[2][0] =    0.0f;
  transform.m[2][1] = 0.0f;
  _ctx_matrix_multiply (matrix, &transform, matrix);
}

static void
ctx_matrix_skew_y (CtxMatrix *matrix, float angle)
{
  CtxMatrix transform;
  float val_tan = ctx_tanf (angle);
  transform.m[0][0] =    1.0f;
  transform.m[0][1] = val_tan;
  transform.m[1][0] =    0.0f;
  transform.m[1][1] = 1.0f;
  transform.m[2][0] =    0.0f;
  transform.m[2][1] = 0.0f;
  _ctx_matrix_multiply (matrix, &transform, matrix);
}
#endif


void
ctx_identity (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_IDENTITY);
}



void
ctx_apply_transform (Ctx *ctx, float a, float b,  // hscale, hskew
                     float c, float d,  // vskew,  vscale
                     float e, float f)  // htran,  vtran
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_APPLY_TRANSFORM, a, b),
    ctx_f (CTX_CONT,            c, d),
    ctx_f (CTX_CONT,            e, f)
  };
  ctx_process (ctx, command);
}

void
ctx_get_transform  (Ctx *ctx, float *a, float *b,
                    float *c, float *d,
                    float *e, float *f)
{
  if (a) { *a = ctx->state.gstate.transform.m[0][0]; }
  if (b) { *b = ctx->state.gstate.transform.m[0][1]; }
  if (c) { *c = ctx->state.gstate.transform.m[1][0]; }
  if (d) { *d = ctx->state.gstate.transform.m[1][1]; }
  if (e) { *e = ctx->state.gstate.transform.m[2][0]; }
  if (f) { *f = ctx->state.gstate.transform.m[2][1]; }
}

void
ctx_source_transform (Ctx *ctx, float a, float b,  // hscale, hskew
                      float c, float d,  // vskew,  vscale
                      float e, float f)  // htran,  vtran
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_SOURCE_TRANSFORM, a, b),
    ctx_f (CTX_CONT,             c, d),
    ctx_f (CTX_CONT,             e, f)
  };
  ctx_process (ctx, command);
}

void
ctx_source_transform_matrix (Ctx *ctx, CtxMatrix *matrix)
{
  ctx_source_transform (ctx,
    matrix->m[0][0], matrix->m[0][1],
    matrix->m[1][0], matrix->m[1][1],
    matrix->m[2][0], matrix->m[2][1]);
}

void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix)
{
  ctx_apply_transform (ctx,
                       matrix->m[0][0], matrix->m[0][1],
                       matrix->m[1][0], matrix->m[1][1],
                       matrix->m[2][0], matrix->m[2][1]);
}

void ctx_get_matrix (Ctx *ctx, CtxMatrix *matrix)
{
  *matrix = ctx->state.gstate.transform;
}

void ctx_set_matrix (Ctx *ctx, CtxMatrix *matrix)
{
  ctx_identity (ctx);
  ctx_apply_matrix (ctx, matrix);
}

void ctx_rotate (Ctx *ctx, float x)
{
  if (x == 0.0f)
    return;
  CTX_PROCESS_F1 (CTX_ROTATE, x);
  if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE)
    { ctx->drawlist.count--; }
}

void ctx_scale (Ctx *ctx, float x, float y)
{
  if (x == 1.0f && y == 1.0f)
    return;
  CTX_PROCESS_F (CTX_SCALE, x, y);
  if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE)
    { ctx->drawlist.count--; }
}

void ctx_translate (Ctx *ctx, float x, float y)
{
  if (x == 0.0f && y == 0.0f)
    return;
  CTX_PROCESS_F (CTX_TRANSLATE, x, y);
  if (ctx->transformation & CTX_TRANSFORMATION_SCREEN_SPACE)
    { ctx->drawlist.count--; }
}

void
ctx_matrix_invert (CtxMatrix *m)
{
  CtxMatrix t = *m;
  float invdet, det = m->m[0][0] * m->m[1][1] -
                      m->m[1][0] * m->m[0][1];
  if (det > -0.0000001f && det < 0.0000001f)
    {
      m->m[0][0] = m->m[0][1] =
                     m->m[1][0] = m->m[1][1] =
                                    m->m[2][0] = m->m[2][1] = 0.0;
      return;
    }
  invdet = 1.0f / det;
  m->m[0][0] = t.m[1][1] * invdet;
  m->m[1][0] = -t.m[1][0] * invdet;
  m->m[2][0] = (t.m[1][0] * t.m[2][1] - t.m[1][1] * t.m[2][0]) * invdet;
  m->m[0][1] = -t.m[0][1] * invdet;
  m->m[1][1] = t.m[0][0] * invdet;
  m->m[2][1] = (t.m[0][1] * t.m[2][0] - t.m[0][0] * t.m[2][1]) * invdet ;
}



#endif
#ifndef __CTX_COLOR
#define __CTX_COLOR

int ctx_color_model_get_components (CtxColorModel model)
{
  switch (model)
    {
      case CTX_GRAY:
        return 1;
      case CTX_GRAYA:
      case CTX_GRAYA_A:
        return 1;
      case CTX_RGB:
      case CTX_LAB:
      case CTX_LCH:
      case CTX_DRGB:
        return 3;
      case CTX_CMYK:
      case CTX_DCMYK:
      case CTX_LABA:
      case CTX_LCHA:
      case CTX_RGBA:
      case CTX_DRGBA:
      case CTX_RGBA_A:
      case CTX_RGBA_A_DEVICE:
        return 4;
      case CTX_DCMYKA:
      case CTX_CMYKA:
      case CTX_CMYKA_A:
      case CTX_DCMYKA_A:
        return 5;
    }
  return 0;
}

#if 0
inline static float ctx_u8_to_float (uint8_t val_u8)
{
  float val_f = val_u8 / 255.0;
  return val_f;
}
#else
float ctx_u8_float[256];
#endif

CtxColor *ctx_color_new (void)
{
  CtxColor *color = (CtxColor*)ctx_calloc (sizeof (CtxColor), 1);
  return color;
}

int ctx_color_is_transparent (CtxColor *color)
{
  return color->alpha <= 0.001f;
}


void ctx_color_free (CtxColor *color)
{
  free (color);
}

static void ctx_color_set_RGBA8 (CtxState *state, CtxColor *color, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  color->original = color->valid = CTX_VALID_RGBA_U8;
  color->rgba[0] = r;
  color->rgba[1] = g;
  color->rgba[2] = b;
  color->rgba[3] = a;
#if CTX_ENABLE_CM
  color->space = state->gstate.device_space;
#endif
}

#if 0
static void ctx_color_set_RGBA8_ (CtxColor *color, const uint8_t *in)
{
  ctx_color_set_RGBA8 (color, in[0], in[1], in[2], in[3]);
}
#endif

static void ctx_color_set_graya (CtxState *state, CtxColor *color, float gray, float alpha)
{
  color->original = color->valid = CTX_VALID_GRAYA;
  color->l = gray;
  color->alpha = alpha;
}
#if 0
static void ctx_color_set_graya_ (CtxColor *color, const float *in)
{
  return ctx_color_set_graya (color, in[0], in[1]);
}
#endif

void ctx_color_set_rgba (CtxState *state, CtxColor *color, float r, float g, float b, float a)
{
#if CTX_ENABLE_CM
  color->original = color->valid = CTX_VALID_RGBA;
  color->red      = r;
  color->green    = g;
  color->blue     = b;
  color->space    = state->gstate.rgb_space;
#else
  color->original     = color->valid = CTX_VALID_RGBA_DEVICE;
  color->device_red   = r;
  color->device_green = g;
  color->device_blue  = b;
#endif
  color->alpha        = a;
}

static void ctx_color_set_drgba (CtxState *state, CtxColor *color, float r, float g, float b, float a)
{
#if CTX_ENABLE_CM
  color->original     = color->valid = CTX_VALID_RGBA_DEVICE;
  color->device_red   = r;
  color->device_green = g;
  color->device_blue  = b;
  color->alpha        = a;
  color->space        = state->gstate.device_space;
#else
  ctx_color_set_rgba (state, color, r, g, b, a);
#endif
}

#if 0
static void ctx_color_set_rgba_ (CtxState *state, CtxColor *color, const float *in)
{
  ctx_color_set_rgba (color, in[0], in[1], in[2], in[3]);
}
#endif

/* the baseline conversions we have whether CMYK support is enabled or not,
 * providing an effort at right rendering
 */
static void ctx_cmyk_to_rgb (float c, float m, float y, float k, float *r, float *g, float *b)
{
  *r = (1.0f-c) * (1.0f-k);
  *g = (1.0f-m) * (1.0f-k);
  *b = (1.0f-y) * (1.0f-k);
}

void ctx_rgb_to_cmyk (float r, float g, float b,
                      float *c_out, float *m_out, float *y_out, float *k_out)
{
  float c = 1.0f - r;
  float m = 1.0f - g;
  float y = 1.0f - b;
  float k = ctx_minf (c, ctx_minf (y, m) );
  if (k < 1.0f)
    {
      c = (c - k) / (1.0f - k);
      m = (m - k) / (1.0f - k);
      y = (y - k) / (1.0f - k);
    }
  else
    {
      c = m = y = 0.0f;
    }
  *c_out = c;
  *m_out = m;
  *y_out = y;
  *k_out = k;
}

#if CTX_ENABLE_CMYK
static void ctx_color_set_cmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a)
{
  color->original = color->valid = CTX_VALID_CMYKA;
  color->cyan     = c;
  color->magenta  = m;
  color->yellow   = y;
  color->key      = k;
  color->alpha    = a;
#if CTX_ENABLE_CM
  color->space    = state->gstate.cmyk_space;
#endif
}

static void ctx_color_set_dcmyka (CtxState *state, CtxColor *color, float c, float m, float y, float k, float a)
{
  color->original       = color->valid = CTX_VALID_DCMYKA;
  color->device_cyan    = c;
  color->device_magenta = m;
  color->device_yellow  = y;
  color->device_key     = k;
  color->alpha          = a;
#if CTX_ENABLE_CM
  color->space = state->gstate.device_space;
#endif
}

#endif

#if CTX_ENABLE_CM

static void ctx_rgb_user_to_device (CtxState *state, float rin, float gin, float bin,
                                    float *rout, float *gout, float *bout)
{
#if CTX_BABL
#if 0
  fprintf (stderr, "-[%p %p\n",
    state->gstate.fish_rgbaf_user_to_device,
    state->gstate.fish_rgbaf_device_to_user);
#endif
  if (state->gstate.fish_rgbaf_user_to_device)
  {
    float rgbaf[4]={rin,gin,bin,1.0};
    float rgbafo[4];
    babl_process (state->gstate.fish_rgbaf_user_to_device,
                  rgbaf, rgbafo, 1);

    *rout = rgbafo[0];
    *gout = rgbafo[1];
    *bout = rgbafo[2];
    return;
  }
#endif
  *rout = rin;
  *gout = gin;
  *bout = bin;
}

static void ctx_rgb_device_to_user (CtxState *state, float rin, float gin, float bin,
                                    float *rout, float *gout, float *bout)
{
#if CTX_BABL
#if 0
  fprintf (stderr, "=[%p %p\n",
    state->gstate.fish_rgbaf_user_to_device,
    state->gstate.fish_rgbaf_device_to_user);
#endif
  if (state->gstate.fish_rgbaf_device_to_user)
  {
    float rgbaf[4]={rin,gin,bin,1.0};
    float rgbafo[4];
    babl_process (state->gstate.fish_rgbaf_device_to_user,
                  rgbaf, rgbafo, 1);

    *rout = rgbafo[0];
    *gout = rgbafo[1];
    *bout = rgbafo[2];
    return;
  }
#endif
  *rout = rin;
  *gout = gin;
  *bout = bin;
}
#endif

static void ctx_color_get_drgba (CtxState *state, CtxColor *color, float *out)
{
  if (! (color->valid & CTX_VALID_RGBA_DEVICE) )
    {
#if CTX_ENABLE_CM
      if (color->valid & CTX_VALID_RGBA)
        {
          ctx_rgb_user_to_device (state, color->red, color->green, color->blue,
                                  & (color->device_red), & (color->device_green), & (color->device_blue) );
        }
      else
#endif
        if (color->valid & CTX_VALID_RGBA_U8)
          {
            float red = ctx_u8_to_float (color->rgba[0]);
            float green = ctx_u8_to_float (color->rgba[1]);
            float blue = ctx_u8_to_float (color->rgba[2]);
#if CTX_ENABLE_CM
            ctx_rgb_user_to_device (state, red, green, blue,
                                  & (color->device_red), & (color->device_green), & (color->device_blue) );
#else
            color->device_red = red;
            color->device_green = green;
            color->device_blue = blue;
#endif
            color->alpha        = ctx_u8_to_float (color->rgba[3]);
          }
#if CTX_ENABLE_CMYK
        else if (color->valid & CTX_VALID_CMYKA)
          {
            ctx_cmyk_to_rgb (color->cyan, color->magenta, color->yellow, color->key,
                             &color->device_red,
                             &color->device_green,
                             &color->device_blue);
          }
#endif
        else if (color->valid & CTX_VALID_GRAYA)
          {
            color->device_red   =
              color->device_green =
                color->device_blue  = color->l;
          }
      color->valid |= CTX_VALID_RGBA_DEVICE;
    }
  out[0] = color->device_red;
  out[1] = color->device_green;
  out[2] = color->device_blue;
  out[3] = color->alpha;
}


static inline void
_ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out)
{
#if CTX_ENABLE_CM
  if (! (color->valid & CTX_VALID_RGBA) )
    {
      ctx_color_get_drgba (state, color, out);
      if (color->valid & CTX_VALID_RGBA_DEVICE)
        {
          ctx_rgb_device_to_user (state, color->device_red, color->device_green, color->device_blue,
                                  & (color->red), & (color->green), & (color->blue) );
        }
      color->valid |= CTX_VALID_RGBA;
    }
  out[0] = color->red;
  out[1] = color->green;
  out[2] = color->blue;
  out[3] = color->alpha;
#else
  ctx_color_get_drgba (state, color, out);
#endif
}

void ctx_color_get_rgba (CtxState *state, CtxColor *color, float *out)
{
  _ctx_color_get_rgba (state, color, out);
}



float ctx_float_color_rgb_to_gray (CtxState *state, const float *rgb)
{
        // XXX todo replace with correct according to primaries
  return CTX_CSS_RGB_TO_LUMINANCE(rgb);
}
uint8_t ctx_u8_color_rgb_to_gray (CtxState *state, const uint8_t *rgb)
{
        // XXX todo replace with correct according to primaries
  return CTX_CSS_RGB_TO_LUMINANCE(rgb);
}

void ctx_color_get_graya (CtxState *state, CtxColor *color, float *out)
{
  if (! (color->valid & CTX_VALID_GRAYA) )
    {
      float rgba[4];
      ctx_color_get_drgba (state, color, rgba);
      color->l = ctx_float_color_rgb_to_gray (state, rgba);
      color->valid |= CTX_VALID_GRAYA;
    }
  out[0] = color->l;
  out[1] = color->alpha;
}

#if CTX_ENABLE_CMYK
void ctx_color_get_cmyka (CtxState *state, CtxColor *color, float *out)
{
  if (! (color->valid & CTX_VALID_CMYKA) )
    {
      if (color->valid & CTX_VALID_GRAYA)
        {
          color->cyan = color->magenta = color->yellow = 0.0;
          color->key = color->l;
        }
      else
        {
          float rgba[4];
          ctx_color_get_rgba (state, color, rgba);
          ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2],
                           &color->cyan, &color->magenta, &color->yellow, &color->key);
          color->alpha = rgba[3];
        }
      color->valid |= CTX_VALID_CMYKA;
    }
  out[0] = color->cyan;
  out[1] = color->magenta;
  out[2] = color->yellow;
  out[3] = color->key;
  out[4] = color->alpha;
}

#if 0
static void ctx_color_get_cmyka_u8 (CtxState *state, CtxColor *color, uint8_t *out)
{
  if (! (color->valid & CTX_VALID_CMYKA_U8) )
    {
      float cmyka[5];
      ctx_color_get_cmyka (color, cmyka);
      for (int i = 0; i < 5; i ++)
        { color->cmyka[i] = ctx_float_to_u8 (cmyka[i]); }
      color->valid |= CTX_VALID_CMYKA_U8;
    }
  out[0] = color->cmyka[0];
  out[1] = color->cmyka[1];
  out[2] = color->cmyka[2];
  out[3] = color->cmyka[3];
}
#endif
#endif

static inline void
_ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out)
{
  if (! (color->valid & CTX_VALID_RGBA_U8) )
    {
      float rgba[4];
      ctx_color_get_drgba (state, color, rgba);
      for (int i = 0; i < 4; i ++)
        { color->rgba[i] = ctx_float_to_u8 (rgba[i]); }
      color->valid |= CTX_VALID_RGBA_U8;
    }
  out[0] = color->rgba[0];
  out[1] = color->rgba[1];
  out[2] = color->rgba[2];
  out[3] = color->rgba[3];
}

void
ctx_color_get_rgba8 (CtxState *state, CtxColor *color, uint8_t *out)
{
  _ctx_color_get_rgba8 (state, color, out);
}

void ctx_color_get_graya_u8 (CtxState *state, CtxColor *color, uint8_t *out)
{
  if (! (color->valid & CTX_VALID_GRAYA_U8) )
    {
      float graya[2];
      ctx_color_get_graya (state, color, graya);
      color->l_u8 = ctx_float_to_u8 (graya[0]);
      color->rgba[3] = ctx_float_to_u8 (graya[1]);
      color->valid |= CTX_VALID_GRAYA_U8;
    }
  out[0] = color->l_u8;
  out[1] = color->rgba[3];
}

#if 0
void
ctx_get_rgba (Ctx *ctx, float *rgba)
{
  ctx_color_get_rgba (& (ctx->state), &ctx->state.gstate.source.color, rgba);
}

void
ctx_get_drgba (Ctx *ctx, float *rgba)
{
  ctx_color_get_drgba (& (ctx->state), &ctx->state.gstate.source.color, rgba);
}
#endif

int ctx_in_fill (Ctx *ctx, float x, float y)
{
  float x1, y1, x2, y2;
  ctx_path_extents (ctx, &x1, &y1, &x2, &y2);

  if (x1 <= x && x <= x2 && // XXX - just bounding box for now
      y1 <= y && y <= y2)   //
    return 1;
  return 0;
}


#if CTX_ENABLE_CMYK
#if 0
void
ctx_get_cmyka (Ctx *ctx, float *cmyka)
{
  ctx_color_get_cmyka (& (ctx->state), &ctx->state.gstate.source.color, cmyka);
}
#endif
#endif
#if 0
void
ctx_get_graya (Ctx *ctx, float *ya)
{
  ctx_color_get_graya (& (ctx->state), &ctx->state.gstate.source.color, ya);
}
#endif

void ctx_stroke_source (Ctx *ctx)
{
  CtxEntry set_stroke = ctx_void (CTX_STROKE_SOURCE);
  ctx_process (ctx, &set_stroke);
}


static void ctx_color_raw (Ctx *ctx, CtxColorModel model, float *components, int stroke)
{
#if 0
  CtxSource *source = stroke?
          &ctx->state.gstate.source_stroke:
          &ctx->state.gstate.source_fill;

  if (model == CTX_RGB || model == CTX_RGBA)
  {
    float rgba[4];
  // XXX it should be possible to disable this, to get a more accurate record
  // when it is intentional
    float a = 1.0f;
    if (model == CTX_RGBA) a = components[3];
    ctx_color_get_rgba (&ctx->state, &source->color, rgba);
    if (rgba[0] == components[0] && rgba[1] == components[1] && rgba[2] == components[2] && rgba[3] == a)
     return;
  }
#endif

  if (stroke)
  {
    ctx_stroke_source (ctx);
  }

  CtxEntry command[3]= {
  ctx_f (CTX_COLOR, model, 0)
  };
  switch (model)
  {
    case CTX_RGBA:
    case CTX_RGBA_A:
    case CTX_RGBA_A_DEVICE:
    case CTX_DRGBA:
    case CTX_LABA:
    case CTX_LCHA:
      command[2].data.f[0]=components[3];
      /*FALLTHROUGH*/
    case CTX_RGB:
    case CTX_LAB:
    case CTX_LCH:
    case CTX_DRGB:
      command[0].data.f[1]=components[0];
      command[1].data.f[0]=components[1];
      command[1].data.f[1]=components[2];
      break;
    case CTX_DCMYKA:
    case CTX_CMYKA:
    case CTX_DCMYKA_A:
    case CTX_CMYKA_A:
      command[2].data.f[1]=components[4];
      /*FALLTHROUGH*/
    case CTX_CMYK:
    case CTX_DCMYK:
      command[0].data.f[1]=components[0];
      command[1].data.f[0]=components[1];
      command[1].data.f[1]=components[2];
      command[2].data.f[0]=components[3];
      break;
    case CTX_GRAYA:
    case CTX_GRAYA_A:
      command[1].data.f[0]=components[1];
      /*FALLTHROUGH*/
    case CTX_GRAY:
      command[0].data.f[1]=components[0];
      break;
  }
  ctx_process (ctx, command);
}

void ctx_rgba (Ctx *ctx, float r, float g, float b, float a)
{
  float components[4]={r,g,b,a};
  ctx_color_raw (ctx, CTX_RGBA, components, 0);
}

void ctx_rgba_stroke (Ctx *ctx, float r, float g, float b, float a)
{
  float components[4]={r,g,b,a};
  ctx_color_raw (ctx, CTX_RGBA, components, 1);
}

void ctx_rgb (Ctx *ctx, float   r, float   g, float   b)
{
  ctx_rgba (ctx, r, g, b, 1.0f);
}

void ctx_rgb_stroke (Ctx *ctx, float   r, float   g, float   b)
{
  ctx_rgba_stroke (ctx, r, g, b, 1.0f);
}

void ctx_gray_stroke   (Ctx *ctx, float gray)
{
  ctx_color_raw (ctx, CTX_GRAY, &gray, 1);
}
void ctx_gray (Ctx *ctx, float gray)
{
  ctx_color_raw (ctx, CTX_GRAY, &gray, 0);
}

void ctx_drgba_stroke (Ctx *ctx, float r, float g, float b, float a)
{
  float components[4]={r,g,b,a};
  ctx_color_raw (ctx, CTX_DRGBA, components, 1);
}
void ctx_drgba (Ctx *ctx, float r, float g, float b, float a)
{
  float components[4]={r,g,b,a};
  ctx_color_raw (ctx, CTX_DRGBA, components, 0);
}

#if CTX_ENABLE_CMYK

void ctx_cmyka_stroke (Ctx *ctx, float c, float m, float y, float k, float a)
{
  float components[5]={c,m,y,k,a};
  ctx_color_raw (ctx, CTX_CMYKA, components, 1);
}
void ctx_cmyka (Ctx *ctx, float c, float m, float y, float k, float a)
{
  float components[5]={c,m,y,k,a};
  ctx_color_raw (ctx, CTX_CMYKA, components, 0);
}
void ctx_cmyk_stroke   (Ctx *ctx, float c, float m, float y, float k)
{
  float components[4]={c,m,y,k};
  ctx_color_raw (ctx, CTX_CMYK, components, 1);
}
void ctx_cmyk (Ctx *ctx, float c, float m, float y, float k)
{
  float components[4]={c,m,y,k};
  ctx_color_raw (ctx, CTX_CMYK, components, 0);
}

static void ctx_dcmyk_raw (Ctx *ctx, float c, float m, float y, float k, int stroke)
{
  float components[5]={c,m,y,k,1.0f};
  ctx_color_raw (ctx, CTX_DCMYKA, components, stroke);
}

static void ctx_dcmyka_raw (Ctx *ctx, float c, float m, float y, float k, float a, int stroke)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_COLOR, CTX_DCMYKA + 512 * stroke, c),
    ctx_f (CTX_CONT, m, y),
    ctx_f (CTX_CONT, k, a)
  };
  ctx_process (ctx, command);
}

void ctx_dcmyk_stroke   (Ctx *ctx, float c, float m, float y, float k)
{
  float components[5]={c,m,y,k,1.0f};
  ctx_color_raw (ctx, CTX_DCMYK, components, 1);
}
void ctx_dcmyk (Ctx *ctx, float c, float m, float y, float k)
{
  float components[5]={c,m,y,k,1.0f};
  ctx_color_raw (ctx, CTX_DCMYK, components, 0);
}

void ctx_dcmyka_stroke   (Ctx *ctx, float c, float m, float y, float k, float a)
{
  float components[5]={c,m,y,k,a};
  ctx_color_raw (ctx, CTX_DCMYKA, components, 1);
}
void ctx_dcmyka (Ctx *ctx, float c, float m, float y, float k, float a)
{
  float components[5]={c,m,y,k,a};
  ctx_color_raw (ctx, CTX_DCMYKA, components, 0);
}

#endif

/* XXX: missing CSS1:
 *
 *   EM { color: rgb(110%, 0%, 0%) }  // clipped to 100% 
 *
 *
 *   :first-letter
 *   :first-list
 *   :link :visited :active
 *
 */

typedef struct ColorDef {
  uint64_t name;
  float r;
  float g;
  float b;
  float a;
} ColorDef;

#if 0
#define CTX_silver 	CTX_STRH('s','i','l','v','e','r',0,0,0,0,0,0,0,0)
#define CTX_fuchsia 	CTX_STRH('f','u','c','h','s','i','a',0,0,0,0,0,0,0)
#define CTX_gray 	CTX_STRH('g','r','a','y',0,0,0,0,0,0,0,0,0,0)
#define CTX_yellow 	CTX_STRH('y','e','l','l','o','w',0,0,0,0,0,0,0,0)
#define CTX_white 	CTX_STRH('w','h','i','t','e',0,0,0,0,0,0,0,0,0)
#define CTX_maroon 	CTX_STRH('m','a','r','o','o','n',0,0,0,0,0,0,0,0)
#define CTX_magenta 	CTX_STRH('m','a','g','e','n','t','a',0,0,0,0,0,0,0)
#define CTX_blue 	CTX_STRH('b','l','u','e',0,0,0,0,0,0,0,0,0,0)
#define CTX_green 	CTX_STRH('g','r','e','e','n',0,0,0,0,0,0,0,0,0)
#define CTX_red 	CTX_STRH('r','e','d',0,0,0,0,0,0,0,0,0,0,0)
#define CTX_purple 	CTX_STRH('p','u','r','p','l','e',0,0,0,0,0,0,0,0)
#define CTX_olive 	CTX_STRH('o','l','i','v','e',0,0,0,0,0,0,0,0,0)
#define CTX_teal        CTX_STRH('t','e','a','l',0,0,0,0,0,0,0,0,0,0)
#define CTX_black 	CTX_STRH('b','l','a','c','k',0,0,0,0,0,0,0,0,0)
#define CTX_cyan 	CTX_STRH('c','y','a','n',0,0,0,0,0,0,0,0,0,0)
#define CTX_navy 	CTX_STRH('n','a','v','y',0,0,0,0,0,0,0,0,0,0)
#define CTX_lime 	CTX_STRH('l','i','m','e',0,0,0,0,0,0,0,0,0,0)
#define CTX_aqua 	CTX_STRH('a','q','u','a',0,0,0,0,0,0,0,0,0,0)
#define CTX_transparent CTX_STRH('t','r','a','n','s','p','a','r','e','n','t',0,0,0)
#endif

static ColorDef _ctx_colors[]={
  {CTX_black,    0, 0, 0, 1},
  {CTX_red,      1, 0, 0, 1},
  {CTX_green,    0, 1, 0, 1},
  {CTX_yellow,   1, 1, 0, 1},
  {CTX_blue,     0, 0, 1, 1},
  {CTX_fuchsia,  1, 0, 1, 1},
  {CTX_cyan,     0, 1, 1, 1},
  {CTX_white,    1, 1, 1, 1},
  {CTX_silver,   0.75294, 0.75294, 0.75294, 1},
  {CTX_gray,     0.50196, 0.50196, 0.50196, 1},
  {CTX_magenta,  0.50196, 0, 0.50196, 1},
  {CTX_maroon,   0.50196, 0, 0, 1},
  {CTX_purple,   0.50196, 0, 0.50196, 1},
  {CTX_green,    0, 0.50196, 0, 1},
  {CTX_lime,     0, 1, 0, 1},
  {CTX_olive,    0.50196, 0.50196, 0, 1},
  {CTX_navy,     0, 0,      0.50196, 1},
  {CTX_teal,     0, 0.50196, 0.50196, 1},
  {CTX_aqua,     0, 1, 1, 1},
  {CTX_transparent, 0, 0, 0, 0},
  {CTX_none,     0, 0, 0, 0},
};

static int xdigit_value(const char xdigit)
{
  if (xdigit >= '0' && xdigit <= '9')
   return xdigit - '0';
  switch (xdigit)
  {
    case 'A':case 'a': return 10;
    case 'B':case 'b': return 11;
    case 'C':case 'c': return 12;
    case 'D':case 'd': return 13;
    case 'E':case 'e': return 14;
    case 'F':case 'f': return 15;
  }
  return 0;
}

static int
ctx_color_parse_rgb (CtxState *ctxstate, CtxColor *color, const char *color_string)
{
  float dcolor[4] = {0,0,0,1};
  while (*color_string && *color_string != '(')
    color_string++;
  if (*color_string) color_string++;

  {
    int n_floats = 0;
    char *p =    (char*)color_string;
    char *prev = (char*)NULL;
    for (; p && n_floats < 4 && p != prev && *p; )
    {
      float val;
      prev = p;
      val = _ctx_parse_float (p, &p);
      if (p != prev)
      {
        if (n_floats < 3)
          dcolor[n_floats++] = val/255.0;
        else
          dcolor[n_floats++] = val;

        while (*p == ' ' || *p == ',')
        {
          p++;
          prev++;
        }
      }
    }
  }
  ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]);
  return 0;
}

static int ctx_isxdigit (uint8_t ch)
{
  if (ch >= '0' && ch <= '9') return 1;
  if (ch >= 'a' && ch <= 'f') return 1;
  if (ch >= 'A' && ch <= 'F') return 1;
  return 0;
}

static int
mrg_color_parse_hex (CtxState *ctxstate, CtxColor *color, const char *color_string)
{
  float dcolor[4]={0,0,0,1};
  int string_length = strlen (color_string);
  int i;
  dcolor[3] = 1.0;

  if (string_length == 7 ||  /* #rrggbb   */
      string_length == 9)    /* #rrggbbaa */
    {
      int num_iterations = (string_length - 1) / 2;
  
      for (i = 0; i < num_iterations; ++i)
        {
          if (ctx_isxdigit (color_string[2 * i + 1]) &&
              ctx_isxdigit (color_string[2 * i + 2]))
            {
              dcolor[i] = (xdigit_value (color_string[2 * i + 1]) << 4 |
                           xdigit_value (color_string[2 * i + 2])) / 255.f;
            }
          else
            {
              return 0;
            }
        }
      /* Successful #rrggbb(aa) parsing! */
      ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]);
      return 1;
    }
  else if (string_length == 4 ||  /* #rgb  */
           string_length == 5)    /* #rgba */
    {
      int num_iterations = string_length - 1;
      for (i = 0; i < num_iterations; ++i)
        {
          if (ctx_isxdigit (color_string[i + 1]))
            {
              dcolor[i] = (xdigit_value (color_string[i + 1]) << 4 |
                           xdigit_value (color_string[i + 1])) / 255.f;
            }
          else
            {
              return 0;
            }
        }
      ctx_color_set_rgba (ctxstate, color, dcolor[0], dcolor[1],dcolor[2],dcolor[3]);
      /* Successful #rgb(a) parsing! */
      return 0;
    }
  /* String was of unsupported length. */
  return 1;
}

//#define CTX_currentColor 	CTX_STRH('c','u','r','r','e','n','t','C','o','l','o','r',0,0)

int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string)
{
  int i;
  uint64_t hash = ctx_strhash (string);
//  ctx_color_set_rgba (&(ctx->state), color, 0.4,0.1,0.9,1.0);
//  return 0;
    //rgba[0], rgba[1], rgba[2], rgba[3]);

  if (hash == CTX_currentColor)
  {
    float rgba[4];
    CtxColor ccolor;
    ctx_get_color (ctx, CTX_color, &ccolor);
    ctx_color_get_rgba (&(ctx->state), &ccolor, rgba);
    ctx_color_set_rgba (&(ctx->state), color, rgba[0], rgba[1], rgba[2], rgba[3]);
    return 0;
  }

  for (i = (sizeof(_ctx_colors)/sizeof(_ctx_colors[0]))-1; i>=0; i--)
  {
    if (hash == _ctx_colors[i].name)
    {
      ctx_color_set_rgba (&(ctx->state), color,
       _ctx_colors[i].r, _ctx_colors[i].g, _ctx_colors[i].b, _ctx_colors[i].a);
      return 0;
    }
  }

  if (string[0] == '#')
    mrg_color_parse_hex (&(ctx->state), color, string);
  else if (string[0] == 'r' &&
      string[1] == 'g' &&
      string[2] == 'b'
      )
    ctx_color_parse_rgb (&(ctx->state), color, string);

  return 0;
}

int ctx_color (Ctx *ctx, const char *string)
{
  CtxColor color = {0,};
  ctx_color_set_from_string (ctx, &color, string);
  float rgba[4];
  ctx_color_get_rgba (&(ctx->state), &color, rgba);
  ctx_color_raw (ctx, CTX_RGBA, rgba, 0);
  return 0;
}

void
ctx_rgba8 (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
#if 0
  CtxEntry command = ctx_u8 (CTX_SET_RGBA_U8, r, g, b, a, 0, 0, 0, 0);

  uint8_t rgba[4];
  ctx_color_get_rgba8 (&ctx->state, &ctx->state.gstate.source.color, rgba);
  if (rgba[0] == r && rgba[1] == g && rgba[2] == b && rgba[3] == a)
     return;

  ctx_process (ctx, &command);
#else
  ctx_rgba (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f);
#endif
}

void ctx_rgba8_stroke (Ctx *ctx, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  ctx_rgba_stroke (ctx, r/255.0f, g/255.0f, b/255.0f, a/255.0f);
}


#endif 

#if CTX_BABL
void ctx_rasterizer_colorspace_babl (CtxState      *state,
                                     CtxColorSpace  space_slot,
                                     const Babl    *space)
{
  switch (space_slot)
  {
    case CTX_COLOR_SPACE_DEVICE_RGB:
      state->gstate.device_space = space;
      break;
    case CTX_COLOR_SPACE_DEVICE_CMYK:
      state->gstate.device_space = space;
      break;
    case CTX_COLOR_SPACE_USER_RGB:
      state->gstate.rgb_space = space;
      break;
    case CTX_COLOR_SPACE_USER_CMYK:
      state->gstate.cmyk_space = space;
      break;
    case CTX_COLOR_SPACE_TEXTURE:
      state->gstate.texture_space = space;
      break;
  }

  const Babl *srgb = babl_space ("sRGB");
  if (!state->gstate.texture_space) 
       state->gstate.texture_space = srgb;
  if (!state->gstate.device_space) 
       state->gstate.device_space = srgb;
  if (!state->gstate.rgb_space) 
       state->gstate.rgb_space = srgb;

  //fprintf (stderr, "%s\n", babl_get_name (state->gstate.device_space));

  state->gstate.fish_rgbaf_device_to_user = babl_fish (
       babl_format_with_space ("R'G'B'A float", state->gstate.device_space),
       babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space));
  state->gstate.fish_rgbaf_user_to_device = babl_fish (
       babl_format_with_space ("R'G'B'A float", state->gstate.rgb_space),
       babl_format_with_space ("R'G'B'A float", state->gstate.device_space));
  state->gstate.fish_rgbaf_texture_to_device = babl_fish (
       babl_format_with_space ("R'G'B'A float", state->gstate.texture_space),
       babl_format_with_space ("R'G'B'A float", state->gstate.device_space));
}
#endif

void ctx_rasterizer_colorspace_icc (CtxState      *state,
                                    CtxColorSpace  space_slot,
                                    char          *icc_data,
                                    int            icc_length)
{
#if CTX_BABL
   const char *error = NULL;
   const Babl *space = NULL;

   if (icc_data == NULL) space = babl_space ("sRGB");
   else if (icc_length < 32)
   {
      if (icc_data[0] == '0' && icc_data[1] == 'x')
        sscanf (icc_data, "%p", &space);
      else
      {
        char tmp[24];
        int i;
        for (i = 0; i < icc_length; i++)
          tmp[i]= (icc_data[i]>='A' && icc_data[i]<='Z')?icc_data[i]+('a'-'A'):icc_data[i];
        tmp[icc_length]=0;
        if (!strcmp (tmp, "srgb"))            space = babl_space ("sRGB");
        else if (!strcmp (tmp, "scrgb"))      space = babl_space ("scRGB");
        else if (!strcmp (tmp, "acescg"))     space = babl_space ("ACEScg");
        else if (!strcmp (tmp, "adobe"))      space = babl_space ("Adobe");
        else if (!strcmp (tmp, "apple"))      space = babl_space ("Apple");
        else if (!strcmp (tmp, "rec2020"))    space = babl_space ("Rec2020");
        else if (!strcmp (tmp, "aces2065-1")) space = babl_space ("ACES2065-1");
      }
   }

   if (!space)
   {
     space = babl_space_from_icc (icc_data, icc_length, BABL_ICC_INTENT_RELATIVE_COLORIMETRIC, &error);
   }
   if (space)
   {
     ctx_rasterizer_colorspace_babl (state, space_slot, space);
   }
#endif
}

void ctx_colorspace (Ctx           *ctx,
                     CtxColorSpace  space_slot,
                     unsigned char *data,
                     int            data_length)
{
  if (data)
  {
    if (data_length <= 0) data_length = (int)strlen ((char*)data);
    ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, (char*)data, space_slot, 0, data_length);
  }
  else
  {
    ctx_process_cmd_str_with_len (ctx, CTX_COLOR_SPACE, "sRGB", space_slot, 0, 4);
  }
}

void ctx_gradient_add_stop_u8
(Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  CtxEntry entry = ctx_f (CTX_GRADIENT_STOP, pos, 0);
  entry.data.u8[4+0] = r;
  entry.data.u8[4+1] = g;
  entry.data.u8[4+2] = b;
  entry.data.u8[4+3] = a;
  ctx_process (ctx, &entry);
}

void ctx_gradient_add_stop
(Ctx *ctx, float pos, float r, float g, float b, float a)
{
  int ir = r * 255;
  int ig = g * 255;
  int ib = b * 255;
  int ia = a * 255;
  ir = CTX_CLAMP (ir, 0,255);
  ig = CTX_CLAMP (ig, 0,255);
  ib = CTX_CLAMP (ib, 0,255);
  ia = CTX_CLAMP (ia, 0,255);
  ctx_gradient_add_stop_u8 (ctx, pos, ir, ig, ib, ia);
}

void ctx_gradient_add_stop_string
(Ctx *ctx, float pos, const char *string)
{
  CtxColor color = {0,};
  ctx_color_set_from_string (ctx, &color, string);
  float rgba[4];
  ctx_color_get_rgba (&(ctx->state), &color, rgba);
  ctx_gradient_add_stop (ctx, pos, rgba[0], rgba[1], rgba[2], rgba[3]);
}

//  deviceRGB .. settable when creating an RGB image surface..
//               queryable when running in terminal - is it really needed?
//               though it is settable ; and functional for changing this state at runtime..
//
//  userRGB - settable at any time, stored in save|restore 
//  texture - set as the space of data on subsequent 

static float ctx_state_get (CtxState *state, uint64_t hash)
{
  for (int i = state->gstate.keydb_pos-1; i>=0; i--)
    {
      if (state->keydb[i].key == hash)
        { return state->keydb[i].value; }
    }
  return -0.0;
}

static void ctx_state_set (CtxState *state, uint64_t key, float value)
{
  if (key != CTX_new_state)
    {
      if (ctx_state_get (state, key) == value)
        { return; }
      for (int i = state->gstate.keydb_pos-1;
           state->keydb[i].key != CTX_new_state && i >=0;
           i--)
        {
          if (state->keydb[i].key == key)
            {
              state->keydb[i].value = value;
              return;
            }
        }
    }
  if (state->gstate.keydb_pos >= CTX_MAX_KEYDB)
    { return; }
  state->keydb[state->gstate.keydb_pos].key = key;
  state->keydb[state->gstate.keydb_pos].value = value;
  state->gstate.keydb_pos++;
}


#define CTX_KEYDB_STRING_START (-90000.0)
#define CTX_KEYDB_STRING_END   (CTX_KEYDB_STRING_START + CTX_STRINGPOOL_SIZE)

static int ctx_float_is_string (float val)
{
  return val >= CTX_KEYDB_STRING_START && val <= CTX_KEYDB_STRING_END;
}

static int ctx_float_to_string_index (float val)
{
  int idx = -1;
  if (ctx_float_is_string (val))
  {
    idx = val - CTX_KEYDB_STRING_START;
  }
  return idx;
}

static float ctx_string_index_to_float (int index)
{
  return CTX_KEYDB_STRING_START + index;
}

static void *ctx_state_get_blob (CtxState *state, uint64_t key)
{
  float stored = ctx_state_get (state, key);
  int idx = ctx_float_to_string_index (stored);
  if (idx >= 0)
  {
     // can we know length?
     return &state->stringpool[idx];
  }

  // format number as string?
  return NULL;
}

static const char *ctx_state_get_string (CtxState *state, uint64_t key)
{
  const char *ret = (char*)ctx_state_get_blob (state, key);
  if (ret && ret[0] == 127)
    return NULL;
  return ret;
}


static void ctx_state_set_blob (CtxState *state, uint64_t key, uint8_t *data, int len)
{
  int idx = state->gstate.stringpool_pos;

  if (idx + len > CTX_STRINGPOOL_SIZE)
  {
    ctx_log ("blowing varpool size [%c..]\n", data[0]);
    //fprintf (stderr, "blowing varpool size [%c%c%c..]\n", data[0],data[1], data[1]?data[2]:0);
#if 0
    for (int i = 0; i< CTX_STRINGPOOL_SIZE; i++)
    {
       if (i==0) fprintf (stderr, "\n%i ", i);
       else      fprintf (stderr, "%c", state->stringpool[i]);
    }
#endif
    return;
  }

  memcpy (&state->stringpool[idx], data, len);
  state->gstate.stringpool_pos+=len;
  state->stringpool[state->gstate.stringpool_pos++]=0;
  ctx_state_set (state, key, ctx_string_index_to_float (idx));
}

static void ctx_state_set_string (CtxState *state, uint64_t key, const char *string)
{
  float old_val = ctx_state_get (state, key);
  int   old_idx = ctx_float_to_string_index (old_val);

  if (old_idx >= 0)
  {
    const char *old_string = ctx_state_get_string (state, key);
    if (old_string && !strcmp (old_string, string))
      return;
  }

  if (ctx_str_is_number (string))
  {
    ctx_state_set (state, key, strtod (string, NULL));
    return;
  }
  // should do same with color
 
  // XXX should special case when the string modified is at the
  //     end of the stringpool.
  //
  //     for clips the behavior is howevre ideal, since
  //     we can have more than one clip per save/restore level
  ctx_state_set_blob (state, key, (uint8_t*)string, strlen(string));
}

static int ctx_state_get_color (CtxState *state, uint64_t key, CtxColor *color)
{
  CtxColor *stored = (CtxColor*)ctx_state_get_blob (state, key);
  if (stored)
  {
    if (stored->magic == 127)
    {
      *color = *stored;
      return 0;
    }
  }
  return -1;
}

static void ctx_state_set_color (CtxState *state, uint64_t key, CtxColor *color)
{
  CtxColor mod_color;
  CtxColor old_color;
  mod_color = *color;
  mod_color.magic = 127;
  if (ctx_state_get_color (state, key, &old_color)==0)
  {
    if (!memcmp (&mod_color, &old_color, sizeof (mod_color)))
      return;
  }
  ctx_state_set_blob (state, key, (uint8_t*)&mod_color, sizeof (CtxColor));
}

const char *ctx_get_string (Ctx *ctx, uint64_t hash)
{
  return ctx_state_get_string (&ctx->state, hash);
}
float ctx_get_float (Ctx *ctx, uint64_t hash)
{
  return ctx_state_get (&ctx->state, hash);
}
int ctx_get_int (Ctx *ctx, uint64_t hash)
{
  return ctx_state_get (&ctx->state, hash);
}
void ctx_set_float (Ctx *ctx, uint64_t hash, float value)
{
  ctx_state_set (&ctx->state, hash, value);
}
void ctx_set_string (Ctx *ctx, uint64_t hash, const char *value)
{
  ctx_state_set_string (&ctx->state, hash, value);
}
void ctx_set_color (Ctx *ctx, uint64_t hash, CtxColor *color)
{
  ctx_state_set_color (&ctx->state, hash, color);
}
int  ctx_get_color (Ctx *ctx, uint64_t hash, CtxColor *color)
{
  return ctx_state_get_color (&ctx->state, hash, color);
}
int ctx_is_set (Ctx *ctx, uint64_t hash)
{
  return ctx_get_float (ctx, hash) != -0.0f;
}
int ctx_is_set_now (Ctx *ctx, uint64_t hash)
{
  return ctx_is_set (ctx, hash);
}

#if CTX_COMPOSITE

#define CTX_REFERENCE 0


#define CTX_RGBA8_R_SHIFT  0
#define CTX_RGBA8_G_SHIFT  8
#define CTX_RGBA8_B_SHIFT  16
#define CTX_RGBA8_A_SHIFT  24

#define CTX_RGBA8_R_MASK   (0xff << CTX_RGBA8_R_SHIFT)
#define CTX_RGBA8_G_MASK   (0xff << CTX_RGBA8_G_SHIFT)
#define CTX_RGBA8_B_MASK   (0xff << CTX_RGBA8_B_SHIFT)
#define CTX_RGBA8_A_MASK   (0xff << CTX_RGBA8_A_SHIFT)

#define CTX_RGBA8_RB_MASK  (CTX_RGBA8_R_MASK | CTX_RGBA8_B_MASK)
#define CTX_RGBA8_GA_MASK  (CTX_RGBA8_G_MASK | CTX_RGBA8_A_MASK)


CTX_INLINE static void
ctx_RGBA8_associate_alpha (uint8_t *u8)
{
#if 1
  uint32_t val = *((uint32_t*)(u8));
  uint8_t a = u8[3];
  uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK;
  uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK;
  *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT);
#else
  uint32_t a = u8[3];
  u8[0] = (u8[0] * a + 255) >> 8;
  u8[1] = (u8[1] * a + 255) >> 8;
  u8[2] = (u8[2] * a + 255) >> 8;
#endif
}

CTX_INLINE static void
ctx_RGBA8_associate_alpha_probably_opaque (uint8_t *u8)
{
  uint32_t val = *((uint32_t*)(u8));
  uint32_t a = val>>24;//u8[3];
  //if (CTX_UNLIKELY(a==0))
  //   *((uint32_t*)(u8)) = 0;
  if (CTX_UNLIKELY(a!=255))
  {
     uint32_t g = (((val & CTX_RGBA8_G_MASK) * a) >> 8) & CTX_RGBA8_G_MASK;
     uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * a) >> 8) & CTX_RGBA8_RB_MASK;
     *((uint32_t*)(u8)) = g|rb|(a << CTX_RGBA8_A_SHIFT);
  }
}

CTX_INLINE static uint32_t ctx_bi_RGBA8 (uint32_t isrc00, uint32_t isrc01, uint32_t isrc10, uint32_t isrc11, uint8_t dx, uint8_t dy)
{
#if 0
#if 0
  uint8_t ret[4];
  uint8_t *src00 = (uint8_t*)&isrc00;
  uint8_t *src10 = (uint8_t*)&isrc10;
  uint8_t *src01 = (uint8_t*)&isrc01;
  uint8_t *src11 = (uint8_t*)&isrc11;
  for (int c = 0; c < 4; c++)
  {
    ret[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx),
                         ctx_lerp_u8 (src10[c], src11[c], dx), dy);
  }
  return  ((uint32_t*)&ret[0])[0];
#else
  return ctx_lerp_RGBA8 (ctx_lerp_RGBA8 (isrc00, isrc01, dx),
                         ctx_lerp_RGBA8 (isrc10, isrc11, dx), dy);
#endif
#else
  uint32_t s0_ga, s0_rb, s1_ga, s1_rb;
  ctx_lerp_RGBA8_split (isrc00, isrc01, dx, &s0_ga, &s0_rb);
  ctx_lerp_RGBA8_split (isrc10, isrc11, dx, &s1_ga, &s1_rb);
  return ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, dy);
#endif
}

#if CTX_GRADIENTS
#if CTX_GRADIENT_CACHE
static uint8_t ctx_gradient_cache_u8[CTX_GRADIENT_CACHE_ELEMENTS][4];
extern int ctx_gradient_cache_valid;


inline static int ctx_grad_index (float v)
{
  int ret = v * (CTX_GRADIENT_CACHE_ELEMENTS - 1) + 0.5f;
  ret = ctx_maxi (0, ret);
  ret = ctx_mini (CTX_GRADIENT_CACHE_ELEMENTS-1, ret);
  return ret;
}

inline static int ctx_grad_index_i (int v)
{
  v = v >> 8;
  return ctx_maxi (0, ctx_mini (CTX_GRADIENT_CACHE_ELEMENTS-1, v));
}


//static void
//ctx_gradient_cache_reset (void)
//{
//  ctx_gradient_cache_valid = 0;
//}
#endif


CTX_INLINE static void
_ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba)
{
  float v = x;
  CtxGradient *g = &rasterizer->state->gradient;
  if (v < 0) { v = 0; }
  if (v > 1) { v = 1; }

  if (g->n_stops == 0)
    {
      rgba[0] = rgba[1] = rgba[2] = v * 255;
      rgba[3] = 255;
      return;
    }
  CtxGradientStop *stop      = NULL;
  CtxGradientStop *next_stop = &g->stops[0];
  CtxColor *color;
  for (int s = 0; s < g->n_stops; s++)
    {
      stop      = &g->stops[s];
      next_stop = &g->stops[s+1];
      if (s + 1 >= g->n_stops) { next_stop = NULL; }
      if (v >= stop->pos && next_stop && v < next_stop->pos)
        { break; }
      stop = NULL;
      next_stop = NULL;
    }
  if (stop == NULL && next_stop)
    {
      color = & (next_stop->color);
    }
  else if (stop && next_stop == NULL)
    {
      color = & (stop->color);
    }
  else if (stop && next_stop)
    {
      uint8_t stop_rgba[4];
      uint8_t next_rgba[4];
      ctx_color_get_rgba8 (rasterizer->state, & (stop->color), stop_rgba);
      ctx_color_get_rgba8 (rasterizer->state, & (next_stop->color), next_rgba);
      int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos);
#if 1
      ((uint32_t*)rgba)[0] = ctx_lerp_RGBA8 (((uint32_t*)stop_rgba)[0],
                                             ((uint32_t*)next_rgba)[0], dx);
#else
      for (int c = 0; c < 4; c++)
        { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); }
#endif
      ctx_RGBA8_associate_alpha (rgba);
      return;
    }
  else
    {
      color = & (g->stops[g->n_stops-1].color);
    }
  ctx_color_get_rgba8 (rasterizer->state, color, rgba);
  if (rasterizer->swap_red_green)
  {
    uint8_t tmp = rgba[0];
    rgba[0] = rgba[2];
    rgba[2] = tmp;
  }
  ctx_RGBA8_associate_alpha (rgba);
}

#if CTX_GRADIENT_CACHE
static void
ctx_gradient_cache_prime (CtxRasterizer *rasterizer);
#endif

CTX_INLINE static void
ctx_fragment_gradient_1d_RGBA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba)
{
#if CTX_GRADIENT_CACHE
  *((uint32_t*)rgba) = *((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(x)][0]));
#else
 _ctx_fragment_gradient_1d_RGBA8 (rasterizer, x, y, rgba);
#endif
}
#endif

CTX_INLINE static void
ctx_u8_associate_alpha (int components, uint8_t *u8)
{
  for (int c = 0; c < components-1; c++)
    u8[c] = (u8[c] * u8[components-1] + 255)>>8;
}

#if CTX_GRADIENTS
#if CTX_GRADIENT_CACHE
static void
ctx_gradient_cache_prime (CtxRasterizer *rasterizer)
{
  if (ctx_gradient_cache_valid)
    return;
  for (int u = 0; u < CTX_GRADIENT_CACHE_ELEMENTS; u++)
  {
    float v = u / (CTX_GRADIENT_CACHE_ELEMENTS - 1.0f);
    _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0f, &ctx_gradient_cache_u8[u][0]);
    //*((uint32_t*)(&ctx_gradient_cache_u8_a[u][0]))= *((uint32_t*)(&ctx_gradient_cache_u8[u][0]));
    //memcpy(&ctx_gradient_cache_u8_a[u][0], &ctx_gradient_cache_u8[u][0], 4);
    //ctx_RGBA8_associate_alpha (&ctx_gradient_cache_u8_a[u][0]);
  }
  ctx_gradient_cache_valid = 1;
}
#endif

CTX_INLINE static void
ctx_fragment_gradient_1d_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, uint8_t *rgba)
{
  float v = x;
  CtxGradient *g = &rasterizer->state->gradient;
  if (v < 0) { v = 0; }
  if (v > 1) { v = 1; }
  if (g->n_stops == 0)
    {
      rgba[0] = rgba[1] = rgba[2] = v * 255;
      rgba[1] = 255;
      return;
    }
  CtxGradientStop *stop      = NULL;
  CtxGradientStop *next_stop = &g->stops[0];
  CtxColor *color;
  for (int s = 0; s < g->n_stops; s++)
    {
      stop      = &g->stops[s];
      next_stop = &g->stops[s+1];
      if (s + 1 >= g->n_stops) { next_stop = NULL; }
      if (v >= stop->pos && next_stop && v < next_stop->pos)
        { break; }
      stop = NULL;
      next_stop = NULL;
    }
  if (stop == NULL && next_stop)
    {
      color = & (next_stop->color);
    }
  else if (stop && next_stop == NULL)
    {
      color = & (stop->color);
    }
  else if (stop && next_stop)
    {
      uint8_t stop_rgba[4];
      uint8_t next_rgba[4];
      ctx_color_get_graya_u8 (rasterizer->state, & (stop->color), stop_rgba);
      ctx_color_get_graya_u8 (rasterizer->state, & (next_stop->color), next_rgba);
      int dx = (v - stop->pos) * 255 / (next_stop->pos - stop->pos);
      for (int c = 0; c < 2; c++)
        { rgba[c] = ctx_lerp_u8 (stop_rgba[c], next_rgba[c], dx); }
      return;
    }
  else
    {
      color = & (g->stops[g->n_stops-1].color);
    }
  ctx_color_get_graya_u8 (rasterizer->state, color, rgba);
}

CTX_INLINE static void
ctx_fragment_gradient_1d_RGBAF (CtxRasterizer *rasterizer, float v, float y, float *rgba)
{
  CtxGradient *g = &rasterizer->state->gradient;
  if (v < 0) { v = 0; }
  if (v > 1) { v = 1; }
  if (g->n_stops == 0)
    {
      rgba[0] = rgba[1] = rgba[2] = v;
      rgba[3] = 1.0;
      return;
    }
  CtxGradientStop *stop      = NULL;
  CtxGradientStop *next_stop = &g->stops[0];
  CtxColor *color;
  for (int s = 0; s < g->n_stops; s++)
    {
      stop      = &g->stops[s];
      next_stop = &g->stops[s+1];
      if (s + 1 >= g->n_stops) { next_stop = NULL; }
      if (v >= stop->pos && next_stop && v < next_stop->pos)
        { break; }
      stop = NULL;
      next_stop = NULL;
    }
  if (stop == NULL && next_stop)
    {
      color = & (next_stop->color);
    }
  else if (stop && next_stop == NULL)
    {
      color = & (stop->color);
    }
  else if (stop && next_stop)
    {
      float stop_rgba[4];
      float next_rgba[4];
      ctx_color_get_rgba (rasterizer->state, & (stop->color), stop_rgba);
      ctx_color_get_rgba (rasterizer->state, & (next_stop->color), next_rgba);
      int dx = (v - stop->pos) / (next_stop->pos - stop->pos);
      for (int c = 0; c < 4; c++)
        { rgba[c] = ctx_lerpf (stop_rgba[c], next_rgba[c], dx); }
      return;
    }
  else
    {
      color = & (g->stops[g->n_stops-1].color);
    }
  ctx_color_get_rgba (rasterizer->state, color, rgba);
}
#endif

static void
ctx_fragment_image_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed;
  ctx_assert (rasterizer);
  ctx_assert (g);
  ctx_assert (buffer);

  for (int i = 0; i < count; i ++)
  {

  int u = x;
  int v = y;
  int width = buffer->width;
  int height = buffer->height;
  if ( u < 0 || v < 0 ||
       u >= width ||
       v >= height)
    {
      *((uint32_t*)(rgba)) = 0;
    }
  else
    {
      int bpp = buffer->format->bpp/8;
      if (rasterizer->state->gstate.image_smoothing)
      {
      uint8_t *src00 = (uint8_t *) buffer->data;
      src00 += v * buffer->stride + u * bpp;
      uint8_t *src01 = src00;
      if ( u + 1 < width)
      {
        src01 = src00 + bpp;
      }
      uint8_t *src11 = src01;
      uint8_t *src10 = src00;
      if ( v + 1 < height)
      {
        src10 = src00 + buffer->stride;
        src11 = src01 + buffer->stride;
      }
      float dx = (x-(int)(x)) * 255.9;
      float dy = (y-(int)(y)) * 255.9;

      switch (bpp)
      {
      case 1:
        rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx),
                               ctx_lerp_u8 (src10[0], src11[0], dx), dy);
        rgba[3] = 255;
        break;
      case 2:
        rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dx),
                               ctx_lerp_u8 (src10[0], src11[0], dx), dy);
        rgba[3] = ctx_lerp_u8 (ctx_lerp_u8 (src00[1], src01[1], dx),
                               ctx_lerp_u8 (src10[1], src11[1], dx), dy);
        break;
      case 3:
      for (int c = 0; c < bpp; c++)
        { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx),
                                 ctx_lerp_u8 (src10[c], src11[c], dx), dy);
                
        }
        rgba[3]=255;
        break;
      break;
      case 4:
      for (int c = 0; c < bpp; c++)
        { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx),
                                 ctx_lerp_u8 (src10[c], src11[c], dx), dy);
                
        }
      }
      }
      else
      {
      uint8_t *src = (uint8_t *) buffer->data;
      src += v * buffer->stride + u * bpp;
      switch (bpp)
        {
          case 1:
            for (int c = 0; c < 3; c++)
              { rgba[c] = src[0]; }
            rgba[3] = 255;
            break;
          case 2:
            for (int c = 0; c < 3; c++)
              { rgba[c] = src[0]; }
            rgba[3] = src[1];
            break;
          case 3:
            for (int c = 0; c < 3; c++)
              { rgba[c] = src[c]; }
            rgba[3] = 255;
            break;
          case 4:
            for (int c = 0; c < 4; c++)
              { rgba[c] = src[c]; }
            break;
        }
      }
      if (rasterizer->swap_red_green)
      {
        uint8_t tmp = rgba[0];
        rgba[0] = rgba[2];
        rgba[2] = tmp;
      }
    }
    ctx_RGBA8_associate_alpha_probably_opaque (rgba); // XXX: really?
    rgba += 4;
    x += dx;
    y += dy;
  }
}

#if CTX_DITHER
static inline int ctx_dither_mask_a (int x, int y, int c, int divisor)
{
  /* https://pippin.gimp.org/a_dither/ */
  return ( ( ( ( (x + c * 67) + y * 236) * 119) & 255 )-127) / divisor;
}

inline static void
ctx_dither_rgba_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green)
{
  if (dither_red_blue == 0)
    { return; }
  for (int c = 0; c < 3; c ++)
    {
      int val = rgba[c] + ctx_dither_mask_a (x, y, 0, c==1?dither_green:dither_red_blue);
      rgba[c] = CTX_CLAMP (val, 0, 255);
    }
}

inline static void
ctx_dither_graya_u8 (uint8_t *rgba, int x, int y, int dither_red_blue, int dither_green)
{
  if (dither_red_blue == 0)
    { return; }
  for (int c = 0; c < 1; c ++)
    {
      int val = rgba[c] + ctx_dither_mask_a (x, y, 0, dither_red_blue);
      rgba[c] = CTX_CLAMP (val, 0, 255);
    }
}
#endif

CTX_INLINE static void
ctx_RGBA8_deassociate_alpha (const uint8_t *in, uint8_t *out)
{
    uint32_t val = *((uint32_t*)(in));
    int a = val >> CTX_RGBA8_A_SHIFT;
    if (a)
    {
    if (a ==255)
    {
      *((uint32_t*)(out)) = val;
    } else
    {
      uint32_t g = (((val & CTX_RGBA8_G_MASK) * 255 / a) >> 8) & CTX_RGBA8_G_MASK;
      uint32_t rb =(((val & CTX_RGBA8_RB_MASK) * 255 / a) >> 8) & CTX_RGBA8_RB_MASK;
      *((uint32_t*)(out)) = g|rb|(a << CTX_RGBA8_A_SHIFT);
    }
    }
    else
    {
      *((uint32_t*)(out)) = 0;
    }
}

CTX_INLINE static void
ctx_u8_deassociate_alpha (int components, const uint8_t *in, uint8_t *out)
{
  if (in[components-1])
  {
    if (in[components-1] != 255)
    for (int c = 0; c < components-1; c++)
      out[c] = (in[c] * 255) / in[components-1];
    else
    for (int c = 0; c < components-1; c++)
      out[c] = in[c];
    out[components-1] = in[components-1];
  }
  else
  {
  for (int c = 0; c < components; c++)
    out[c] = 0;
  }
}

CTX_INLINE static void
ctx_float_associate_alpha (int components, float *rgba)
{
  float alpha = rgba[components-1];
  for (int c = 0; c < components-1; c++)
    rgba[c] *= alpha;
}

CTX_INLINE static void
ctx_float_deassociate_alpha (int components, float *rgba, float *dst)
{
  float ralpha = rgba[components-1];
  if (ralpha != 0.0) ralpha = 1.0/ralpha;

  for (int c = 0; c < components-1; c++)
    dst[c] = (rgba[c] * ralpha);
  dst[components-1] = rgba[components-1];
}

CTX_INLINE static void
ctx_RGBAF_associate_alpha (float *rgba)
{
  ctx_float_associate_alpha (4, rgba);
}

CTX_INLINE static void
ctx_RGBAF_deassociate_alpha (float *rgba, float *dst)
{
  ctx_float_deassociate_alpha (4, rgba, dst);
}


static inline void ctx_swap_red_green_u8 (void *data)
{
  uint8_t *rgba = (uint8_t*)data;
  uint8_t tmp = rgba[0];
  rgba[0] = rgba[2];
  rgba[2] = tmp;
}

static void
ctx_fragment_swap_red_green_u8 (void *out, int count)
{
  uint8_t *rgba = (uint8_t*)out;
  for (int x = 0; x < count; x++)
  {
    ctx_swap_red_green_u8 (rgba);
    rgba += 4;
  }
}

/**** rgb8 ***/

static void
ctx_fragment_image_rgb8_RGBA8_box (CtxRasterizer *rasterizer,
                                   float x,
                                   float y,
                                   void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed;
  int width = buffer->width;
  int height = buffer->height;

  for (int i = 0; i < count; i++)
  {

  int u = x;
  int v = y;
  if ( u < 0 || v < 0 ||
       u >= width ||
       v >= height)
    {
      *((uint32_t*)(rgba))= 0;
    }
  else
    {
      int bpp = 3;
      rgba[3]=255;
      float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
          int dim = (1.0 / factor) / 2;
          uint64_t sum[4]={0,0,0,0};
          int count = 0;
          for (int ou = - dim; ou < dim; ou++)
          for (int ov = - dim; ov < dim; ov++)
          {
            uint8_t *src = (uint8_t *) buffer->data;

            if (v+ov >= 0 && u+ou >=0 && u + ou < width && v + ov < height)
            {
              int o = (v+ov) * width + (u + ou);
              src += o * bpp;

              for (int c = 0; c < bpp; c++)
                sum[c] += src[c];
              count ++;
            }
          }
          if (count)
          {
            int recip = 65536/count;
            for (int c = 0; c < bpp; c++)
              rgba[c] = sum[c] * recip >> 16;
          }
          ctx_RGBA8_associate_alpha_probably_opaque (rgba);
    }
    rgba += 4;
    x += dx;
    y += dy;
  }
#if CTX_DITHER
//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
//                    rasterizer->format->dither_green);
#endif
}

static void
ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer,
                                  float x,
                                  float y,
                                  void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static void
ctx_fragment_image_rgb8_RGBA8_bi (CtxRasterizer *rasterizer,
                                  float x,
                                  float y,
                                  void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed;
  int width = buffer->width;
  int height = buffer->height;

  for (int i = 0; i < count; i++)
  {

  int u = x;
  int v = y;
  if ( u < 0 || v < 0 ||
       u >= width ||
       v >= height)
    {
      *((uint32_t*)(rgba))= 0;
    }
  else
    {
      int bpp = 3;
      rgba[3]=255;
      uint8_t *src00 = (uint8_t *) buffer->data;
      int stride = buffer->stride;
      src00 += v * stride + u * bpp;
      uint8_t *src01 = src00;
      if ( u + 1 < width)
      {
        src01 = src00 + bpp;
      }
      uint8_t *src11 = src01;
      uint8_t *src10 = src00;
      if ( v + 1 < height)
      {
        src10 = src00 + stride;
        src11 = src01 + stride;
      }
      float dx = (x-(int)(x)) * 255.9f;
      float dy = (y-(int)(y)) * 255.9f;
      for (int c = 0; c < bpp; c++)
      {
        rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dx),
                               ctx_lerp_u8 (src10[c], src11[c], dx), dy);
      }
      ctx_RGBA8_associate_alpha_probably_opaque (rgba);
    }
    x += dx;
    y += dy;
    rgba += 4;
  }
#if CTX_DITHER
//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
//                    rasterizer->format->dither_green);
#endif
}

static void
ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer,
                                  float x,
                                  float y,
                                  void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static CTX_INLINE void
ctx_fragment_image_rgb8_RGBA8_nearest (CtxRasterizer *rasterizer,
                                       float x,
                                       float y,
                                       void *out, int count, float dx, float dy)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer;
  if (buffer->color_managed)
   buffer = buffer->color_managed;
  uint8_t *rgba = (uint8_t *) out;
  uint8_t *src = (uint8_t *) buffer->data;
  int bwidth = buffer->width;
  int bheight = buffer->height;
  int stride = buffer->stride;

  x += 0.5f;
  y += 0.5f;

  if (CTX_UNLIKELY (dy == 0.0f && dx > 0.999f && dx < 1.001f))
  {
    int v = y;
    int u = x;
  
    if (v < buffer->height && v > 0)
    {
      int o = v * stride + u * 3;
      int i;
      for (i = 0; i < count && u < bwidth && u <0; i++)
      {
        *((uint32_t*)(rgba))= 0;
        rgba += 4;
        o += 3;
        u+=1;
      }

      for (; i < count && u < bwidth; i++)
      {
        rgba[0] = src[o];
        rgba[1] = src[o+1];
        rgba[2] = src[o+2]; 
        rgba[3]=255;
        rgba += 4;
        o += 3;
        u+=1;
      }
      for (; i < count; i++)
      {
        *((uint32_t*)(rgba))= 0;
        rgba += 4;
      }
    }
    else
    {
      for (int i = 0; i < count; i++)
      {
        *((uint32_t*)(rgba))= 0;
        rgba+=4;
      }
    }
  }
  else
  {
    int u = x;
    int v = y;
    int i;
    for (i = 0; i < count && u < bwidth && u <0; i++)
    {
      u = x;
      v = y;;
      *((uint32_t*)(rgba))= 0;
      x += dx;
      y += dy;
      rgba += 4;
    }
    for (; i < count && u < bwidth; i++)
    {
      u = x;
      v = y;
    if (CTX_UNLIKELY(v < 0 || v >= bheight))
      {
        *((uint32_t*)(rgba))= 0;
      }
    else
      {
        int o = v * stride + u * 3;
        rgba[0] = src[o];
        rgba[1] = src[o+1];
        rgba[2] = src[o+2]; 
        rgba[3]=255;
      }
  
      rgba += 4;
      x += dx;
      y += dy;
    }
      for (; i < count; i++)
      {
        *((uint32_t*)(rgba))= 0;
        rgba += 4;
      }
  }
#if CTX_DITHER
  //ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
  //                    rasterizer->format->dither_green);
#endif
}


static CTX_INLINE void
ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer,
                                                      float x,
                                                      float y,
                                                      void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static void
ctx_fragment_image_rgb8_RGBA8 (CtxRasterizer *rasterizer,
                               float x,
                               float y,
                               void *out, int count, float dx, float dy)
{
  if (rasterizer->state->gstate.image_smoothing)
  {
    float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
    if (factor <= 0.50f)
    {
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgb8_RGBA8_box (rasterizer, x, y, out, count, dx, dy);
    }
#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
    else if (factor > 0.99f && factor < 1.01f)
    {
      // XXX missing translate test
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
    }
#endif
    else
    {
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgb8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy);
    }
  }
  else
  {
    if (rasterizer->swap_red_green)
      ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy);
    else
      ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
  }
#if CTX_DITHER
  {
  uint8_t *rgba = (uint8_t*)out;
  ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
  }
#endif
}


/************** rgba8 */

static void
ctx_fragment_image_rgba8_RGBA8_box (CtxRasterizer *rasterizer,
                                    float x,
                                    float y,
                                    void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed;

  for (int i = 0; i < count; i ++)
  {

  int u = x;
  int v = y;
  if ( u < 0 || v < 0 ||
       u >= buffer->width ||
       v >= buffer->height)
    {
      *((uint32_t*)(rgba))= 0;
    }
  else
    {
      int bpp = 4;
      float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
          int dim = (1.0 / factor) / 2;
          uint64_t sum[4]={0,0,0,0};
          int count = 0;
          int width = buffer->width;
          int height = buffer->height;
          for (int ou = - dim; ou < dim; ou++)
          for (int ov = - dim; ov < dim; ov++)
          {

            if (v+ov >= 0 && u+ou >=0 && u + ou < width && v + ov < height)
            {
              int o = (v+ov) * width + (u + ou);
              uint8_t *src = (uint8_t *) buffer->data + o * bpp;

              for (int c = 0; c < bpp; c++)
                sum[c] += src[c];
              count ++;
            }
          }
          if (count)
          {
            int recip = 65536/count;
            for (int c = 0; c < bpp; c++)
              rgba[c] = sum[c]*recip>>16;
          }
          ctx_RGBA8_associate_alpha_probably_opaque (rgba);
    }
    rgba += 4;
    x += dx;
    y += dy;
  }
#if CTX_DITHER
//ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
//                    rasterizer->format->dither_green);
#endif
}


static void
ctx_fragment_image_rgba8_RGBA8_nearest (CtxRasterizer *rasterizer,
                                        float x,
                                        float y,
                                        void *out, int count, float dx, float dy)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer;
  if (buffer->color_managed)
    buffer = buffer->color_managed;
  int ideltax = dx * 65536;
  int ideltay = dy * 65536;
  uint32_t *src = (uint32_t *) buffer->data;
  uint32_t *dst = (uint32_t*)out;
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  x += 0.5f;
  y += 0.5f;

#if 1
  if (CTX_UNLIKELY(ideltay == 0 && ideltax == 65536))
  {
    int i = 0;
    int u = x;
    int v = y;
    if (!(v >= 0 && v < bheight))
    {
      for (i = 0 ; i < count; i++)
        *dst++ = 0;
      return;
    }
    src += bwidth * v + u;
    while (count && !(u >= 0))
    {
      *dst++ = 0;
      src ++;
      u++;
      count--;
    }
    int limit = ctx_mini (count, bwidth - u);
    if (limit>0)
    {
      memcpy (dst, src, limit * 4);
      dst += limit;
      i = limit;
    }
    for (;i < count; i++)
      *dst++ = 0;
    return;
  }
#endif

  {
    int i = 0;
    float u1 = x + dx * (count-1);
    float v1 = y + dy * (count-1);
    uint32_t *edst = ((uint32_t*)out)+count;
    for (; i < count; )
    {
      if ((u1 < 0.0f || v1 < 0.0f || u1 >= bwidth || v1 >= bheight))
      {
        *edst-- = 0;
        count --;
        u1 -= dx;
        v1 -= dy;
      }
      else break;
    }


    for (i = 0; i < count; i ++)
    {
      if ((x < 0.0f || y < 0.0f || x >= bwidth || y >= bheight))
      {
        *dst = 0;
        dst++;
        x += dx;
        y += dy;
      }
      else break;
    }


    uint32_t ix = x * 65536;
    uint32_t iy = y * 65536;

    for (; i < count; i ++)
    {
      *dst = src[(iy>>16) * bwidth + (ix>>16)];
      ix += ideltax;
      iy += ideltay;
      dst++;
    }
  }
}



static void
ctx_fragment_image_rgba8_RGBA8_bi (CtxRasterizer *rasterizer,
                                   float x,
                                   float y,
                                   void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  float ox = (x-(int)(x));
  float oy = (y-(int)(y));

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  int i = 0;

  if (dy == 0.0f && dx > 0.0f)
  {
    if ((dx > 0.99f && dx < 1.01f && 
         ox < 0.01 && oy < 0.01) || y < 0 || y > bheight-1)
    {
      /* TODO: this could have been rigged up in composite_setup */
      ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer,
                                   x, y, out, count, dx, dy);
      return;
    }
    x+=1; // XXX off by one somewhere? ,, needed for alignment with nearest

    uint32_t *data = ((uint32_t*)buffer->data);
    uint32_t yi = y * 65536;
    uint32_t xi = x * 65536;
    int xi_delta = dx * 65536;

    for (i= 0; i < count; i ++)
    {
      int u = xi >> 16;
      if ( u  < 0 || u >= bwidth-1)
      {
        *((uint32_t*)(rgba))= 0;
        xi += xi_delta;
        rgba += 4;
      }
      else
        break;
    }

  int loaded = -4;
  uint32_t s0_ga = 0, s0_rb = 0, s1_ga = 0, s1_rb = 0;
 
  int v = yi >> 16;
  data += bwidth * v;
  int dv = (yi >> 8) & 0xff;

  int u = xi >> 16;

  uint32_t *ndata = data;
  if (v < bheight-1) ndata += bwidth;

  uint32_t *src0 = data, *src1 = ndata;


  if (xi_delta == 65536 && u < bwidth -1)
  {
    int du = (xi >> 8);

    src0 = data + u;
    src1 = ndata + u;
    ctx_lerp_RGBA8_split (src0[0],src1[0], dv, &s1_ga, &s1_rb);

    int limit = bwidth-u;
    limit = ctx_mini(count,limit);

    for (; i < limit; i ++)
    {
      s0_ga = s1_ga;
      s0_rb = s1_rb;
      ctx_lerp_RGBA8_split (src0[1],src1[1], dv, &s1_ga, &s1_rb);
      ((uint32_t*)(&rgba[0]))[0] = 
      ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, du);
      rgba += 4;
      u++;
      src0 ++;
      src1 ++;
    }
  }
  else
  {

  for (; i < count; i ++)
  {
    if (CTX_UNLIKELY(u >= bwidth-1))
    {
      break;
    }
    else if (CTX_LIKELY(loaded + 1 == u))
    {
      s0_ga = s1_ga;
      s0_rb = s1_rb;
      ctx_lerp_RGBA8_split (src0[1],src1[1], dv, &s1_ga, &s1_rb);
      src0 ++;
      src1 ++;
    }
    else if (loaded != u)
    {
      src0 = data + u;
      src1 = ndata + u;
      ctx_lerp_RGBA8_split (src0[0],src1[0], dv, &s0_ga, &s0_rb);
      ctx_lerp_RGBA8_split (src0[1],src1[1], dv, &s1_ga, &s1_rb);
    }
    loaded = u;
    ((uint32_t*)(&rgba[0]))[0] = 
      ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, (xi>>8));
    xi += xi_delta;
    rgba += 4;
    u = xi >> 16;
  }
  }

  }
  else
  {
    uint32_t *data = ((uint32_t*)buffer->data);
    for (i= 0; i < count; i ++)
    {
      int u = x;
      int v = y;
      int ut = x + 1.5;
      int vt = y + 1.5;
      if ( ut  <= 0 || vt  <= 0 || u >= buffer->width || v >= buffer->height)
      {
        *((uint32_t*)(rgba))= 0;
      }
      else
        break;
      x += dx;
      y += dy;
      rgba += 4;
    }

  uint32_t yi = y * 65536;
  uint32_t xi = x * 65536;

  int yi_delta = dy * 65536;
  int xi_delta = dx * 65536;

  int loaded = -4;
  uint32_t *src00=data;
  uint32_t *src01=data;
  uint32_t *src10=data;
  uint32_t *src11=data;

  int u = xi >> 16;
  int v = yi >> 16;
  int offset = bwidth * v + u;

  for (; i < count; i ++)
  {
  if (CTX_UNLIKELY(
       u >= buffer->width ||
       v  <= -65536 ||
       u  <= -65536 ||
       v >= buffer->height))
    {
      break;
    }
#if 1
  else if (CTX_UNLIKELY(u < 0 || v < 0)) // default to next sample down and to right
  {
      int got_prev_pix = (u >= 0);
      int got_prev_row = (v>=0);
      src11 = data  + offset + bwidth + 1;
      src10 = src11 - got_prev_pix;
      src01 = src11 - bwidth * got_prev_row;
      src00 = src10 - bwidth * got_prev_row;
  }
#endif
#if 1
  else if (loaded + 1 == offset)
  {
      src00++;
      src01++;
      src10++;
      src11++;
  }
#endif
  else if (loaded != offset)
  {
      int next_row = ( v + 1 < bheight) * bwidth;
      int next_pix = (u + 1 < bwidth);
      src00 = data  + offset;
      src01 = src00 + next_pix;
      src10 = src00 + next_row;
      src11 = src01 + next_row;
  }
    loaded = offset;
    ((uint32_t*)(&rgba[0]))[0] = ctx_bi_RGBA8 (*src00,*src01,*src10,*src11, (xi>>8),(yi>>8)); // the argument type does the & 0xff
    xi += xi_delta;
    yi += yi_delta;
    rgba += 4;

    u = xi >> 16;
    v = yi >> 16;
    offset = bwidth * v + u;
  }
  }

  for (; i < count; i ++)
  {
    *((uint32_t*)(rgba))= 0;
    rgba += 4;
  }
}

#define ctx_clampi(val,min,max) \
     ctx_mini (ctx_maxi ((val), (min)), (max))

static inline uint32_t ctx_yuv_to_rgba32 (uint8_t y, uint8_t u, uint8_t v)
{
  int cy  = ((y - 16) * 76309) >> 16;
  int cr  = (v - 128);
  int cb  = (u - 128);
  int red = cy + ((cr * 104597) >> 16);
  int green = cy - ((cb * 25674 + cr * 53278) >> 16);
  int blue = cy + ((cb * 132201) >> 16);
  return  ctx_clampi (red, 0, 255) |
          (ctx_clampi (green, 0, 255) << 8) |
          (ctx_clampi (blue, 0, 255) << 16) |
          (0xff << 24);
}

static void
ctx_fragment_image_yuv420_RGBA8_nearest (CtxRasterizer *rasterizer,
                                         float x,
                                         float y,
                                         void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer;
  if (buffer->color_managed)
    buffer = buffer->color_managed;
  uint8_t *src = (uint8_t *) buffer->data;
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  int bwidth_div_2  = bwidth/2;
  int bheight_div_2  = bheight/2;
  x += 0.5f;
  y += 0.5f;

  {
    int i = 0;

    for (; i < count; i ++)
    {
      int u = x;
      int v = y;
      if ((u < 0 || v < 0 || u >= bwidth || v >= bheight))
      {
        *((uint32_t*)(rgba))= 0;
      }
      else
      {
        break;
      }
      x += dx;
      y += dy;
      rgba += 4;
    }

    uint32_t u_offset = bheight * bwidth;
    uint32_t v_offset = u_offset + bheight_div_2 * bwidth_div_2;
    if (rasterizer->swap_red_green)
    {
      v_offset = bheight * bwidth;
      u_offset = v_offset + bheight_div_2 * bwidth_div_2;
    }

    int ix = x * 65536;
    int iy = y * 65536;

    int ideltax = dx * 65536;
    int ideltay = dy * 65536;

    for (; i < count; i ++)
    {
      int u = ix >> 16;
      int v = iy >> 16;
      if (u >= 0 && v >= 0 && u < bwidth && v < bheight)
      {
        uint32_t y  = v * bwidth + u;
        uint32_t uv = (v / 2) * bwidth_div_2 + (u / 2);

        *((uint32_t*)(rgba))= ctx_yuv_to_rgba32 (src[y],
                        //127, 127);
                        src[u_offset+uv], src[v_offset+uv]);
        //ctx_RGBA8_associate_alpha_probably_opaque (rgba);
#if CTX_DITHER
       ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                           rasterizer->format->dither_green);
#endif
      }
      else
      {
        break;
      }
      ix += ideltax;
      iy += ideltay;
      rgba += 4;
    }

    for (; i < count; i++)
    {
      *((uint32_t*)(rgba))= 0;
      rgba += 4;
    }
  }
}

static void
ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (CtxRasterizer *rasterizer,
                                    float x,
                                    float y,
                                    void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static void
ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (CtxRasterizer *rasterizer,
                                    float x,
                                    float y,
                                    void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static void
ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (CtxRasterizer *rasterizer,
                                    float x,
                                    float y,
                                    void *out, int count, float dx, float dy)
{
  ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
  ctx_fragment_swap_red_green_u8 (out, count);
}

static void
ctx_fragment_image_rgba8_RGBA8 (CtxRasterizer *rasterizer,
                                float x,
                                float y,
                                void *out, int count, float dx, float dy)
{
  if (rasterizer->state->gstate.image_smoothing)
  {
    float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
    if (factor <= 0.50f)
    {
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgba8_RGBA8_box_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, out, count, dx, dy);
    }
#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
    else if (factor > 0.99f && factor < 1.01f)
    {
      // XXX: also verify translate == 0 for this fast path to be valid
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
    }
#endif
    else
    {
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (rasterizer, x, y, out, count, dx, dy);
      else
        ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, out, count, dx, dy);
    }
  }
  else
  {
    if (rasterizer->swap_red_green)
      ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, out, count, dx, dy);
    else
      ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, out, count, dx, dy);
  }
  //ctx_fragment_swap_red_green_u8 (out, count);
#if CTX_DITHER
  uint8_t *rgba = (uint8_t*)out;
  ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
}

static void
ctx_fragment_image_gray1_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer;
  ctx_assert (rasterizer);
  ctx_assert (g);
  ctx_assert (buffer);
  for (int i = 0; i < count; i ++)
  {
  int u = x;
  int v = y;
  if ( u < 0 || v < 0 ||
       u >= buffer->width ||
       v >= buffer->height)
    {
      rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0;
    }
  else
    {
      uint8_t *src = (uint8_t *) buffer->data;
      src += v * buffer->stride + u / 8;
      if (*src & (1<< (u & 7) ) )
        {
          rgba[0] = rgba[1] = rgba[2] = rgba[3] = 0;
        }
      else
        {
          for (int c = 0; c < 4; c++)
            { rgba[c] = 255;
            }//g->texture.rgba[c];
            //}
        }
    }

    rgba += 4;
    x += dx;
    y += dy;
  }
}

#if CTX_GRADIENTS
static void
ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i <  count; i ++)
  {
    float v = (ctx_hypotf_fast (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) -
              g->radial_gradient.r0) * (g->radial_gradient.rdelta);
#if CTX_GRADIENT_CACHE
    uint32_t *rgbap = (uint32_t*)&ctx_gradient_cache_u8[ctx_grad_index(v)][0];
    *((uint32_t*)rgba) = *rgbap;
#else
    ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba);
#endif
#if CTX_DITHER
    ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                        rasterizer->format->dither_green);
#endif
    rgba += 4;
    x += dx;
    y += dy;
  }
}

static void
ctx_fragment_linear_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
#if 0
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i <  count; i ++)
  {
  float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) /
                g->linear_gradient.length) -
              g->linear_gradient.start) * (g->linear_gradient.rdelta);
#if CTX_GRADIENT_CACHE
  uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index(v)][0]));
  *((uint32_t*)rgba) = *rgbap;
#else
  _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba);
#endif
#if CTX_DITHER
  ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
    rgba += 4;
    x += dx;
    y += dy;
  }
#else
  uint8_t *rgba = (uint8_t *) out;

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  float u0 = x; float v0 = y;
  float ud = dx; float vd = dy;
  float linear_gradient_rdelta = g->linear_gradient.rdelta;
  float linear_gradient_length = g->linear_gradient.length;
  float linear_gradient_length_recip = 1.0f/linear_gradient_length;
  float linear_gradient_dx = g->linear_gradient.dx *linear_gradient_length_recip * linear_gradient_rdelta;
  float linear_gradient_dy = g->linear_gradient.dy *linear_gradient_length_recip * linear_gradient_rdelta;
  float linear_gradient_start = g->linear_gradient.start * linear_gradient_rdelta;

#if CTX_DITHER
  int dither_red_blue = rasterizer->format->dither_red_blue;
  int dither_green = rasterizer->format->dither_green;
#endif

  u0 *= linear_gradient_dx;
  v0 *= linear_gradient_dy;
  ud *= linear_gradient_dx;
  vd *= linear_gradient_dy;

#if CTX_GRADIENT_CACHE
  int vv = ((u0 + v0) - linear_gradient_start) * (CTX_GRADIENT_CACHE_ELEMENTS-1) * 256;
  int ud_plus_vd = (ud + vd) * (CTX_GRADIENT_CACHE_ELEMENTS-1) * 256;
#else
  float vv = ((u0 + v0) - linear_gradient_start);
  float ud_plus_vd = (ud + vd);
#endif

  for (int x = 0; x < count ; x++)
  {
#if CTX_GRADIENT_CACHE
  uint32_t*rgbap = ((uint32_t*)(&ctx_gradient_cache_u8[ctx_grad_index_i (vv)][0]));
  *((uint32_t*)rgba) = *rgbap;
#else
  _ctx_fragment_gradient_1d_RGBA8 (rasterizer, vv, 1.0, rgba);
#endif
#if CTX_DITHER
      ctx_dither_rgba_u8 (rgba, u0, v0, dither_red_blue, dither_green);
#endif
    rgba+= 4;
    vv += ud_plus_vd;
  }
#endif
}

#endif

static void
ctx_fragment_color_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t *rgba_out = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  _ctx_color_get_rgba8 (rasterizer->state, &g->color, rgba_out);
  ctx_RGBA8_associate_alpha (rgba_out);
  if (rasterizer->swap_red_green)
  {
    int tmp = rgba_out[0];
    rgba_out[0] = rgba_out[2];
    rgba_out[2] = tmp;
  }
  for (int i = 1; i < count; i++, rgba_out+=4)
    memcpy (rgba_out + count * 4, rgba_out, 4);
}
#if CTX_ENABLE_FLOAT

#if CTX_GRADIENTS
static void
ctx_fragment_linear_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float *rgba = (float *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i < count; i++)
  {
    float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) /
                  g->linear_gradient.length) -
                g->linear_gradient.start) * (g->linear_gradient.rdelta);
    ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0f, rgba);
    x += dx;
    y += dy;
    rgba += 4;
  }
}

static void
ctx_fragment_radial_gradient_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float *rgba = (float *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i < count; i++)
  {
  float v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y);
        v = (v - g->radial_gradient.r0) * (g->radial_gradient.rdelta);
  ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0f, rgba);
    x+=dx;
    y+=dy;
    rgba +=4;
  }
}
#endif


static void
ctx_fragment_color_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float *rgba = (float *) out;
  for (int i = 0; i < count; i++)
  {
    CtxSource *g = &rasterizer->state->gstate.source_fill;
    ctx_color_get_rgba (rasterizer->state, &g->color, rgba);
    for (int c = 0; c < 3; c++)
      rgba[c] *= rgba[3];
    rgba += 4;
  }
}

static void ctx_fragment_image_RGBAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float *outf = (float *) out;
  uint8_t rgba[4];
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxBuffer *buffer = gstate->source_fill.texture.buffer;
  switch (buffer->format->bpp)
    {
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);       break;
    }
  for (int c = 0; c < 4 * count; c ++) { outf[c] = ctx_u8_to_float (rgba[c]); }
}

static CtxFragment ctx_rasterizer_get_fragment_RGBAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:         return ctx_fragment_image_RGBAF;
      case CTX_SOURCE_COLOR:           return ctx_fragment_color_RGBAF;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBAF;
      case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBAF;
#endif
    }
  return ctx_fragment_color_RGBAF;
}
#endif

static CtxFragment ctx_rasterizer_get_fragment_RGBA8 (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxBuffer *buffer = gstate->source_fill.texture.buffer;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:
        if (!buffer || !buffer->format)
          return ctx_fragment_color_RGBA8;

        if (buffer->format->pixel_format == CTX_FORMAT_YUV420)
        {
          return ctx_fragment_image_yuv420_RGBA8_nearest;
        }
        else
        switch (buffer->format->bpp)
          {
            case 1:  return ctx_fragment_image_gray1_RGBA8;
            case 24: 
              {
                if (gstate->image_smoothing)
                {
                  float factor = ctx_matrix_get_scale (&gstate->transform);
                          //fprintf (stderr, "{%.3f}", factor);
                  if (factor < 0.5f)
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgb8_RGBA8_box_swap_red_green;
                    return ctx_fragment_image_rgb8_RGBA8_box;
                  }
#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
                  else if (factor > 0.99f && factor < 1.01f)
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green;
                    return ctx_fragment_image_rgb8_RGBA8_nearest;
                  }
#endif
                  else
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green;
                    return ctx_fragment_image_rgb8_RGBA8_bi;
                  }
                }
                else
                {
                  if (rasterizer->swap_red_green)
                    return ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green;
                  return ctx_fragment_image_rgb8_RGBA8_nearest;
                }
              }
              break;
            case 32:
              {
                if (gstate->image_smoothing)
                {
                  float factor = ctx_matrix_get_scale (&gstate->transform);
                          //fprintf (stderr, "[%.3f]", factor);
                  if (factor < 0.5f)
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgba8_RGBA8_box_swap_red_green;
                    return ctx_fragment_image_rgba8_RGBA8_box;
                  }
#if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
                  else if (factor > 0.99f && factor < 1.01f)
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green;
                    return ctx_fragment_image_rgba8_RGBA8_nearest;
                  }
#endif
                  else
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green;
                    return ctx_fragment_image_rgba8_RGBA8_bi;
                  }
                }
                else
                {
                  if (rasterizer->swap_red_green)
                    return ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green;
                  return ctx_fragment_image_rgba8_RGBA8_nearest;
                }
              }
            default: return ctx_fragment_image_RGBA8;
          }
      case CTX_SOURCE_COLOR:           return ctx_fragment_color_RGBA8;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_RGBA8;
      case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_RGBA8;
#endif
    }
  return ctx_fragment_color_RGBA8;
}

static void
ctx_init_uv (CtxRasterizer *rasterizer,
             int x0, int count,
             float *u0, float *v0, float *ud, float *vd)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  *u0 = x0;
  *v0 = rasterizer->scanline / 15;//rasterizer->aa;
  float u1 = *u0 + count;
  float v1 = *v0;

  _ctx_matrix_apply_transform (&gstate->source_fill.transform, u0, v0);
  _ctx_matrix_apply_transform (&gstate->source_fill.transform, &u1, &v1);

  *ud = (u1-*u0) / (count);
  *vd = (v1-*v0) / (count);
}


static void
ctx_u8_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS)
{
  if (CTX_UNLIKELY(rasterizer->fragment))
    {
      float u0 = 0; float v0 = 0;
      float ud = 0; float vd = 0;
      ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);
      while (count--)
      {
        uint8_t cov = *coverage;
        if (CTX_UNLIKELY(cov == 0))
        {
          u0+=ud;
          v0+=vd;
        }
        else
        {
          rasterizer->fragment (rasterizer, u0, v0, src, 1, ud, vd);
          u0+=ud;
          v0+=vd;
          if (cov == 255)
          {
            for (int c = 0; c < components; c++)
              dst[c] = src[c];
          }
          else
          {
            uint8_t rcov = 255 - cov;
            for (int c = 0; c < components; c++)
              { dst[c] = (src[c]*cov + dst[c]*rcov)/255; }
          }
        }
        dst += components;
        coverage ++;
      }
      return;
    }

  while (count--)
  {
    uint8_t cov = *coverage;
    uint8_t rcov = 255-cov;
    for (int c = 0; c < components; c++)
      { dst[c] = (src[c]*cov+dst[c]*rcov)/255; }
    dst += components;
    coverage ++;
  }
}

static void
ctx_u8_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS)
{
  while (count--)
  {
    uint8_t cov = *coverage;
    for (int c = 0; c < components; c++)
      { dst[c] = (dst[c] * (256-cov)) >> 8; }
    coverage ++;
    dst += components;
  }
}

typedef enum {
  CTX_PORTER_DUFF_0,
  CTX_PORTER_DUFF_1,
  CTX_PORTER_DUFF_ALPHA,
  CTX_PORTER_DUFF_1_MINUS_ALPHA,
} CtxPorterDuffFactor;

#define  \
ctx_porter_duff_factors(mode, foo, bar)\
{\
  switch (mode)\
  {\
     case CTX_COMPOSITE_SOURCE_ATOP:\
        f_s = CTX_PORTER_DUFF_ALPHA;\
        f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
      break;\
     case CTX_COMPOSITE_DESTINATION_ATOP:\
        f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
        f_d = CTX_PORTER_DUFF_ALPHA;\
      break;\
     case CTX_COMPOSITE_DESTINATION_IN:\
        f_s = CTX_PORTER_DUFF_0;\
        f_d = CTX_PORTER_DUFF_ALPHA;\
      break;\
     case CTX_COMPOSITE_DESTINATION:\
        f_s = CTX_PORTER_DUFF_0;\
        f_d = CTX_PORTER_DUFF_1;\
       break;\
     case CTX_COMPOSITE_SOURCE_OVER:\
        f_s = CTX_PORTER_DUFF_1;\
        f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
       break;\
     case CTX_COMPOSITE_DESTINATION_OVER:\
        f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
        f_d = CTX_PORTER_DUFF_1;\
       break;\
     case CTX_COMPOSITE_XOR:\
        f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
        f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
       break;\
     case CTX_COMPOSITE_DESTINATION_OUT:\
        f_s = CTX_PORTER_DUFF_0;\
        f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
       break;\
     case CTX_COMPOSITE_SOURCE_OUT:\
        f_s = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
        f_d = CTX_PORTER_DUFF_0;\
       break;\
     case CTX_COMPOSITE_SOURCE_IN:\
        f_s = CTX_PORTER_DUFF_ALPHA;\
        f_d = CTX_PORTER_DUFF_0;\
       break;\
     case CTX_COMPOSITE_COPY:\
        f_s = CTX_PORTER_DUFF_1;\
        f_d = CTX_PORTER_DUFF_1_MINUS_ALPHA;\
       break;\
     default:\
     case CTX_COMPOSITE_CLEAR:\
        f_s = CTX_PORTER_DUFF_0;\
        f_d = CTX_PORTER_DUFF_0;\
       break;\
  }\
}

static void
ctx_u8_source_over_normal_color (int components,
                                 CtxRasterizer         *rasterizer,
                                 uint8_t * __restrict__ dst,
                                 uint8_t * __restrict__ src,
                                 int                    x0,
                                 uint8_t * __restrict__ coverage,
                                 int                    count)
{
  uint8_t tsrc[5];
  *((uint32_t*)tsrc) = *((uint32_t*)src);

  while (count--)
  {
    for (int c = 0; c < components; c++)
      //dst[c] =  ((tsrc[c] * *coverage)>>8) + (dst[c] * (((65536)-(tsrc[components-1] * *coverage)))>>16);
      dst[c] =  ((((tsrc[c] * *coverage)) + (dst[c] * (((255)-(((255+(tsrc[components-1] * *coverage))>>8))))))>>8);
    coverage ++;
    dst+=components;
  }
}

static void
ctx_u8_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS)
{
  while (count--)
  {
    for (int c = 0; c < components; c++)
      dst[c] =  ctx_lerp_u8(dst[c],src[c],coverage[0]);
    coverage ++;
    dst+=components;
  }
}

static inline void
ctx_RGBA8_source_over_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc)
{
  while (count--)
  {
     uint32_t si_ga = ((*((uint32_t*)tsrc)) & 0xff00ff00) >> 8;
     uint32_t si_rb = (*((uint32_t*)tsrc)) & 0x00ff00ff;
//     uint32_t di_ga = ((*((uint32_t*)dst)) & 0xff00ff00) >> 8;
//     uint32_t di_rb = (*((uint32_t*)dst)) & 0x00ff00ff;
     uint32_t si_a  = si_ga >> 16;
     uint32_t cov = *coverage;
     uint32_t racov = (255-((255+si_a*cov)>>8));
     *((uint32_t*)(dst)) =

     (((si_rb*cov+0xff00ff+(((*((uint32_t*)(dst)))&0x00ff00ff)*racov))>>8)&0x00ff00ff)|
     ((si_ga*cov+0xff00ff+((((*((uint32_t*)(dst)))&0xff00ff00)>>8)*racov))&0xff00ff00);

     coverage ++;
     tsrc += 4;
     dst  += 4;
  }
}

static inline void
ctx_RGBA8_source_over_normal_full_cov_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc)
{
  while (count--)
  {
     uint32_t si_ga = ((*((uint32_t*)tsrc)) & 0xff00ff00) >> 8;
     uint32_t si_rb = (*((uint32_t*)tsrc)) & 0x00ff00ff;
     uint32_t si_a  = si_ga >> 16;
     uint32_t racov = (255-si_a);
     *((uint32_t*)(dst)) =
     (((si_rb*255+0xff00ff+(((*((uint32_t*)(dst)))&0x00ff00ff)*racov))>>8)&0x00ff00ff)|
     ((si_ga*255+0xff00ff+((((*((uint32_t*)(dst)))&0xff00ff00)>>8)*racov))&0xff00ff00);
     tsrc += 4;
     dst  += 4;
  }
}

static void
ctx_RGBA8_source_copy_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *tsrc)
{
  while (count--)
  {
    ((uint32_t*)dst)[0]=ctx_lerp_RGBA8 (((uint32_t*)dst)[0],
                                        ((uint32_t*)tsrc)[0], coverage[0]);
    coverage ++;
    tsrc += 4;
    dst  += 4;
  }
}

static void
ctx_RGBA8_source_over_normal_fragment (CTX_COMPOSITE_ARGUMENTS)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);
  uint8_t _tsrc[4 * (count)];
  rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd);
  ctx_RGBA8_source_over_normal_buf (rasterizer,
                       dst, src, x0, coverage, count, &_tsrc[0]);
}

static inline void
ctx_RGBA8_source_over_normal_full_cov_fragment (CTX_COMPOSITE_ARGUMENTS)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);
  uint8_t _tsrc[4 * (count)];
  rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd);
  ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
                       dst, src, x0, coverage, count, &_tsrc[0]);
}

static void
ctx_RGBA8_source_copy_normal_fragment (CTX_COMPOSITE_ARGUMENTS)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);
  uint8_t _tsrc[4 * (count)];
  rasterizer->fragment (rasterizer, u0, v0, &_tsrc[0], count, ud, vd);
  ctx_RGBA8_source_copy_normal_buf (rasterizer,
                       dst, src, x0, coverage, count, &_tsrc[0]);
}


static void
ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
#if CTX_REFERENCE
  ctx_u8_source_over_normal_color (4, rasterizer, dst, src, x0, coverage, count);
#else
  uint32_t si_ga = ((uint32_t*)rasterizer->color)[1];
  uint32_t si_rb = ((uint32_t*)rasterizer->color)[2];
  uint32_t si_a  = si_ga >> 16;

  while (count--)
  {
     uint32_t cov   = *coverage++;
     uint32_t rcov  = (((255+si_a * cov)>>8))^255;
     uint32_t di    = *((uint32_t*)dst);
     uint32_t di_ga = ((di & 0xff00ff00) >> 8);
     uint32_t di_rb = (di & 0x00ff00ff);
     *((uint32_t*)(dst)) =
     (((si_rb * cov + 0xff00ff + di_rb * rcov) & 0xff00ff00) >> 8)  |
      ((si_ga * cov + 0xff00ff + di_ga * rcov) & 0xff00ff00);
     dst+=4;
  }
#endif
}

static void
ctx_RGBA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
#if CTX_REFERENCE
  ctx_u8_source_copy_normal_color (4, rasterizer, dst, src, x0, coverage, count);
#else
  uint32_t si_ga = ((uint32_t*)rasterizer->color)[1];
  uint32_t si_rb = ((uint32_t*)rasterizer->color)[2];

  while (count--)
  {
     uint32_t cov   = *coverage++;
     uint32_t di    = *((uint32_t*)dst);
     uint32_t di_ga = (di & 0xff00ff00);
     uint32_t di_rb = (di & 0x00ff00ff);

     uint32_t d_rb  = si_rb - di_rb;
     uint32_t d_ga  = si_ga - (di_ga>>8);

     *((uint32_t*)(dst)) =

     (((di_rb + ((d_rb * cov)>>8)) & 0x00ff00ff))  |
      ((di_ga + ((d_ga * cov)      & 0xff00ff00)));
     dst +=4;
  }
#endif
}

static void
ctx_RGBA8_clear_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_u8_clear_normal (4, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_u8_blend_normal (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count)
{
  for (int j = 0; j < count; j++)
  {
  switch (components)
  {
     case 3:
       ((uint8_t*)(blended))[2] = ((uint8_t*)(src))[2];
       *((uint16_t*)(blended)) = *((uint16_t*)(src));
       break;
     case 2:
       *((uint16_t*)(blended)) = *((uint16_t*)(src));
       break;
     case 5:
       *((uint32_t*)(blended)) = *((uint32_t*)(src));
       ((uint8_t*)(blended))[4] = ((uint8_t*)(src))[4];
       break;
     case 4:
       *((uint32_t*)(blended)) = *((uint32_t*)(src));
       break;
     default:
       {
        for (int i = 0; i<components;i++)
           blended[i] = src[i];
       }
       break;
  }
    blended+=components;
    src+=components;
  }
}

/* branchless 8bit add that maxes out at 255 */
static inline uint8_t ctx_sadd8(uint8_t a, uint8_t b)
{
  uint16_t s = (uint16_t)a+b;
  return -(s>>8) | (uint8_t)s;
}

#if CTX_BLENDING_AND_COMPOSITING

#define ctx_u8_blend_define(name, CODE) \
static void \
ctx_u8_blend_##name (int components, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count)\
{\
  for (int j = 0; j < count; j++) { \
  uint8_t *s=src; uint8_t b[components];\
  ctx_u8_deassociate_alpha (components, dst, b);\
    CODE;\
  blended[components-1] = src[components-1];\
  ctx_u8_associate_alpha (components, blended);\
  src += components;\
  dst += components;\
  blended += components;\
  }\
}

#define ctx_u8_blend_define_seperable(name, CODE) \
        ctx_u8_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \

ctx_u8_blend_define_seperable(multiply,     blended[c] = (b[c] * s[c])/255;)
ctx_u8_blend_define_seperable(screen,       blended[c] = s[c] + b[c] - (s[c] * b[c])/255;)
ctx_u8_blend_define_seperable(overlay,      blended[c] = b[c] < 127 ? (s[c] * b[c])/255 :
                                                         s[c] + b[c] - (s[c] * b[c])/255;)
ctx_u8_blend_define_seperable(darken,       blended[c] = ctx_mini (b[c], s[c]))
ctx_u8_blend_define_seperable(lighten,      blended[c] = ctx_maxi (b[c], s[c]))
ctx_u8_blend_define_seperable(color_dodge,  blended[c] = b[c] == 0 ? 0 :
                                     s[c] == 255 ? 255 : ctx_mini(255, (255 * b[c]) / (255-s[c])))
ctx_u8_blend_define_seperable(color_burn,   blended[c] = b[c] == 1 ? 1 :
                                     s[c] == 0 ? 0 : 255 - ctx_mini(255, (255*(255 - b[c])) / s[c]))
ctx_u8_blend_define_seperable(hard_light,   blended[c] = s[c] < 127 ? (b[c] * s[c])/255 :
                                                          b[c] + s[c] - (b[c] * s[c])/255;)
ctx_u8_blend_define_seperable(difference,   blended[c] = (b[c] - s[c]))
ctx_u8_blend_define_seperable(divide,       blended[c] = s[c]?(255 * b[c]) / s[c]:0)
ctx_u8_blend_define_seperable(addition,     blended[c] = ctx_sadd8 (s[c], b[c]))
ctx_u8_blend_define_seperable(subtract,     blended[c] = ctx_maxi(0, s[c]-b[c]))
ctx_u8_blend_define_seperable(exclusion,    blended[c] = b[c] + s[c] - 2 * (b[c] * s[c]/255))
ctx_u8_blend_define_seperable(soft_light,
  if (s[c] <= 255/2)
  {
    blended[c] = b[c] - (255 - 2 * s[c]) * b[c] * (255 - b[c]) / (255 * 255);
  }
  else
  {
    int d;
    if (b[c] <= 255/4)
      d = (((16 * b[c] - 12 * 255)/255 * b[c] + 4 * 255) * b[c])/255;
    else
      d = ctx_sqrtf(b[c]/255.0) * 255.4;
    blended[c] = (b[c] + (2 * s[c] - 255) * (d - b[c]))/255;
  }
)

static int ctx_int_get_max (int components, int *c)
{
  int max = 0;
  for (int i = 0; i < components - 1; i ++)
  {
    if (c[i] > max) max = c[i];
  }
  return max;
}

static int ctx_int_get_min (int components, int *c)
{
  int min = 400;
  for (int i = 0; i < components - 1; i ++)
  {
    if (c[i] < min) min = c[i];
  }
  return min;
}

static int ctx_int_get_lum (int components, int *c)
{
  switch (components)
  {
    case 3:
    case 4:
            return CTX_CSS_RGB_TO_LUMINANCE(c);
    case 1:
    case 2:
            return c[0];
            break;
    default:
       {
         int sum = 0;
         for (int i = 0; i < components - 1; i ++)
         {
           sum += c[i];
         }
         return sum / (components - 1);
       }
            break;
  }
}

static int ctx_u8_get_lum (int components, uint8_t *c)
{
  switch (components)
  {
    case 3:
    case 4:
            return CTX_CSS_RGB_TO_LUMINANCE(c);
    case 1:
    case 2:
            return c[0];
            break;
    default:
       {
         int sum = 0;
         for (int i = 0; i < components - 1; i ++)
         {
           sum += c[i];
         }
         return sum / (components - 1);
       }
            break;
  }
}
static int ctx_u8_get_sat (int components, uint8_t *c)
{
  switch (components)
  {
    case 3:
    case 4:
            { int r = c[0];
              int g = c[1];
              int b = c[2];
              return ctx_maxi(r, ctx_maxi(g,b)) - ctx_mini(r,ctx_mini(g,b));
            }
            break;
    case 1:
    case 2:
            return 0.0;
            break;
    default:
       {
         int min = 1000;
         int max = -1000;
         for (int i = 0; i < components - 1; i ++)
         {
           if (c[i] < min) min = c[i];
           if (c[i] > max) max = c[i];
         }
         return max-min;
       }
       break;
  }
}

static void ctx_u8_set_lum (int components, uint8_t *c, uint8_t lum)
{
  int d = lum - ctx_u8_get_lum (components, c);
  int tc[components];
  for (int i = 0; i < components - 1; i++)
  {
    tc[i] = c[i] + d;
  }

  int l = ctx_int_get_lum (components, tc);
  int n = ctx_int_get_min (components, tc);
  int x = ctx_int_get_max (components, tc);

  if (n < 0 && l!=n)
  {
    for (int i = 0; i < components - 1; i++)
      tc[i] = l + (((tc[i] - l) * l) / (l-n));
  }

  if (x > 255 && x!=l)
  {
    for (int i = 0; i < components - 1; i++)
      tc[i] = l + (((tc[i] - l) * (255 - l)) / (x-l));
  }
  for (int i = 0; i < components - 1; i++)
    c[i] = tc[i];
}

static void ctx_u8_set_sat (int components, uint8_t *c, uint8_t sat)
{
  int max = 0, mid = 1, min = 2;
  
  if (c[min] > c[mid]){int t = min; min = mid; mid = t;}
  if (c[mid] > c[max]){int t = mid; mid = max; max = t;}
  if (c[min] > c[mid]){int t = min; min = mid; mid = t;}

  if (c[max] > c[min])
  {
    c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]);
    c[max] = sat;
  }
  else
  {
    c[mid] = c[max] = 0;
  }
  c[min] = 0;
}

ctx_u8_blend_define(color,
  for (int i = 0; i < components; i++)
    blended[i] = s[i];
  ctx_u8_set_lum(components, blended, ctx_u8_get_lum (components, s));
)

ctx_u8_blend_define(hue,
  int in_sat = ctx_u8_get_sat(components, b);
  int in_lum = ctx_u8_get_lum(components, b);
  for (int i = 0; i < components; i++)
    blended[i] = s[i];
  ctx_u8_set_sat(components, blended, in_sat);
  ctx_u8_set_lum(components, blended, in_lum);
)

ctx_u8_blend_define(saturation,
  int in_sat = ctx_u8_get_sat(components, s);
  int in_lum = ctx_u8_get_lum(components, b);
  for (int i = 0; i < components; i++)
    blended[i] = b[i];
  ctx_u8_set_sat(components, blended, in_sat);
  ctx_u8_set_lum(components, blended, in_lum);
)

ctx_u8_blend_define(luminosity,
  int in_lum = ctx_u8_get_lum(components, s);
  for (int i = 0; i < components; i++)
    blended[i] = b[i];
  ctx_u8_set_lum(components, blended, in_lum);
)
#endif

CTX_INLINE static void
ctx_u8_blend (int components, CtxBlend blend, uint8_t * __restrict__ dst, uint8_t *src, uint8_t *blended, int count)
{
#if CTX_BLENDING_AND_COMPOSITING
  switch (blend)
  {
    case CTX_BLEND_NORMAL:      ctx_u8_blend_normal      (components, dst, src, blended, count); break;
    case CTX_BLEND_MULTIPLY:    ctx_u8_blend_multiply    (components, dst, src, blended, count); break;
    case CTX_BLEND_SCREEN:      ctx_u8_blend_screen      (components, dst, src, blended, count); break;
    case CTX_BLEND_OVERLAY:     ctx_u8_blend_overlay     (components, dst, src, blended, count); break;
    case CTX_BLEND_DARKEN:      ctx_u8_blend_darken      (components, dst, src, blended, count); break;
    case CTX_BLEND_LIGHTEN:     ctx_u8_blend_lighten     (components, dst, src, blended, count); break;
    case CTX_BLEND_COLOR_DODGE: ctx_u8_blend_color_dodge (components, dst, src, blended, count); break;
    case CTX_BLEND_COLOR_BURN:  ctx_u8_blend_color_burn  (components, dst, src, blended, count); break;
    case CTX_BLEND_HARD_LIGHT:  ctx_u8_blend_hard_light  (components, dst, src, blended, count); break;
    case CTX_BLEND_SOFT_LIGHT:  ctx_u8_blend_soft_light  (components, dst, src, blended, count); break;
    case CTX_BLEND_DIFFERENCE:  ctx_u8_blend_difference  (components, dst, src, blended, count); break;
    case CTX_BLEND_EXCLUSION:   ctx_u8_blend_exclusion   (components, dst, src, blended, count); break;
    case CTX_BLEND_COLOR:       ctx_u8_blend_color       (components, dst, src, blended, count); break;
    case CTX_BLEND_HUE:         ctx_u8_blend_hue         (components, dst, src, blended, count); break;
    case CTX_BLEND_SATURATION:  ctx_u8_blend_saturation  (components, dst, src, blended, count); break;
    case CTX_BLEND_LUMINOSITY:  ctx_u8_blend_luminosity  (components, dst, src, blended, count); break;
    case CTX_BLEND_ADDITION:    ctx_u8_blend_addition    (components, dst, src, blended, count); break;
    case CTX_BLEND_DIVIDE:      ctx_u8_blend_divide      (components, dst, src, blended, count); break;
    case CTX_BLEND_SUBTRACT:    ctx_u8_blend_subtract    (components, dst, src, blended, count); break;
  }
#else
  switch (blend)
  {
    default:                    ctx_u8_blend_normal      (components, dst, src, blended, count); break;
  }

#endif
}

CTX_INLINE static void
__ctx_u8_porter_duff (CtxRasterizer         *rasterizer,
                     int                    components,
                     uint8_t *              dst,
                     uint8_t *              src,
                     int                    x0,
                     uint8_t * __restrict__ coverage,
                     int                    count,
                     CtxCompositingMode     compositing_mode,
                     CtxFragment            fragment,
                     CtxBlend               blend)
{
  CtxPorterDuffFactor f_s, f_d;
  ctx_porter_duff_factors (compositing_mode, &f_s, &f_d);
  CtxGState *gstate = &rasterizer->state->gstate;
  uint8_t global_alpha_u8 = gstate->global_alpha_u8;
  uint8_t tsrc[components * count];
  int src_step = 0;

  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
  {
    src = &tsrc[0];
    fragment (rasterizer, 0, 0, src, 1, 0, 0);
    if (blend != CTX_BLEND_NORMAL)
      ctx_u8_blend (components, blend, dst, src, src, 1);
  }
  else
  {
    float u0 = 0; float v0 = 0;
    float ud = 0; float vd = 0;
    src = &tsrc[0];

    ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);
    fragment (rasterizer, u0, v0, src, count, ud, vd);
    if (blend != CTX_BLEND_NORMAL)
      ctx_u8_blend (components, blend, dst, src, src, count);
    src_step = components;
  }

  while (count--)
  {
    uint32_t cov = *coverage;

    if (CTX_UNLIKELY(global_alpha_u8 != 255))
      cov = (cov * global_alpha_u8 + 255) >> 8;

    uint8_t csrc[components];
    for (int c = 0; c < components; c++)
      csrc[c] = (src[c] * cov + 255) >> 8;

    for (int c = 0; c < components; c++)
    {
      uint32_t res = 0;
#if 1
      switch (f_s)
      {
        case CTX_PORTER_DUFF_0:             break;
        case CTX_PORTER_DUFF_1:             res += (csrc[c] ); break;
        case CTX_PORTER_DUFF_ALPHA:         res += (csrc[c] * dst[components-1] + 255) >> 8; break;
        case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (256-dst[components-1])) >> 8; break;
      }
      switch (f_d)
      {
        case CTX_PORTER_DUFF_0: break;
        case CTX_PORTER_DUFF_1:             res += dst[c]; break;
        case CTX_PORTER_DUFF_ALPHA:         res += (dst[c] * csrc[components-1] + 255) >> 8; break;
        case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (256-csrc[components-1])) >> 8; break;
      }
#else
      switch (f_s)
      {
        case CTX_PORTER_DUFF_0:             break;
        case CTX_PORTER_DUFF_1:             res += (csrc[c] ); break;
        case CTX_PORTER_DUFF_ALPHA:         res += (csrc[c] * dst[components-1])/255; break;
        case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (csrc[c] * (255-dst[components-1]))/255; break;
      }
      switch (f_d)
      {
        case CTX_PORTER_DUFF_0: break;
        case CTX_PORTER_DUFF_1:             res += dst[c]; break;
        case CTX_PORTER_DUFF_ALPHA:         res += (dst[c] * csrc[components-1])/255; break;
        case CTX_PORTER_DUFF_1_MINUS_ALPHA: res += (dst[c] * (255-csrc[components-1]))/255; break;
      }
#endif
      dst[c] = res;
    }
    coverage ++;
    src+=src_step;
    dst+=components;
  }
}

CTX_INLINE static void
_ctx_u8_porter_duff (CtxRasterizer         *rasterizer,
                     int                    components,
                     uint8_t *              dst,
                     uint8_t * __restrict__ src,
                     int                    x0,
                     uint8_t *              coverage,
                     int                    count,
                     CtxCompositingMode     compositing_mode,
                     CtxFragment            fragment,
                     CtxBlend               blend)
{
  __ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, compositing_mode, fragment, blend);
}

#define _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend) \
   switch (rasterizer->state->gstate.compositing_mode) \
   { \
     case CTX_COMPOSITE_SOURCE_ATOP: \
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \
        CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION_ATOP:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION_IN:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_OVER:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\
       break;\
     case CTX_COMPOSITE_DESTINATION_OVER:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\
       break;\
     case CTX_COMPOSITE_XOR:\
      _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_XOR, fragment, blend);\
       break;\
     case CTX_COMPOSITE_DESTINATION_OUT:\
       _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_OUT:\
       _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_IN:\
       _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_IN, fragment, blend);\
       break;\
     case CTX_COMPOSITE_COPY:\
       _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_COPY, fragment, blend);\
       break;\
     case CTX_COMPOSITE_CLEAR:\
       _ctx_u8_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_CLEAR, fragment, blend);\
       break;\
   }

/* generating one function per compositing_mode would be slightly more efficient,
 * but on embedded targets leads to slightly more code bloat,
 * here we trade off a slight amount of performance
 */
#define ctx_u8_porter_duff(comp_format, components, source, fragment, blend) \
static void \
ctx_##comp_format##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \
{ \
  _ctx_u8_porter_duffs(comp_format, components, source, fragment, blend);\
}

ctx_u8_porter_duff(RGBA8, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode)
//ctx_u8_porter_duff(comp_name, components,color_##blend_name,  NULL, blend_mode)

static void
ctx_RGBA8_nop (CTX_COMPOSITE_ARGUMENTS)
{
}


static void
ctx_setup_RGBA8 (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  int components       = 4;
  rasterizer->fragment = ctx_rasterizer_get_fragment_RGBA8 (rasterizer);
  rasterizer->comp_op  = ctx_RGBA8_porter_duff_generic;
  rasterizer->comp = CTX_COV_PATH_FALLBACK;

  int blend_mode       = gstate->blend_mode;
  int compositing_mode = gstate->compositing_mode;

  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
      ctx_fragment_color_RGBA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0);
      if (gstate->global_alpha_u8 != 255)
      {
        for (int c = 0; c < 4; c ++)
          rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8 + 255)>>8;
      }
      uint32_t src_pix    = ((uint32_t*)rasterizer->color)[0];
      uint32_t si_ga      = (src_pix & 0xff00ff00) >> 8;
      uint32_t si_rb      = src_pix & 0x00ff00ff;
      uint32_t si_ga_full = si_ga * 255;
      uint32_t si_rb_full = si_rb * 255;
//      uint32_t si_a       = si_ga >> 16;

      ((uint32_t*)rasterizer->color)[1] = si_ga;
      ((uint32_t*)rasterizer->color)[2] = si_rb;
      ((uint32_t*)rasterizer->color)[3] = si_ga_full;
      ((uint32_t*)rasterizer->color)[4] = si_rb_full;

      if (blend_mode == CTX_BLEND_NORMAL &&
           compositing_mode == CTX_COMPOSITE_COPY)
      {
        rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color;
        rasterizer->comp = CTX_COV_PATH_COPY;
      }
      else if (blend_mode == CTX_BLEND_NORMAL &&
          compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
     {
       if (rasterizer->color[components-1] == 255)
       {
        rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color;
        rasterizer->comp = CTX_COV_PATH_COPY;
       }
       else
       {
        rasterizer->comp_op = ctx_RGBA8_source_over_normal_color;
        rasterizer->comp = CTX_COV_PATH_OVER;
       }
     }
     else if (compositing_mode == CTX_COMPOSITE_CLEAR)
     {
       rasterizer->comp_op = ctx_RGBA8_clear_normal;
     }
  }
  else if (blend_mode == CTX_BLEND_NORMAL &&
           compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
  {
     rasterizer->comp_op = ctx_RGBA8_source_over_normal_fragment;
     rasterizer->comp = CTX_COV_PATH_OVER_FRAGMENT;
  }
  else if (blend_mode == CTX_BLEND_NORMAL &&
           compositing_mode == CTX_COMPOSITE_COPY)
  {
     rasterizer->comp_op = ctx_RGBA8_source_copy_normal_fragment;
     rasterizer->comp = CTX_COV_PATH_COPY_FRAGMENT;
  }
}

static void
ctx_composite_convert (CTX_COMPOSITE_ARGUMENTS)
{
  uint8_t pixels[count * rasterizer->format->ebpp];
  rasterizer->format->to_comp (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  rasterizer->format->from_comp (rasterizer, x0, &pixels[0], dst, count);
}

#if CTX_ENABLE_FLOAT
static void
ctx_float_copy_normal (int components, CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;
  float *srcf = (float*)src;
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;

  ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);

  while (count--)
  {
    uint8_t cov = *coverage;
    float covf = ctx_u8_to_float (cov);
    for (int c = 0; c < components; c++)
      dstf[c] = dstf[c]*(1.0-covf) + srcf[c]*covf;
    dstf += components;
    coverage ++;
  }
}

static void
ctx_float_clear_normal (int components, CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;
  while (count--)
  {
#if 0
    uint8_t cov = *coverage;
    if (cov == 0)
    {
    }
    else if (cov == 255)
    {
#endif
      switch (components)
      {
        case 2:
          ((uint64_t*)(dst))[0] = 0;
          break;
        case 4:
          ((uint64_t*)(dst))[0] = 0;
          ((uint64_t*)(dst))[1] = 0;
          break;
        default:
          for (int c = 0; c < components; c++)
            dstf[c] = 0.0f;
      }
#if 0
    }
    else
    {
      float ralpha = 1.0 - ctx_u8_to_float (cov);
      for (int c = 0; c < components; c++)
        { dstf[c] = (dstf[c] * ralpha); }
    }
    coverage ++;
#endif
    dstf += components;
  }
}


static void
ctx_float_source_over_normal_color (int components, CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;
  float *srcf = (float*)src;
  while (count--)
  {
    uint8_t cov = *coverage;
    float fcov = ctx_u8_to_float (cov);
    float ralpha = 1.0f - fcov * srcf[components-1];
    for (int c = 0; c < components-1; c++)
      dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha);
    coverage ++;
    dstf+= components;
  }
}

static void
ctx_float_source_copy_normal_color (int components, CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;
  float *srcf = (float*)src;

  while (count--)
  {
    uint8_t cov = *coverage;
    float fcov = ctx_u8_to_float (cov);
    float ralpha = 1.0f - fcov;
    for (int c = 0; c < components-1; c++)
      dstf[c] = (srcf[c]*fcov + dstf[c] * ralpha);
    coverage ++;
    dstf+= components;
  }
}

inline static void
ctx_float_blend_normal (int components, float *dst, float *src, float *blended)
{
  float a = src[components-1];
  for (int c = 0; c <  components - 1; c++)
    blended[c] = src[c] * a;
  blended[components-1]=a;
}

static float ctx_float_get_max (int components, float *c)
{
  float max = -1000.0f;
  for (int i = 0; i < components - 1; i ++)
  {
    if (c[i] > max) max = c[i];
  }
  return max;
}

static float ctx_float_get_min (int components, float *c)
{
  float min = 400.0;
  for (int i = 0; i < components - 1; i ++)
  {
    if (c[i] < min) min = c[i];
  }
  return min;
}

static float ctx_float_get_lum (int components, float *c)
{
  switch (components)
  {
    case 3:
    case 4:
            return CTX_CSS_RGB_TO_LUMINANCE(c);
    case 1:
    case 2:
            return c[0];
            break;
    default:
       {
         float sum = 0;
         for (int i = 0; i < components - 1; i ++)
         {
           sum += c[i];
         }
         return sum / (components - 1);
       }
  }
}

static float ctx_float_get_sat (int components, float *c)
{
  switch (components)
  {
    case 3:
    case 4:
            { float r = c[0];
              float g = c[1];
              float b = c[2];
              return ctx_maxf(r, ctx_maxf(g,b)) - ctx_minf(r,ctx_minf(g,b));
            }
            break;
    case 1:
    case 2: return 0.0;
            break;
    default:
       {
         float min = 1000;
         float max = -1000;
         for (int i = 0; i < components - 1; i ++)
         {
           if (c[i] < min) min = c[i];
           if (c[i] > max) max = c[i];
         }
         return max-min;
       }
  }
}

static void ctx_float_set_lum (int components, float *c, float lum)
{
  float d = lum - ctx_float_get_lum (components, c);
  float tc[components];
  for (int i = 0; i < components - 1; i++)
  {
    tc[i] = c[i] + d;
  }

  float l = ctx_float_get_lum (components, tc);
  float n = ctx_float_get_min (components, tc);
  float x = ctx_float_get_max (components, tc);

  if (n < 0.0f && l != n)
  {
    for (int i = 0; i < components - 1; i++)
      tc[i] = l + (((tc[i] - l) * l) / (l-n));
  }

  if (x > 1.0f && x != l)
  {
    for (int i = 0; i < components - 1; i++)
      tc[i] = l + (((tc[i] - l) * (1.0f - l)) / (x-l));
  }
  for (int i = 0; i < components - 1; i++)
    c[i] = tc[i];
}

static void ctx_float_set_sat (int components, float *c, float sat)
{
  int max = 0, mid = 1, min = 2;
  
  if (c[min] > c[mid]){int t = min; min = mid; mid = t;}
  if (c[mid] > c[max]){int t = mid; mid = max; max = t;}
  if (c[min] > c[mid]){int t = min; min = mid; mid = t;}

  if (c[max] > c[min])
  {
    c[mid] = ((c[mid]-c[min]) * sat) / (c[max] - c[min]);
    c[max] = sat;
  }
  else
  {
    c[mid] = c[max] = 0.0f;
  }
  c[min] = 0.0f;

}

#define ctx_float_blend_define(name, CODE) \
static void \
ctx_float_blend_##name (int components, float * __restrict__ dst, float *src, float *blended)\
{\
  float *s = src; float b[components];\
  ctx_float_deassociate_alpha (components, dst, b);\
    CODE;\
  blended[components-1] = s[components-1];\
  ctx_float_associate_alpha (components, blended);\
}

#define ctx_float_blend_define_seperable(name, CODE) \
        ctx_float_blend_define(name, for (int c = 0; c < components-1; c++) { CODE ;}) \

ctx_float_blend_define_seperable(multiply,    blended[c] = (b[c] * s[c]);)
ctx_float_blend_define_seperable(screen,      blended[c] = b[c] + s[c] - (b[c] * s[c]);)
ctx_float_blend_define_seperable(overlay,     blended[c] = b[c] < 0.5f ? (s[c] * b[c]) :
                                                          s[c] + b[c] - (s[c] * b[c]);)
ctx_float_blend_define_seperable(darken,      blended[c] = ctx_minf (b[c], s[c]))
ctx_float_blend_define_seperable(lighten,     blended[c] = ctx_maxf (b[c], s[c]))
ctx_float_blend_define_seperable(color_dodge, blended[c] = (b[c] == 0.0f) ? 0.0f :
                                     s[c] == 1.0f ? 1.0f : ctx_minf(1.0f, (b[c]) / (1.0f-s[c])))
ctx_float_blend_define_seperable(color_burn,  blended[c] = (b[c] == 1.0f) ? 1.0f :
                                     s[c] == 0.0f ? 0.0f : 1.0f - ctx_minf(1.0f, ((1.0f - b[c])) / s[c]))
ctx_float_blend_define_seperable(hard_light,  blended[c] = s[c] < 0.f ? (b[c] * s[c]) :
                                                          b[c] + s[c] - (b[c] * s[c]);)
ctx_float_blend_define_seperable(difference,  blended[c] = (b[c] - s[c]))

ctx_float_blend_define_seperable(divide,      blended[c] = s[c]?(b[c]) / s[c]:0.0f)
ctx_float_blend_define_seperable(addition,    blended[c] = s[c]+b[c])
ctx_float_blend_define_seperable(subtract,    blended[c] = s[c]-b[c])

ctx_float_blend_define_seperable(exclusion,   blended[c] = b[c] + s[c] - 2.0f * b[c] * s[c])
ctx_float_blend_define_seperable(soft_light,
  if (s[c] <= 0.5f)
  {
    blended[c] = b[c] - (1.0f - 2.0f * s[c]) * b[c] * (1.0f - b[c]);
  }
  else
  {
    int d;
    if (b[c] <= 255/4)
      d = (((16 * b[c] - 12.0f) * b[c] + 4.0f) * b[c]);
    else
      d = ctx_sqrtf(b[c]);
    blended[c] = (b[c] + (2.0f * s[c] - 1.0f) * (d - b[c]));
  }
)


ctx_float_blend_define(color,
  for (int i = 0; i < components; i++)
    blended[i] = s[i];
  ctx_float_set_lum(components, blended, ctx_float_get_lum (components, s));
)

ctx_float_blend_define(hue,
  float in_sat = ctx_float_get_sat(components, b);
  float in_lum = ctx_float_get_lum(components, b);
  for (int i = 0; i < components; i++)
    blended[i] = s[i];
  ctx_float_set_sat(components, blended, in_sat);
  ctx_float_set_lum(components, blended, in_lum);
)

ctx_float_blend_define(saturation,
  float in_sat = ctx_float_get_sat(components, s);
  float in_lum = ctx_float_get_lum(components, b);
  for (int i = 0; i < components; i++)
    blended[i] = b[i];
  ctx_float_set_sat(components, blended, in_sat);
  ctx_float_set_lum(components, blended, in_lum);
)

ctx_float_blend_define(luminosity,
  float in_lum = ctx_float_get_lum(components, s);
  for (int i = 0; i < components; i++)
    blended[i] = b[i];
  ctx_float_set_lum(components, blended, in_lum);
)

inline static void
ctx_float_blend (int components, CtxBlend blend, float * __restrict__ dst, float *src, float *blended)
{
  switch (blend)
  {
    case CTX_BLEND_NORMAL:      ctx_float_blend_normal      (components, dst, src, blended); break;
    case CTX_BLEND_MULTIPLY:    ctx_float_blend_multiply    (components, dst, src, blended); break;
    case CTX_BLEND_SCREEN:      ctx_float_blend_screen      (components, dst, src, blended); break;
    case CTX_BLEND_OVERLAY:     ctx_float_blend_overlay     (components, dst, src, blended); break;
    case CTX_BLEND_DARKEN:      ctx_float_blend_darken      (components, dst, src, blended); break;
    case CTX_BLEND_LIGHTEN:     ctx_float_blend_lighten     (components, dst, src, blended); break;
    case CTX_BLEND_COLOR_DODGE: ctx_float_blend_color_dodge (components, dst, src, blended); break;
    case CTX_BLEND_COLOR_BURN:  ctx_float_blend_color_burn  (components, dst, src, blended); break;
    case CTX_BLEND_HARD_LIGHT:  ctx_float_blend_hard_light  (components, dst, src, blended); break;
    case CTX_BLEND_SOFT_LIGHT:  ctx_float_blend_soft_light  (components, dst, src, blended); break;
    case CTX_BLEND_DIFFERENCE:  ctx_float_blend_difference  (components, dst, src, blended); break;
    case CTX_BLEND_EXCLUSION:   ctx_float_blend_exclusion   (components, dst, src, blended); break;
    case CTX_BLEND_COLOR:       ctx_float_blend_color       (components, dst, src, blended); break;
    case CTX_BLEND_HUE:         ctx_float_blend_hue         (components, dst, src, blended); break;
    case CTX_BLEND_SATURATION:  ctx_float_blend_saturation  (components, dst, src, blended); break;
    case CTX_BLEND_LUMINOSITY:  ctx_float_blend_luminosity  (components, dst, src, blended); break;
    case CTX_BLEND_ADDITION:    ctx_float_blend_addition    (components, dst, src, blended); break;
    case CTX_BLEND_SUBTRACT:    ctx_float_blend_subtract    (components, dst, src, blended); break;
    case CTX_BLEND_DIVIDE:      ctx_float_blend_divide      (components, dst, src, blended); break;
  }
}

/* this is the grunt working function, when inlined code-path elimination makes
 * it produce efficient code.
 */
CTX_INLINE static void
ctx_float_porter_duff (CtxRasterizer         *rasterizer,
                       int                    components,
                       uint8_t * __restrict__ dst,
                       uint8_t * __restrict__ src,
                       int                    x0,
                       uint8_t * __restrict__ coverage,
                       int                    count,
                       CtxCompositingMode     compositing_mode,
                       CtxFragment            fragment,
                       CtxBlend               blend)
{
  float *dstf = (float*)dst;

  CtxPorterDuffFactor f_s, f_d;
  ctx_porter_duff_factors (compositing_mode, &f_s, &f_d);
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  float   global_alpha_f = rasterizer->state->gstate.global_alpha_f;
  
  {
    float tsrc[components];
    float u0 = 0; float v0 = 0;
    float ud = 0; float vd = 0;

    ctx_init_uv (rasterizer, x0, count, &u0, &v0, &ud, &vd);

    while (count--)
    {
      uint8_t cov = *coverage;
#if 1
      if (
        CTX_UNLIKELY((compositing_mode == CTX_COMPOSITE_DESTINATION_OVER && dst[components-1] == 1.0f)||
        (cov == 0 && (compositing_mode == CTX_COMPOSITE_SOURCE_OVER ||
        compositing_mode == CTX_COMPOSITE_XOR               ||
        compositing_mode == CTX_COMPOSITE_DESTINATION_OUT   ||
        compositing_mode == CTX_COMPOSITE_SOURCE_ATOP      
        ))))
      {
        u0 += ud;
        v0 += vd;
        coverage ++;
        dstf+=components;
        continue;
      }
#endif

      fragment (rasterizer, u0, v0, tsrc, 1, ud, vd);
      if (blend != CTX_BLEND_NORMAL)
        ctx_float_blend (components, blend, dstf, tsrc, tsrc);
      u0 += ud;
      v0 += vd;
      float covf = ctx_u8_to_float (cov);

      if (global_alpha_u8 != 255)
        covf = covf * global_alpha_f;

      if (covf != 1.0f)
      {
        for (int c = 0; c < components; c++)
          tsrc[c] *= covf;
      }

      for (int c = 0; c < components; c++)
      {
        float res;
        /* these switches and this whole function is written to be
         * inlined when compiled when the enum values passed in are
         * constants.
         */
        switch (f_s)
        {
          case CTX_PORTER_DUFF_0: res = 0.0f; break;
          case CTX_PORTER_DUFF_1:             res = (tsrc[c]); break;
          case CTX_PORTER_DUFF_ALPHA:         res = (tsrc[c] *       dstf[components-1]); break;
          case CTX_PORTER_DUFF_1_MINUS_ALPHA: res = (tsrc[c] * (1.0f-dstf[components-1])); break;
        }
        switch (f_d)
        {
          case CTX_PORTER_DUFF_0: dstf[c] = res; break;
          case CTX_PORTER_DUFF_1:             dstf[c] = res + (dstf[c]); break;
          case CTX_PORTER_DUFF_ALPHA:         dstf[c] = res + (dstf[c] *       tsrc[components-1]); break;
          case CTX_PORTER_DUFF_1_MINUS_ALPHA: dstf[c] = res + (dstf[c] * (1.0f-tsrc[components-1])); break;
        }
      }
      coverage ++;
      dstf     +=components;
    }
  }
}

/* generating one function per compositing_mode would be slightly more efficient,
 * but on embedded targets leads to slightly more code bloat,
 * here we trade off a slight amount of performance
 */
#define ctx_float_porter_duff(compformat, components, source, fragment, blend) \
static void \
ctx_##compformat##_porter_duff_##source (CTX_COMPOSITE_ARGUMENTS) \
{ \
   switch (rasterizer->state->gstate.compositing_mode) \
   { \
     case CTX_COMPOSITE_SOURCE_ATOP: \
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count, \
        CTX_COMPOSITE_SOURCE_ATOP, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION_ATOP:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_ATOP, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION_IN:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_IN, fragment, blend);\
      break;\
     case CTX_COMPOSITE_DESTINATION:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_OVER:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_OVER, fragment, blend);\
       break;\
     case CTX_COMPOSITE_DESTINATION_OVER:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_OVER, fragment, blend);\
       break;\
     case CTX_COMPOSITE_XOR:\
      ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_XOR, fragment, blend);\
       break;\
     case CTX_COMPOSITE_DESTINATION_OUT:\
       ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_DESTINATION_OUT, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_OUT:\
       ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_OUT, fragment, blend);\
       break;\
     case CTX_COMPOSITE_SOURCE_IN:\
       ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_SOURCE_IN, fragment, blend);\
       break;\
     case CTX_COMPOSITE_COPY:\
       ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_COPY, fragment, blend);\
       break;\
     case CTX_COMPOSITE_CLEAR:\
       ctx_float_porter_duff (rasterizer, components, dst, src, x0, coverage, count,\
        CTX_COMPOSITE_CLEAR, fragment, blend);\
       break;\
   }\
}
#endif

#if CTX_ENABLE_RGBAF

ctx_float_porter_duff(RGBAF, 4,color,   rasterizer->fragment, rasterizer->state->gstate.blend_mode)
ctx_float_porter_duff(RGBAF, 4,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_INLINED_NORMAL
#if CTX_GRADIENTS
ctx_float_porter_duff(RGBAF, 4,linear_gradient, ctx_fragment_linear_gradient_RGBAF, rasterizer->state->gstate.blend_mode)
ctx_float_porter_duff(RGBAF, 4,radial_gradient, ctx_fragment_radial_gradient_RGBAF, rasterizer->state->gstate.blend_mode)
#endif
ctx_float_porter_duff(RGBAF, 4,image,           ctx_fragment_image_RGBAF,           rasterizer->state->gstate.blend_mode)


#if CTX_GRADIENTS
#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\
ctx_float_porter_duff(comp_name, components,color_##blend_name,            rasterizer->fragment,                               blend_mode)\
ctx_float_porter_duff(comp_name, components,generic_##blend_name,          rasterizer->fragment,               blend_mode)\
ctx_float_porter_duff(comp_name, components,linear_gradient_##blend_name,  ctx_fragment_linear_gradient_RGBA8, blend_mode)\
ctx_float_porter_duff(comp_name, components,radial_gradient_##blend_name,  ctx_fragment_radial_gradient_RGBA8, blend_mode)\
ctx_float_porter_duff(comp_name, components,image_##blend_name,            ctx_fragment_image_RGBAF,           blend_mode)
#else
#define ctx_float_porter_duff_blend(comp_name, components, blend_mode, blend_name)\
ctx_float_porter_duff(comp_name, components,color_##blend_name,            rasterizer->fragment,                               blend_mode)\
ctx_float_porter_duff(comp_name, components,generic_##blend_name,          rasterizer->fragment,               blend_mode)\
ctx_float_porter_duff(comp_name, components,image_##blend_name,            ctx_fragment_image_RGBAF,           blend_mode)
#endif

ctx_float_porter_duff_blend(RGBAF, 4, CTX_BLEND_NORMAL, normal)


static void
ctx_RGBAF_copy_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_copy_normal (4, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_RGBAF_clear_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_clear_normal (4, rasterizer, dst, src, x0, coverage, count);
}

#if 1
static void
ctx_RGBAF_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_source_over_normal_color (4, rasterizer, dst, rasterizer->color, x0, coverage, count);
}
#endif
#endif

static void
ctx_setup_RGBAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  int components = 4;
  rasterizer->fragment = ctx_rasterizer_get_fragment_RGBAF (rasterizer);
  rasterizer->comp = CTX_COV_PATH_FALLBACK;
#if 1
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
      rasterizer->comp_op = ctx_RGBAF_porter_duff_color;
      ctx_fragment_color_RGBAF (rasterizer, 0,0, rasterizer->color, 1, 0,0);
      if (gstate->global_alpha_u8 != 255)
        for (int c = 0; c < components; c ++)
          ((float*)rasterizer->color)[c] *= gstate->global_alpha_f;
    }
  else
#endif
  {
    rasterizer->comp_op = ctx_RGBAF_porter_duff_generic;
  }

#if CTX_INLINED_NORMAL
  if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR)
    rasterizer->comp_op = ctx_RGBAF_clear_normal;
  else
    switch (gstate->blend_mode)
    {
      case CTX_BLEND_NORMAL:
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_RGBAF_copy_normal;
        }
        else if (gstate->global_alpha_u8 == 0)
        {
          rasterizer->comp_op = ctx_RGBA8_nop;
        }
        else
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            //if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
            //{
            //  rasterizer->comp_op = ctx_RGBAF_source_over_normal_color;
           // }
           // else
            {
              rasterizer->comp_op = ctx_RGBAF_porter_duff_color_normal;
            }
            break;
#if CTX_GRADIENTS
          case CTX_SOURCE_LINEAR_GRADIENT:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient_normal;
            break;
          case CTX_SOURCE_RADIAL_GRADIENT:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient_normal;
            break;
#endif
          case CTX_SOURCE_TEXTURE:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_image_normal;
            break;
          default:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_generic_normal;
            break;
        }
        break;
      default:
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_color;
            //rasterizer->fragment = NULL;
            break;
#if CTX_GRADIENTS
          case CTX_SOURCE_LINEAR_GRADIENT:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_linear_gradient;
            break;
          case CTX_SOURCE_RADIAL_GRADIENT:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_radial_gradient;
            break;
#endif
          case CTX_SOURCE_TEXTURE:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_image;
            break;
          default:
            rasterizer->comp_op = ctx_RGBAF_porter_duff_generic;
            break;
        }
        break;
    }
#endif
}

#endif
#if CTX_ENABLE_GRAYAF

#if CTX_GRADIENTS
static void
ctx_fragment_linear_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float rgba[4];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0 ; i < count; i++)
  {
  float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) /
                g->linear_gradient.length) -
              g->linear_gradient.start) * (g->linear_gradient.rdelta);
  ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 1.0, rgba);
  ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba);
  ((float*)out)[1] = rgba[3];
     out = ((float*)(out)) + 2;
     x += dx;
     y += dy;
  }
}

static void
ctx_fragment_radial_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float rgba[4];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i < count; i ++)
  {
  float v = 0.0f;
  if ((g->radial_gradient.r1-g->radial_gradient.r0) > 0.0f)
    {
      v = ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y);
      v = (v - g->radial_gradient.r0) / (g->radial_gradient.rdelta);
    }
  ctx_fragment_gradient_1d_RGBAF (rasterizer, v, 0.0, rgba);
  ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgba);
  ((float*)out)[1] = rgba[3];
     out = ((float*)(out)) + 2;
     x += dx;
     y += dy;
  }
}
#endif

static void
ctx_fragment_color_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  for (int i = 0; i < count; i++)
  {
     ctx_color_get_graya (rasterizer->state, &g->color, (float*)out);
     out = ((float*)(out)) + 2;
     x += dx;
     y += dy;
  }
}

static void ctx_fragment_image_GRAYAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t rgba[4];
  float rgbaf[4];
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxBuffer *buffer = gstate->source_fill.texture.buffer;
  switch (buffer->format->bpp)
    {
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);       break;
    }
  for (int c = 0; c < 2 * count; c ++) { 
    rgbaf[c] = ctx_u8_to_float (rgba[c]);
    ((float*)out)[0] = ctx_float_color_rgb_to_gray (rasterizer->state, rgbaf);
    ((float*)out)[1] = rgbaf[3];
    out = ((float*)out) + 2;
  }
}

static CtxFragment ctx_rasterizer_get_fragment_GRAYAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:           return ctx_fragment_image_GRAYAF;
      case CTX_SOURCE_COLOR:           return ctx_fragment_color_GRAYAF;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYAF;
      case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYAF;
#endif
    }
  return ctx_fragment_color_GRAYAF;
}

ctx_float_porter_duff(GRAYAF, 2,color,   rasterizer->fragment, rasterizer->state->gstate.blend_mode)
ctx_float_porter_duff(GRAYAF, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_INLINED_NORMAL
ctx_float_porter_duff(GRAYAF, 2,color_normal,   rasterizer->fragment, CTX_BLEND_NORMAL)
ctx_float_porter_duff(GRAYAF, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL)

static void
ctx_GRAYAF_copy_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_copy_normal (2, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_GRAYAF_clear_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_clear_normal (2, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_GRAYAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count);
}
#endif

static void
ctx_setup_GRAYAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  int components = 2;
  rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYAF (rasterizer);
  rasterizer->comp = CTX_COV_PATH_FALLBACK;
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
      rasterizer->comp_op = ctx_GRAYAF_porter_duff_color;
  //  rasterizer->fragment = NULL;
      ctx_color_get_rgba (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color);
      if (gstate->global_alpha_u8 != 255)
        for (int c = 0; c < components; c ++)
          ((float*)rasterizer->color)[c] *= gstate->global_alpha_f;
    }
  else
  {
    rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic;
  }

#if CTX_INLINED_NORMAL
  if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR)
    rasterizer->comp_op = ctx_GRAYAF_clear_normal;
  else
    switch (gstate->blend_mode)
    {
      case CTX_BLEND_NORMAL:
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_GRAYAF_copy_normal;
        }
        else if (gstate->global_alpha_u8 == 0)
          rasterizer->comp_op = ctx_RGBA8_nop;
        else
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
            {
              if (((float*)rasterizer->color)[components-1] == 0.0f)
                rasterizer->comp_op = ctx_RGBA8_nop;
#if 1
              else //if (((float*)rasterizer->color)[components-1] == 0.0f)
                rasterizer->comp_op = ctx_GRAYAF_source_copy_normal_color;
#endif
              //else
          //      rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal;
//            rasterizer->fragment = NULL;
            }
            else
            {
              rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal;
//            rasterizer->fragment = NULL;
            }
            break;
          default:
            rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic_normal;
            break;
        }
        break;
      default:
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            rasterizer->comp_op = ctx_GRAYAF_porter_duff_color;
//          rasterizer->fragment = NULL;
            break;
          default:
            rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic;
            break;
        }
        break;
    }
#endif
}

#endif
#if CTX_ENABLE_GRAYF

static void
ctx_composite_GRAYF (CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;

  float temp[count*2];
  for (int i = 0; i < count; i++)
  {
    temp[i*2] = dstf[i];
    temp[i*2+1] = 1.0f;
  }
  rasterizer->comp_op (rasterizer, (uint8_t*)temp, rasterizer->color, x0, coverage, count);
  for (int i = 0; i < count; i++)
  {
    dstf[i] = temp[i*2];
  }
}

#endif
#if CTX_ENABLE_BGRA8

inline static void
ctx_swap_red_green (uint8_t *rgba)
{
  uint32_t *buf  = (uint32_t *) rgba;
  uint32_t  orig = *buf;
  uint32_t  green_alpha = (orig & 0xff00ff00);
  uint32_t  red_blue    = (orig & 0x00ff00ff);
  uint32_t  red         = red_blue << 16;
  uint32_t  blue        = red_blue >> 16;
  *buf = green_alpha | red | blue;
}

static void
ctx_BGRA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  uint32_t *srci = (uint32_t *) buf;
  uint32_t *dsti = (uint32_t *) rgba;
  while (count--)
    {
      uint32_t val = *srci++;
      ctx_swap_red_green ( (uint8_t *) &val);
      *dsti++      = val;
    }
}

static void
ctx_RGBA8_to_BGRA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  ctx_BGRA8_to_RGBA8 (rasterizer, x, rgba, (uint8_t *) buf, count);
}

static void
ctx_composite_BGRA8 (CTX_COMPOSITE_ARGUMENTS)
{
  // for better performance, this could be done without a pre/post conversion,
  // by swapping R and B of source instead... as long as it is a color instead
  // of gradient or image
  //
  //
  uint8_t pixels[count * 4];
  ctx_BGRA8_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_BGRA8_to_RGBA8  (rasterizer, x0, &pixels[0], dst, count);
}


#endif
#if CTX_ENABLE_CMYKAF

static void
ctx_fragment_other_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  float *cmyka = (float*)out;
  float _rgba[4 * count];
  float *rgba = &_rgba[0];
  CtxGState *gstate = &rasterizer->state->gstate;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:
        ctx_fragment_image_RGBAF (rasterizer, x, y, rgba, count, dx, dy);
        break;
      case CTX_SOURCE_COLOR:
        ctx_fragment_color_RGBAF (rasterizer, x, y, rgba, count, dx, dy);
        break;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT:
        ctx_fragment_linear_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy);
        break;
      case CTX_SOURCE_RADIAL_GRADIENT:
        ctx_fragment_radial_gradient_RGBAF (rasterizer, x, y, rgba, count, dx, dy);
        break;
#endif
      default:
        rgba[0]=rgba[1]=rgba[2]=rgba[3]=0.0f;
        break;
    }
  for (int i = 0; i < count; i++)
  {
    cmyka[4]=rgba[3];
    ctx_rgb_to_cmyk (rgba[0], rgba[1], rgba[2], &cmyka[0], &cmyka[1], &cmyka[2], &cmyka[3]);
    cmyka += 5;
    rgba += 4;
  }
}

static void
ctx_fragment_color_CMYKAF (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  float *cmyka = (float*)out;
  float cmyka_in[5];
  ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, cmyka_in);
  for (int i = 0; i < count; i++)
  {
    for (int c = 0; c < 4; c ++)
    {
      cmyka[c] = (1.0f - cmyka_in[c]);
    }
    cmyka[4] = cmyka_in[4];
    cmyka += 5;
  }
}

static CtxFragment ctx_rasterizer_get_fragment_CMYKAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_COLOR:
        return ctx_fragment_color_CMYKAF;
    }
  return ctx_fragment_other_CMYKAF;
}

ctx_float_porter_duff (CMYKAF, 5,color,           rasterizer->fragment, rasterizer->state->gstate.blend_mode)
ctx_float_porter_duff (CMYKAF, 5,generic,         rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_INLINED_NORMAL
ctx_float_porter_duff (CMYKAF, 5,color_normal,            rasterizer->fragment, CTX_BLEND_NORMAL)
ctx_float_porter_duff (CMYKAF, 5,generic_normal,          rasterizer->fragment, CTX_BLEND_NORMAL)

static void
ctx_CMYKAF_copy_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_copy_normal (5, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_CMYKAF_clear_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_clear_normal (5, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_CMYKAF_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_float_source_copy_normal_color (5, rasterizer, dst, rasterizer->color, x0, coverage, count);
}
#endif

static void
ctx_setup_CMYKAF (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  int components = 5;
  rasterizer->fragment = ctx_rasterizer_get_fragment_CMYKAF (rasterizer);
  rasterizer->comp = CTX_COV_PATH_FALLBACK;
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
      rasterizer->comp_op = ctx_CMYKAF_porter_duff_color;
      rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic;
 //     rasterizer->fragment = NULL;
      ctx_color_get_cmyka (rasterizer->state, &gstate->source_fill.color, (float*)rasterizer->color);
      if (gstate->global_alpha_u8 != 255)
        ((float*)rasterizer->color)[components-1] *= gstate->global_alpha_f;
    }
  else
  {
    rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic;
  }

#if CTX_INLINED_NORMAL
  if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR)
    rasterizer->comp_op = ctx_CMYKAF_clear_normal;
#if 1
  else
    switch (gstate->blend_mode)
    {
      case CTX_BLEND_NORMAL:
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_CMYKAF_copy_normal;
        }
        else if (gstate->global_alpha_u8 == 0)
          rasterizer->comp_op = ctx_RGBA8_nop;
        else
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
            {
              if (((float*)rasterizer->color)[components-1] == 0.0f)
                rasterizer->comp_op = ctx_RGBA8_nop;
#if 1
              else //if (((float*)rasterizer->color)[components-1] == 1.0f)
                rasterizer->comp_op = ctx_CMYKAF_source_copy_normal_color;
   //           else
   //             rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal;
              rasterizer->fragment = NULL;
#endif
            }
            else
            {
              rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal;
   //         rasterizer->fragment = NULL;
            }
            break;
          default:
            rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic_normal;
            break;
        }
        break;
      default:
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            rasterizer->comp_op = ctx_CMYKAF_porter_duff_color;
    //      rasterizer->fragment = NULL;
            break;
          default:
            rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic;
            break;
        }
        break;
    }
#endif
#endif
}

#endif
#if CTX_ENABLE_CMYKA8

static void
ctx_CMYKA8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count)
{
  for (int i = 0; i < count; i ++)
    {
      for (int c = 0; c < 4; c ++)
        { dst[c] = ctx_u8_to_float ( (255-src[c]) ); }
      dst[4] = ctx_u8_to_float (src[4]);
      for (int c = 0; c < 4; c++)
        { dst[c] *= dst[4]; }
      src += 5;
      dst += 5;
    }
}
static void
ctx_CMYKAF_to_CMYKA8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count)
{
  for (int i = 0; i < count; i ++)
    {
      int a = ctx_float_to_u8 (src[4]);
      if (a != 0 && a != 255)
      {
        float recip = 1.0f/src[4];
        for (int c = 0; c < 4; c++)
        {
          dst[c] = ctx_float_to_u8 (1.0f - src[c] * recip);
        }
      }
      else
      {
        for (int c = 0; c < 4; c++)
          dst[c] = 255 - ctx_float_to_u8 (src[c]);
      }
      dst[4]=a;

      src += 5;
      dst += 5;
    }
}

static void
ctx_composite_CMYKA8 (CTX_COMPOSITE_ARGUMENTS)
{
  float pixels[count * 5];
  ctx_CMYKA8_to_CMYKAF (rasterizer, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_CMYKAF_to_CMYKA8 (rasterizer, &pixels[0], dst, count);
}

#endif
#if CTX_ENABLE_CMYK8

static void
ctx_CMYK8_to_CMYKAF (CtxRasterizer *rasterizer, uint8_t *src, float *dst, int count)
{
  for (int i = 0; i < count; i ++)
    {
      dst[0] = ctx_u8_to_float (255-src[0]);
      dst[1] = ctx_u8_to_float (255-src[1]);
      dst[2] = ctx_u8_to_float (255-src[2]);
      dst[3] = ctx_u8_to_float (255-src[3]);
      dst[4] = 1.0f;
      src += 4;
      dst += 5;
    }
}
static void
ctx_CMYKAF_to_CMYK8 (CtxRasterizer *rasterizer, float *src, uint8_t *dst, int count)
{
  for (int i = 0; i < count; i ++)
    {
      float c = src[0];
      float m = src[1];
      float y = src[2];
      float k = src[3];
      float a = src[4];
      if (a != 0.0f && a != 1.0f)
        {
          float recip = 1.0f/a;
          c *= recip;
          m *= recip;
          y *= recip;
          k *= recip;
        }
      c = 1.0 - c;
      m = 1.0 - m;
      y = 1.0 - y;
      k = 1.0 - k;
      dst[0] = ctx_float_to_u8 (c);
      dst[1] = ctx_float_to_u8 (m);
      dst[2] = ctx_float_to_u8 (y);
      dst[3] = ctx_float_to_u8 (k);
      src += 5;
      dst += 4;
    }
}

static void
ctx_composite_CMYK8 (CTX_COMPOSITE_ARGUMENTS)
{
  float pixels[count * 5];
  ctx_CMYK8_to_CMYKAF (rasterizer, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, (uint8_t *) &pixels[0], src, x0, coverage, count);
  ctx_CMYKAF_to_CMYK8 (rasterizer, &pixels[0], dst, count);
}
#endif

#if CTX_ENABLE_RGB8

inline static void
ctx_RGB8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (const uint8_t *) buf;
  while (count--)
    {
      rgba[0] = pixel[0];
      rgba[1] = pixel[1];
      rgba[2] = pixel[2];
      rgba[3] = 255;
      pixel+=3;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_RGB8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      pixel[0] = rgba[0];
      pixel[1] = rgba[1];
      pixel[2] = rgba[2];
      pixel+=3;
      rgba +=4;
    }
}

#endif
#if CTX_ENABLE_GRAY1

#if CTX_NATIVE_GRAYA8
inline static void
ctx_GRAY1_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      rgba[0] = 255 * (*pixel & (1<< (x&7) ) );
      rgba[1] = 255;
      pixel+= ( (x&7) ==7);
      x++;
      rgba +=2;
    }
}

inline static void
ctx_GRAYA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  *pixel = 0;
  while (count--)
    {
      int gray = rgba[0];
      //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127);
      if (gray >= 127)
        {
          *pixel = *pixel | ((1<< (x&7) ) * (gray >= 127));
        }
#if 0
      else
      {
          *pixel = *pixel & (~ (1<< (x&7) ) );
      }
#endif
      if ( (x&7) ==7)
        { pixel+=1;
          if(count>0)*pixel = 0;
        }
      x++;
      rgba +=2;
    }
}

#else

inline static void
ctx_GRAY1_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      *((uint32_t*)(rgba))=0xff000000 + 0x00ffffff * ((*pixel & (1<< (x&7) ) )!=0);
      pixel+= ( (x&7) ==7);
      x++;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_GRAY1 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  *pixel = 0;
  while (count--)
    {
      int gray = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba);
      //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127);
      if (gray <= 127)
        {
          //*pixel = *pixel & (~ (1<< (x&7) ) );
        }
      else
        {
          *pixel = *pixel | (1<< (x&7) );
        }
      if ( (x&7) ==7)
        { pixel+=1;
          if(count>0)*pixel = 0;
        }
      x++;
      rgba +=4;
    }
}
#endif

#endif
#if CTX_ENABLE_GRAY2

#if CTX_NATIVE_GRAYA8
inline static void
ctx_GRAY2_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1);
      val <<= 6;
      rgba[0] = val;
      rgba[1] = 255;
      if ( (x&3) ==3)
        { pixel+=1; }
      x++;
      rgba +=2;
    }
}

inline static void
ctx_GRAYA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = rgba[0];
      val >>= 6;
      *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) );
      *pixel = *pixel | ( (val << ( (x&3) <<1) ) );
      if ( (x&3) ==3)
        { pixel+=1; }
      x++;
      rgba +=2;
    }
}
#else

inline static void
ctx_GRAY2_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = (*pixel & (3 << ( (x & 3) <<1) ) ) >> ( (x&3) <<1);
      val <<= 6;
      rgba[0] = val;
      rgba[1] = val;
      rgba[2] = val;
      rgba[3] = 255;
      if ( (x&3) ==3)
        { pixel+=1; }
      x++;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_GRAY2 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba);
      val >>= 6;
      *pixel = *pixel & (~ (3 << ( (x&3) <<1) ) );
      *pixel = *pixel | ( (val << ( (x&3) <<1) ) );
      if ( (x&3) ==3)
        { pixel+=1; }
      x++;
      rgba +=4;
    }
}
#endif

#endif
#if CTX_ENABLE_GRAY4

#if CTX_NATIVE_GRAYA8
inline static void
ctx_GRAY4_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2);
      val <<= 4;
      rgba[0] = val;
      rgba[1] = 255;
      if ( (x&1) ==1)
        { pixel+=1; }
      x++;
      rgba +=2;
    }
}

inline static void
ctx_GRAYA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = rgba[0];
      val >>= 4;
      *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) );
      *pixel = *pixel | ( (val << ( (x&1) <<2) ) );
      if ( (x&1) ==1)
        { pixel+=1; }
      x++;
      rgba +=2;
    }
}
#else
inline static void
ctx_GRAY4_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = (*pixel & (15 << ( (x & 1) <<2) ) ) >> ( (x&1) <<2);
      val <<= 4;
      rgba[0] = val;
      rgba[1] = val;
      rgba[2] = val;
      rgba[3] = 255;
      if ( (x&1) ==1)
        { pixel+=1; }
      x++;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_GRAY4 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int val = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba);
      val >>= 4;
      *pixel = *pixel & (~ (15 << ( (x&1) <<2) ) );
      *pixel = *pixel | ( (val << ( (x&1) <<2) ) );
      if ( (x&1) ==1)
        { pixel+=1; }
      x++;
      rgba +=4;
    }
}
#endif

#endif
#if CTX_ENABLE_GRAY8

#if CTX_NATIVE_GRAYA8
inline static void
ctx_GRAY8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      rgba[0] = pixel[0];
      rgba[1] = 255;
      pixel+=1;
      rgba +=2;
    }
}

inline static void
ctx_GRAYA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      pixel[0] = rgba[0];
      pixel+=1;
      rgba +=2;
    }
}
#else
inline static void
ctx_GRAY8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      rgba[0] = pixel[0];
      rgba[1] = pixel[0];
      rgba[2] = pixel[0];
      rgba[3] = 255;
      pixel+=1;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_GRAY8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  for (int i = 0; i < count; i ++)
    {
      pixel[i] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba + i * 4);
    }
}
#endif

#endif
#if CTX_ENABLE_GRAYA8

inline static void
ctx_GRAYA8_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (const uint8_t *) buf;
  while (count--)
    {
      rgba[0] = pixel[0];
      rgba[1] = pixel[0];
      rgba[2] = pixel[0];
      rgba[3] = pixel[1];
      pixel+=2;
      rgba +=4;
    }
}

inline static void
ctx_RGBA8_to_GRAYA8 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      pixel[0] = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba);
      pixel[1] = rgba[3];
      pixel+=2;
      rgba +=4;
    }
}

#if CTX_NATIVE_GRAYA8
CTX_INLINE static void ctx_rgba_to_graya_u8 (CtxState *state, uint8_t *in, uint8_t *out)
{
  out[0] = ctx_u8_color_rgb_to_gray (state, in);
  out[1] = in[3];
}

#if CTX_GRADIENTS
static void
ctx_fragment_linear_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
        uint8_t *dst = (uint8_t*)out;
  for (int i = 0; i < count;i ++)
  {
  float v = ( ( (g->linear_gradient.dx * x + g->linear_gradient.dy * y) /
                g->linear_gradient.length) -
              g->linear_gradient.start) * (g->linear_gradient.rdelta);
  {
    uint8_t rgba[4];
    ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba);
    ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst);
   
  }


#if CTX_DITHER
  ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
  dst += 2;
  x += dx;
  y += dy;
  }
}

#if 0
static void
ctx_fragment_radial_gradient_RGBA8 (CtxRasterizer *rasterizer, float x, float y, void *out)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) -
              g->radial_gradient.r0) * (g->radial_gradient.rdelta);
  ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0, rgba);
#if CTX_DITHER
  ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
}
#endif


static void
ctx_fragment_radial_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
        uint8_t *dst = (uint8_t*)out;
  for (int i = 0; i < count;i ++)
  {
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  float v = (ctx_hypotf (g->radial_gradient.x0 - x, g->radial_gradient.y0 - y) -
              g->radial_gradient.r0) * (g->radial_gradient.rdelta);
  {
    uint8_t rgba[4];
    ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 1.0, rgba);
    ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst);
  }
#if CTX_DITHER
  ctx_dither_graya_u8 ((uint8_t*)dst, x, y, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
  dst += 2;
  x += dx;
  y += dy;
  }
}
#endif

static void
ctx_fragment_color_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  uint16_t *dst = (uint16_t*)out;
  uint16_t pix;
  ctx_color_get_graya_u8 (rasterizer->state, &g->color, (void*)&pix);
  for (int i = 0; i <count; i++)
  {
    dst[i]=pix;
  }
}

static void ctx_fragment_image_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, void *out, int count, float dx, float dy)
{
  uint8_t rgba[4*count];
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxBuffer *buffer = gstate->source_fill.texture.buffer;
  switch (buffer->format->bpp)
    {
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, rgba, count, dx, dy); break;
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, rgba, count, dx, dy);       break;
    }
  for (int i = 0; i < count; i++)
    ctx_rgba_to_graya_u8 (rasterizer->state, &rgba[i*4], &((uint8_t*)out)[i*2]);
}

static CtxFragment ctx_rasterizer_get_fragment_GRAYA8 (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:           return ctx_fragment_image_GRAYA8;
      case CTX_SOURCE_COLOR:           return ctx_fragment_color_GRAYA8;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT: return ctx_fragment_linear_gradient_GRAYA8;
      case CTX_SOURCE_RADIAL_GRADIENT: return ctx_fragment_radial_gradient_GRAYA8;
#endif
    }
  return ctx_fragment_color_GRAYA8;
}

//ctx_u8_porter_duff(GRAYA8, 2,color,   rasterizer->fragment, rasterizer->state->gstate.blend_mode)
ctx_u8_porter_duff(GRAYA8, 2,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_INLINED_NORMAL
//ctx_u8_porter_duff(GRAYA8, 2,color_normal,   rasterizer->fragment, CTX_BLEND_NORMAL)
ctx_u8_porter_duff(GRAYA8, 2,generic_normal, rasterizer->fragment, CTX_BLEND_NORMAL)

static void
ctx_GRAYA8_copy_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_u8_copy_normal (2, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_GRAYA8_clear_normal (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_u8_clear_normal (2, rasterizer, dst, src, x0, coverage, count);
}

static void
ctx_GRAYA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_u8_source_over_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count);
}

static void
ctx_GRAYA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS)
{
  ctx_u8_source_copy_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count);
}
#endif

inline static int
ctx_is_opaque_color (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  if (gstate->global_alpha_u8 != 255)
    return 0;
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
  {
    uint8_t ga[2];
    ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga);
    return ga[1] == 255;
  }
  return 0;
}

static void
ctx_setup_GRAYA8 (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  int components = 2;
  rasterizer->fragment = ctx_rasterizer_get_fragment_GRAYA8 (rasterizer);
  rasterizer->comp_op  = ctx_GRAYA8_porter_duff_generic;
  rasterizer->comp = CTX_COV_PATH_FALLBACK;
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
      ctx_fragment_color_GRAYA8 (rasterizer, 0,0, rasterizer->color, 1, 0,0);
      if (gstate->global_alpha_u8 != 255)
        for (int c = 0; c < components; c ++)
          rasterizer->color[c] = (rasterizer->color[c] * gstate->global_alpha_u8)/255;
    }

#if CTX_INLINED_NORMAL
  if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR)
    rasterizer->comp_op = ctx_GRAYA8_clear_normal;
  else
    switch (gstate->blend_mode)
    {
      case CTX_BLEND_NORMAL:
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_GRAYA8_copy_normal;
        }
        else if (gstate->global_alpha_u8 == 0)
          rasterizer->comp_op = ctx_RGBA8_nop;
        else
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
            {
              if (rasterizer->color[components-1] == 0)
                rasterizer->comp_op = ctx_RGBA8_nop;
              else if (rasterizer->color[components-1] == 255)
                rasterizer->comp_op = ctx_GRAYA8_source_copy_normal_color;
              else
                rasterizer->comp_op = ctx_GRAYA8_source_over_normal_color;
            }
            else
            {
              rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal;
            }
            break;
          default:
            rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic_normal;
            break;
        }
        break;
      default:
        rasterizer->comp_op = ctx_GRAYA8_porter_duff_generic;
        break;
    }
#endif
}
#endif

#endif
#if CTX_ENABLE_RGB332

inline static void
ctx_332_unpack (uint8_t pixel,
                uint8_t *red,
                uint8_t *green,
                uint8_t *blue)
{
  *blue   = (pixel & 3) <<6;
  *green = ( (pixel >> 2) & 7) <<5;
  *red   = ( (pixel >> 5) & 7) <<5;
  if (*blue > 223)  { *blue  = 255; }
  if (*green > 223) { *green = 255; }
  if (*red > 223)   { *red   = 255; }
}

static inline uint8_t
ctx_332_pack (uint8_t red,
              uint8_t green,
              uint8_t blue)
{
  uint8_t c  = (red >> 5) << 5;
  c |= (green >> 5) << 2;
  c |= (blue >> 6);
  return c;
}

static inline void
ctx_RGB332_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      ctx_332_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2]);
#if CTX_RGB332_ALPHA
      if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0)
        { rgba[3] = 0; }
      else
#endif
        { rgba[3] = 255; }
      pixel+=1;
      rgba +=4;
    }
}

static inline void
ctx_RGBA8_to_RGB332 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
#if CTX_RGB332_ALPHA
      if (rgba[3]==0)
        { pixel[0] = ctx_332_pack (255, 0, 255); }
      else
#endif
        { pixel[0] = ctx_332_pack (rgba[0], rgba[1], rgba[2]); }
      pixel+=1;
      rgba +=4;
    }
}

#endif
#if CTX_ENABLE_RGB565 | CTX_ENABLE_RGB565_BYTESWAPPED

static inline void
ctx_565_unpack (const uint16_t pixel,
                uint8_t *red,
                uint8_t *green,
                uint8_t *blue,
                const int byteswap)
{
  uint16_t byteswapped;
  if (byteswap)
    { byteswapped = (pixel>>8) | (pixel<<8); }
  else
    { byteswapped  = pixel; }
  *blue   =  (byteswapped & 31) <<3;
  *green = ( (byteswapped>>5) & 63) <<2;
  *red   = ( (byteswapped>>11) & 31) <<3;
#if 0
  if (*blue > 248) { *blue = 255; }
  if (*green > 248) { *green = 255; }
  if (*red > 248) { *red = 255; }
#endif
}

static inline uint32_t
ctx_565_unpack_32 (const uint16_t pixel,
                   const int byteswap)
{
  uint16_t byteswapped;
  if (byteswap)
    { byteswapped = (pixel>>8) | (pixel<<8); }
  else
    { byteswapped  = pixel; }
  uint8_t blue   = (byteswapped & 31) <<3;
  uint8_t green = ( (byteswapped>>5) & 63) <<2;
  uint8_t red   = ( (byteswapped>>11) & 31) <<3;
#if 0
  if (*blue > 248) { *blue = 255; }
  if (*green > 248) { *green = 255; }
  if (*red > 248) { *red = 255; }
#endif
  return red +  (green << 8) + (blue << 16) + (0xff << 24);
}

static inline uint16_t
ctx_565_pack (const uint8_t  red,
              const uint8_t  green,
              const uint8_t  blue,
              const int      byteswap)
{
  uint32_t c = (red >> 3) << 11;
  c |= (green >> 2) << 5;
  c |= blue >> 3;
  if (byteswap)
    { return (c>>8) | (c<<8); } /* swap bytes */
  return c;
}

static inline uint16_t
ctx_888_to_565 (uint32_t in, int byteswap)
{
  uint8_t *rgb=(uint8_t*)(&in);
  return ctx_565_pack (rgb[0],rgb[1],rgb[2], byteswap);
}

static inline uint32_t
ctx_565_to_888 (uint16_t in, int byteswap)
{
  uint32_t ret = 0;
  uint8_t *rgba=(uint8_t*)&ret;
  ctx_565_unpack (in,
                  &rgba[0],
                  &rgba[1],
                  &rgba[2],
                  byteswap);
  return ret;
}

#endif
#if CTX_ENABLE_RGB565


static inline void
ctx_RGB565_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint16_t *pixel = (uint16_t *) buf;
  while (count--)
    {
      ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 0);
#if CTX_RGB565_ALPHA
      if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0)
        { rgba[3] = 0; }
#endif
      pixel+=1;
      rgba +=4;
    }
}

static inline void
ctx_RGBA8_to_RGB565 (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint16_t *pixel = (uint16_t *) buf;
  while (count--)
    {
#if CTX_RGB565_ALPHA
      if (rgba[3]==0)
        { pixel[0] = ctx_565_pack (255, 0, 255, 0); }
      else
#endif
        { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 0); }
      pixel+=1;
      rgba +=4;
    }
}

static void
ctx_RGBA8_source_over_normal_color (CTX_COMPOSITE_ARGUMENTS);
static void
ctx_RGBA8_source_copy_normal_color (CTX_COMPOSITE_ARGUMENTS);

static void
ctx_composite_RGB565 (CTX_COMPOSITE_ARGUMENTS)
{
#if 0
  if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color))
  {
     int byteswap = 0;
     uint32_t si    = *((uint32_t*)(src));
     uint16_t si_16 = ctx_888_to_565 (si, byteswap);
     uint32_t sval  = (si_16 & ( (31 << 11 ) | 31));
     uint32_t sg = (si_16 & (63 << 5)) >> 5;
     uint32_t si_a = si >> (24 + 3);
     while (count--)
     {
        uint32_t di_16 = *((uint16_t*)(dst));
        uint32_t cov = (*coverage) >> 3;
        uint32_t racov = (32-((31+si_a*cov)>>5));
        uint32_t dval = (di_16 & ( (31 << 11 ) | 31));
        uint32_t dg = (di_16 >> 5) & 63; // faster outside than
                                         // remerged as part of dval
        *((uint16_t*)(dst)) =
                ((               
                  (((sval * cov) + (dval * racov)) >> 5)
                 ) & ((31 << 11 )|31)) |
                  ((((sg * cov) + (dg * racov)) & 63) );

     }
     return;
  }
#endif
  uint8_t pixels[count * 4];
  ctx_RGB565_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_RGBA8_to_RGB565 (rasterizer, x0, &pixels[0], dst, count);
}
#endif
#if CTX_ENABLE_RGB565_BYTESWAPPED

static inline void
ctx_RGB565_BS_to_RGBA8 (CtxRasterizer *rasterizer, int x, const void *buf, uint8_t *rgba, int count)
{
  const uint16_t *pixel = (uint16_t *) buf;
  while (count--)
    {
      //ctx_565_unpack (*pixel, &rgba[0], &rgba[1], &rgba[2], 1);
      ((uint32_t*)(rgba))[0] = ctx_565_unpack_32 (*pixel, 1);
#if CTX_RGB565_ALPHA
      if (rgba[0]==255 && rgba[2] == 255 && rgba[1]==0)
        { rgba[3] = 0; }
      else
        { rgba[3] = 255; }
#endif
      pixel+=1;
      rgba +=4;
    }
}

static inline void
ctx_RGBA8_to_RGB565_BS (CtxRasterizer *rasterizer, int x, const uint8_t *rgba, void *buf, int count)
{
  uint16_t *pixel = (uint16_t *) buf;
  while (count--)
    {
#if CTX_RGB565_ALPHA
      if (rgba[3]==0)
        { pixel[0] = ctx_565_pack (255, 0, 255, 1); }
      else
#endif
        { pixel[0] = ctx_565_pack (rgba[0], rgba[1], rgba[2], 1); }
      pixel+=1;
      rgba +=4;
    }
}

static void
ctx_composite_RGB565_BS (CTX_COMPOSITE_ARGUMENTS)
{
  uint8_t pixels[count * 4];
  ctx_RGB565_BS_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_RGBA8_to_RGB565_BS (rasterizer, x0, &pixels[0], dst, count);
}
#endif

static CtxPixelFormatInfo ctx_pixel_formats[]=
{
#if CTX_ENABLE_RGBA8
  {
    CTX_FORMAT_RGBA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8,
    NULL, NULL, NULL, ctx_setup_RGBA8
  },
#endif
#if CTX_ENABLE_BGRA8
  {
    CTX_FORMAT_BGRA8, 4, 32, 4, 0, 0, CTX_FORMAT_RGBA8,
    ctx_BGRA8_to_RGBA8, ctx_RGBA8_to_BGRA8, ctx_composite_BGRA8, ctx_setup_RGBA8,
  },
#endif
#if CTX_ENABLE_GRAYF
  {
    CTX_FORMAT_GRAYF, 1, 32, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF,
    NULL, NULL, ctx_composite_GRAYF, ctx_setup_GRAYAF,
  },
#endif
#if CTX_ENABLE_GRAYAF
  {
    CTX_FORMAT_GRAYAF, 2, 64, 4 * 2, 0, 0, CTX_FORMAT_GRAYAF,
    NULL, NULL, NULL, ctx_setup_GRAYAF,
  },
#endif
#if CTX_ENABLE_RGBAF
  {
    CTX_FORMAT_RGBAF, 4, 128, 4 * 4, 0, 0, CTX_FORMAT_RGBAF,
    NULL, NULL, NULL, ctx_setup_RGBAF,
  },
#endif
#if CTX_ENABLE_RGB8
  {
    CTX_FORMAT_RGB8, 3, 24, 4, 0, 0, CTX_FORMAT_RGBA8,
    ctx_RGB8_to_RGBA8, ctx_RGBA8_to_RGB8, ctx_composite_convert, ctx_setup_RGBA8,
  },
#endif
#if CTX_ENABLE_GRAY1
  {
#if CTX_NATIVE_GRAYA8
    CTX_FORMAT_GRAY1, 1, 1, 2, 1, 1, CTX_FORMAT_GRAYA8,
    ctx_GRAY1_to_GRAYA8, ctx_GRAYA8_to_GRAY1, ctx_composite_convert, ctx_setup_GRAYA8,
#else
    CTX_FORMAT_GRAY1, 1, 1, 4, 1, 1, CTX_FORMAT_RGBA8,
    ctx_GRAY1_to_RGBA8, ctx_RGBA8_to_GRAY1, ctx_composite_convert, ctx_setup_RGBA8,
#endif
  },
#endif
#if CTX_ENABLE_GRAY2
  {
#if CTX_NATIVE_GRAYA8
    CTX_FORMAT_GRAY2, 1, 2, 2, 4, 4, CTX_FORMAT_GRAYA8,
    ctx_GRAY2_to_GRAYA8, ctx_GRAYA8_to_GRAY2, ctx_composite_convert, ctx_setup_GRAYA8,
#else
    CTX_FORMAT_GRAY2, 1, 2, 4, 4, 4, CTX_FORMAT_RGBA8,
    ctx_GRAY2_to_RGBA8, ctx_RGBA8_to_GRAY2, ctx_composite_convert, ctx_setup_RGBA8,
#endif
  },
#endif
#if CTX_ENABLE_GRAY4
  {
#if CTX_NATIVE_GRAYA8
    CTX_FORMAT_GRAY4, 1, 4, 2, 16, 16, CTX_FORMAT_GRAYA8,
    ctx_GRAY4_to_GRAYA8, ctx_GRAYA8_to_GRAY4, ctx_composite_convert, ctx_setup_GRAYA8,
#else
    CTX_FORMAT_GRAY4, 1, 4, 4, 16, 16, CTX_FORMAT_GRAYA8,
    ctx_GRAY4_to_RGBA8, ctx_RGBA8_to_GRAY4, ctx_composite_convert, ctx_setup_RGBA8,
#endif
  },
#endif
#if CTX_ENABLE_GRAY8
  {
#if CTX_NATIVE_GRAYA8
    CTX_FORMAT_GRAY8, 1, 8, 2, 0, 0, CTX_FORMAT_GRAYA8,
    ctx_GRAY8_to_GRAYA8, ctx_GRAYA8_to_GRAY8, ctx_composite_convert, ctx_setup_GRAYA8,
#else
    CTX_FORMAT_GRAY8, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8,
    ctx_GRAY8_to_RGBA8, ctx_RGBA8_to_GRAY8, ctx_composite_convert, ctx_setup_RGBA8,
#endif
  },
#endif
#if CTX_ENABLE_GRAYA8
  {
#if CTX_NATIVE_GRAYA8
    CTX_FORMAT_GRAYA8, 2, 16, 2, 0, 0, CTX_FORMAT_GRAYA8,
    ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, NULL, ctx_setup_GRAYA8,
#else
    CTX_FORMAT_GRAYA8, 2, 16, 4, 0, 0, CTX_FORMAT_RGBA8,
    ctx_GRAYA8_to_RGBA8, ctx_RGBA8_to_GRAYA8, ctx_composite_convert, ctx_setup_RGBA8,
#endif
  },
#endif
#if CTX_ENABLE_RGB332
  {
    CTX_FORMAT_RGB332, 3, 8, 4, 10, 12, CTX_FORMAT_RGBA8,
    ctx_RGB332_to_RGBA8, ctx_RGBA8_to_RGB332,
    ctx_composite_convert, ctx_setup_RGBA8,
  },
#endif
#if CTX_ENABLE_RGB565
  {
    CTX_FORMAT_RGB565, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8,
    ctx_RGB565_to_RGBA8, ctx_RGBA8_to_RGB565,
    ctx_composite_RGB565, ctx_setup_RGBA8,
  },
#endif
#if CTX_ENABLE_RGB565_BYTESWAPPED
  {
    CTX_FORMAT_RGB565_BYTESWAPPED, 3, 16, 4, 32, 64, CTX_FORMAT_RGBA8,
    ctx_RGB565_BS_to_RGBA8,
    ctx_RGBA8_to_RGB565_BS,
    ctx_composite_RGB565_BS, ctx_setup_RGBA8,
  },
#endif
#if CTX_ENABLE_CMYKAF
  {
    CTX_FORMAT_CMYKAF, 5, 160, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF,
    NULL, NULL, NULL, ctx_setup_CMYKAF,
  },
#endif
#if CTX_ENABLE_CMYKA8
  {
    CTX_FORMAT_CMYKA8, 5, 40, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF,
    NULL, NULL, ctx_composite_CMYKA8, ctx_setup_CMYKAF,
  },
#endif
#if CTX_ENABLE_CMYK8
  {
    CTX_FORMAT_CMYK8, 5, 32, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF,
    NULL, NULL, ctx_composite_CMYK8, ctx_setup_CMYKAF,
  },
#endif
#if CTX_ENABLE_YUV420
  {
    CTX_FORMAT_YUV420, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8,
    NULL, NULL, ctx_composite_convert, ctx_setup_RGBA8,
  },
#endif
  {
    CTX_FORMAT_NONE
  }
};


CtxPixelFormatInfo *
ctx_pixel_format_info (CtxPixelFormat format)
{
  for (unsigned int i = 0; ctx_pixel_formats[i].pixel_format; i++)
    {
      if (ctx_pixel_formats[i].pixel_format == format)
        {
          return &ctx_pixel_formats[i];
        }
    }
  return NULL;
}

#endif
#if CTX_RASTERIZER
#define CTX_AA_HALFSTEP2   (CTX_FULL_AA/2)
#define CTX_AA_HALFSTEP    ((CTX_FULL_AA/2)+1)

static void
ctx_gradient_cache_prime (CtxRasterizer *rasterizer);

static inline void
_ctx_setup_compositor (CtxRasterizer *rasterizer)
{
  if (CTX_UNLIKELY (rasterizer->comp_op==0))
  {
    rasterizer->format->setup (rasterizer);
#if CTX_GRADIENTS
#if CTX_GRADIENT_CACHE
  switch (rasterizer->state->gstate.source_fill.type)
  {
    case CTX_SOURCE_LINEAR_GRADIENT:
    case CTX_SOURCE_RADIAL_GRADIENT:
      ctx_gradient_cache_prime (rasterizer);
      break;
    case CTX_SOURCE_TEXTURE:

      _ctx_matrix_multiply (&rasterizer->state->gstate.source_fill.transform,
                            &rasterizer->state->gstate.source_fill.set_transform,
                            &rasterizer->state->gstate.transform);

      ctx_matrix_invert (&rasterizer->state->gstate.source_fill.transform);

      if (!rasterizer->state->gstate.source_fill.texture.buffer->color_managed)
        _ctx_texture_prepare_color_management (rasterizer,
        rasterizer->state->gstate.source_fill.texture.buffer);
      break;
  }
#endif
#endif
  }
}

#define CTX_FULL_AA 15
inline static void
ctx_rasterizer_apply_coverage (CtxRasterizer *rasterizer,
                               uint8_t * dst,
                               int       x,
                               uint8_t * coverage,
                               int       count)
{
  if (CTX_UNLIKELY(rasterizer->format->apply_coverage))
    rasterizer->format->apply_coverage(rasterizer, dst, rasterizer->color, x, coverage, count);
  else
    /* it is faster to dispatch in this condition, than using a shared
     * direct trampoline
     */
    rasterizer->comp_op (rasterizer, dst, rasterizer->color, x, coverage, count);
}

static void
ctx_rasterizer_gradient_add_stop (CtxRasterizer *rasterizer, float pos, float *rgba)
{
  /* FIXME XXX we only have one gradient, but might need separate gradients
   * for fill/stroke !
   * 
   */
  CtxGradient *gradient = &rasterizer->state->gradient;
  CtxGradientStop *stop = &gradient->stops[gradient->n_stops];
  stop->pos = pos;
  ctx_color_set_rgba (rasterizer->state, & (stop->color), rgba[0], rgba[1], rgba[2], rgba[3]);
  if (gradient->n_stops < 15) //we'll keep overwriting the last when out of stops
    { gradient->n_stops++; }
}

static inline int ctx_rasterizer_add_point (CtxRasterizer *rasterizer, int x1, int y1)
{
  CtxSegment entry = {CTX_EDGE, {{0},}};
  rasterizer->scan_min = ctx_mini (y1, rasterizer->scan_min);
  rasterizer->scan_max = ctx_maxi (y1, rasterizer->scan_max);

  rasterizer->col_min = ctx_mini (x1, rasterizer->col_min);
  rasterizer->col_max = ctx_maxi (x1, rasterizer->col_max);

  entry.data.s16[0]=rasterizer->inner_x;
  entry.data.s16[1]=rasterizer->inner_y;

  entry.data.s16[2]=x1;
  entry.data.s16[3]=y1;

  rasterizer->inner_x = x1;
  rasterizer->inner_y = y1;
#if 0
  if (entry.data.s16[3] < entry.data.s16[1])
  {
    entry = ctx_segment_s16 (CTX_EDGE_FLIPPED,
                            entry.data.s16[2], entry.data.s16[3],
                            entry.data.s16[0], entry.data.s16[1]);
  }
#endif

  return ctx_edgelist_add_single (&rasterizer->edge_list, (CtxEntry*)&entry);
}

#if 0
#define CTX_SHAPE_CACHE_PRIME1   7853
#define CTX_SHAPE_CACHE_PRIME2   4129
#define CTX_SHAPE_CACHE_PRIME3   3371
#define CTX_SHAPE_CACHE_PRIME4   4221
#else
#define CTX_SHAPE_CACHE_PRIME1   283
#define CTX_SHAPE_CACHE_PRIME2   599
#define CTX_SHAPE_CACHE_PRIME3   101
#define CTX_SHAPE_CACHE_PRIME4   661
#endif

float ctx_shape_cache_rate = 0.0;
#if CTX_SHAPE_CACHE
int   _ctx_shape_cache_enabled = 1;

//static CtxShapeCache ctx_cache = {{NULL,}, 0};

static long ctx_shape_cache_hits   = 0;
static long ctx_shape_cache_misses = 0;


/* this returns the buffer to use for rendering, it always
   succeeds..
 */
static inline CtxShapeEntry *ctx_shape_entry_find (CtxRasterizer *rasterizer, uint32_t hash, int width, int height)
{
  /* use both some high and some low bits  */
  int entry_no = ( (hash >> 10) ^ (hash & 1023) ) % CTX_SHAPE_CACHE_ENTRIES;
  int i;
  {
    static int i = 0;
    i++;
    if (i>1000)
      {
        ctx_shape_cache_rate = ctx_shape_cache_hits * 100.0  / (ctx_shape_cache_hits+ctx_shape_cache_misses);
        i = 0;
        ctx_shape_cache_hits = 0;
        ctx_shape_cache_misses = 0;
      }
  }
// XXX : this 1 one is needed  to silence a false positive:
// ==90718== Invalid write of size 1
// ==90718==    at 0x1189EF: ctx_rasterizer_generate_coverage (ctx.h:4786)
// ==90718==    by 0x118E57: ctx_rasterizer_rasterize_edges (ctx.h:4907)
//
  int size = sizeof (CtxShapeEntry) + width * height + 1;

  i = entry_no;
  if (rasterizer->shape_cache.entries[i])
    {
      CtxShapeEntry *entry = rasterizer->shape_cache.entries[i];
      int old_size = sizeof (CtxShapeEntry) + width + height + 1;
      if (entry->hash == hash &&
          entry->width == width &&
          entry->height == height)
        {
          if (entry->uses < 1<<30)
            { entry->uses++; }
          ctx_shape_cache_hits ++;
          return entry;
        }

      if (old_size >= size)
      {
      }
      else
      {
        rasterizer->shape_cache.entries[i] = NULL;
        rasterizer->shape_cache.size -= entry->width * entry->height;
        rasterizer->shape_cache.size -= sizeof (CtxShapeEntry);
        free (entry);
        rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1);
      }
    }
  else
    {
        rasterizer->shape_cache.entries[i] = (CtxShapeEntry *) calloc (size, 1);
    }

  ctx_shape_cache_misses ++;
  rasterizer->shape_cache.size              += size;
  rasterizer->shape_cache.entries[i]->hash   = hash;
  rasterizer->shape_cache.entries[i]->width  = width;
  rasterizer->shape_cache.entries[i]->height = height;
  rasterizer->shape_cache.entries[i]->uses = 0;
  return rasterizer->shape_cache.entries[i];
}

#endif

static uint32_t ctx_rasterizer_poly_to_hash (CtxRasterizer *rasterizer)
{
  int x = 0;
  int y = 0;

  CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0];

  int ox = entry->data.s16[2];
  int oy = entry->data.s16[3];
  uint32_t hash = rasterizer->edge_list.count;
  hash = ox;//(ox % CTX_SUBDIV);
  hash *= CTX_SHAPE_CACHE_PRIME1;
  hash += oy; //(oy % CTX_RASTERIZER_AA);
  for (int i = 0; i < rasterizer->edge_list.count; i++)
    {
      CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[i];
      x = entry->data.s16[2];
      y = entry->data.s16[3];
      int dx = x-ox;
      int dy = y-oy;
      ox = x;
      oy = y;
      hash *= CTX_SHAPE_CACHE_PRIME3;
      hash += dx;
      hash *= CTX_SHAPE_CACHE_PRIME4;
      hash += dy;
    }
  return hash;
}

static uint32_t ctx_rasterizer_poly_to_edges (CtxRasterizer *rasterizer)
{
#if CTX_SHAPE_CACHE
  int x = 0;
  int y = 0;
#endif
  int count = rasterizer->edge_list.count;
  if (CTX_UNLIKELY (count == 0))
     return 0;
  CtxSegment *entry = (CtxSegment*)&rasterizer->edge_list.entries[0];
#if CTX_SHAPE_CACHE
#if 1
  int ox = entry->data.s16[2];
  int oy = entry->data.s16[3];
#endif
  uint32_t hash = rasterizer->edge_list.count;
  hash = (ox & CTX_SUBDIV);
  hash *= CTX_SHAPE_CACHE_PRIME1;
  hash += (oy & CTX_SUBDIV);
#endif
  //CtxSegment *entry = &(((CtxSegment*)(rasterizer->edge_list.entries)))[0];
  for (int i = 0; i < count; i++)
    {
#if CTX_SHAPE_CACHE
      x = entry->data.s16[2];
      y = entry->data.s16[3];
      int dx = x-ox;
      int dy = y-oy;
      ox = x;
      oy = y;
      hash *= CTX_SHAPE_CACHE_PRIME3;
      hash += dx;
      hash *= CTX_SHAPE_CACHE_PRIME4;
      hash += dy;
#endif
#if 1
      if (entry->data.s16[3] < entry->data.s16[1])
        {
          *entry = ctx_segment_s16 (CTX_EDGE_FLIPPED,
                            entry->data.s16[2], entry->data.s16[3],
                            entry->data.s16[0], entry->data.s16[1]);
        }
#endif
      entry++;
    }
#if CTX_SHAPE_CACHE
  return hash;
#else
  return 0;
#endif
}

static inline void ctx_rasterizer_finish_shape (CtxRasterizer *rasterizer)
{
  if (rasterizer->has_shape && rasterizer->has_prev)
    {
      ctx_rasterizer_line_to (rasterizer, rasterizer->first_x, rasterizer->first_y);
      rasterizer->has_prev = 0;
    }
}

static inline void ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y)
{
  float tx = x; float ty = y;
  int aa = 15;//rasterizer->aa;
  rasterizer->x        = x;
  rasterizer->y        = y;
  rasterizer->first_x  = x;
  rasterizer->first_y  = y;
  rasterizer->has_prev = -1;
  if (rasterizer->uses_transforms)
    {
      _ctx_user_to_device (rasterizer->state, &tx, &ty);
    }

  tx = (tx - rasterizer->blit_x) * CTX_SUBDIV;
  ty = ty * aa;

  rasterizer->inner_x = tx;
  rasterizer->inner_y = ty;

  rasterizer->scan_min = ctx_mini (ty, rasterizer->scan_min);
  rasterizer->scan_max = ctx_maxi (ty, rasterizer->scan_max);

  rasterizer->col_min = ctx_mini (tx, rasterizer->col_min);
  rasterizer->col_max = ctx_maxi (tx, rasterizer->col_max);
}

static inline void ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y)
{
  float tx = x;
  float ty = y;
  //float ox = rasterizer->x;
  //float oy = rasterizer->y;
  if ((rasterizer->uses_transforms))
    {
      _ctx_user_to_device (rasterizer->state, &tx, &ty);
    }
  tx -= rasterizer->blit_x;
#define MIN_Y -1000
#define MAX_Y 1400

  ty = ctx_maxf (MIN_Y, ty);
  ty = ctx_minf (MAX_Y, ty);
  
  ctx_rasterizer_add_point (rasterizer, tx * CTX_SUBDIV, ty * CTX_FULL_AA);//rasterizer->aa);

  if (CTX_UNLIKELY(rasterizer->has_prev<=0))
    {
#if 0
      if ((rasterizer->uses_transforms))
      {
        // storing transformed would save some processing for a tiny
        // amount of runtime RAM XXX
        _ctx_user_to_device (rasterizer->state, &ox, &oy);
      }
#endif
      //ox -= rasterizer->blit_x;
      //oy = ctx_maxf (oy, MIN_Y);
      //oy = ctx_minf (oy, MAX_Y);
      CtxSegment *entry = & ((CtxSegment*)rasterizer->edge_list.entries)[rasterizer->edge_list.count-1];
      //entry->data.s16[0] = ox * CTX_SUBDIV;
      //entry->data.s16[1] = oy * CTX_FULL_AA;
      entry->code = CTX_NEW_EDGE;
      rasterizer->has_prev = 1;
    }
  rasterizer->has_shape = 1;
  rasterizer->y         = y;
  rasterizer->x         = x;
}


CTX_INLINE static float
ctx_bezier_sample_1d (float x0, float x1, float x2, float x3, float dt)
{
  float ab   = ctx_lerpf (x0, x1, dt);
  float bc   = ctx_lerpf (x1, x2, dt);
  float cd   = ctx_lerpf (x2, x3, dt);
  float abbc = ctx_lerpf (ab, bc, dt);
  float bccd = ctx_lerpf (bc, cd, dt);
  return ctx_lerpf (abbc, bccd, dt);
}

inline static void
ctx_bezier_sample (float x0, float y0,
                   float x1, float y1,
                   float x2, float y2,
                   float x3, float y3,
                   float dt, float *x, float *y)
{
  *x = ctx_bezier_sample_1d (x0, x1, x2, x3, dt);
  *y = ctx_bezier_sample_1d (y0, y1, y2, y3, dt);
}

static inline void
ctx_rasterizer_bezier_divide (CtxRasterizer *rasterizer,
                              float ox, float oy,
                              float x0, float y0,
                              float x1, float y1,
                              float x2, float y2,
                              float sx, float sy,
                              float ex, float ey,
                              float s,
                              float e,
                              int   iteration,
                              float tolerance)
{
  float t = (s + e) * 0.5f;
  float x, y, lx, ly, dx, dy;
  ctx_bezier_sample (ox, oy, x0, y0, x1, y1, x2, y2, t, &x, &y);
  if (iteration)
    {
      lx = ctx_lerpf (sx, ex, t);
      ly = ctx_lerpf (sy, ey, t);
      dx = lx - x;
      dy = ly - y;
      if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance))
        /* bailing - because for the mid-point straight line difference is
           tiny */
        { return; }
      dx = sx - ex;
      dy = ey - ey;
      if (CTX_UNLIKELY( (dx*dx+dy*dy) < tolerance))
        /* bailing on tiny segments */
        { return; }
    }
  if (iteration < 8)
  {
  ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2,
                                sx, sy, x, y, s, t, iteration + 1,
                                tolerance);
  ctx_rasterizer_line_to (rasterizer, x, y);
  ctx_rasterizer_bezier_divide (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2,
                                x, y, ex, ey, t, e, iteration + 1,
                                tolerance);
  }
}

static void
ctx_rasterizer_curve_to (CtxRasterizer *rasterizer,
                         float x0, float y0,
                         float x1, float y1,
                         float x2, float y2)
{
  //float tolerance =
  //  1.0f*(ctx_pow2 (rasterizer->state->gstate.transform.m[0][0]) +
  //  ctx_pow2 (rasterizer->state->gstate.transform.m[1][1]));
  float tolerance = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
  float ox = rasterizer->x;
  float oy = rasterizer->y;
  //tolerance *= tolerance;
  tolerance = 2.0/(tolerance*tolerance);
  ox = rasterizer->state->x;
  oy = rasterizer->state->y;
  //tolerance = 10.0/(tolerance*tolerance);
  //tolerance = 10.0f/tolerance;
#if 0 // skipping this to preserve hash integrity
  if (tolerance == 1.0f || 1)
  {
  float maxx = ctx_maxf (x1,x2);
  maxx = ctx_maxf (maxx, ox);
  maxx = ctx_maxf (maxx, x0);
  float maxy = ctx_maxf (y1,y2);
  maxy = ctx_maxf (maxy, oy);
  maxy = ctx_maxf (maxy, y0);
  float minx = ctx_minf (x1,x2);
  minx = ctx_minf (minx, ox);
  minx = ctx_minf (minx, x0);
  float miny = ctx_minf (y1,y2);
  miny = ctx_minf (miny, oy);
  miny = ctx_minf (miny, y0);
  
  _ctx_user_to_device (rasterizer->state, &minx, &miny);
  _ctx_user_to_device (rasterizer->state, &maxx, &maxy);
#if 1
    if(
        (minx > rasterizer->blit_x + rasterizer->blit_width) ||
        (miny > rasterizer->blit_y + rasterizer->blit_height) ||
        (maxx < rasterizer->blit_x) ||
        (maxy < rasterizer->blit_y) )
    {
    }
    else
#endif
    {
      ctx_rasterizer_bezier_divide (rasterizer,
                                    ox, oy, x0, y0,
                                    x1, y1, x2, y2,
                                    ox, oy, x2, y2,
                                    0.0f, 1.0f, 0.0f, tolerance);
    }
  }
  else
#endif
    {
      ctx_rasterizer_bezier_divide (rasterizer,
                                    ox, oy, x0, y0,
                                    x1, y1, x2, y2,
                                    ox, oy, x2, y2,
                                    0.0f, 1.0f, 0.0f, tolerance);
    }
  ctx_rasterizer_line_to (rasterizer, x2, y2);
}

static void
ctx_rasterizer_rel_move_to (CtxRasterizer *rasterizer, float x, float y)
{
  //if (CTX_UNLIKELY(x == 0.f && y == 0.f))
  //{ return; }
  x += rasterizer->x;
  y += rasterizer->y;
  ctx_rasterizer_move_to (rasterizer, x, y);
}

static void
ctx_rasterizer_rel_line_to (CtxRasterizer *rasterizer, float x, float y)
{
  //if (CTX_UNLIKELY(x== 0.f && y==0.f))
  //  { return; }
  x += rasterizer->x;
  y += rasterizer->y;
  ctx_rasterizer_line_to (rasterizer, x, y);
}

static void
ctx_rasterizer_rel_curve_to (CtxRasterizer *rasterizer,
                             float x0, float y0, float x1, float y1, float x2, float y2)
{
  x0 += rasterizer->x;
  y0 += rasterizer->y;
  x1 += rasterizer->x;
  y1 += rasterizer->y;
  x2 += rasterizer->x;
  y2 += rasterizer->y;
  ctx_rasterizer_curve_to (rasterizer, x0, y0, x1, y1, x2, y2);
}


static int
ctx_rasterizer_find_texture (CtxRasterizer *rasterizer,
                             const char *eid)
{
  int no;
  for (no = 0; no < CTX_MAX_TEXTURES; no++)
  {
    if (rasterizer->texture_source->texture[no].data &&
        rasterizer->texture_source->texture[no].eid &&
        !strcmp (rasterizer->texture_source->texture[no].eid, eid))
      return no;
  }
  return -1;
}

static void
ctx_rasterizer_set_texture (CtxRasterizer *rasterizer,
                            const char *eid,
                            float x,
                            float y)
{
  int is_stroke = (rasterizer->state->source != 0);
  CtxSource *source = is_stroke && (rasterizer->state->gstate.source_stroke.type != CTX_SOURCE_INHERIT_FILL)?
                        &rasterizer->state->gstate.source_stroke:
                        &rasterizer->state->gstate.source_fill;
  rasterizer->state->source = 0;

  int no = ctx_rasterizer_find_texture (rasterizer, eid);
  if (no < 0 || no >= CTX_MAX_TEXTURES) { no = 0; }
  if (rasterizer->texture_source->texture[no].data == NULL)
    {
      fprintf (stderr, "ctx tex fail %p %s %i\n", rasterizer->texture_source, eid, no);
      return;
    }
  else
  {
    rasterizer->texture_source->texture[no].frame = rasterizer->texture_source->frame;
  }
  source->type = CTX_SOURCE_TEXTURE;
  source->texture.buffer = &rasterizer->texture_source->texture[no];
  ctx_matrix_identity (&source->set_transform);
  ctx_matrix_translate (&source->set_transform, x, y);
}


static void ctx_rasterizer_define_texture (CtxRasterizer *rasterizer,
                                           const char *eid,
                                           int width,
                                           int height,
                                           int format,
                                           char unsigned *data)
{
  _ctx_texture_lock (); // we're using the same texture_source from all threads, keeping allocaitons down
                        // need synchronizing (it could be better to do a pre-pass)
  ctx_texture_init (rasterizer->texture_source,
                    eid,
                    width,
                    height,
                    ctx_pixel_format_get_stride ((CtxPixelFormat)format, width),
                    (CtxPixelFormat)format,
#if CTX_ENABLE_CM
                    (void*)rasterizer->state->gstate.texture_space,
#else
                    NULL,
#endif
                    data,
                    ctx_buffer_pixels_free, (void*)23);
                    /*  when userdata for ctx_buffer_pixels_free is 23, texture_init dups the data on
                     *  use
                     */

  ctx_rasterizer_set_texture (rasterizer, eid, 0.0, 0.0);
  _ctx_texture_unlock ();
}


CTX_INLINE static int ctx_compare_edges (const void *ap, const void *bp)
{
  const CtxSegment *a = (const CtxSegment *) ap;
  const CtxSegment *b = (const CtxSegment *) bp;
  return a->data.s16[1] - b->data.s16[1];
}

CTX_INLINE static int ctx_edge_qsort_partition (CtxSegment *A, int low, int high)
{
  CtxSegment pivot = A[ (high+low) /2];
  int i = low;
  int j = high;
  while (i <= j)
    {
      while (ctx_compare_edges (&A[i], &pivot) < 0) { i ++; }
      while (ctx_compare_edges (&pivot, &A[j]) < 0) { j --; }
      if (i <= j)
        {
          CtxSegment tmp = A[i];
          A[i] = A[j];
          A[j] = tmp;
          i++;
          j--;
        }
    }
  return i;
}

static inline void ctx_edge_qsort (CtxSegment *entries, int low, int high)
{
  {
    int p = ctx_edge_qsort_partition (entries, low, high);
    if (low < p -1 )
      { ctx_edge_qsort (entries, low, p - 1); }
    if (low < high)
      { ctx_edge_qsort (entries, p, high); }
  }
}

static CTX_INLINE void ctx_rasterizer_sort_edges (CtxRasterizer *rasterizer)
{
  ctx_edge_qsort ((CtxSegment*)& (rasterizer->edge_list.entries[0]), 0, rasterizer->edge_list.count-1);
}

static inline void ctx_rasterizer_discard_edges (CtxRasterizer *rasterizer)
{
  int scanline = rasterizer->scanline;
  int next_scanline = rasterizer->scanline + CTX_FULL_AA;
  int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3;
  //if (rasterizer->fast_aa)
    limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA;
  CtxSegment *segments = &((CtxSegment*)(rasterizer->edge_list.entries))[0];
  int *edges = rasterizer->edges;
  for (int i = 0; i < rasterizer->active_edges; i++)
    {
      CtxSegment *segment = segments + edges[i];
      int edge_end = segment->data.s16[3]-1;
      if ((edge_end < scanline))
        {

          int dx_dy = abs(segment->delta);
          rasterizer->needs_aa3  -= (dx_dy > limit3);
          rasterizer->needs_aa5  -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5);
          rasterizer->needs_aa15 -= (dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15);
          rasterizer->edges[i] = rasterizer->edges[rasterizer->active_edges-1];
          rasterizer->active_edges--;
          i--;
        }
      else if (edge_end < next_scanline)
        rasterizer->ending_edges++;
    }
#if 0
  // we should - but for 99% of the cases we do not need to, so we skip it
  for (int i = 0; i < rasterizer->pending_edges; i++)
    {
      int edge_end = ((CtxSegment*)(rasterizer->edge_list.entries))[rasterizer->edges[CTX_MAX_EDGES-1-i]].data.s16[3]-1;
      if (edge_end < scanline + CTX_FULL_AA)
        rasterizer->ending_edges++;
    }
#endif
}

inline static void ctx_rasterizer_increment_edges (CtxRasterizer *rasterizer, int count)
{
  rasterizer->scanline += count;
  CtxSegment *segments = &((CtxSegment*)(rasterizer->edge_list.entries))[0];
  for (int i = 0; i < rasterizer->active_edges; i++)
    {
      CtxSegment *segment = segments + rasterizer->edges[i];
      segment->val += segment->delta * count;
    }
  for (int i = 0; i < rasterizer->pending_edges; i++)
    {
      CtxSegment *segment = segments + rasterizer->edges[CTX_MAX_EDGES-1-i];
      segment->val += segment->delta * count;
    }
}

/* feeds up to rasterizer->scanline,
   keeps a pending buffer of edges - that encompass
   the full incoming scanline,
   feed until the start of the scanline and check for need for aa
   in all of pending + active edges, then
   again feed_edges until middle of scanline if doing non-AA
   or directly render when doing AA
*/
CTX_INLINE static void ctx_edge2_insertion_sort (CtxSegment *segments, int *entries, int count)
{
  for(int i=1; i<count; i++)
   {
     int temp = entries[i];
     int j = i-1;
     while (j >= 0 && segments[temp].val - segments[entries[j]].val < 0)
     {
       entries[j+1] = entries[j];
       j--;
     }
     entries[j+1] = temp;
   }
}

CTX_INLINE static int ctx_edge2_compare2 (CtxSegment *segments, int a, int b)
{
  CtxSegment *seg_a = &segments[a];
  CtxSegment *seg_b = &segments[b];
  int minval_a = ctx_mini (seg_a->val - seg_a->delta * CTX_AA_HALFSTEP2, seg_a->val + seg_a->delta * CTX_AA_HALFSTEP);
  int minval_b = ctx_mini (seg_b->val - seg_b->delta * CTX_AA_HALFSTEP2, seg_b->val + seg_b->delta * CTX_AA_HALFSTEP);
  return minval_a - minval_b;
}

CTX_INLINE static void ctx_edge2_insertion_sort2 (CtxSegment *segments, int *entries, int count)
{
  for(int i=1; i<count; i++)
   {
     int temp = entries[i];
     int j = i-1;
     while (j >= 0 && ctx_edge2_compare2 (segments, temp, entries[j]) < 0)
     {
       entries[j+1] = entries[j];
       j--;
     }
     entries[j+1] = temp;
   }
}

inline static void ctx_rasterizer_feed_edges (CtxRasterizer *rasterizer, int apply2_sort)
{
  int miny;
  CtxSegment *entries = (CtxSegment*)&rasterizer->edge_list.entries[0];
  rasterizer->horizontal_edges = 0;
  rasterizer->ending_edges = 0;
  for (int i = 0; i < rasterizer->pending_edges; i++)
    {
      if (entries[rasterizer->edges[CTX_MAX_EDGES-1-i]].data.s16[1] - 1 <= rasterizer->scanline)
        {
          if (CTX_LIKELY(rasterizer->active_edges < CTX_MAX_EDGES-2))
            {
              int no = rasterizer->active_edges;
              rasterizer->active_edges++;
              rasterizer->edges[no] = rasterizer->edges[CTX_MAX_EDGES-1-i];
              rasterizer->edges[CTX_MAX_EDGES-1-i] =
                rasterizer->edges[CTX_MAX_EDGES-1-rasterizer->pending_edges + 1];
              rasterizer->pending_edges--;
              i--;
            }
        }
    }
  int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3;
  //if (rasterizer->fast_aa)
    limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA;
  int scanline = rasterizer->scanline;
  int next_scanline = scanline + CTX_FULL_AA;
  int edge_pos = rasterizer->edge_pos;
  int edge_count = rasterizer->edge_list.count;
  int *edges = rasterizer->edges;
  while ((edge_pos < edge_count &&
         (miny=entries[edge_pos].data.s16[1]-1)  <= next_scanline))
    {
      int maxy=entries[edge_pos].data.s16[3]-1;
      if ((rasterizer->active_edges < CTX_MAX_EDGES-2 &&
          maxy >= scanline))
        {
          int dy = (entries[edge_pos].data.s16[3] - 1 - miny);
          if ((dy))
            {
              int yd = scanline - miny;
              int no = rasterizer->active_edges;
              rasterizer->active_edges++;
              int index = edges[no] = edge_pos;
              int x0 = entries[index].data.s16[0];
              int x1 = entries[index].data.s16[2];
              int dx_dy = CTX_RASTERIZER_EDGE_MULTIPLIER * (x1 - x0) / dy;
              entries[index].delta = dx_dy;
              entries[index].val = x0 * CTX_RASTERIZER_EDGE_MULTIPLIER +
                                         (yd * dx_dy);

              {
                int abs_dx_dy = abs(dx_dy);
                rasterizer->needs_aa3  += (abs_dx_dy > limit3);
                rasterizer->needs_aa5  += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT5);
                rasterizer->needs_aa15 += (abs_dx_dy > CTX_RASTERIZER_AA_SLOPE_LIMIT15);
              }

              if ((miny > scanline) )
                {
                  /* it is a pending edge - we add it to the end of the array
                     and keep a different count for items stored here, like
                     a heap and stack growing against each other
                  */
                  if ((rasterizer->pending_edges < CTX_MAX_PENDING-1))
                  {
                    edges[CTX_MAX_EDGES-1-rasterizer->pending_edges] =
                    rasterizer->edges[no];
                    rasterizer->pending_edges++;
                    rasterizer->active_edges--;
                  }
                }
            }
          else
            rasterizer->horizontal_edges ++;
        }
      edge_pos++;
    }
    rasterizer->edge_pos = edge_pos;
    ctx_rasterizer_discard_edges (rasterizer);
    if (apply2_sort)
      ctx_edge2_insertion_sort2 ((CtxSegment*)rasterizer->edge_list.entries, rasterizer->edges, rasterizer->active_edges);
    else
      ctx_edge2_insertion_sort ((CtxSegment*)rasterizer->edge_list.entries, rasterizer->edges, rasterizer->active_edges);
}


#undef CTX_CMPSWP

static inline void ctx_coverage_post_process (CtxRasterizer *rasterizer, int minx, int maxx, uint8_t *coverage, int *first_col, int *last_col)
{
  int scanline     = rasterizer->scanline - CTX_FULL_AA; // we do the
                                                 // post process after
                                                 // coverage generation icnrement
#if CTX_ENABLE_SHADOW_BLUR
  if (CTX_UNLIKELY(rasterizer->in_shadow))
  {
    float radius = rasterizer->state->gstate.shadow_blur;
    int dim = 2 * radius + 1;
    if (CTX_UNLIKELY (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM))
      dim = CTX_MAX_GAUSSIAN_KERNEL_DIM;
    {
      uint16_t temp[maxx-minx+1];
      memset (temp, 0, sizeof (temp));
      for (int x = dim/2; x < maxx-minx + 1 - dim/2; x ++)
        for (int u = 0; u < dim; u ++)
        {
          temp[x] += coverage[minx+x+u-dim/2] * rasterizer->kernel[u] * 256;
        }
      for (int x = 0; x < maxx-minx + 1; x ++)
        coverage[minx+x] = temp[x] >> 8;
    }
  }
#endif

#if CTX_ENABLE_CLIP
  if (CTX_UNLIKELY(rasterizer->clip_buffer &&  !rasterizer->clip_rectangle))
  {
    /* perhaps not working right for clear? */
    int y = scanline / CTX_FULL_AA;//rasterizer->aa;
    uint8_t *clip_line = &((uint8_t*)(rasterizer->clip_buffer->data))[rasterizer->blit_width*y];
    // XXX SIMD candidate
    for (int x = minx; x <= maxx; x ++)
    {
#if CTX_1BIT_CLIP
       coverage[x] = (coverage[x] * ((clip_line[x/8]&(1<<(x&8)))?255:0))/255;
#else
       coverage[x] = (255 + coverage[x] * clip_line[x-rasterizer->blit_x])>>8;
#endif
    }
  }
  if (CTX_UNLIKELY(rasterizer->aa == 1))
  {
    for (int x = minx; x <= maxx; x ++)
     coverage[x] = coverage[x] > 127?255:0;
  }
#endif
}

#define CTX_EDGE(no)      entries[edges[no]]
#define CTX_EDGE_YMIN     (segment->data.s16[1]-1)

#define UPDATE_PARITY \
        { \
          if (scanline!=CTX_EDGE_YMIN)\
            parity = (is_winding)? \
             parity + -1+2*(segment->code == CTX_EDGE_FLIPPED) : \
                        1 - parity;\
        }

inline static void
ctx_rasterizer_generate_coverage (CtxRasterizer *rasterizer,
                                  int            minx,
                                  int            maxx,
                                  uint8_t       *coverage,
                                  int            is_winding,
                                  const uint8_t  aa_factor)
{
  CtxSegment *entries      = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  int        *edges        = rasterizer->edges;
  int         scanline     = rasterizer->scanline;
  int         active_edges = rasterizer->active_edges;
  int         parity       = 0;
  coverage -= minx;
  uint8_t fraction = 255/aa_factor;
  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment *segment = &entries[edges[t]];
      UPDATE_PARITY;

      if (parity)
        {
          CtxSegment *next_segment = &entries[edges[t+1]];
          const int x0 = segment->val;
          const int x1 = next_segment->val;
          int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int grayend   = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int first     = graystart >> 8;
          int last      = grayend   >> 8;

          if ((first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if ((last > maxx))
          {
            last = maxx;
            grayend=255;
          }

          graystart = fraction- (graystart&0xff)/aa_factor;
          grayend   = (grayend & 0xff) / aa_factor;

          if (first < last)
          {
              coverage[first] += graystart;
              for (int x = first + 1; x < last; x++)
                coverage[x]  += fraction;
              coverage[last] += grayend;
          }
          else if (first == last)
            coverage[first] += (graystart-(fraction-grayend));
        }
   }
}

inline static void
ctx_rasterizer_generate_coverage_set (CtxRasterizer *rasterizer,
                                      int            minx,
                                      int            maxx,
                                      uint8_t       *coverage,
                                      int            is_winding)
{
  CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  int      *edges = rasterizer->edges;
  int scanline     = rasterizer->scanline;
  int active_edges = rasterizer->active_edges;
  int parity = 0;
  coverage -= minx;
  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment *segment = &entries[edges[t]];
      UPDATE_PARITY;

      if (parity)
        {
          CtxSegment *next_segment = &entries[edges[t+1]];
          const int x0        = segment->val;
          const int x1        = next_segment->val;
          int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int grayend   = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int first     = graystart >> 8;
          int last      = grayend   >> 8;

          if ((first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if ((last > maxx))
          {
            last = maxx;
            grayend=255;
          }

          graystart = 255 - (graystart&0xff);
          grayend   = (grayend & 0xff);

          if (first < last)
          {
              coverage[first] += graystart;
#if 0
              for (int x = first + 1; x < last; x++)
                coverage[x] = 255;
#else
              memset(&coverage[first+1], 255, last-(first+1));
#endif
              coverage[last]  += grayend;
          }
          else if (first == last)
            coverage[first] += (graystart-(255-grayend));
        }
   }
}

static inline uint32_t
ctx_over_RGBA8 (uint32_t dst, uint32_t src, uint32_t cov)
{
  uint32_t si_ga = (src & 0xff00ff00) >> 8;
  uint32_t si_rb = src & 0x00ff00ff;
  uint32_t si_a  = si_ga >> 16;
  uint32_t rcov  = ((255+si_a * cov)>>8)^255;
  uint32_t di_ga = ( dst & 0xff00ff00) >> 8;
  uint32_t di_rb = dst & 0x00ff00ff;
  return
     ((((si_rb * cov) + 0xff00ff + (di_rb * rcov)) & 0xff00ff00) >> 8)  |
      (((si_ga * cov) + 0xff00ff + (di_ga * rcov)) & 0xff00ff00);
}


static inline uint32_t
ctx_over_RGBA8_full (uint32_t dst, uint32_t src)
{
  uint32_t si_ga = (src & 0xff00ff00) >> 8;
  uint32_t si_rb = src & 0x00ff00ff;
  uint32_t si_a  = si_ga >> 16;
  uint32_t rcov  = si_a^255;
  uint32_t di_ga = (dst & 0xff00ff00) >> 8;
  uint32_t di_rb = dst & 0x00ff00ff;
  return
     ((((si_rb * 255) + 0xff00ff + (di_rb * rcov)) & 0xff00ff00) >> 8)  |
      (((si_ga * 255) + 0xff00ff + (di_ga * rcov)) & 0xff00ff00);
}

static inline uint32_t
ctx_over_RGBA8_2 (uint32_t dst, uint32_t si_ga, uint32_t si_rb, uint32_t si_a, uint32_t cov)
{
  uint32_t rcov  = ((si_a * cov)/255)^255;
  uint32_t di_ga = (dst & 0xff00ff00) >> 8;
  uint32_t di_rb = dst & 0x00ff00ff;
  return
     ((((si_rb * cov) + 0xff00ff + (di_rb * rcov)) & 0xff00ff00) >> 8)  |
      (((si_ga * cov) + 0xff00ff + (di_ga * rcov)) & 0xff00ff00);
}

static inline uint32_t
ctx_over_RGBA8_full_2 (uint32_t dst, uint32_t si_ga_full, uint32_t si_rb_full, uint32_t si_a)
{
  uint32_t rcov = si_a^255;
  uint32_t di_ga = ( dst & 0xff00ff00) >> 8;
  uint32_t di_rb = dst & 0x00ff00ff;
  return
     ((((si_rb_full) + (di_rb * rcov)) & 0xff00ff00) >> 8)  |
      (((si_ga_full) + (di_ga * rcov)) & 0xff00ff00);
}

static inline void ctx_span_set_color (uint32_t *dst_pix, uint32_t val, int count)
{
  if (count>0)
  while(count--)
    *dst_pix++=val;
}

inline static void
ctx_rasterizer_generate_coverage_apply (CtxRasterizer *rasterizer,
                                        int            minx,
                                        int            maxx,
                                        uint8_t       *coverage,
                                        int            is_winding,
                                        CtxCovPath     comp)
{
  CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  int *edges  = rasterizer->edges;
  int scanline        = rasterizer->scanline;
  const int bpp             = rasterizer->format->bpp/8;
  int active_edges    = rasterizer->active_edges;
  int parity        = 0;
  const uint32_t src_pix    = ((uint32_t*)rasterizer->color)[0];
  const uint32_t si_ga = ((uint32_t*)rasterizer->color)[1];
  const uint32_t si_rb = ((uint32_t*)rasterizer->color)[2];
  const uint32_t si_ga_full = ((uint32_t*)rasterizer->color)[3];
  const uint32_t si_rb_full = ((uint32_t*)rasterizer->color)[4];
  const uint32_t si_a  = si_ga >> 16;

  uint8_t *dst = ( (uint8_t *) rasterizer->buf) +
         (rasterizer->blit_stride * (scanline / CTX_FULL_AA));
  int accumulator_x=0;
  uint8_t accumulated = 0;
  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment *segment = &entries[edges[t]];
      UPDATE_PARITY;

       if (parity)
        {
          CtxSegment   *next_segment = &entries[edges[t+1]];
          const int x0        = segment->val;
          const int x1        = next_segment->val;
          int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int grayend   = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int first     = graystart >> 8;
          int last      = grayend   >> 8;

          if ((first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if ((last > maxx))
          {
            last = maxx;
            grayend=255;
          }

          graystart = 255 - (graystart&0xff);
          grayend   = (grayend & 0xff);

          if (accumulated)
          {
            if (accumulator_x == first)
            {
              graystart += accumulated;
            }
            else
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]);
              switch (comp)
              {
                case CTX_COV_PATH_COPY:
                  *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated);
                  break;
                case CTX_COV_PATH_OVER:
                  *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated);
                  break;
                default:
                  ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1);
              }
            }
            accumulated = 0;
          }

          if (first < last)
          {
            switch (comp)
            {
              case CTX_COV_PATH_COPY:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]);
              *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, graystart);

              dst_pix++;
              ctx_span_set_color (dst_pix, src_pix, last - first - 1);
#if 0
              for (int i = first + 1; i < last; i++)
              {
                *dst_pix = src_pix;
                dst_pix++;
              }
#endif
            }
            break;
              case CTX_COV_PATH_OVER:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]);
              *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, graystart);
              dst_pix++;
              for (int i = first + 1; i < last; i++)
              {
                *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a);
                dst_pix++;
              }
            }
            break;
              case CTX_COV_PATH_COPY_FRAGMENT:
            {
              float u0 = 0; float v0 = 0;
              float ud = 0; float vd = 0;
              uint8_t gs = graystart;
              ctx_RGBA8_source_copy_normal_fragment (rasterizer, &dst[(first * bpp)], NULL, first, &gs, 1);
              ctx_init_uv (rasterizer, first+1, last-first-1, &u0, &v0, &ud, &vd);
              rasterizer->fragment (rasterizer, u0, v0, &dst[(first+1)*bpp], last-first-1, ud, vd);
            }
            break;
              case CTX_COV_PATH_OVER_FRAGMENT:
            {
              uint8_t gs = graystart;
              ctx_RGBA8_source_over_normal_fragment (rasterizer, &dst[(first * bpp)], NULL, first, &gs, 1);
              ctx_RGBA8_source_over_normal_full_cov_fragment (rasterizer,
                                                     &dst[((first+1)*bpp)], NULL, first + 1, NULL, last-first-1);
            }
            break;
              default:
            {
              uint8_t opaque[last-first];
              memset (opaque, 255, sizeof (opaque));
              opaque[0] = graystart;
              ctx_rasterizer_apply_coverage (rasterizer,
                                             &dst[(first * bpp)], first, opaque, last-first);
            }
            }
            accumulated = grayend;
          }
          else if (first == last)
          {
            accumulated = (graystart-(255-grayend));
          }
          accumulator_x = last;
        }
   }

   if (accumulated)
   {
     uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)]);
     switch (comp)
     {
       case CTX_COV_PATH_COPY:
         *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated);
         break;
       case CTX_COV_PATH_OVER:
         *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated);
         break;
       default:
         ctx_rasterizer_apply_coverage (rasterizer, (uint8_t*)dst_pix, accumulator_x, &accumulated, 1);
     }
   }
}

inline static int ctx_rasterizer_is_simple (CtxRasterizer *rasterizer)
{
  if (rasterizer->fast_aa == 0 ||
      rasterizer->ending_edges ||
      rasterizer->pending_edges)
   return 0;
  int *edges  = rasterizer->edges;
  CtxSegment *segments = &((CtxSegment*)(rasterizer->edge_list.entries))[0];

  int active_edges = rasterizer->active_edges;
  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment *segment0 = segments + edges[t];
      CtxSegment *segment1 = segments + edges[t+1];
      const int delta0    = segment0->delta;
      const int delta1    = segment1->delta;
      const int x0        = segment0->val;
      const int x1        = segment1->val;
      int x0_end   = x0 + delta0 * CTX_AA_HALFSTEP;
      int x1_end   = x1 + delta1 * CTX_AA_HALFSTEP;
      int x0_start = x0 - delta0 * CTX_AA_HALFSTEP2;
      int x1_start = x1 - delta1 * CTX_AA_HALFSTEP2;
      if (x1_end < x0_end   ||
          x1_start < x0_end ||
          x1_end < x0_start
         )
         return 0;
    }
  return 1;
}


inline static void
ctx_rasterizer_generate_coverage_set2 (CtxRasterizer *rasterizer,
                                         int            minx,
                                         int            maxx,
                                         uint8_t       *coverage,
                                         int            is_winding)
{
  CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  int *edges  = rasterizer->edges;
  int scanline        = rasterizer->scanline;
  int active_edges    = rasterizer->active_edges;
  int parity        = 0;

  coverage -= minx;

  const int minx_ = minx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV;
  const int maxx_ = maxx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV;

  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment   *segment = &entries[edges[t]];
      UPDATE_PARITY;

       if (parity)
        {
          CtxSegment   *next_segment = &entries[edges[t+1]];
          const int x0        = segment->val;
          const int x1        = next_segment->val;
          const int delta0    = segment->delta;
          const int delta1    = next_segment->delta;

          int x0_start = x0 - delta0 * CTX_AA_HALFSTEP2;
          int x1_start = x1 - delta1 * CTX_AA_HALFSTEP2;
          int x0_end   = x0 + delta0 * CTX_AA_HALFSTEP;
          int x1_end   = x1 + delta1 * CTX_AA_HALFSTEP;

          int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int grayend   = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int first     = graystart >> 8;
          int last      = grayend   >> 8;

          first = ctx_maxi (first, minx);
          last  = ctx_mini (last, maxx);

          if (first < last)
          {
            int pre = 1;
            int post = 1;

            if (abs(delta0) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA)
            {
              graystart = 255 - (graystart&0xff);
              coverage[first] += graystart;
            }
            else
            {
              int u0 = ctx_mini (x0_start, x0_end);
              int u1 = ctx_maxi (x0_start, x0_end);
              u0 = ctx_maxi (u0, minx_);
              u1 = ctx_mini (u1, maxx_);
              u1 = ctx_maxi (u1, minx_);
              u0 = ctx_mini (u0, maxx_);
  
              int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
              int count = 0;

              int mod = (255-(u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)) *
                         (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255);
              int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV)/255);

              for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
              {
                coverage[us + count] = (u - u0 + mod) / sum;
                count++;
              }
              pre = (us+count-1)-first+1;
            }
  
            if (abs(delta1) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA)
            {
               grayend   = (grayend & 0xff);
               coverage[last] += grayend;
            }
            else
            {
              int u0 = ctx_mini (x1_start, x1_end);
              int u1 = ctx_maxi (x1_start, x1_end);
              u0 = ctx_maxi (u0, minx_);
              u1 = ctx_mini (u1, maxx_);
              u1 = ctx_maxi (u1, minx_);
              u0 = ctx_mini (u0, maxx_);
              int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
              int count = 0;
              int mod = ((255-(u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)+64) *
                    (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255));
              int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 1.25)/255);
              for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
              {
                coverage[us + count] = 255-((u - u0 + mod)/ sum);
                count++;
              }
              post = last-us+1;
            }
            for (int i = first + pre; i <= last - post; i++)
              coverage[i] = 255;
          }
          else if (first == last)
          {
            graystart = 255 - (graystart&0xff);
            grayend   = (grayend & 0xff);
            coverage[last]+=(graystart-(255-grayend));
          }
        }
   }
}


inline static void
ctx_rasterizer_generate_coverage_apply2 (CtxRasterizer *rasterizer,
                                         int            minx,
                                         int            maxx,
                                         uint8_t       *coverage,
                                         int            is_winding,
                                         CtxCovPath     comp)
{
  CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  int *edges          = rasterizer->edges;
  int  scanline       = rasterizer->scanline;
  const int  bpp      = rasterizer->format->bpp/8;
  int  active_edges   = rasterizer->active_edges;
  int  parity         = 0;

  const uint32_t src_pix    = ((uint32_t*)rasterizer->color)[0];
  const uint32_t si_ga      = ((uint32_t*)rasterizer->color)[1];
  const uint32_t si_rb      = ((uint32_t*)rasterizer->color)[2];
  const uint32_t si_ga_full = ((uint32_t*)rasterizer->color)[3];
  const uint32_t si_rb_full = ((uint32_t*)rasterizer->color)[4];
  const uint32_t si_a  = src_pix >> 24;

  uint8_t *dst = ( (uint8_t *) rasterizer->buf) +
         (rasterizer->blit_stride * (scanline / CTX_FULL_AA));

  coverage -= minx;

  const int minx_ = minx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV;
  const int maxx_ = maxx * CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV;

  int accumulated_x0 = 65538;
  int accumulated_x1 = 65536;

  for (int t = 0; t < active_edges -1;t++)
    {
      CtxSegment   *segment = &entries[edges[t]];
      UPDATE_PARITY;

       if (parity)
        {
          CtxSegment   *next_segment = &entries[edges[t+1]];
          const int x0        = segment->val;
          const int x1        = next_segment->val;
          const int delta0    = segment->delta;
          const int delta1    = next_segment->delta;

          int x0_start = x0 - delta0 * CTX_AA_HALFSTEP2;
          int x1_start = x1 - delta1 * CTX_AA_HALFSTEP2;
          int x0_end   = x0 + delta0 * CTX_AA_HALFSTEP;
          int x1_end   = x1 + delta1 * CTX_AA_HALFSTEP;

          int graystart = x0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int grayend   = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int first     = graystart >> 8;
          int last      = grayend   >> 8;

          first = ctx_maxi (first, minx);
          last = ctx_mini (last, maxx);
          graystart = 255 - (graystart&0xff);
          grayend   = (grayend & 0xff);

          if (first < last)
          {
            int pre = 1;
            int post = 1;

          if (abs(delta0) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA)
          {
             coverage[first] += graystart;

            accumulated_x1 = first;
            accumulated_x0 = ctx_mini (accumulated_x0, first);
          }
          else
          {
            int u0 = ctx_mini (x0_start, x0_end);
            int u1 = ctx_maxi (x0_start, x0_end);
            u0 = ctx_maxi (u0, minx_);
            u1 = ctx_mini (u1, maxx_);
            u1 = ctx_maxi (u1, minx_);
            u0 = ctx_mini (u0, maxx_);

            int mod = (255-(u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)) *
                    (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255);
            int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV)/255);

            int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
            int count = 0;
            for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
            {
              coverage[us + count] = (u - u0 + mod) / sum;
              count++;
            }
            pre = (us+count-1)-first+1;

#if 0 // the CTX_UNLIKELY helps - but this is a big constant overhead
      // which ends up penalizing us in benchmarks, it needs to be
      // a shape with really large interior emptiness for this
      // to be worthwhile
          if (CTX_UNLIKELY(
              us - accumulated_x1 > 16 &&
              accumulated_x1-accumulated_x0>=0
                          ))
          {
             switch (comp)
             {
                case CTX_COV_PATH_OVER:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                    {
                      *dst_i = ctx_over_RGBA8_2 (*dst_i, si_ga, si_rb, si_a, coverage[accumulated_x0+i]);
                      dst_i++;
                    }
                }
                break;
                case CTX_COV_PATH_COPY:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                  {
                    *dst_i = ctx_lerp_RGBA8_2 (*dst_i, si_ga, si_rb, coverage[accumulated_x0+i]);
                    dst_i++;
                  }
                }
                  break;
                default:
                ctx_rasterizer_apply_coverage (rasterizer,
                          &dst[((accumulated_x0) * bpp)],
                          accumulated_x0,
                          &coverage[accumulated_x0],
                          accumulated_x1-accumulated_x0+1);
             }
          }
#endif

            accumulated_x0 = ctx_mini (accumulated_x0, us);
            accumulated_x1 = us + count - 1;
          }

   if (accumulated_x1-accumulated_x0>=0)
          {
             switch (comp)
             {
                case CTX_COV_PATH_OVER:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                    {
                      *dst_i = ctx_over_RGBA8_2 (*dst_i, si_ga, si_rb, si_a, coverage[accumulated_x0+i]);
                      dst_i++;
                    }
                }
                break;
                case CTX_COV_PATH_COPY:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                  {
                    *dst_i = ctx_lerp_RGBA8_2 (*dst_i, si_ga, si_rb, coverage[accumulated_x0+i]);
                    dst_i++;
                  }
                }
                  break;
                default:
                ctx_rasterizer_apply_coverage (rasterizer,
                          &dst[((accumulated_x0) * bpp)],
                          accumulated_x0,
                          &coverage[accumulated_x0],
                          accumulated_x1-accumulated_x0+1);
             }
             accumulated_x0 = 65538;
             accumulated_x1 = 65536;
          }

          if (abs(delta1) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA)
          {
             coverage[last] += grayend;
             accumulated_x1 = last;
             accumulated_x0 = last;
          }
          else
          {
            int u0 = ctx_mini (x1_start, x1_end);
            int u1 = ctx_maxi (x1_start, x1_end);
            u0 = ctx_maxi (u0, minx_);
            u1 = ctx_mini (u1, maxx_);
            u1 = ctx_maxi (u1, minx_);
            u0 = ctx_mini (u0, maxx_);
            int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
            int count = 0;

            int mod = ((255-(u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)+64) *
                    (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255));
            int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 1.25)/255);

            for (int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
            {
                coverage[us + count] = 255-((u - u0 + mod)/ sum);
              count++;
            }
            post = last-us+1;

            accumulated_x1 = us + count;
            accumulated_x0 = us;
          }
            switch (comp)
            {
              case CTX_COV_PATH_COPY:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]);
              dst_pix+=pre;
              ctx_span_set_color (dst_pix, src_pix, last-first-pre-post + 1);
            }
            break;
              case CTX_COV_PATH_OVER:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)]);
              dst_pix+=pre;
              for (int i = first + pre; i <= last - post; i++)
              {
                *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a);
                dst_pix++;
              }
            }
            break;
              case CTX_COV_PATH_COPY_FRAGMENT:
              {
              int width = last-first-pre-post+1;
            if (width>0)
            {
              {
                float u0 = 0; float v0 = 0;
                float ud = 0; float vd = 0;
                ctx_init_uv (rasterizer, first+pre, width, &u0, &v0, &ud, &vd);
                rasterizer->fragment (rasterizer, u0, v0, &dst[(first+pre)*bpp],                                      width, ud, vd);
              }
            }
              }
            break;
              case CTX_COV_PATH_OVER_FRAGMENT:
              {
                int width = last-first-pre-post+1;
                if (width>0)
                ctx_RGBA8_source_over_normal_full_cov_fragment (rasterizer,
                               &dst[((first+pre)*bpp)],
                               NULL,
                               first + pre,
                               NULL,
                               width);
              }
              break;
              default:
              {
                int width = last-first-pre-post+1;
                if (width > 0)
                {
                uint8_t opaque[width];
                memset (opaque, 255, sizeof (opaque));
                ctx_rasterizer_apply_coverage (rasterizer,
                            &dst[((first + pre) * bpp)],
                            first + pre,
                            opaque,
                            width);
                }
              }
            }
            //}
          }
          else if (first == last)
          {
            coverage[last]+=(graystart-(255-grayend));

            accumulated_x1 = last;
            accumulated_x0 = ctx_mini (accumulated_x0, last);
          }
        }
   }

   if (accumulated_x1-accumulated_x0>=0)
   {
             switch (comp)
             {
                case CTX_COV_PATH_OVER:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                    {
                      *dst_i = ctx_over_RGBA8_2 (*dst_i, si_ga, si_rb, si_a, coverage[accumulated_x0+i]);
                      dst_i++;
                    }
                }
                break;
                case CTX_COV_PATH_COPY:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)];
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                  {
                    *dst_i = ctx_lerp_RGBA8_2 (*dst_i, si_ga, si_rb, coverage[accumulated_x0+i]);
                    dst_i++;
                  }
                }
                  break;
                default:
                ctx_rasterizer_apply_coverage (rasterizer,
                          &dst[((accumulated_x0) * bpp)],
                          accumulated_x0,
                          &coverage[accumulated_x0],
                          accumulated_x1-accumulated_x0+1);
             }
   }
}

#undef CTX_EDGE_Y0
#undef CTX_EDGE

static inline void
ctx_rasterizer_reset (CtxRasterizer *rasterizer)
{
  rasterizer->pending_edges   = 0;
  rasterizer->active_edges    = 0;
  rasterizer->has_shape       = 0;
  rasterizer->has_prev        = 0;
  rasterizer->edge_list.count = 0; // ready for new edges
  rasterizer->edge_pos        = 0;
  rasterizer->scanline        = 0;
  if (CTX_LIKELY(!rasterizer->preserve))
  {
    rasterizer->scan_min      = 5000;
    rasterizer->scan_max      = -5000;
    rasterizer->col_min       = 5000;
    rasterizer->col_max       = -5000;
  }
  //rasterizer->comp_op       = NULL; // keep comp_op cached 
  //     between rasterizations where rendering attributes are
  //     nonchanging
}

static void
ctx_rasterizer_rasterize_edges (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               )
{
  int      is_winding = fill_rule == CTX_FILL_RULE_WINDING;
  const CtxCovPath comp = rasterizer->comp;
  const int real_aa = rasterizer->aa;
  uint8_t *dst = ( (uint8_t *) rasterizer->buf);


  int scan_start = rasterizer->blit_y * CTX_FULL_AA;
  int scan_end   = scan_start + (rasterizer->blit_height - 1) * CTX_FULL_AA;
  const int blit_width = rasterizer->blit_width;
  const int blit_max_x = rasterizer->blit_x + blit_width;
  int minx       = rasterizer->col_min / CTX_SUBDIV - rasterizer->blit_x;
  int maxx       = (rasterizer->col_max + CTX_SUBDIV-1) / CTX_SUBDIV - rasterizer->blit_x;
  const int blit_stride = rasterizer->blit_stride;

  rasterizer->prev_active_edges = -1;
  if (
#if CTX_SHAPE_CACHE
    !shape &&
#endif
    maxx > blit_max_x - 1)
    { maxx = blit_max_x - 1; }

  minx = ctx_maxi (rasterizer->state->gstate.clip_min_x, minx);
  maxx = ctx_mini (rasterizer->state->gstate.clip_max_x, maxx);
  minx = ctx_maxi (0, minx); // redundant?
  if (minx >= maxx)
    {
      ctx_rasterizer_reset (rasterizer);
      return;
    }
#if CTX_SHAPE_CACHE
  uint8_t _coverage[shape?2:maxx-minx+1];
#else
  uint8_t _coverage[maxx-minx+1];
#endif
  uint8_t *coverage = &_coverage[0];

  int coverage_size = 
#if CTX_SHAPE_CACHE
                  shape?shape->width:
#endif
                  sizeof (_coverage);

#if CTX_SHAPE_CACHE
  if (shape)
    {
      coverage = &shape->data[0];
    }
#endif
  //ctx_assert (coverage);
  rasterizer->scan_min -= (rasterizer->scan_min % CTX_FULL_AA);
#if CTX_SHAPE_CACHE
  if (shape)
    {
      scan_start = rasterizer->scan_min;
      scan_end   = rasterizer->scan_max;
    }
  else
#endif
    {
      if (rasterizer->scan_min > scan_start)
        {
          dst += (rasterizer->blit_stride * (rasterizer->scan_min-scan_start) / CTX_FULL_AA);
          scan_start = rasterizer->scan_min;
        }
      scan_end = ctx_mini (rasterizer->scan_max, scan_end);
    }
  if (CTX_UNLIKELY(rasterizer->state->gstate.clip_min_y * CTX_FULL_AA > scan_start ))
    { 
       dst += (rasterizer->blit_stride * (rasterizer->state->gstate.clip_min_y * CTX_FULL_AA -scan_start) / CTX_FULL_AA);
       scan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA; 
    }
  scan_end = ctx_mini (rasterizer->state->gstate.clip_max_y * CTX_FULL_AA, scan_end);
  if (CTX_UNLIKELY(scan_start > scan_end ||
      (scan_start > (rasterizer->blit_y + (rasterizer->blit_height-1)) * CTX_FULL_AA) ||
      (scan_end < (rasterizer->blit_y) * CTX_FULL_AA)))
  { 
    /* not affecting this rasterizers scanlines */
    ctx_rasterizer_reset (rasterizer);
    return;
  }

  rasterizer->horizontal_edges = 0;
  {
    rasterizer->needs_aa3  = 0;
    rasterizer->needs_aa5  = 0;
    rasterizer->needs_aa15 = 0;
    ctx_rasterizer_sort_edges (rasterizer);
    rasterizer->scanline = scan_start;
    ctx_rasterizer_feed_edges (rasterizer, 0); 

      int avoid_direct = (0 
#if CTX_ENABLE_CLIP
         || rasterizer->clip_buffer
#endif
#if CTX_ENABLE_SHADOW_BLUR
         || rasterizer->in_shadow
#endif
#if CTX_SHAPE_CACHE
         || shape != NULL
#endif
         );

  for (; rasterizer->scanline <= scan_end;)
    {

      int needs_full_aa =
          ( (rasterizer->horizontal_edges!=0) 
          | (rasterizer->active_edges != rasterizer->prev_active_edges)
          | (rasterizer->active_edges + rasterizer->pending_edges == rasterizer->ending_edges)
          );

    if ((needs_full_aa))
    {
        int increment = CTX_FULL_AA/real_aa;
        memset (coverage, 0, coverage_size);
        for (int i = 0; i < real_aa; i++)
        {
          ctx_rasterizer_feed_edges (rasterizer, 0);
          ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, is_winding, real_aa);
          ctx_rasterizer_increment_edges (rasterizer, increment);
        }
    }
    else if (! avoid_direct & (rasterizer->needs_aa3 == 0))
    {
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2);
      ctx_rasterizer_feed_edges (rasterizer, 0);

      ctx_rasterizer_generate_coverage_apply (rasterizer, minx, maxx, coverage, is_winding,
                      comp);
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP);

      dst += blit_stride;
      rasterizer->prev_active_edges = rasterizer->active_edges;
      continue;
    }
    else if (avoid_direct & (rasterizer->needs_aa3 == 0))
    {
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2);
      ctx_rasterizer_feed_edges (rasterizer, 0);

      memset (coverage, 0, coverage_size);
      ctx_rasterizer_generate_coverage_set (rasterizer, minx, maxx, coverage, is_winding);
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP);
    }
    else if (ctx_rasterizer_is_simple (rasterizer))
    {
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP2);
      ctx_rasterizer_feed_edges (rasterizer, 1);
      memset (coverage, 0, coverage_size);
      if (!avoid_direct)
      {

        ctx_rasterizer_generate_coverage_apply2 (rasterizer, minx, maxx, coverage, is_winding,
                      comp);
        ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP);

        dst += blit_stride;
        rasterizer->prev_active_edges = rasterizer->active_edges;
        continue;
      }
      ctx_rasterizer_generate_coverage_set2 (rasterizer, minx, maxx, coverage, is_winding);
      ctx_rasterizer_increment_edges (rasterizer, CTX_AA_HALFSTEP);
    }
    else 
    {
      int aa = 3;
      if (rasterizer->needs_aa5 && real_aa >=5)
      {
         aa = 5;
         if (rasterizer->needs_aa15 && real_aa >=15)
           aa = 15;
      }
      int scanline_increment = 15/aa;

      memset (coverage, 0, coverage_size);
      for (int i = 0; i < CTX_FULL_AA; i+= scanline_increment)
      {
        ctx_rasterizer_feed_edges (rasterizer, 0);
        ctx_rasterizer_generate_coverage (rasterizer, minx, maxx, coverage, is_winding, aa);
        ctx_rasterizer_increment_edges (rasterizer, scanline_increment);
      }
    }

  ctx_coverage_post_process (rasterizer, minx, maxx, coverage - minx,
                  NULL, NULL);
#if CTX_SHAPE_CACHE
  if (shape == NULL)
#endif
  {
    ctx_rasterizer_apply_coverage (rasterizer,
                         &dst[(minx * rasterizer->format->bpp) /8],
                         minx,
                         coverage,
                         maxx-minx+ 1);
  }
#if CTX_SHAPE_CACHE
      if (shape)
        {
          coverage += shape->width;
        }
#endif
      dst += blit_stride;
      rasterizer->prev_active_edges = rasterizer->active_edges;
    }
  }

  if (CTX_UNLIKELY(rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OUT ||
      rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_IN ||
      rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_IN ||
      rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_DESTINATION_ATOP ||
      rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_CLEAR))
  {
     /* fill in the rest of the blitrect when compositing mode permits it */
     uint8_t nocoverage[rasterizer->blit_width];
     //int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA;
     int gscan_start = rasterizer->state->gstate.clip_min_y * CTX_FULL_AA;
     int gscan_end = rasterizer->state->gstate.clip_max_y * CTX_FULL_AA;
     memset (nocoverage, 0, sizeof(nocoverage));
     int startx   = rasterizer->state->gstate.clip_min_x;
     int endx     = rasterizer->state->gstate.clip_max_x;
     int clipw    = endx-startx + 1;
     uint8_t *dst = ( (uint8_t *) rasterizer->buf);

     dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (gscan_start / CTX_FULL_AA);
     for (rasterizer->scanline = gscan_start; rasterizer->scanline < scan_start;)
     {
       ctx_rasterizer_apply_coverage (rasterizer,
                                      &dst[ (startx * rasterizer->format->bpp) /8],
                                      0,
                                      nocoverage, clipw);
       rasterizer->scanline += CTX_FULL_AA;
       dst += rasterizer->blit_stride;
     }
     if (minx < startx)
     {
     dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA);
     for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;)
     {
       ctx_rasterizer_apply_coverage (rasterizer,
                                      &dst[ (startx * rasterizer->format->bpp) /8],
                                      0,
                                      nocoverage, minx-startx);
       dst += blit_stride;
     }
     }

     if (endx > maxx)
     {
     dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_start / CTX_FULL_AA);
     for (rasterizer->scanline = scan_start; rasterizer->scanline < scan_end;)
     {
       ctx_rasterizer_apply_coverage (rasterizer,
                                      &dst[ (maxx * rasterizer->format->bpp) /8],
                                      0,
                                      nocoverage, endx-maxx);

       rasterizer->scanline += CTX_FULL_AA;
       dst += rasterizer->blit_stride;
     }
     }
#if 1
     dst = (uint8_t*)(rasterizer->buf) + rasterizer->blit_stride * (scan_end / CTX_FULL_AA);
     // XXX valgrind/asan this
     if(0)for (rasterizer->scanline = scan_end; rasterizer->scanline/CTX_FULL_AA < gscan_end-1;)
     {
       ctx_rasterizer_apply_coverage (rasterizer,
                                      &dst[ (startx * rasterizer->format->bpp) /8],
                                      0,
                                      nocoverage, clipw-1);

       rasterizer->scanline += CTX_FULL_AA;
       dst += blit_stride;
     }
#endif
  }
  ctx_rasterizer_reset (rasterizer);
}

inline static int
ctx_is_transparent (CtxRasterizer *rasterizer, int stroke)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  if (gstate->global_alpha_u8 == 0)
    return 1;
  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
  {
    uint8_t ga[2];
    ctx_color_get_graya_u8 (rasterizer->state, &gstate->source_fill.color, ga);
    if (ga[1] == 0)
      return 1;
  }
  return 0;
}

#define CTX_RECT_FILL 1

#if CTX_RECT_FILL
static void
ctx_rasterizer_fill_rect (CtxRasterizer *rasterizer,
                          int          x0,
                          int          y0,
                          int          x1,
                          int          y1,
                          uint8_t      cov)
{
  int blit_x = rasterizer->blit_x;
  int blit_y = rasterizer->blit_y;
  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;
  int blit_stride = rasterizer->blit_stride;
  x0 = ctx_maxi (x0, blit_x);
  x1 = ctx_mini (x1, blit_x + blit_width);

  int width = x1 - x0;

  if (CTX_UNLIKELY(width <=0))
    return;

  void (*comp_op)(CTX_COMPOSITE_ARGUMENTS) = rasterizer->comp_op;

  y0 = ctx_maxi (y0, blit_y);
  y1 = ctx_mini (y1, blit_y + blit_height);
  rasterizer->scanline = y0 * CTX_FULL_AA;
  _ctx_setup_compositor (rasterizer);
  uint8_t *dst = ( (uint8_t *) rasterizer->buf);

  dst += (y0 - blit_y) * blit_stride;
  dst += (x0) * rasterizer->format->bpp/8;

  if (cov == 255)
  {
    if (comp_op == ctx_RGBA8_source_copy_normal_color)
    {
      uint32_t color = *((uint32_t*)rasterizer->color);
      if ((width == 1))
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          *dst_i = color;
          dst += blit_stride;
        }
        return;
      }
      else
      {
        for (int y = y0; y < y1; y++)
        {
          ctx_span_set_color ((uint32_t*)&dst[0], color, width);
          dst += blit_stride;
        }
        return;
      }
    }
    else if (comp_op == ctx_RGBA8_source_over_normal_color)
    {
      uint32_t si_ga_full = ((uint32_t*)rasterizer->color)[3];
      uint32_t si_rb_full = ((uint32_t*)rasterizer->color)[4];
      uint32_t si_a  = rasterizer->color[3];

      if ((width == 1))
      {
        for (int y = y0; y < y1; y++)
        {
          ((uint32_t*)(dst))[0] = ctx_over_RGBA8_full_2 (
             ((uint32_t*)(dst))[0], si_ga_full, si_rb_full, si_a);
          dst += blit_stride;
        }
        return;
      }
      else
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          for (int i = 0; i < width; i++)
          {
            dst_i[i] = ctx_over_RGBA8_full_2 (dst_i[i], si_ga_full, si_rb_full, si_a);
          }
          dst += blit_stride;
        }
        return;
      }
    }
    else if (comp_op == ctx_RGBA8_source_copy_normal_fragment)
    {
      for (int y = y0; y < y1; y++)
      {
        float u0 = 0; float v0 = 0;
        float ud = 0; float vd = 0;
        ctx_init_uv (rasterizer, x0, width, &u0, &v0, &ud, &vd);
        rasterizer->fragment (rasterizer, u0, v0, &dst[0], width, ud, vd);
        rasterizer->scanline += CTX_FULL_AA;
        dst += blit_stride;
      }
      return;
    }
    else if (comp_op == ctx_RGBA8_source_over_normal_fragment)
    {
      for (int y = y0; y < y1; y++)
      {
        ctx_RGBA8_source_over_normal_full_cov_fragment (rasterizer,
                                &dst[0], NULL, x0, NULL, width);
        rasterizer->scanline += CTX_FULL_AA;
        dst += blit_stride;
      }
      return;
    }
  }
  else
  {
  if (comp_op == ctx_RGBA8_source_copy_normal_color)
    {
      uint32_t color = *((uint32_t*)rasterizer->color);
      if ((width == 1))
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          *dst_i = ctx_lerp_RGBA8 (*dst_i, color, cov);
          dst += blit_stride;
        }
        return;
      }
      else
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          for (int i = 0; i < width; i++)
          {
            dst_i[i] = ctx_lerp_RGBA8 (dst_i[i], color, cov);
          }
          dst += blit_stride;
        }
        return;
      }
    }
    else if (comp_op == ctx_RGBA8_source_over_normal_color)
    {
      uint32_t color = *((uint32_t*)rasterizer->color);
      if ((width == 1))
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          *dst_i = ctx_over_RGBA8 (*dst_i, color, cov);
          dst += blit_stride;
        }
        return;
      }
      else
      {
        for (int y = y0; y < y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          for (int i = 0; i < width; i++)
          {
            dst_i[i] = ctx_over_RGBA8 (dst_i[i], color, cov);
          }
          dst += blit_stride;
        }
        return;
      }
    }
  }

  {
    uint8_t coverage[width];
    memset (coverage, cov, sizeof (coverage) );
    for (int y = y0; y < y1; y++)
    {
      ctx_rasterizer_apply_coverage (rasterizer, &dst[0], x0, coverage, width);
      rasterizer->scanline += CTX_FULL_AA;
      dst += blit_stride;
    }
  }
}
#endif

static inline float ctx_fmod1f (float val)
{
  int vali = val;
  return val - vali;
}

static void
ctx_rasterizer_fill (CtxRasterizer *rasterizer)
{
  int count = rasterizer->preserve?rasterizer->edge_list.count:0;
  int blit_x = rasterizer->blit_x;
  int blit_y = rasterizer->blit_y;
  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;
  int blit_stride = rasterizer->blit_stride;

  CtxSegment temp[count]; /* copy of already built up path's poly line
                          XXX - by building a large enough path
                          the stack can be smashed!
                         */
  if (CTX_UNLIKELY(rasterizer->preserve))
    { memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) ); }

#if CTX_ENABLE_SHADOW_BLUR
  if (CTX_UNLIKELY(rasterizer->in_shadow))
  {
  for (int i = 0; i < rasterizer->edge_list.count; i++)
    {
      CtxSegment *entry = &((CtxSegment*)rasterizer->edge_list.entries)[i];
      entry->data.s16[2] += rasterizer->shadow_x * CTX_SUBDIV;
      entry->data.s16[3] += rasterizer->shadow_y * CTX_FULL_AA;
    }
    rasterizer->scan_min += rasterizer->shadow_y * CTX_FULL_AA;
    rasterizer->scan_max += rasterizer->shadow_y * CTX_FULL_AA;
    rasterizer->col_min  += (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV;
    rasterizer->col_max  += (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV;
  }
#endif

  if (CTX_UNLIKELY(ctx_is_transparent (rasterizer, 0) ||
      rasterizer->scan_min > CTX_FULL_AA * (blit_y + blit_height) ||
      rasterizer->scan_max < CTX_FULL_AA * blit_y ||
      rasterizer->col_min > CTX_SUBDIV * (blit_x + blit_width) ||
      rasterizer->col_max < CTX_SUBDIV * blit_x))
    {
      ctx_rasterizer_reset (rasterizer);
    }
  else
  {
    _ctx_setup_compositor (rasterizer);

#if 1
    rasterizer->state->min_x = ctx_mini (rasterizer->state->min_x, rasterizer->col_min / CTX_SUBDIV);
    rasterizer->state->max_x = ctx_maxi (rasterizer->state->min_x, rasterizer->col_max / CTX_SUBDIV);
    rasterizer->state->min_y = ctx_mini (rasterizer->state->min_y, rasterizer->scan_min / CTX_FULL_AA);
    rasterizer->state->max_y = ctx_maxi (rasterizer->state->max_y, rasterizer->scan_max / CTX_FULL_AA);
#else
    if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV < rasterizer->state->min_x))
       rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV;
    if (CTX_UNLIKELY ( rasterizer->col_min / CTX_SUBDIV > rasterizer->state->max_x))
       rasterizer->state->min_x = rasterizer->col_min / CTX_SUBDIV;

    if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA < rasterizer->state->min_y))
       rasterizer->state->min_y = rasterizer->scan_min / CTX_FULL_AA;
    if (CTX_UNLIKELY ( rasterizer->scan_min / CTX_FULL_AA > rasterizer->state->max_y))
       rasterizer->state->max_y = rasterizer->scan_max / CTX_FULL_AA;
#endif

#if CTX_RECT_FILL
  if (rasterizer->edge_list.count == 5)
    {
      CtxSegment *entry0 = &(((CtxSegment*)(rasterizer->edge_list.entries)))[0];
      CtxSegment *entry1 = &(((CtxSegment*)(rasterizer->edge_list.entries)))[1];
      CtxSegment *entry2 = &(((CtxSegment*)(rasterizer->edge_list.entries)))[2];
      CtxSegment *entry3 = &(((CtxSegment*)(rasterizer->edge_list.entries)))[3];

      if ((!rasterizer->state->gstate.clipped != 0) &
          (entry0->data.s16[2] == entry1->data.s16[2]) &
          (entry0->data.s16[3] == entry3->data.s16[3]) &
          (entry1->data.s16[3] == entry2->data.s16[3]) &
          (entry2->data.s16[2] == entry3->data.s16[2])
#if CTX_ENABLE_SHADOW_BLUR
           && !rasterizer->in_shadow
#endif
         )
       {
         if(((entry1->data.s16[2] % (CTX_SUBDIV))  == 0) &
            ((entry1->data.s16[3] % (CTX_FULL_AA)) == 0) &
            ((entry3->data.s16[2] % (CTX_SUBDIV))  == 0) &
            ((entry3->data.s16[3] % (CTX_FULL_AA)) == 0))
         {
           /* best-case axis aligned rectangle */
           int x0 = entry3->data.s16[2] / CTX_SUBDIV;
           int y0 = entry3->data.s16[3] / CTX_FULL_AA;
           int x1 = entry1->data.s16[2] / CTX_SUBDIV;
           int y1 = entry1->data.s16[3] / CTX_FULL_AA;

           ctx_rasterizer_fill_rect (rasterizer, x0, y0, x1, y1, 255);
           ctx_rasterizer_reset (rasterizer);
           goto done;
         }
        else
         {
           float x0 = entry3->data.s16[2] * (1.0f / CTX_SUBDIV);
           float y0 = entry3->data.s16[3] * (1.0f / CTX_FULL_AA);
           float x1 = entry1->data.s16[2] * (1.0f / CTX_SUBDIV);
           float y1 = entry1->data.s16[3] * (1.0f / CTX_FULL_AA);

           x0 = ctx_maxf (x0, blit_x);
           y0 = ctx_maxf (y0, blit_y);
           x1 = ctx_minf (x1, blit_x + blit_width);
           y1 = ctx_minf (y1, blit_y + blit_height);

           uint8_t left = 255-ctx_fmod1f (x0) * 255;
           uint8_t top  = 255-ctx_fmod1f (y0) * 255;
           uint8_t right  = ctx_fmod1f (x1) * 255;
           uint8_t bottom = ctx_fmod1f (y1) * 255;

           x0 = ctx_floorf (x0);
           y0 = ctx_floorf (y0);
           x1 = ctx_floorf (x1+7/8.0f);
           y1 = ctx_floorf (y1+14/15.0f);

           int has_top    = (top < 255);
           int has_bottom = (bottom <255);
           int has_right  = (right >0);
           int has_left   = (left >0);

           int width = x1 - x0;

           if (CTX_LIKELY(width >0))
           {
              uint8_t *dst = ( (uint8_t *) rasterizer->buf);
              uint8_t coverage[width+2];
              dst += (((int)y0) - blit_y) * blit_stride;
              dst += ((int)x0) * rasterizer->format->bpp/8;

              if (has_top)
              {
                int i = 0;
                if (has_left)
                {
                  coverage[i++] = top * left / 255;
                }
                for (int x = x0 + has_left; x < x1 - has_right; x++)
                  coverage[i++] = top;
                coverage[i++]= top * right / 255;

                  ctx_rasterizer_apply_coverage (rasterizer,dst,
                                                 x0,
                                                 coverage, width);
                 dst += blit_stride;
               }

#if 0
           if (!(
            (rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_COPY||
             rasterizer->state->gstate.compositing_mode == CTX_COMPOSITE_SOURCE_OVER) &&
             rasterizer->state->gstate.blend_mode == CTX_BLEND_NORMAL &&
             rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR
             ))
           {
             int i = 0;
             if (has_left)
             {
               coverage[i++] = left;
             }
             for (int x = x0 + has_left; x < x1 - has_right; x++)
               coverage[i++] = 255;
             coverage[i++] = right;

             for (int ty = y0+has_top; ty < y1-has_bottom; ty++)
             {
               ctx_rasterizer_apply_coverage (rasterizer, dst, x0, coverage, width);
               dst += blit_stride;
             }
           }
           else
#endif
           {
             if (has_left)
               ctx_rasterizer_fill_rect (rasterizer, x0, y0 + has_top, x0+1, y1 - has_bottom, left);
             if (has_right)
               ctx_rasterizer_fill_rect (rasterizer, x1-1, y0 + has_top, x1, y1 - has_bottom, right);
             x0 += has_left;
             y0 += has_top;
             y1 -= has_bottom;
             x1 -= has_right;
             ctx_rasterizer_fill_rect (rasterizer, x0,y0,x1,y1,255);

             dst += blit_stride * ((((int)y1)-has_bottom) - (((int)y0)+has_top));
           }

           if (has_bottom)
           {
             int i = 0;
             if (has_left)
               coverage[i++] = bottom * left / 255;
             for (int x = x0 + has_left; x < x1 - has_right; x++)
               coverage[i++] = bottom;
             coverage[i++]= bottom * right / 255;

             ctx_rasterizer_apply_coverage (rasterizer,dst, x0, coverage, width);
           }
           }

           ctx_rasterizer_reset (rasterizer);
           goto done;
         }

       }
    }
#endif
    ctx_rasterizer_finish_shape (rasterizer);

    uint32_t hash = ctx_rasterizer_poly_to_edges (rasterizer);
    if (hash){};

#if CTX_SHAPE_CACHE
    int width = (rasterizer->col_max + (CTX_SUBDIV-1) ) / CTX_SUBDIV - rasterizer->col_min/CTX_SUBDIV + 1;
    int height = (rasterizer->scan_max + (CTX_FULL_AA-1) ) / CTX_FULL_AA - rasterizer->scan_min / CTX_FULL_AA + 1;
    if (width * height < CTX_SHAPE_CACHE_DIM && width >=1 && height >= 1
        && width < CTX_SHAPE_CACHE_MAX_DIM
        && height < CTX_SHAPE_CACHE_MAX_DIM 
#if CTX_ENABLE_SHADOW_BLUR
        && !rasterizer->in_shadow
#endif
        )
      {
        int scan_min = rasterizer->scan_min;
        int col_min = rasterizer->col_min;
        scan_min -= (scan_min % CTX_FULL_AA);
        int y0 = scan_min / CTX_FULL_AA;
        int y1 = y0 + height;
        int x0 = col_min / CTX_SUBDIV;
        int ymin = y0;
        int x1 = x0 + width;
        int clip_x_min = blit_x;
        int clip_x_max = blit_x + blit_width - 1;
        int clip_y_min = blit_y;
        int clip_y_max = blit_y + blit_height - 1;

        int dont_cache = 0;
        if (CTX_UNLIKELY(x1 >= clip_x_max))
          { x1 = clip_x_max;
            dont_cache = 1;
          }
        int xo = 0;
        if (CTX_UNLIKELY(x0 < clip_x_min))
          {
            xo = clip_x_min - x0;
            x0 = clip_x_min;
            dont_cache = 1;
          }
        if (CTX_UNLIKELY(y0 < clip_y_min || y1 >= clip_y_max))
          dont_cache = 1;
        if (dont_cache || !_ctx_shape_cache_enabled)
        {
          ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule
#if CTX_SHAPE_CACHE
                                        , NULL
#endif
                                       );
        }
        else
        {
        rasterizer->scanline = scan_min;
        CtxShapeEntry *shape = ctx_shape_entry_find (rasterizer, hash, width, height); 

        if (shape->uses == 0)
          {
            CtxBuffer *buffer_backup = rasterizer->clip_buffer;
            rasterizer->clip_buffer = NULL;
            ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule, shape);
            rasterizer->clip_buffer = buffer_backup;
          }

        int ewidth = x1 - x0;
        if (ewidth>0)
        {
          rasterizer->scanline = scan_min;
          int bpp = rasterizer->format->bpp;
          if (rasterizer->clip_buffer && !rasterizer->clip_rectangle)
          {
          uint8_t composite[ewidth];
          uint8_t *clip_data = (uint8_t*)rasterizer->clip_buffer->data;
          int shape_width = shape->width;
          for (int y = y0; y < y1; y++)
            {
              if ( (y >= clip_y_min) && (y <= clip_y_max) )
                {
                    for (int x = 0; x < ewidth; x++)
                    {
                      int val = shape->data[shape_width * (int)(y-ymin) + xo + x];
                      // XXX : not valid for 1bit clip buffers
                      val = (val*(clip_data) [
                              ((y-blit_y) * blit_width) + x0 + x])/255;
                      composite[x] = val;
                    }
                    ctx_rasterizer_apply_coverage (rasterizer,
                                                 ( (uint8_t *) rasterizer->buf) + (y-blit_y) * blit_stride + (int) (x0) * bpp/8,
                                                 x0, // is 0
                                                 composite,
                                                 ewidth );
               rasterizer->scanline += CTX_FULL_AA;
            }
          }
          }
          else
          for (int y = y0; y < y1; y++)
            {
              if (CTX_LIKELY((y >= clip_y_min) && (y <= clip_y_max) ))
                {
                    ctx_rasterizer_apply_coverage (rasterizer,
                                                 ( (uint8_t *) rasterizer->buf) + (y-blit_y) * blit_stride + (int) (x0) * bpp/8,
                                                 x0,
                                                 &shape->data[shape->width * (int) (y-ymin) + xo],
                                                 ewidth );
                }
               rasterizer->scanline += CTX_FULL_AA;
            }
        }
        if (shape->uses != 0)
          {
            ctx_rasterizer_reset (rasterizer);
          }
        }
      }
    else
#endif
    {
            
    ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule
#if CTX_SHAPE_CACHE
                                    , NULL
#endif
                                   );
    }
  }
done:
  if (CTX_UNLIKELY(rasterizer->preserve))
    {
      memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) );
      rasterizer->edge_list.count = count;
    }
#if CTX_ENABLE_SHADOW_BLUR
  if (CTX_UNLIKELY(rasterizer->in_shadow))
  {
    rasterizer->scan_min -= rasterizer->shadow_y * CTX_FULL_AA;
    rasterizer->scan_max -= rasterizer->shadow_y * CTX_FULL_AA;
    rasterizer->col_min  -= (rasterizer->shadow_x - rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV;
    rasterizer->col_max  -= (rasterizer->shadow_x + rasterizer->state->gstate.shadow_blur * 3 + 1) * CTX_SUBDIV;
  }
#endif
  rasterizer->preserve = 0;
}

#if 0
static void
ctx_rasterizer_triangle (CtxRasterizer *rasterizer,
                         int x0, int y0,
                         int x1, int y1,
                         int x2, int y2,
                         int r0, int g0, int b0, int a0,
                         int r1, int g1, int b1, int a1,
                         int r2, int g2, int b2, int a2,
                         int u0, int v0,
                         int u1, int v1)
{

}
#endif


typedef struct _CtxTermGlyph CtxTermGlyph;

struct _CtxTermGlyph
{
  uint32_t unichar;
  int      col;
  int      row;
  uint8_t  rgba_bg[4];
  uint8_t  rgba_fg[4];
};

static int _ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke);
static void
ctx_rasterizer_glyph (CtxRasterizer *rasterizer, uint32_t unichar, int stroke)
{
  float tx = rasterizer->state->x;
  float ty = rasterizer->state->y - rasterizer->state->gstate.font_size;
  float tx2 = rasterizer->state->x + rasterizer->state->gstate.font_size;
  float ty2 = rasterizer->state->y + rasterizer->state->gstate.font_size;
  _ctx_user_to_device (rasterizer->state, &tx, &ty);
  _ctx_user_to_device (rasterizer->state, &tx2, &ty2);

  if (tx2 < rasterizer->blit_x || ty2 < rasterizer->blit_y) return;
  if (tx  > rasterizer->blit_x + rasterizer->blit_width ||
      ty  > rasterizer->blit_y + rasterizer->blit_height)
          return;

#if CTX_BRAILLE_TEXT
  float font_size = 0;
  int ch = 1;
  int cw = 1;

  if (rasterizer->term_glyphs)
  {
    float tx = 0;
    float ty = rasterizer->state->gstate.font_size;
    float txb = 0;
    float tyb = 0;

    ch = ctx_term_get_cell_height (rasterizer->ctx);
    cw = ctx_term_get_cell_width (rasterizer->ctx);

    _ctx_user_to_device (rasterizer->state, &tx, &ty);
    _ctx_user_to_device (rasterizer->state, &txb, &tyb);
    font_size = ty-tyb;
  }
  if (rasterizer->term_glyphs && !stroke &&
      fabs (font_size - ch) < 0.5)
  {
    float tx = rasterizer->x;
    float ty = rasterizer->y;
    _ctx_user_to_device (rasterizer->state, &tx, &ty);
    int col = tx / cw + 1;
    int row = ty / ch + 1;
    CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1);
    ctx_list_append (&rasterizer->glyphs, glyph);
    glyph->unichar = unichar;
    glyph->col = col;
    glyph->row = row;
    ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color,
                         &glyph->rgba_fg[0]);
  }
  else
#endif
  _ctx_glyph (rasterizer->ctx, unichar, stroke);
}


static void
_ctx_text (Ctx        *ctx,
           const char *string,
           int         stroke,
           int         visible);
static void
ctx_rasterizer_text (CtxRasterizer *rasterizer, const char *string, int stroke)
{
#if CTX_BRAILLE_TEXT
  float font_size = 0;
  if (rasterizer->term_glyphs)
  {
  float tx = 0;
  float ty = rasterizer->state->gstate.font_size;
  _ctx_user_to_device (rasterizer->state, &tx, &ty);
  font_size = ty;
  }
  int   ch = ctx_term_get_cell_height (rasterizer->ctx);
  int   cw = ctx_term_get_cell_width (rasterizer->ctx);

  if (rasterizer->term_glyphs && !stroke &&
      fabs (font_size - ch) < 0.5)
  {
    float tx = rasterizer->x;
    float ty = rasterizer->y;
    _ctx_user_to_device (rasterizer->state, &tx, &ty);
    int col = tx / cw + 1;
    int row = ty / ch + 1;
    for (int i = 0; string[i]; i++, col++)
    {
      CtxTermGlyph *glyph = ctx_calloc (sizeof (CtxTermGlyph), 1);
      ctx_list_prepend (&rasterizer->glyphs, glyph);
      glyph->unichar = string[i];
      glyph->col = col;
      glyph->row = row;
      ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color,
                      glyph->rgba_fg);
    }
  }
  else
#endif
  {
    _ctx_text (rasterizer->ctx, string, stroke, 1);
  }
}

void
_ctx_font (Ctx *ctx, const char *name);
static void
ctx_rasterizer_set_font (CtxRasterizer *rasterizer, const char *font_name)
{
  _ctx_font (rasterizer->ctx, font_name);
}

static void
ctx_rasterizer_arc (CtxRasterizer *rasterizer,
                    float          x,
                    float          y,
                    float          radius,
                    float          start_angle,
                    float          end_angle,
                    int            anticlockwise)
{
  int full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS;
  full_segments = radius * CTX_PI * 2 / 4.0;
  if (full_segments > CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS)
    { full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; }
  if (full_segments < 24) full_segments = 24;
  float step = CTX_PI*2.0/full_segments;
  int steps;

  if (end_angle < -30.0)
    end_angle = -30.0;
  if (start_angle < -30.0)
    start_angle = -30.0;
  if (end_angle > 30.0)
    end_angle = 30.0;
  if (start_angle > 30.0)
    start_angle = 30.0;

  if (radius <= 0.0001)
          return;

  if (end_angle == start_angle)
          // XXX also detect arcs fully outside render view
    {
    if (rasterizer->has_prev!=0)
      ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius,
                              y + ctx_sinf (end_angle) * radius);
      else
      ctx_rasterizer_move_to (rasterizer, x + ctx_cosf (end_angle) * radius,
                            y + ctx_sinf (end_angle) * radius);
      return;
    }
#if 1
  if ( (!anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f)  ||
       ( (anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f ) ) 
  ||   (anticlockwise && fabsf((end_angle - start_angle) - CTX_PI*2) < 0.01f)  ||  (!anticlockwise && fabsf((start_angle - end_angle) - CTX_PI*2) < 0.01f )  )
    {
      start_angle = start_angle;
      steps = full_segments - 1;
    }
  else
#endif
    {
      steps = (end_angle - start_angle) / (CTX_PI*2) * full_segments;
      if (anticlockwise)
        { steps = full_segments - steps; };
   // if (steps > full_segments)
   //   steps = full_segments;
    }
  if (anticlockwise) { step = step * -1; }
  int first = 1;
  if (steps == 0 /* || steps==full_segments -1  || (anticlockwise && steps == full_segments) */)
    {
      float xv = x + ctx_cosf (start_angle) * radius;
      float yv = y + ctx_sinf (start_angle) * radius;
      if (!rasterizer->has_prev)
        { ctx_rasterizer_move_to (rasterizer, xv, yv); }
      first = 0;
    }
  else
    {
      for (float angle = start_angle, i = 0; i < steps; angle += step, i++)
        {
          float xv = x + ctx_cosf (angle) * radius;
          float yv = y + ctx_sinf (angle) * radius;
          if (first && !rasterizer->has_prev)
            { ctx_rasterizer_move_to (rasterizer, xv, yv); }
          else
            { ctx_rasterizer_line_to (rasterizer, xv, yv); }
          first = 0;
        }
    }
  ctx_rasterizer_line_to (rasterizer, x + ctx_cosf (end_angle) * radius,
                          y + ctx_sinf (end_angle) * radius);
}

static void
ctx_rasterizer_quad_to (CtxRasterizer *rasterizer,
                        float        cx,
                        float        cy,
                        float        x,
                        float        y)
{
  /* XXX : it is probably cheaper/faster to do quad interpolation directly -
   *       though it will increase the code-size, an
   *       alternative is to turn everything into cubic
   *       and deal with cubics more directly during
   *       rasterization
   */
  ctx_rasterizer_curve_to (rasterizer,
                           (cx * 2 + rasterizer->x) / 3.0f, (cy * 2 + rasterizer->y) / 3.0f,
                           (cx * 2 + x) / 3.0f,           (cy * 2 + y) / 3.0f,
                           x,                              y);
}

static void
ctx_rasterizer_rel_quad_to (CtxRasterizer *rasterizer,
                            float cx, float cy,
                            float x,  float y)
{
  ctx_rasterizer_quad_to (rasterizer, cx + rasterizer->x, cy + rasterizer->y,
                          x  + rasterizer->x, y  + rasterizer->y);
}

#define LENGTH_OVERSAMPLE 1
static void
ctx_rasterizer_pset (CtxRasterizer *rasterizer, int x, int y, uint8_t cov)
{
  // XXX - we avoid rendering here x==0 - to keep with
  //  an off-by one elsewhere
  //
  //  XXX onlt works in rgba8 formats
  if (x <= 0 || y < 0 || x >= rasterizer->blit_width ||
      y >= rasterizer->blit_height)
    { return; }
  uint8_t fg_color[4];
  ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, fg_color);
  uint8_t pixel[4];
  uint8_t *dst = ( (uint8_t *) rasterizer->buf);
  dst += y * rasterizer->blit_stride;
  dst += x * rasterizer->format->bpp / 8;
  if (!rasterizer->format->to_comp ||
      !rasterizer->format->from_comp)
    { return; }
  if (cov == 255)
    {
      for (int c = 0; c < 4; c++)
        {
          pixel[c] = fg_color[c];
        }
    }
  else
    {
      rasterizer->format->to_comp (rasterizer, x, dst, &pixel[0], 1);
      for (int c = 0; c < 4; c++)
        {
          pixel[c] = ctx_lerp_u8 (pixel[c], fg_color[c], cov);
        }
    }
  rasterizer->format->from_comp (rasterizer, x, &pixel[0], dst, 1);
}

static void
ctx_rasterizer_stroke_1px (CtxRasterizer *rasterizer)
{
  int count = rasterizer->edge_list.count;
  CtxSegment *temp = (CtxSegment*)rasterizer->edge_list.entries;
  float prev_x = 0.0f;
  float prev_y = 0.0f;
  int aa = 15;//rasterizer->aa;
  int start = 0;
  int end = 0;
#if 0
  float factor = ctx_matrix_get_scale (&state->gstate.transform);
#endif

  while (start < count)
    {
      int started = 0;
      int i;
      for (i = start; i < count; i++)
        {
          CtxSegment *entry = &temp[i];
          float x, y;
          if (entry->code == CTX_NEW_EDGE)
            {
              if (started)
                {
                  end = i - 1;
                  goto foo;
                }
              prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
              prev_y = entry->data.s16[1] * 1.0f / aa;
              started = 1;
              start = i;
            }
          x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
          y = entry->data.s16[3] * 1.0f / aa;
          int dx = x - prev_x;
          int dy = y - prev_y;
          int length = ctx_maxf (abs (dx), abs (dy) );
          if (length)
            {
              length *= LENGTH_OVERSAMPLE;
              int len = length;
              int tx = prev_x * 256;
              int ty = prev_y * 256;
              dx *= 256;
              dy *= 256;
              dx /= length;
              dy /= length;
              for (int i = 0; i < len; i++)
                {
                  ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255);
                  tx += dx;
                  ty += dy;
                  ctx_rasterizer_pset (rasterizer, tx/256, ty/256, 255);
                }
            }
          prev_x = x;
          prev_y = y;
        }
      end = i-1;
foo:
      start = end+1;
    }
  ctx_rasterizer_reset (rasterizer);
}

static void
ctx_rasterizer_stroke (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxSource source_backup;
  if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL)
  {
    source_backup = gstate->source_fill;
    gstate->source_fill = rasterizer->state->gstate.source_stroke;
  }
  int count = rasterizer->edge_list.count;
  int preserved = rasterizer->preserve;
  float factor = ctx_matrix_get_scale (&gstate->transform);
  float line_width = gstate->line_width * factor;

  CtxSegment temp[count]; /* copy of already built up path's poly line  */
  memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) );

#if CTX_RECT_FILL
  if (rasterizer->edge_list.count == 5)
    {
      CtxSegment *entry0 = &((CtxSegment*)rasterizer->edge_list.entries)[0];
      CtxSegment *entry1 = &((CtxSegment*)rasterizer->edge_list.entries)[1];
      CtxSegment *entry2 = &((CtxSegment*)rasterizer->edge_list.entries)[2];
      CtxSegment *entry3 = &((CtxSegment*)rasterizer->edge_list.entries)[3];
      //fprintf (stderr, "{%i %.2f %.2f}", lw, lwmod, line_width);

      if (!rasterizer->state->gstate.clipped &&
          (entry0->data.s16[2] == entry1->data.s16[2]) &&
          (entry0->data.s16[3] == entry3->data.s16[3]) &&
          (entry1->data.s16[3] == entry2->data.s16[3]) &&
          (entry2->data.s16[2] == entry3->data.s16[2])
#if CTX_ENABLE_SHADOW_BLUR
           && !rasterizer->in_shadow
#endif
         )
       {
      float lwmod = ctx_fmod1f (line_width);
      int lw = ctx_floorf (line_width + 0.5f);
      int is_compat_even = (lw % 2 == 0) && (lwmod < 0.1); // only even linewidths implemented properly
      int is_compat_odd = (lw % 2 == 1) && (lwmod < 0.1); // only even linewidths implemented properly

      int off_x = 0;
      int off_y = 0;


      if (is_compat_odd)
      {
        off_x = CTX_SUBDIV/2;
        off_y = CTX_FULL_AA/2;
      }

      if((is_compat_odd || is_compat_even) &&
         (((entry1->data.s16[2]-off_x) % (CTX_SUBDIV))  == 0)  &&
         (((entry1->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0) &&
         (((entry3->data.s16[2]-off_x) % (CTX_SUBDIV))  == 0)  &&
         (((entry3->data.s16[3]-off_y) % (CTX_FULL_AA)) == 0))
      {
        float x0 = entry3->data.s16[2] * 1.0f / CTX_SUBDIV;
        float y0 = entry3->data.s16[3] * 1.0f / CTX_FULL_AA;
        float x1 = entry1->data.s16[2] * 1.0f / CTX_SUBDIV;
        float y1 = entry1->data.s16[3] * 1.0f / CTX_FULL_AA;

        int bw = lw/2+1;
        int bwb = lw/2;

        if (is_compat_even)
        {
          bw = lw/2;
        }
        ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0-bwb, x1+bw, y0+bw, 255);
        ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y1-bwb, x1-bwb, y1+bw, 255);
        ctx_rasterizer_fill_rect (rasterizer, x0-bwb, y0, x0+bw, y1, 255);
        ctx_rasterizer_fill_rect (rasterizer, x1-bwb, y0, x1+bw, y1+bw, 255);
        ctx_rasterizer_reset (rasterizer);
        goto done;
      }
       }
    }
#endif
  
    {

  int aa = CTX_FULL_AA;
#if 0
  if (CTX_UNLIKELY(gstate->line_width * factor <= 0.0f &&
      gstate->line_width * factor > -10.0f))
    {
      ctx_rasterizer_stroke_1px (rasterizer);
    }
  else
#endif
    {
      if (line_width < 5.0f)
      {
      factor *= 0.89; /* this hack adjustment makes sharp 1px and 2px strokewidths
      //                 end up sharp without erronious AA; we seem to be off by
      //                 one somewhere else, causing the need for this
      //                 */
      line_width *= 0.89f;
      }
      ctx_rasterizer_reset (rasterizer); /* then start afresh with our stroked shape  */
      CtxMatrix transform_backup = gstate->transform;
      _ctx_matrix_identity (&gstate->transform);
      float prev_x = 0.0f;
      float prev_y = 0.0f;
      float half_width_x = line_width/2;
      float half_width_y = line_width/2;
      if (CTX_UNLIKELY(line_width <= 0.0f))
        { // makes 0 width be hairline
          half_width_x = .5f;
          half_width_y = .5f;
        }
      int start = 0;
      int end   = 0;
      while (start < count)
        {
          int started = 0;
          int i;
          for (i = start; i < count; i++)
            {
              CtxSegment *entry = &temp[i];
              float x, y;
              if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE))
                {
                  if (CTX_LIKELY(started))
                    {
                      end = i - 1;
                      goto foo;
                    }
                  prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
                  prev_y = entry->data.s16[1] * 1.0f / aa;
                  started = 1;
                  start = i;
                }
              x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
              y = entry->data.s16[3] * 1.0f / aa;
              float dx = x - prev_x;
              float dy = y - prev_y;
              float length = ctx_fast_hypotf (dx, dy);
              if (CTX_LIKELY(length>0.001f))
                {
                  float recip_length = 1.0/length;
                  dx = dx * recip_length * half_width_x;
                  dy = dy * recip_length * half_width_y;
                  if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE))
                    {
                      ctx_rasterizer_finish_shape (rasterizer);
                      ctx_rasterizer_move_to (rasterizer, prev_x+dy, prev_y-dx);
                    }
                  ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx);
                  
                  // we need to know the slope of the other side

                  // XXX possible miter line-to
                  //ctx_rasterizer_line_to (rasterizer, prev_x-dy+4, prev_y+dx+10);
                  //ctx_rasterizer_line_to (rasterizer, prev_x-dy+8, prev_y+dx+0);


                  ctx_rasterizer_line_to (rasterizer, x-dy, y+dx);
                }
              prev_x = x;
              prev_y = y;
            }
          end = i-1;
foo:
          for (int i = end; i >= start; i--)
            {
              CtxSegment *entry = &temp[i];
              float x, y, dx, dy;
              x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
              y = entry->data.s16[3] * 1.0f / aa;
              dx = x - prev_x;
              dy = y - prev_y;
              float length = ctx_fast_hypotf (dx, dy);
              float recip_length = 1.0f/length;
              dx = dx * recip_length * half_width_x;
              dy = dy * recip_length * half_width_y;
              if (CTX_LIKELY(length>0.001f))
                {
                  ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx);
                  // XXX possible miter line-to
             //   ctx_rasterizer_line_to (rasterizer, prev_x-dy+10, prev_y+dx+10);
                  ctx_rasterizer_line_to (rasterizer, x-dy,      y+dx);
                }
              prev_x = x;
              prev_y = y;
              if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE))
                {
                  x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
                  y = entry->data.s16[1] * 1.0f / aa;
                  dx = x - prev_x;
                  dy = y - prev_y;
                  length = ctx_fast_hypotf (dx, dy);
                  recip_length = 1.0f/length;
                  if (CTX_LIKELY(length>0.001f))
                    {
                      dx = dx * recip_length * half_width_x;
                      dy = dy * recip_length * half_width_y;
                      ctx_rasterizer_line_to (rasterizer, prev_x-dy, prev_y+dx);
                      ctx_rasterizer_line_to (rasterizer, x-dy, y+dx);
                    }
                }
              if ( (prev_x != x) && (prev_y != y) )
                {
                  prev_x = x;
                  prev_y = y;
                }
            }
          start = end+1;
        }
      ctx_rasterizer_finish_shape (rasterizer);
      switch (gstate->line_cap)
        {
          case CTX_CAP_SQUARE: // XXX: incorrect - if rectangles were in
                               //                  reverse order - rotation would be off
                               //                  better implement correct here
            {
              float x = 0, y = 0;
              int has_prev = 0;
              for (int i = 0; i < count; i++)
                {
                  CtxSegment *entry = &temp[i];
                  if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE))
                    {
                      if (has_prev)
                        {
                          ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x, half_width_y);
                          ctx_rasterizer_finish_shape (rasterizer);
                        }
                      x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
                      y = entry->data.s16[1] * 1.0f / aa;
                      ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2);
                      ctx_rasterizer_finish_shape (rasterizer);
                    }
                  x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
                  y = entry->data.s16[3] * 1.0f / aa;
                  has_prev = 1;
                }
              ctx_rasterizer_rectangle (rasterizer, x - half_width_x, y - half_width_y, half_width_x * 2, half_width_y * 2);
              ctx_rasterizer_finish_shape (rasterizer);
            }
            break;
          case CTX_CAP_NONE: /* nothing to do */
            break;
          case CTX_CAP_ROUND:
            {
              float x = 0, y = 0;
              int has_prev = 0;
              for (int i = 0; i < count; i++)
                {
                  CtxSegment *entry = &temp[i];
                  if (CTX_UNLIKELY(entry->code == CTX_NEW_EDGE))
                    {
                      if (has_prev)
                        {
                          ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1);
                          ctx_rasterizer_finish_shape (rasterizer);
                        }
                      x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
                      y = entry->data.s16[1] * 1.0f / aa;
                      ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1);
                      ctx_rasterizer_finish_shape (rasterizer);
                    }
                  x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
                  y = entry->data.s16[3] * 1.0f / aa;
                  has_prev = 1;
                }
              ctx_rasterizer_move_to (rasterizer, x, y);
              ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*3, 0, 1);
              ctx_rasterizer_finish_shape (rasterizer);
              break;
            }
        }
      switch (gstate->line_join)
        {
          case CTX_JOIN_BEVEL:
          case CTX_JOIN_MITER:
            break;
          case CTX_JOIN_ROUND:
            {
              float x = 0, y = 0;
              for (int i = 0; i < count-1; i++)
                {
                  CtxSegment *entry = &temp[i];
                  x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
                  y = entry->data.s16[3] * 1.0f / aa;
                  if (CTX_UNLIKELY(entry[1].code == CTX_EDGE))
                    {
                      ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*2, 0, 1);
                      ctx_rasterizer_finish_shape (rasterizer);
                    }
                }
              break;
            }
        }
      CtxFillRule rule_backup = gstate->fill_rule;
      gstate->fill_rule = CTX_FILL_RULE_WINDING;
      rasterizer->preserve = 0; // so fill isn't tripped
      ctx_rasterizer_fill (rasterizer);
      gstate->fill_rule = rule_backup;
      gstate->transform = transform_backup;
    }
  }
done:
  if (preserved)
    {
      memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) );
      rasterizer->edge_list.count = count;
      rasterizer->preserve = 0;
    }
  if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL)
    gstate->source_fill = source_backup;
}

#if CTX_1BIT_CLIP
#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY1
#else
#define CTX_CLIP_FORMAT CTX_FORMAT_GRAY8
#endif


static void
ctx_rasterizer_clip_reset (CtxRasterizer *rasterizer)
{
#if CTX_ENABLE_CLIP
  if (rasterizer->clip_buffer)
   ctx_buffer_free (rasterizer->clip_buffer);
  rasterizer->clip_buffer = NULL;
#endif
  rasterizer->state->gstate.clip_min_x = rasterizer->blit_x;
  rasterizer->state->gstate.clip_min_y = rasterizer->blit_y;

  rasterizer->state->gstate.clip_max_x = rasterizer->blit_x + rasterizer->blit_width - 1;
  rasterizer->state->gstate.clip_max_y = rasterizer->blit_y + rasterizer->blit_height - 1;
}

static void
ctx_rasterizer_clip_apply (CtxRasterizer *rasterizer,
                           CtxSegment    *edges)
{
  int count = edges[0].data.u32[0];

  int minx = 5000;
  int miny = 5000;
  int maxx = -5000;
  int maxy = -5000;
  int prev_x = 0;
  int prev_y = 0;
  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;

  int aa = 15;//rasterizer->aa;
  float coords[6][2];

  for (int i = 0; i < count; i++)
    {
      CtxSegment *entry = &edges[i+1];
      float x, y;
      if (entry->code == CTX_NEW_EDGE)
        {
          prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
          prev_y = entry->data.s16[1] * 1.0f / aa;
          if (prev_x < minx) { minx = prev_x; }
          if (prev_y < miny) { miny = prev_y; }
          if (prev_x > maxx) { maxx = prev_x; }
          if (prev_y > maxy) { maxy = prev_y; }
        }
      x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
      y = entry->data.s16[3] * 1.0f / aa;
      if (x < minx) { minx = x; }
      if (y < miny) { miny = y; }
      if (x > maxx) { maxx = x; }
      if (y > maxy) { maxy = y; }

      if (i < 6)
      {
        coords[i][0] = x;
        coords[i][1] = y;
      }
    }

#if CTX_ENABLE_CLIP

  if ((rasterizer->clip_rectangle==1
       || !rasterizer->clip_buffer)
      )
  {
    if (count == 5)
    {
      if (coords[0][0] == coords[1][0] &&
          coords[0][1] == coords[4][1] &&
          coords[0][1] == coords[3][1] &&
          coords[1][1] == coords[2][1] &&
          coords[3][0] == coords[4][0]
          )
      {
#if 0
        printf ("%d,%d %dx%d\n", minx, miny,
                                       maxx-minx+1, maxy-miny+1);
#endif

         rasterizer->state->gstate.clip_min_x =
            ctx_maxi (minx, rasterizer->state->gstate.clip_min_x);
         rasterizer->state->gstate.clip_min_y =
            ctx_maxi (miny, rasterizer->state->gstate.clip_min_y);
         rasterizer->state->gstate.clip_max_x =
            ctx_mini (maxx, rasterizer->state->gstate.clip_max_x);
         rasterizer->state->gstate.clip_max_y =
            ctx_mini (maxy, rasterizer->state->gstate.clip_max_y);

         rasterizer->clip_rectangle = 1;

#if 0
         if (!rasterizer->clip_buffer)
           rasterizer->clip_buffer = ctx_buffer_new (blit_width,
                                                     blit_height,
                                                     CTX_CLIP_FORMAT);

         memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height);
         int i = 0;
         for (int y = rasterizer->state->gstate.clip_min_y;
                  y <= rasterizer->state->gstate.clip_max_y;
                  y++)
         for (int x = rasterizer->state->gstate.clip_min_x;
                  x <= rasterizer->state->gstate.clip_max_x;
                  x++, i++)
         {
           ((uint8_t*)(rasterizer->clip_buffer->data))[i] = 255;
         }
#endif

         return;
      }
#if 0
      else
      {
        printf ("%d,%d %dx%d  0,0:%.2f 0,1:%.2f 1,0:%.2f 11:%.2f 20:%.2f 21:%2.f 30:%.2f 31:%.2f 40:%.2f 41:%.2f\n", minx, miny,
                                       maxx-minx+1, maxy-miny+1
                                       
         ,coords[0][0] ,  coords[0][1]
         ,coords[1][0] ,  coords[1][1]
         ,coords[2][0] ,  coords[2][1]
         ,coords[3][0] ,  coords[3][1]
         ,coords[4][0] ,  coords[4][1]
         );
      }
#endif
    }
  }
  rasterizer->clip_rectangle = 0;

  if ((minx == maxx) || (miny == maxy)) // XXX : reset hack
  {
    ctx_rasterizer_clip_reset (rasterizer);
    return;//goto done;
  }

  int we_made_it = 0;
  CtxBuffer *clip_buffer;

  if (!rasterizer->clip_buffer)
  {
    rasterizer->clip_buffer = ctx_buffer_new (blit_width,
                                              blit_height,
                                              CTX_CLIP_FORMAT);
    clip_buffer = rasterizer->clip_buffer;
    we_made_it = 1;
    if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1)
      memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height/8);
    else
      memset (rasterizer->clip_buffer->data, 0, blit_width * blit_height);
  }
  else
  {
    clip_buffer = ctx_buffer_new (blit_width, blit_height,
                                  CTX_CLIP_FORMAT);
  }

  {

  int prev_x = 0;
  int prev_y = 0;

    Ctx *ctx = ctx_new_for_framebuffer (clip_buffer->data, blit_width, blit_height,
       blit_width,
       CTX_CLIP_FORMAT);

  for (int i = 0; i < count; i++)
    {
      CtxSegment *entry = &edges[i+1];
      float x, y;
      if (entry->code == CTX_NEW_EDGE)
        {
          prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
          prev_y = entry->data.s16[1] * 1.0f / aa;
          ctx_move_to (ctx, prev_x, prev_y);
        }
      x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
      y = entry->data.s16[3] * 1.0f / aa;
      ctx_line_to (ctx, x, y);
    }
    ctx_gray (ctx, 1.0f);
    ctx_fill (ctx);
    ctx_free (ctx);
  }

  int maybe_rect = 1;
  rasterizer->clip_rectangle = 0;

  if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1)
  {
    int count = blit_width * blit_height / 8;
    for (int i = 0; i < count; i++)
    {
      ((uint8_t*)rasterizer->clip_buffer->data)[i] =
      (((uint8_t*)rasterizer->clip_buffer->data)[i] &
      ((uint8_t*)clip_buffer->data)[i]);
    }
  }
  else
  {
    int count = blit_width * blit_height;


    int i;
    int x0 = 0;
    int y0 = 0;
    int width = -1;
    int next_stage = 0;
    uint8_t *p_data = (uint8_t*)rasterizer->clip_buffer->data;
    uint8_t *data = (uint8_t*)clip_buffer->data;

    i=0;
    /* find upper left */
    for (; i < count && maybe_rect && !next_stage; i++)
    {
      uint8_t val = (p_data[i] * data[i])/255;
      data[i] = val;
      switch (val)
      {
        case 255:
          x0 = i % blit_width;
          y0 = i / blit_width;
          next_stage = 1;
          break;
        case 0: break;
        default:
          maybe_rect = 0;
          break;
      }
    }

    next_stage = 0;
    /* figure out with */
    for (; i < count && !next_stage && maybe_rect; i++)
    {
      int x = i % blit_width;
      int y = i / blit_width;
      uint8_t val = (p_data[i] * data[i])/255;
      data[i] = val;

      if (y == y0)
      {
        switch (val)
        {
          case 255:
            width = x - x0 + 1;
            break;
          case 0:
            next_stage = 1;
            break;
          default:
            maybe_rect = 0;
            break;
        }
        if (x % blit_width == blit_width - 1) next_stage = 1;
      }
      else next_stage = 1;
    }

    next_stage = 0;
    /* body */
    for (; i < count && maybe_rect && !next_stage; i++)
    {
      int x = i % blit_width;
      uint8_t val = (p_data[i] * data[i])/255;
      data[i] = val;

      if (x < x0)
      {
        if (val != 0){ maybe_rect = 0; next_stage = 1; }
      } else if (x < x0 + width)
      {
        if (val != 255){ if (val != 0) maybe_rect = 0; next_stage = 1; }
      } else {
        if (val != 0){ maybe_rect = 0; next_stage = 1; }
      }
    }

    next_stage = 0;
    /* foot */
    for (; i < count && maybe_rect && !next_stage; i++)
    {
      uint8_t val = (p_data[i] * data[i])/255;
      data[i] = val;

      if (val != 0){ maybe_rect = 0; next_stage = 1; }
    }


    for (; i < count; i++)
    {
      uint8_t val = (p_data[i] * data[i])/255;
      data[i] = val;
    }

    if (maybe_rect)
       rasterizer->clip_rectangle = 1;
  }
  if (!we_made_it)
   ctx_buffer_free (clip_buffer);
#else
  if (coords[0][0]){};
#endif
  
  rasterizer->state->gstate.clip_min_x = ctx_maxi (minx,
                                         rasterizer->state->gstate.clip_min_x);
  rasterizer->state->gstate.clip_min_y = ctx_maxi (miny,
                                         rasterizer->state->gstate.clip_min_y);
  rasterizer->state->gstate.clip_max_x = ctx_mini (maxx,
                                         rasterizer->state->gstate.clip_max_x);
  rasterizer->state->gstate.clip_max_y = ctx_mini (maxy,
                                         rasterizer->state->gstate.clip_max_y);
}

static void
ctx_rasterizer_clip (CtxRasterizer *rasterizer)
{
  int count = rasterizer->edge_list.count;
  CtxSegment temp[count+1]; /* copy of already built up path's poly line  */
  rasterizer->state->has_clipped=1;
  rasterizer->state->gstate.clipped=1;
  //if (rasterizer->preserve)
    { memcpy (temp + 1, rasterizer->edge_list.entries, sizeof (temp) - sizeof (temp[0]));
      temp[0].code = CTX_NOP;
      temp[0].data.u32[0] = count;
      ctx_state_set_blob (rasterizer->state, CTX_clip, (uint8_t*)temp, sizeof(temp));
    }
  ctx_rasterizer_clip_apply (rasterizer, temp);
  ctx_rasterizer_reset (rasterizer);
  if (rasterizer->preserve)
    {
      memcpy (rasterizer->edge_list.entries, temp + 1, sizeof (temp) - sizeof(temp[0]));
      rasterizer->edge_list.count = count;
      rasterizer->preserve = 0;
    }
}


#if 0
static void
ctx_rasterizer_load_image (CtxRasterizer *rasterizer,
                           const char  *path,
                           float x,
                           float y)
{
  // decode PNG, put it in image is slot 1,
  // magic width height stride format data
  ctx_buffer_load_png (&rasterizer->ctx->texture[0], path);
  ctx_rasterizer_set_texture (rasterizer, 0, x, y);
}
#endif


CTX_INLINE void
ctx_rasterizer_rectangle (CtxRasterizer *rasterizer,
                          float x,
                          float y,
                          float width,
                          float height)
{
  ctx_rasterizer_move_to (rasterizer, x, y);
  ctx_rasterizer_rel_line_to (rasterizer, width, 0);
  ctx_rasterizer_rel_line_to (rasterizer, 0, height);
  ctx_rasterizer_rel_line_to (rasterizer, -width, 0);
  ctx_rasterizer_rel_line_to (rasterizer, 0, -height);
  //ctx_rasterizer_rel_line_to (rasterizer, width/2, 0);
  ctx_rasterizer_finish_shape (rasterizer);
}

static void
ctx_rasterizer_set_pixel (CtxRasterizer *rasterizer,
                          uint16_t x,
                          uint16_t y,
                          uint8_t r,
                          uint8_t g,
                          uint8_t b,
                          uint8_t a)
{
  rasterizer->state->gstate.source_fill.type = CTX_SOURCE_COLOR;
  ctx_color_set_RGBA8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, r, g, b, a);
  rasterizer->comp_op = NULL;
#if 0
  // XXX : doesn't take transforms into account - and has
  // received less testing than code paths part of protocol,
  // using rectangle properly will trigger the fillrect fastpath
  ctx_rasterizer_pset (rasterizer, x, y, 255);
#else
  ctx_rasterizer_rectangle (rasterizer, x, y, 1.0, 1.0);
  ctx_rasterizer_fill (rasterizer);
#endif
}

#if CTX_ENABLE_SHADOW_BLUR
static float
ctx_gaussian (float x, float mu, float sigma)
{
  float a = ( x- mu) / sigma;
  return ctx_expf (-0.5 * a * a);
}

static void
ctx_compute_gaussian_kernel (int dim, float radius, float *kernel)
{
  float sigma = radius / 2;
  float sum = 0.0;
  int i = 0;
  //for (int row = 0; row < dim; row ++)
    for (int col = 0; col < dim; col ++, i++)
    {
      float val = //ctx_gaussian (row, radius, sigma) *
                            ctx_gaussian (col, radius, sigma);
      kernel[i] = val;
      sum += val;
    }
  i = 0;
  //for (int row = 0; row < dim; row ++)
    for (int col = 0; col < dim; col ++, i++)
        kernel[i] /= sum;
}
#endif

static void
ctx_rasterizer_round_rectangle (CtxRasterizer *rasterizer, float x, float y, float width, float height, float corner_radius)
{
  float aspect  = 1.0f;
  float radius  = corner_radius / aspect;
  float degrees = CTX_PI / 180.0f;

  if (radius > width*0.5f) radius = width/2;
  if (radius > height*0.5f) radius = height/2;

  ctx_rasterizer_finish_shape (rasterizer);
  ctx_rasterizer_arc (rasterizer, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees, 0);
  ctx_rasterizer_arc (rasterizer, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees, 0);
  ctx_rasterizer_arc (rasterizer, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees, 0);
  ctx_rasterizer_arc (rasterizer, x + radius, y + radius, radius, 180 * degrees, 270 * degrees, 0);

  ctx_rasterizer_finish_shape (rasterizer);
}

static void
ctx_rasterizer_process (void *user_data, CtxCommand *command);

int
_ctx_is_rasterizer (Ctx *ctx)
{
  if (ctx->renderer && ctx->renderer->process == ctx_rasterizer_process)
    return 1;
  return 0;
}

#if CTX_COMPOSITING_GROUPS
static void
ctx_rasterizer_start_group (CtxRasterizer *rasterizer)
{
  CtxEntry save_command = ctx_void(CTX_SAVE);
  // allocate buffer, and set it as temporary target
  int no;
  if (rasterizer->group[0] == NULL) // first group
  {
    rasterizer->saved_buf = rasterizer->buf;
  }
  for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++);

  if (no >= CTX_GROUP_MAX)
     return;
  rasterizer->group[no] = ctx_buffer_new (rasterizer->blit_width,
                                          rasterizer->blit_height,
                                          rasterizer->format->composite_format);
  rasterizer->buf = rasterizer->group[no]->data;
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command);
}

static void
ctx_rasterizer_end_group (CtxRasterizer *rasterizer)
{
  CtxEntry restore_command = ctx_void(CTX_RESTORE);
  CtxEntry save_command = ctx_void(CTX_SAVE);
  int no = 0;
  for (no = 0; rasterizer->group[no] && no < CTX_GROUP_MAX; no++);
  no--;

  if (no < 0)
    return;

  CtxCompositingMode comp = rasterizer->state->gstate.compositing_mode;
  CtxBlend blend = rasterizer->state->gstate.blend_mode;
  float global_alpha = rasterizer->state->gstate.global_alpha_f;
  // fetch compositing, blending, global alpha
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command);
  CtxEntry set_state[3]=
  {
    ctx_u32 (CTX_COMPOSITING_MODE, comp,  0),
    ctx_u32 (CTX_BLEND_MODE,       blend, 0),
    ctx_f  (CTX_GLOBAL_ALPHA,     global_alpha, 0.0)
  };
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[0]);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[1]);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_state[2]);
  if (no == 0)
  {
    rasterizer->buf = rasterizer->saved_buf;
  }
  else
  {
    rasterizer->buf = rasterizer->group[no-1]->data;
  }
  // XXX use texture_source ?
   ctx_texture_init (rasterizer->ctx, ".ctx-group", // XXX ? count groups..
                  rasterizer->blit_width,  // or have group based on thread-id?
                  rasterizer->blit_height, // .. this would mean threadsafe
                                           // allocation
                  rasterizer->blit_width * rasterizer->format->bpp/8,
                  rasterizer->format->pixel_format,
                  NULL, // space
                  (uint8_t*)rasterizer->group[no]->data,
                  NULL, NULL);
  {
     const char *eid = ".ctx-group";
     int   eid_len = strlen (eid);

     CtxEntry commands[4] =
      {
       ctx_f  (CTX_TEXTURE, rasterizer->blit_x, rasterizer->blit_y), 
       ctx_u32 (CTX_DATA, eid_len, eid_len/9+1),
       ctx_u32 (CTX_CONT, 0,0),
       ctx_u32 (CTX_CONT, 0,0)
      };
     memcpy( (char *) &commands[2].data.u8[0], eid, eid_len);
     ( (char *) (&commands[2].data.u8[0]) ) [eid_len]=0;

     ctx_rasterizer_process (rasterizer, (CtxCommand*)commands);
  }
  {
    CtxEntry commands[2]=
    {
      ctx_f (CTX_RECTANGLE, rasterizer->blit_x, rasterizer->blit_y),
      ctx_f (CTX_CONT,      rasterizer->blit_width, rasterizer->blit_height)
    };
    ctx_rasterizer_process (rasterizer, (CtxCommand*)commands);
  }
  {
    CtxEntry commands[1]= { ctx_void (CTX_FILL) };
    ctx_rasterizer_process (rasterizer, (CtxCommand*)commands);
  }
  //ctx_texture_release (rasterizer->ctx, ".ctx-group");
  ctx_buffer_free (rasterizer->group[no]);
  rasterizer->group[no] = 0;
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command);
}
#endif

#if CTX_ENABLE_SHADOW_BLUR
static void
ctx_rasterizer_shadow_stroke (CtxRasterizer *rasterizer)
{
  CtxColor color;
  CtxEntry save_command = ctx_void(CTX_SAVE);

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0)
    ctx_color_get_rgba (rasterizer->state, &color, rgba);

  CtxEntry set_color_command [3]=
  {
    ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]),
    ctx_f (CTX_CONT, rgba[1], rgba[2]),
    ctx_f (CTX_CONT, rgba[3], 0)
  };
  CtxEntry restore_command = ctx_void(CTX_RESTORE);
  float radius = rasterizer->state->gstate.shadow_blur;
  int dim = 2 * radius + 1;
  if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM)
    dim = CTX_MAX_GAUSSIAN_KERNEL_DIM;
  ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command);
  {
    int i = 0;
    for (int v = 0; v < dim; v += 1, i++)
      {
        float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2;
        set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3];
        ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command[0]);
#if CTX_ENABLE_SHADOW_BLUR
        rasterizer->in_shadow = 1;
#endif
        rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x;
        rasterizer->shadow_y = dy;
        rasterizer->preserve = 1;
        ctx_rasterizer_stroke (rasterizer);
#if CTX_ENABLE_SHADOW_BLUR
        rasterizer->in_shadow = 0;
#endif
      }
  }
  //free (kernel);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command);
}

static void
ctx_rasterizer_shadow_text (CtxRasterizer *rasterizer, const char *str)
{
  float x = rasterizer->state->x;
  float y = rasterizer->state->y;
  CtxColor color;
  CtxEntry save_command = ctx_void(CTX_SAVE);

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0)
    ctx_color_get_rgba (rasterizer->state, &color, rgba);

  CtxEntry set_color_command [3]=
  {
    ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]),
    ctx_f (CTX_CONT, rgba[1], rgba[2]),
    ctx_f (CTX_CONT, rgba[3], 0)
  };
  CtxEntry move_to_command [1]=
  {
    ctx_f (CTX_MOVE_TO, x, y),
  };
  CtxEntry restore_command = ctx_void(CTX_RESTORE);
  float radius = rasterizer->state->gstate.shadow_blur;
  int dim = 2 * radius + 1;
  if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM)
    dim = CTX_MAX_GAUSSIAN_KERNEL_DIM;
  ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command);

  {
      {
        move_to_command[0].data.f[0] = x;
        move_to_command[0].data.f[1] = y;
        set_color_command[2].data.f[0] = rgba[3];
        ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command);
        ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command);
        rasterizer->in_shadow=1;
        ctx_rasterizer_text (rasterizer, str, 0);
        rasterizer->in_shadow=0;
      }
  }
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command);
  move_to_command[0].data.f[0] = x;
  move_to_command[0].data.f[1] = y;
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&move_to_command);
}

static void
ctx_rasterizer_shadow_fill (CtxRasterizer *rasterizer)
{
  CtxColor color;
  CtxEntry save_command = ctx_void(CTX_SAVE);

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->ctx, CTX_shadowColor, &color) == 0)
    ctx_color_get_rgba (rasterizer->state, &color, rgba);

  CtxEntry set_color_command [3]=
  {
    ctx_f (CTX_COLOR, CTX_RGBA, rgba[0]),
    ctx_f (CTX_CONT, rgba[1], rgba[2]),
    ctx_f (CTX_CONT, rgba[3], 0)
  };
  CtxEntry restore_command = ctx_void(CTX_RESTORE);
  float radius = rasterizer->state->gstate.shadow_blur;
  int dim = 2 * radius + 1;
  if (dim > CTX_MAX_GAUSSIAN_KERNEL_DIM)
    dim = CTX_MAX_GAUSSIAN_KERNEL_DIM;
  ctx_compute_gaussian_kernel (dim, radius, rasterizer->kernel);
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&save_command);

  {
    for (int v = 0; v < dim; v ++)
      {
        int i = v;
        float dy = rasterizer->state->gstate.shadow_offset_y + v - dim/2;
        set_color_command[2].data.f[0] = rasterizer->kernel[i] * rgba[3];
        ctx_rasterizer_process (rasterizer, (CtxCommand*)&set_color_command);
        rasterizer->in_shadow = 1;
        rasterizer->shadow_x = rasterizer->state->gstate.shadow_offset_x;
        rasterizer->shadow_y = dy;
        rasterizer->preserve = 1;
        ctx_rasterizer_fill (rasterizer);
        rasterizer->in_shadow = 0;
      }
  }
  ctx_rasterizer_process (rasterizer, (CtxCommand*)&restore_command);
}
#endif

static void
ctx_rasterizer_line_dash (CtxRasterizer *rasterizer, int count, float *dashes)
{
  if (!dashes)
  {
    rasterizer->state->gstate.n_dashes = 0;
    return;
  }
  count = CTX_MIN(count, CTX_PARSER_MAX_ARGS-1);
  rasterizer->state->gstate.n_dashes = count;
  memcpy(&rasterizer->state->gstate.dashes[0], dashes, count * sizeof(float));
  for (int i = 0; i < count; i ++)
  {
    if (rasterizer->state->gstate.dashes[i] < 0.0001f)
      rasterizer->state->gstate.dashes[i] = 0.0001f; // hang protection
  }
}


static void
ctx_rasterizer_process (void *user_data, CtxCommand *command)
{
  CtxEntry *entry = &command->entry;
  CtxRasterizer *rasterizer = (CtxRasterizer *) user_data;
  CtxState *state = rasterizer->state;
  CtxCommand *c = (CtxCommand *) entry;
  int clear_clip = 0;
  ctx_interpret_style (state, entry, NULL);
  switch (c->code)
    {
#if CTX_ENABLE_SHADOW_BLUR
      case CTX_SHADOW_COLOR:
        {
          CtxColor  col;
          CtxColor *color = &col;
          //state->gstate.source_fill.type = CTX_SOURCE_COLOR;
          switch ((int)c->rgba.model)
            {
              case CTX_RGB:
                ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f);
                break;
              case CTX_RGBA:
                //ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
                ctx_color_set_rgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
                break;
              case CTX_DRGBA:
                ctx_color_set_drgba (state, color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
                break;
#if CTX_ENABLE_CMYK
              case CTX_CMYKA:
                ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a);
                break;
              case CTX_CMYK:
                ctx_color_set_cmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f);
                break;
              case CTX_DCMYKA:
                ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a);
                break;
              case CTX_DCMYK:
                ctx_color_set_dcmyka (state, color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f);
                break;
#endif
              case CTX_GRAYA:
                ctx_color_set_graya (state, color, c->graya.g, c->graya.a);
                break;
              case CTX_GRAY:
                ctx_color_set_graya (state, color, c->graya.g, 1.0f);
                break;
            }
          ctx_set_color (rasterizer->ctx, CTX_shadowColor, color);
        }
        break;
#endif
      case CTX_LINE_DASH:
        if (c->line_dash.count)
          {
            ctx_rasterizer_line_dash (rasterizer, c->line_dash.count, c->line_dash.data);
          }
        else
        ctx_rasterizer_line_dash (rasterizer, 0, NULL);
        break;

      case CTX_LINE_TO:
        ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_LINE_TO:
        ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_MOVE_TO:
        ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_MOVE_TO:
        ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_CURVE_TO:
        ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0,
                                 c->c.x1, c->c.y1,
                                 c->c.x2, c->c.y2);
        break;
      case CTX_REL_CURVE_TO:
        ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0,
                                     c->c.x1, c->c.y1,
                                     c->c.x2, c->c.y2);
        break;
      case CTX_QUAD_TO:
        ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_REL_QUAD_TO:
        ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_ARC:
        ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction);
        break;
      case CTX_RECTANGLE:
        ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y,
                                  c->rectangle.width, c->rectangle.height);
        break;
      case CTX_ROUND_RECTANGLE:
        ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y,
                                        c->rectangle.width, c->rectangle.height,
                                        c->rectangle.radius);
        break;
      case CTX_SET_PIXEL:
        ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y,
                                  c->set_pixel.rgba[0],
                                  c->set_pixel.rgba[1],
                                  c->set_pixel.rgba[2],
                                  c->set_pixel.rgba[3]);
        break;
      case CTX_DEFINE_TEXTURE:
        {
          uint8_t *pixel_data = ctx_define_texture_pixel_data (entry);
          ctx_rasterizer_define_texture (rasterizer, c->define_texture.eid,
                                         c->define_texture.width, c->define_texture.height,
                                         c->define_texture.format,
                                         pixel_data);
          rasterizer->comp_op = NULL;
          rasterizer->fragment = NULL;
        }
        break;
      case CTX_TEXTURE:
        ctx_rasterizer_set_texture (rasterizer, c->texture.eid,
                                    c->texture.x, c->texture.y);
        rasterizer->comp_op = NULL;
        rasterizer->fragment = NULL;
        break;
      case CTX_SOURCE_TRANSFORM:
        ctx_matrix_set (&state->gstate.source_fill.set_transform,
                        ctx_arg_float (0), ctx_arg_float (1),
                        ctx_arg_float (2), ctx_arg_float (3),
                        ctx_arg_float (4), ctx_arg_float (5));
        rasterizer->comp_op = NULL;
        break;
#if 0
      case CTX_LOAD_IMAGE:
        ctx_rasterizer_load_image (rasterizer, ctx_arg_string(),
                                   ctx_arg_float (0), ctx_arg_float (1) );
        break;
#endif
#if CTX_GRADIENTS
      case CTX_GRADIENT_STOP:
        {
          float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+1) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+2) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+3) )
                         };
          ctx_rasterizer_gradient_add_stop (rasterizer,
                                            ctx_arg_float (0), rgba);
          rasterizer->comp_op = NULL;
        }
        break;
      case CTX_LINEAR_GRADIENT:
        ctx_state_gradient_clear_stops (state);
        rasterizer->comp_op = NULL;
        break;
      case CTX_RADIAL_GRADIENT:
        ctx_state_gradient_clear_stops (state);
        rasterizer->comp_op = NULL;
        break;
#endif
      case CTX_PRESERVE:
        rasterizer->preserve = 1;
        break;
      case CTX_COLOR:
      case CTX_COMPOSITING_MODE:
      case CTX_BLEND_MODE:
        rasterizer->comp_op = NULL;
        //_ctx_setup_compositor (rasterizer);
        break;
#if CTX_COMPOSITING_GROUPS
      case CTX_START_GROUP:
        ctx_rasterizer_start_group (rasterizer);
        break;
      case CTX_END_GROUP:
        ctx_rasterizer_end_group (rasterizer);
        break;
#endif

      case CTX_RESTORE:
        for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0;
             i < state->gstate.keydb_pos; i++)
        {
          if (state->keydb[i].key == CTX_clip)
          {
            clear_clip = 1;
          }
        }
        /* FALLTHROUGH */
      case CTX_ROTATE:
      case CTX_SCALE:
      case CTX_TRANSLATE:
      case CTX_IDENTITY:
      case CTX_SAVE:
        rasterizer->comp_op = NULL;
        rasterizer->uses_transforms = 1;
        ctx_interpret_transforms (state, entry, NULL);
        if (clear_clip)
        {
          ctx_rasterizer_clip_reset (rasterizer);
        for (int i = state->gstate_no?state->gstate_stack[state->gstate_no-1].keydb_pos:0;
             i < state->gstate.keydb_pos; i++)
        {
          if (state->keydb[i].key == CTX_clip)
          {
            int idx = ctx_float_to_string_index (state->keydb[i].value);
            if (idx >=0)
            {
              CtxSegment *edges = (CtxSegment*)&state->stringpool[idx];
              ctx_rasterizer_clip_apply (rasterizer, edges);
            }
          }
        }
        }
        break;
      case CTX_STROKE:
#if CTX_ENABLE_SHADOW_BLUR
        if (state->gstate.shadow_blur > 0.0 &&
            !rasterizer->in_text)
          ctx_rasterizer_shadow_stroke (rasterizer);
#endif
        {
        int count = rasterizer->edge_list.count;
        if (state->gstate.n_dashes)
        {
          int n_dashes = state->gstate.n_dashes;
          float *dashes = state->gstate.dashes;
          float factor = ctx_matrix_get_scale (&state->gstate.transform);

          int aa = 15;//rasterizer->aa;
          CtxSegment temp[count]; /* copy of already built up path's poly line  */
          memcpy (temp, rasterizer->edge_list.entries, sizeof (temp));
          int start = 0;
          int end   = 0;
      CtxMatrix transform_backup = state->gstate.transform;
      _ctx_matrix_identity (&state->gstate.transform);
      ctx_rasterizer_reset (rasterizer); /* for dashing we create
                                            a dashed path to stroke */
      float prev_x = 0.0f;
      float prev_y = 0.0f;
      float pos = 0.0;

      int   dash_no  = 0.0;
      float dash_lpos = state->gstate.line_dash_offset * factor;
      int   is_down = 0;

          while (start < count)
          {
            int started = 0;
            int i;
            is_down = 0;

            if (!is_down)
            {
              CtxSegment *entry = &temp[0];
              prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
              prev_y = entry->data.s16[1] * 1.0f / aa;
              ctx_rasterizer_move_to (rasterizer, prev_x, prev_y);
              is_down = 1;
            }


            for (i = start; i < count; i++)
            {
              CtxSegment *entry = &temp[i];
              float x, y;
              if (entry->code == CTX_NEW_EDGE)
                {
                  if (started)
                    {
                      end = i - 1;
                      dash_no = 0;
                      dash_lpos = 0.0;
                      goto foo;
                    }
                  prev_x = entry->data.s16[0] * 1.0f / CTX_SUBDIV;
                  prev_y = entry->data.s16[1] * 1.0f / aa;
                  started = 1;
                  start = i;
                  is_down = 1;
                  ctx_rasterizer_move_to (rasterizer, prev_x, prev_y);
                }

again:

              x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
              y = entry->data.s16[3] * 1.0f / aa;
              float dx = x - prev_x;
              float dy = y - prev_y;
              float length = ctx_fast_hypotf (dx, dy);

              if (dash_lpos + length >= dashes[dash_no] * factor)
              {
                float p = (dashes[dash_no] * factor - dash_lpos) / length;
                float splitx = x * p + (1.0f - p) * prev_x;
                float splity = y * p + (1.0f - p) * prev_y;
                if (is_down)
                {
                  ctx_rasterizer_line_to (rasterizer, splitx, splity);
                  is_down = 0;
                }
                else
                {
                  ctx_rasterizer_move_to (rasterizer, splitx, splity);
                  is_down = 1;
                }
                prev_x = splitx;
                prev_y = splity;
                dash_no++;
                dash_lpos=0;
                if (dash_no >= n_dashes) dash_no = 0;
                goto again;
              }
              else
              {
                pos += length;
                dash_lpos += length;
                {
                  if (is_down)
                    ctx_rasterizer_line_to (rasterizer, x, y);
                }
              }
              prev_x = x;
              prev_y = y;
            }
          end = i-1;
foo:
          start = end+1;
        }
        state->gstate.transform = transform_backup;
        }
        ctx_rasterizer_stroke (rasterizer);
        }

        break;
      case CTX_FONT:
        ctx_rasterizer_set_font (rasterizer, ctx_arg_string() );
        break;
      case CTX_TEXT:
        rasterizer->in_text++;
#if CTX_ENABLE_SHADOW_BLUR
        if (state->gstate.shadow_blur > 0.0)
          ctx_rasterizer_shadow_text (rasterizer, ctx_arg_string ());
#endif
        ctx_rasterizer_text (rasterizer, ctx_arg_string(), 0);
        rasterizer->in_text--;
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_STROKE_TEXT:
        ctx_rasterizer_text (rasterizer, ctx_arg_string(), 1);
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_GLYPH:
        ctx_rasterizer_glyph (rasterizer, entry[0].data.u32[0], entry[0].data.u8[4]);
        break;
      case CTX_FILL:
#if CTX_ENABLE_SHADOW_BLUR
        if (state->gstate.shadow_blur > 0.0 &&
            !rasterizer->in_text)
          ctx_rasterizer_shadow_fill (rasterizer);
#endif
        ctx_rasterizer_fill (rasterizer);
        break;
      case CTX_RESET:
      case CTX_BEGIN_PATH:
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_CLIP:
        ctx_rasterizer_clip (rasterizer);
        break;
      case CTX_CLOSE_PATH:
        ctx_rasterizer_finish_shape (rasterizer);
        break;
      case CTX_IMAGE_SMOOTHING:
        rasterizer->comp_op = NULL;
        break;
    }
  ctx_interpret_pos_bare (state, entry, NULL);
}

void
ctx_rasterizer_deinit (CtxRasterizer *rasterizer)
{
  ctx_drawlist_deinit (&rasterizer->edge_list);
#if CTX_ENABLE_CLIP
  if (rasterizer->clip_buffer)
  {
    ctx_buffer_free (rasterizer->clip_buffer);
    rasterizer->clip_buffer = NULL;
  }
#endif
#if CTX_SHAPE_CACHE
  for (int i = 0; i < CTX_SHAPE_CACHE_ENTRIES; i ++)
    if (rasterizer->shape_cache.entries[i])
    {
      free (rasterizer->shape_cache.entries[i]);
      rasterizer->shape_cache.entries[i] = NULL;
    }

#endif
  free (rasterizer);
}


CtxAntialias ctx_get_antialias (Ctx *ctx)
{
#if CTX_EVENTS
  if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx))
  {
     CtxTiled *fb = (CtxTiled*)(ctx->renderer);
     return fb->antialias;
  }
#endif
  if (!_ctx_is_rasterizer (ctx)) return CTX_ANTIALIAS_DEFAULT;

  switch (((CtxRasterizer*)(ctx->renderer))->aa)
  {
    case 1: return CTX_ANTIALIAS_NONE;
    case 3: return CTX_ANTIALIAS_FAST;
    //case 5: return CTX_ANTIALIAS_GOOD;
    default:
    case 15: return CTX_ANTIALIAS_DEFAULT;
    case 17: return CTX_ANTIALIAS_BEST;
  }
}

static int _ctx_antialias_to_aa (CtxAntialias antialias)
{
  switch (antialias)
  {
    case CTX_ANTIALIAS_NONE: return 1;
    case CTX_ANTIALIAS_FAST: return 3;
    case CTX_ANTIALIAS_GOOD: return 5;
    default:
    case CTX_ANTIALIAS_DEFAULT: return CTX_RASTERIZER_AA;
    case CTX_ANTIALIAS_BEST: return 17;
  }
}

void
ctx_set_antialias (Ctx *ctx, CtxAntialias antialias)
{
#if CTX_EVENTS
  if (ctx_renderer_is_sdl (ctx) || ctx_renderer_is_fb (ctx))
  {
     CtxTiled *fb = (CtxTiled*)(ctx->renderer);
     fb->antialias = antialias;
     for (int i = 0; i < _ctx_max_threads; i++)
     {
       ctx_set_antialias (fb->host[i], antialias);
     }
     return;
  }
#endif
  if (!_ctx_is_rasterizer (ctx)) return;

  ((CtxRasterizer*)(ctx->renderer))->aa = 
     _ctx_antialias_to_aa (antialias);
  ((CtxRasterizer*)(ctx->renderer))->fast_aa = 0;
  if (antialias == CTX_ANTIALIAS_DEFAULT||
      antialias == CTX_ANTIALIAS_FAST)
    ((CtxRasterizer*)(ctx->renderer))->fast_aa = 1;
}

CtxRasterizer *
ctx_rasterizer_init (CtxRasterizer *rasterizer, Ctx *ctx, Ctx *texture_source, CtxState *state, void *data, int x, int y, int width, int height, int stride, CtxPixelFormat pixel_format, CtxAntialias antialias)
{
#if CTX_ENABLE_CLIP
  if (rasterizer->clip_buffer)
    ctx_buffer_free (rasterizer->clip_buffer);
#endif
  if (rasterizer->edge_list.size)
    ctx_drawlist_deinit (&rasterizer->edge_list);

  memset (rasterizer, 0, sizeof (CtxRasterizer) );
  rasterizer->vfuncs.process = ctx_rasterizer_process;
  rasterizer->vfuncs.free    = (CtxDestroyNotify)ctx_rasterizer_deinit;
  rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST;
  rasterizer->state       = state;
  rasterizer->ctx         = ctx;
  rasterizer->texture_source = texture_source?texture_source:ctx;

  rasterizer->aa          = _ctx_antialias_to_aa (antialias);
  rasterizer->fast_aa = (antialias == CTX_ANTIALIAS_DEFAULT||antialias == CTX_ANTIALIAS_FAST);
  ctx_state_init (rasterizer->state);
  rasterizer->buf         = data;
  rasterizer->blit_x      = x;
  rasterizer->blit_y      = y;
  rasterizer->blit_width  = width;
  rasterizer->blit_height = height;
  rasterizer->state->gstate.clip_min_x  = x;
  rasterizer->state->gstate.clip_min_y  = y;
  rasterizer->state->gstate.clip_max_x  = x + width - 1;
  rasterizer->state->gstate.clip_max_y  = y + height - 1;
  rasterizer->blit_stride = stride;
  rasterizer->scan_min    = 5000;
  rasterizer->scan_max    = -5000;

  if (pixel_format == CTX_FORMAT_BGRA8)
  {
    pixel_format = CTX_FORMAT_RGBA8;
    rasterizer->swap_red_green = 1;
  }

  rasterizer->format = ctx_pixel_format_info (pixel_format);

  return rasterizer;
}

Ctx *
ctx_new_for_buffer (CtxBuffer *buffer)
{
  Ctx *ctx = ctx_new ();
  ctx_set_renderer (ctx,
                    ctx_rasterizer_init ( (CtxRasterizer *) malloc (sizeof (CtxRasterizer) ),
                                          ctx, NULL, &ctx->state,
                                          buffer->data, 0, 0, buffer->width, buffer->height,
                                          buffer->stride, buffer->format->pixel_format,
                                          CTX_ANTIALIAS_DEFAULT));
  return ctx;
}

Ctx *
ctx_new_for_framebuffer (void *data, int width, int height,
                         int stride,
                         CtxPixelFormat pixel_format)
{
  Ctx *ctx = ctx_new ();
  CtxRasterizer *r = ctx_rasterizer_init ( (CtxRasterizer *) ctx_calloc (sizeof (CtxRasterizer), 1),
                                          ctx, NULL, &ctx->state, data, 0, 0, width, height,
                                          stride, pixel_format, CTX_ANTIALIAS_DEFAULT);
  ctx_set_renderer (ctx, r);
  return ctx;
}

// ctx_new_for_stream (FILE *stream);

#if 0
CtxRasterizer *ctx_rasterizer_new (void *data, int x, int y, int width, int height,
                                   int stride, CtxPixelFormat pixel_format)
{
  CtxState    *state    = (CtxState *) malloc (sizeof (CtxState) );
  CtxRasterizer *rasterizer = (CtxRasterizer *) malloc (sizeof (CtxRenderer) );
  ctx_rasterizer_init (rasterizer, state, data, x, y, width, height,
                       stride, pixel_format, CTX_ANTIALIAS_DEFAULT);
}
#endif

CtxPixelFormatInfo *
ctx_pixel_format_info (CtxPixelFormat format);

#else

CtxPixelFormatInfo *
ctx_pixel_format_info (CtxPixelFormat format)
{
  return NULL;
}
#endif

void
ctx_current_point (Ctx *ctx, float *x, float *y)
{
  if (!ctx)
    { 
      if (x) { *x = 0.0f; }
      if (y) { *y = 0.0f; }
    }
#if CTX_RASTERIZER
  if (ctx->renderer)
    {
      if (x) { *x = ( (CtxRasterizer *) (ctx->renderer) )->x; }
      if (y) { *y = ( (CtxRasterizer *) (ctx->renderer) )->y; }
      return;
    }
#endif
  if (x) { *x = ctx->state.x; }
  if (y) { *y = ctx->state.y; }
}

float ctx_x (Ctx *ctx)
{
  float x = 0, y = 0;
  ctx_current_point (ctx, &x, &y);
  return x;
}

float ctx_y (Ctx *ctx)
{
  float x = 0, y = 0;
  ctx_current_point (ctx, &x, &y);
  return y;
}

static void
ctx_process (Ctx *ctx, CtxEntry *entry)
{
#if CTX_CURRENT_PATH
  switch (entry->code)
    {
      case CTX_TEXT:
      case CTX_STROKE_TEXT:
      case CTX_BEGIN_PATH:
        ctx->current_path.count = 0;
        break;
      case CTX_CLIP:
      case CTX_FILL:
      case CTX_STROKE:
              // XXX unless preserve
        ctx->current_path.count = 0;
        break;
      case CTX_CLOSE_PATH:
      case CTX_LINE_TO:
      case CTX_MOVE_TO:
      case CTX_QUAD_TO:
      case CTX_SMOOTH_TO:
      case CTX_SMOOTHQ_TO:
      case CTX_REL_QUAD_TO:
      case CTX_REL_SMOOTH_TO:
      case CTX_REL_SMOOTHQ_TO:
      case CTX_CURVE_TO:
      case CTX_REL_CURVE_TO:
      case CTX_ARC:
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
      case CTX_RECTANGLE:
      case CTX_ROUND_RECTANGLE:
        ctx_drawlist_add_entry (&ctx->current_path, entry);
        break;
      default:
        break;
    }
#endif
#if CTX_RASTERIZER
  if (CTX_LIKELY(ctx->renderer && ctx->renderer->process == ctx_rasterizer_process))
    {
      ctx_rasterizer_process (ctx->renderer, (CtxCommand *) entry);
    }
  else
#endif
  if (CTX_LIKELY(ctx->renderer && ctx->renderer->process))
    {
      ctx->renderer->process (ctx->renderer, (CtxCommand *) entry);
    }
  else
    {
      /* these functions might alter the code and coordinates of
         command that in the end gets added to the drawlist
       */
      ctx_interpret_style (&ctx->state, entry, ctx);
      ctx_interpret_transforms (&ctx->state, entry, ctx);
      ctx_interpret_pos (&ctx->state, entry, ctx);
      ctx_drawlist_add_entry (&ctx->drawlist, entry);
    }
}


int ctx_gradient_cache_valid = 0;

void
ctx_state_gradient_clear_stops (CtxState *state)
{
//#if CTX_GRADIENT_CACHE
//  ctx_gradient_cache_reset ();
//#endif
  ctx_gradient_cache_valid = 0;
  state->gradient.n_stops = 0;
}


/****  end of engine ****/

CtxBuffer *ctx_buffer_new_bare (void)
{
  CtxBuffer *buffer = (CtxBuffer *) ctx_calloc (sizeof (CtxBuffer), 1);
  return buffer;
}

void ctx_buffer_set_data (CtxBuffer *buffer,
                          void *data, int width, int height,
                          int stride,
                          CtxPixelFormat pixel_format,
                          void (*freefunc) (void *pixels, void *user_data),
                          void *user_data)
{
  if (buffer->free_func)
    { buffer->free_func (buffer->data, buffer->user_data); }
  if (stride <= 0)
    stride = ctx_pixel_format_get_stride (pixel_format, width);
  buffer->data      = data;
  buffer->width     = width;
  buffer->height    = height;
  buffer->stride    = stride;
  buffer->format    = ctx_pixel_format_info (pixel_format);
  buffer->free_func = freefunc;
  buffer->user_data = user_data;
}

CtxBuffer *ctx_buffer_new_for_data (void *data, int width, int height,
                                    int stride,
                                    CtxPixelFormat pixel_format,
                                    void (*freefunc) (void *pixels, void *user_data),
                                    void *user_data)
{
  CtxBuffer *buffer = ctx_buffer_new_bare ();
  ctx_buffer_set_data (buffer, data, width, height, stride, pixel_format,
                       freefunc, user_data);
  return buffer;
}

void ctx_buffer_pixels_free (void *pixels, void *userdata)
{
  free (pixels);
}

CtxBuffer *ctx_buffer_new (int width, int height,
                           CtxPixelFormat pixel_format)
{
  //CtxPixelFormatInfo *info = ctx_pixel_format_info (pixel_format);
  CtxBuffer *buffer = ctx_buffer_new_bare ();
  int stride = ctx_pixel_format_get_stride (pixel_format, width);
  int data_len = stride * height;
  if (pixel_format == CTX_FORMAT_YUV420)
    data_len = width * height + ((width/2) * (height/2)) * 2;

  uint8_t *pixels = (uint8_t*)ctx_calloc (data_len, 1);

  ctx_buffer_set_data (buffer, pixels, width, height, stride, pixel_format,
                       ctx_buffer_pixels_free, NULL);
  return buffer;
}

static void ctx_buffer_deinit (CtxBuffer *buffer)
{
  if (buffer->free_func)
    buffer->free_func (buffer->data, buffer->user_data);
  if (buffer->eid)
  {
    free (buffer->eid);
  }
  buffer->eid = NULL;
  buffer->data = NULL;
  buffer->free_func = NULL;
  buffer->user_data  = NULL;
  if (buffer->color_managed)
  {
    if (buffer->color_managed != buffer)
    {
      ctx_buffer_free (buffer->color_managed);
    }
    buffer->color_managed = NULL;
  }
}

void ctx_buffer_free (CtxBuffer *buffer)
{
  ctx_buffer_deinit (buffer);
  free (buffer);
}

static int
ctx_texture_check_eid (Ctx *ctx, const char *eid, int *tw, int *th)
{
  for (int i = 0; i <  CTX_MAX_TEXTURES; i++)
  {
    if (ctx->texture[i].data &&
        ctx->texture[i].eid  &&
        !strcmp (ctx->texture[i].eid, eid))
    {
      if (tw) *tw = ctx->texture[i].width;
      if (th) *th = ctx->texture[i].height;
      ctx->texture[i].frame = ctx->texture_cache->frame;
      return i;
    }
  }
  return -1;
}

const char* ctx_texture_init (Ctx           *ctx,
                              const char    *eid,
                              int            width,
                              int            height,
                              int            stride,
                              CtxPixelFormat format,
                              void          *space,
                              uint8_t       *pixels,
                              void (*freefunc) (void *pixels, void *user_data),
                              void *user_data)
{
  int id = 0;
  if (eid)
  {
    for (int i = 0; i <  CTX_MAX_TEXTURES; i++)
    {
      if (ctx->texture[i].data &&
          ctx->texture[i].eid &&
          !strcmp (ctx->texture[i].eid, eid))
      {
        ctx->texture[i].frame = ctx->texture_cache->frame;
        if (freefunc && user_data != (void*)23)
          freefunc (pixels, user_data);
        return ctx->texture[i].eid;
      }
      if (ctx->texture[i].data == NULL 
          ||   (ctx->texture_cache->frame - ctx->texture[i].frame >= 2))
        id = i;
    }
  } else
  {
    for (int i = 0; i <  CTX_MAX_TEXTURES; i++)
    {
      if (ctx->texture[i].data == NULL 
          || (ctx->texture_cache->frame - ctx->texture[i].frame > 2))
        id = i;
    }
  }
  //int bpp = ctx_pixel_format_bits_per_pixel (format);
  ctx_buffer_deinit (&ctx->texture[id]);

  if (stride<=0)
  {
    stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width);
  }

  int data_len = stride * height;
  if (format == CTX_FORMAT_YUV420)
          data_len = width * height +
                  2 * ((width/2)*(height/2));

  if (freefunc == ctx_buffer_pixels_free && user_data == (void*)23)
  {
     uint8_t *tmp = (uint8_t*)malloc (data_len);
     memcpy (tmp, pixels, data_len);
     pixels = tmp;
  }

  ctx_buffer_set_data (&ctx->texture[id],
                       pixels, width, height,
                       stride, format,
                       freefunc, user_data);
#if CTX_ENABLE_CM
  ctx->texture[id].space = space;
#endif
  ctx->texture[id].frame = ctx->texture_cache->frame;
  if (eid)
  {
    /* we got an eid, this is the fast path */
    ctx->texture[id].eid = strdup (eid);
  }
  else
  {
    uint8_t hash[20];
    char ascii[41];

    CtxSHA1 *sha1 = ctx_sha1_new ();
    ctx_sha1_process (sha1, pixels, stride * height);
    ctx_sha1_done (sha1, hash);
    ctx_sha1_free (sha1);
    const char *hex="0123456789abcdef";
    for (int i = 0; i < 20; i ++)
    {
       ascii[i*2]=hex[hash[i]/16];
       ascii[i*2+1]=hex[hash[i]%16];
    }
    ascii[40]=0;
    ctx->texture[id].eid = strdup (ascii);
  }
  return ctx->texture[id].eid;
}

static void
_ctx_texture_prepare_color_management (CtxRasterizer *rasterizer,
                                      CtxBuffer     *buffer)
{
   switch (buffer->format->pixel_format)
   {
#ifndef NO_BABL
#if CTX_BABL
     case CTX_FORMAT_RGBA8:
       if (buffer->space == rasterizer->state->gstate.device_space)
       {
         buffer->color_managed = buffer;
       }
       else
       {
          buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height,
                                                  CTX_FORMAT_RGBA8);
          babl_process (
             babl_fish (babl_format_with_space ("R'G'B'A u8", buffer->space),
                        babl_format_with_space ("R'G'B'A u8", rasterizer->state->gstate.device_space)),
             buffer->data, buffer->color_managed->data,
             buffer->width * buffer->height
             );
       }
       break;
     case CTX_FORMAT_RGB8:
       if (buffer->space == rasterizer->state->gstate.device_space)
       {
         buffer->color_managed = buffer;
       }
       else
       {
         buffer->color_managed = ctx_buffer_new (buffer->width, buffer->height,
                                               CTX_FORMAT_RGB8);
         babl_process (
            babl_fish (babl_format_with_space ("R'G'B' u8", buffer->space),
                       babl_format_with_space ("R'G'B' u8", rasterizer->state->gstate.device_space)),
            buffer->data, buffer->color_managed->data,
            buffer->width * buffer->height
          );
       }
       break;
#endif
#endif
     default:
       buffer->color_managed = buffer;
   }
}



int ctx_utf8_len (const unsigned char first_byte)
{
  if      ( (first_byte & 0x80) == 0)
    { return 1; } /* ASCII */
  else if ( (first_byte & 0xE0) == 0xC0)
    { return 2; }
  else if ( (first_byte & 0xF0) == 0xE0)
    { return 3; }
  else if ( (first_byte & 0xF8) == 0xF0)
    { return 4; }
  return 1;
}


const char *ctx_utf8_skip (const char *s, int utf8_length)
{
  int count;
  if (!s)
    { return NULL; }
  for (count = 0; *s; s++)
    {
      if ( (*s & 0xC0) != 0x80)
        { count++; }
      if (count == utf8_length + 1)
        { return s; }
    }
  return s;
}

//  XXX  :  unused
int ctx_utf8_strlen (const char *s)
{
  int count;
  if (!s)
    { return 0; }
  for (count = 0; *s; s++)
    if ( (*s & 0xC0) != 0x80)
      { count++; }
  return count;
}

int
ctx_unichar_to_utf8 (uint32_t  ch,
                     uint8_t  *dest)
{
  /* http://www.cprogramming.com/tutorial/utf8.c  */
  /*  Basic UTF-8 manipulation routines
    by Jeff Bezanson
    placed in the public domain Fall 2005 ... */
  if (ch < 0x80)
    {
      dest[0] = (char) ch;
      return 1;
    }
  if (ch < 0x800)
    {
      dest[0] = (ch>>6) | 0xC0;
      dest[1] = (ch & 0x3F) | 0x80;
      return 2;
    }
  if (ch < 0x10000)
    {
      dest[0] = (ch>>12) | 0xE0;
      dest[1] = ( (ch>>6) & 0x3F) | 0x80;
      dest[2] = (ch & 0x3F) | 0x80;
      return 3;
    }
  if (ch < 0x110000)
    {
      dest[0] = (ch>>18) | 0xF0;
      dest[1] = ( (ch>>12) & 0x3F) | 0x80;
      dest[2] = ( (ch>>6) & 0x3F) | 0x80;
      dest[3] = (ch & 0x3F) | 0x80;
      return 4;
    }
  return 0;
}

uint32_t
ctx_utf8_to_unichar (const char *input)
{
  const uint8_t *utf8 = (const uint8_t *) input;
  uint8_t c = utf8[0];
  if ( (c & 0x80) == 0)
    { return c; }
  else if ( (c & 0xE0) == 0xC0)
    return ( (utf8[0] & 0x1F) << 6) |
           (utf8[1] & 0x3F);
  else if ( (c & 0xF0) == 0xE0)
    return ( (utf8[0] & 0xF)  << 12) |
           ( (utf8[1] & 0x3F) << 6) |
           (utf8[2] & 0x3F);
  else if ( (c & 0xF8) == 0xF0)
    return ( (utf8[0] & 0x7)  << 18) |
           ( (utf8[1] & 0x3F) << 12) |
           ( (utf8[2] & 0x3F) << 6) |
           (utf8[3] & 0x3F);
  else if ( (c & 0xFC) == 0xF8)
    return ( (utf8[0] & 0x3)  << 24) |
           ( (utf8[1] & 0x3F) << 18) |
           ( (utf8[2] & 0x3F) << 12) |
           ( (utf8[3] & 0x3F) << 6) |
           (utf8[4] & 0x3F);
  else if ( (c & 0xFE) == 0xFC)
    return ( (utf8[0] & 0x1)  << 30) |
           ( (utf8[1] & 0x3F) << 24) |
           ( (utf8[2] & 0x3F) << 18) |
           ( (utf8[3] & 0x3F) << 12) |
           ( (utf8[4] & 0x3F) << 6) |
           (utf8[5] & 0x3F);
  return 0;
}

#if CTX_RASTERIZER



static int
ctx_rect_intersect (const CtxIntRectangle *a, const CtxIntRectangle *b)
{
  if (a->x >= b->x + b->width ||
      b->x >= a->x + a->width ||
      a->y >= b->y + b->height ||
      b->y >= a->y + a->height) return 0;

  return 1;
}

static void
_ctx_add_hash (CtxHasher *hasher, CtxIntRectangle *shape_rect, char *hash)
{
  CtxIntRectangle rect = {0,0, hasher->rasterizer.blit_width/hasher->cols,
                            hasher->rasterizer.blit_height/hasher->rows};
  int hno = 0;
  for (int row = 0; row < hasher->rows; row++)
    for (int col = 0; col < hasher->cols; col++, hno++)
     {
      rect.x = col * rect.width;
      rect.y = row * rect.height;
      if (ctx_rect_intersect (shape_rect, &rect))
      {
        int temp = hasher->hashes[(row * hasher->cols + col)  *20 + 0];
        for (int i = 0; i <19;i++)
           hasher->hashes[(row * hasher->cols + col)  *20 + i] =
             hasher->hashes[(row * hasher->cols + col)  *20 + i+1]^
             hash[i];
        hasher->hashes[(row * hasher->cols + col)  *20 + 19] =
                temp ^ hash[19];
      }
    }
}

static int ctx_str_count_lines (const char *str)
{
  int count = 0;
  for (const char *p = str; *p; p++)
    if (*p == '\n') count ++;
  return count;
}

static void
ctx_hasher_process (void *user_data, CtxCommand *command)
{
  CtxEntry *entry = &command->entry;
  CtxRasterizer *rasterizer = (CtxRasterizer *) user_data;
  CtxHasher *hasher = (CtxHasher*) user_data;
  CtxState *state = rasterizer->state;
  CtxCommand *c = (CtxCommand *) entry;
  int aa = 15;//rasterizer->aa;

  ctx_interpret_pos_bare (rasterizer->state, entry, NULL);
  ctx_interpret_style (rasterizer->state, entry, NULL);

  switch (c->code)
    {
      case CTX_TEXT:
        {
          CtxSHA1 sha1;
          memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1));
          char ctx_sha1_hash[20];
          float width = ctx_text_width (rasterizer->ctx, ctx_arg_string());


          float height = ctx_get_font_size (rasterizer->ctx);
           CtxIntRectangle shape_rect;
          
           shape_rect.x=rasterizer->x;
           shape_rect.y=rasterizer->y - height,
           shape_rect.width = width;
           shape_rect.height = height * (ctx_str_count_lines (ctx_arg_string()) + 1.5);
          switch ((int)ctx_state_get (rasterizer->state, CTX_text_align))
          {
          case CTX_TEXT_ALIGN_LEFT:
          case CTX_TEXT_ALIGN_START:
                  break;
          case CTX_TEXT_ALIGN_END:
          case CTX_TEXT_ALIGN_RIGHT:
           shape_rect.x -= shape_rect.width;
           break;
          case CTX_TEXT_ALIGN_CENTER:
           shape_rect.x -= shape_rect.width/2;
           break;
                   // XXX : doesn't take all text-alignments into account
          }

#if 0
          uint32_t color;
          ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color));
#endif
          ctx_sha1_process(&sha1, (const unsigned char*)ctx_arg_string(), strlen  (ctx_arg_string()));
#if 0
          ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
          ctx_sha1_process(&sha1, (unsigned char*)&color, 4);
#endif
          ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
          _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash);

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
        }
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_STROKE_TEXT:
        {
          CtxSHA1 sha1;
          memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1));
          char ctx_sha1_hash[20];
          float width = ctx_text_width (rasterizer->ctx, ctx_arg_string());
          float height = ctx_get_font_size (rasterizer->ctx);

           CtxIntRectangle shape_rect = {
              (int)rasterizer->x, (int)(rasterizer->y - height),
              (int)width, (int)(height * 2)
           };

#if 0
          uint32_t color;
          ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color));
#endif
          ctx_sha1_process(&sha1, (unsigned char*)ctx_arg_string(), strlen  (ctx_arg_string()));
#if 0
          ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
          ctx_sha1_process(&sha1, (unsigned char*)&color, 4);
#endif
          ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
          _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash);

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
        }
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_GLYPH:
         {
          CtxSHA1 sha1;
          memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1));

          char ctx_sha1_hash[20];
          uint8_t string[8];
          string[ctx_unichar_to_utf8 (c->u32.a0, string)]=0;
          float width = ctx_text_width (rasterizer->ctx, (char*)string);
          float height = ctx_get_font_size (rasterizer->ctx);

          float tx = rasterizer->x;
          float ty = rasterizer->y;
          float tw = width;
          float th = height * 2;

          _ctx_user_to_device (rasterizer->state, &tx, &ty);
          _ctx_user_to_device_distance (rasterizer->state, &tw, &th);
          CtxIntRectangle shape_rect = {(int)tx,(int)(ty-th/2),(int)tw,(int)th};


#if 0
          uint32_t color;
          ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color));
#endif
          ctx_sha1_process(&sha1, string, strlen ((const char*)string));
#if 0
          ctx_sha1_process(&sha1, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
          ctx_sha1_process(&sha1, (unsigned char*)&color, 4);
#endif
          ctx_sha1_process(&sha1, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
          _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash);

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
          ctx_rasterizer_reset (rasterizer);
         }
        break;

      case CTX_FILL:
        {
          CtxSHA1 sha1;
          memcpy (&sha1, &hasher->sha1_fill, sizeof (CtxSHA1));
          char ctx_sha1_hash[20];

          /* we eant this hasher to be as good as possible internally,
           * since it is also used in the small shapes rasterization
           * cache
           */
        uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer); // + hasher->salt;
        CtxIntRectangle shape_rect = {
          (int)(rasterizer->col_min / CTX_SUBDIV - 2),
          (int)(rasterizer->scan_min / aa - 2),
          (int)(3+(rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV),
          (int)(3+(rasterizer->scan_max - rasterizer->scan_min + 1) / aa)
        };

        hash ^= (rasterizer->state->gstate.fill_rule * 23);

        ctx_sha1_process(&sha1, (unsigned char*)&hash, 8);

        {
          int is = rasterizer->state->gstate.image_smoothing;
          ctx_sha1_process(&sha1, (uint8_t*)&is, sizeof(int));
        }

          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
          _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash);

        if (!rasterizer->preserve)
          ctx_rasterizer_reset (rasterizer);
        rasterizer->preserve = 0;
        }
        break;
      case CTX_STROKE:
        {
          CtxSHA1 sha1;
          memcpy (&sha1, &hasher->sha1_stroke, sizeof (CtxSHA1));
          char ctx_sha1_hash[20];
        uint64_t hash = ctx_rasterizer_poly_to_hash (rasterizer);
        CtxIntRectangle shape_rect = {
          (int)(rasterizer->col_min / CTX_SUBDIV - rasterizer->state->gstate.line_width),
          (int)(rasterizer->scan_min / aa - rasterizer->state->gstate.line_width),
          (int)((rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV + rasterizer->state->gstate.line_width),
          (int)((rasterizer->scan_max - rasterizer->scan_min + 1) / aa + rasterizer->state->gstate.line_width)
        };

        shape_rect.width += rasterizer->state->gstate.line_width * 2;
        shape_rect.height += rasterizer->state->gstate.line_width * 2;
        shape_rect.x -= rasterizer->state->gstate.line_width;
        shape_rect.y -= rasterizer->state->gstate.line_width;

        hash ^= (int)(rasterizer->state->gstate.line_width * 110);
        hash ^= (rasterizer->state->gstate.line_cap * 23);
        hash ^= (rasterizer->state->gstate.source_stroke.type * 117);

        ctx_sha1_process(&sha1, (unsigned char*)&hash, 8);

        uint32_t color;
        ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color));

          ctx_sha1_process(&sha1, (unsigned char*)&color, 4);

          ctx_sha1_done(&sha1, (unsigned char*)ctx_sha1_hash);
          _ctx_add_hash (hasher, &shape_rect, ctx_sha1_hash);
        }
        if (!rasterizer->preserve)
          ctx_rasterizer_reset (rasterizer);
        rasterizer->preserve = 0;
        break;
        /* the above cases are the painting cases and 
         * the only ones differing from the rasterizer's process switch
         */

      case CTX_LINE_TO:
        ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_LINE_TO:
        ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_MOVE_TO:
        ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_MOVE_TO:
        ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_CURVE_TO:
        ctx_rasterizer_curve_to (rasterizer, c->c.x0, c->c.y0,
                                 c->c.x1, c->c.y1,
                                 c->c.x2, c->c.y2);
        break;
      case CTX_REL_CURVE_TO:
        ctx_rasterizer_rel_curve_to (rasterizer, c->c.x0, c->c.y0,
                                     c->c.x1, c->c.y1,
                                     c->c.x2, c->c.y2);
        break;
      case CTX_QUAD_TO:
        ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_REL_QUAD_TO:
        ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_ARC:
        ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, c->arc.direction);
        break;
      case CTX_RECTANGLE:
        ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y,
                                  c->rectangle.width, c->rectangle.height);
        break;
      case CTX_ROUND_RECTANGLE:
        ctx_rasterizer_round_rectangle (rasterizer, c->rectangle.x, c->rectangle.y,
                                        c->rectangle.width, c->rectangle.height,
                                        c->rectangle.radius);
        break;
      case CTX_SET_PIXEL:
        ctx_rasterizer_set_pixel (rasterizer, c->set_pixel.x, c->set_pixel.y,
                                  c->set_pixel.rgba[0],
                                  c->set_pixel.rgba[1],
                                  c->set_pixel.rgba[2],
                                  c->set_pixel.rgba[3]);
        break;
      case CTX_PRESERVE:
        rasterizer->preserve = 1;
        break;
      case CTX_ROTATE:
      case CTX_SCALE:
      case CTX_TRANSLATE:
      case CTX_SAVE:
      case CTX_RESTORE:
        rasterizer->uses_transforms = 1;
        ctx_interpret_transforms (rasterizer->state, entry, NULL);

        
        break;
      case CTX_FONT:
        ctx_rasterizer_set_font (rasterizer, ctx_arg_string() );
        break;
      case CTX_BEGIN_PATH:
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_CLIP:
        // should perhaps modify a global state to include
        // in hash?
        ctx_rasterizer_clip (rasterizer);
        break;
      case CTX_CLOSE_PATH:
        ctx_rasterizer_finish_shape (rasterizer);
        break;
      case CTX_DEFINE_TEXTURE:
        {
        ctx_sha1_init (&hasher->sha1_fill);
        ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->define_texture.eid, strlen (c->define_texture.eid));
        ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));

        rasterizer->comp_op = NULL; // why?
        }
        break;
      case CTX_TEXTURE:
        ctx_sha1_init (&hasher->sha1_fill);
        ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)c->texture.eid, strlen (c->texture.eid));
        ctx_sha1_process (&hasher->sha1_fill, (uint8_t*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
        rasterizer->comp_op = NULL; // why?
        break;
      case CTX_COLOR:
        {
          uint32_t color;
          if (((int)(ctx_arg_float(0))&512))
          {
            ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color));
            ctx_sha1_init (&hasher->sha1_stroke);
            ctx_sha1_process(&hasher->sha1_stroke, (unsigned char*)&color, 4);
          }
          else
          {
            ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color));
            ctx_sha1_init (&hasher->sha1_fill);
            ctx_sha1_process(&hasher->sha1_fill, (unsigned char*)&color, 4);
          }
        }
        break;
      case CTX_LINEAR_GRADIENT:
          ctx_sha1_init (&hasher->sha1_fill);
          ctx_sha1_process(&hasher->sha1_fill, 
                           (uint8_t*)c, sizeof (c->linear_gradient));
          ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
        break;
      case CTX_RADIAL_GRADIENT:
          ctx_sha1_init (&hasher->sha1_fill);
          ctx_sha1_process(&hasher->sha1_fill, 
                           (uint8_t*)c, sizeof (c->radial_gradient));
          ctx_sha1_process (&hasher->sha1_fill, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
        //ctx_state_gradient_clear_stops (rasterizer->state);
        break;
#if CTX_GRADIENTS
      case CTX_GRADIENT_STOP:
        {
          float rgba[4]= {ctx_u8_to_float (ctx_arg_u8 (4) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+1) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+2) ),
                          ctx_u8_to_float (ctx_arg_u8 (4+3) )
                         };
          ctx_sha1_process(&hasher->sha1_fill, 
                           (uint8_t*) &rgba[0], sizeof(rgba));
        }
        break;
#endif
    }
  if (command->code == CTX_LINE_WIDTH)
    {
      float x = state->gstate.line_width;
      /* normalize line width according to scaling factor
       */
      x = x * ctx_maxf (ctx_maxf (ctx_fabsf (state->gstate.transform.m[0][0]),
                                  ctx_fabsf (state->gstate.transform.m[0][1]) ),
                        ctx_maxf (ctx_fabsf (state->gstate.transform.m[1][0]),
                                  ctx_fabsf (state->gstate.transform.m[1][1]) ) );
      state->gstate.line_width = x;
    }
}

static CtxRasterizer *
ctx_hasher_init (CtxRasterizer *rasterizer, Ctx *ctx, CtxState *state, int width, int height, int cols, int rows)
{
  CtxHasher *hasher = (CtxHasher*)rasterizer;
  ctx_memset (rasterizer, 0, sizeof (CtxHasher) );
  rasterizer->vfuncs.process = ctx_hasher_process;
  rasterizer->vfuncs.free    = (CtxDestroyNotify)ctx_rasterizer_deinit;
  // XXX need own destructor to not leak ->hashes
  rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST;
  rasterizer->state       = state;
  rasterizer->ctx         = ctx;
  ctx_state_init (rasterizer->state);
  rasterizer->blit_x      = 0;
  rasterizer->blit_y      = 0;
  rasterizer->blit_width  = width;
  rasterizer->blit_height = height;
  rasterizer->state->gstate.clip_min_x  = 0;
  rasterizer->state->gstate.clip_min_y  = 0;
  rasterizer->state->gstate.clip_max_x  = width - 1;
  rasterizer->state->gstate.clip_max_y  = height - 1;
  rasterizer->scan_min    = 5000;
  rasterizer->scan_max    = -5000;
  //rasterizer->aa          = 15;

  hasher->rows = rows;
  hasher->cols = cols;

  hasher->hashes = (uint8_t*)ctx_calloc (20, rows * cols);
  ctx_sha1_init (&hasher->sha1_fill);
  ctx_sha1_init (&hasher->sha1_stroke);

  return rasterizer;
}

Ctx *ctx_hasher_new (int width, int height, int cols, int rows)
{
  Ctx *ctx           = ctx_new ();
  CtxState    *state = &ctx->state;
  CtxRasterizer *rasterizer = (CtxRasterizer *) ctx_calloc (sizeof (CtxHasher), 1);
  ctx_hasher_init (rasterizer, ctx, state, width, height, cols, rows);
  ctx_set_renderer (ctx, (void*)rasterizer);
  return ctx;
}
uint8_t *ctx_hasher_get_hash (Ctx *ctx, int col, int row)
{
  CtxHasher *hasher = (CtxHasher*)ctx->renderer;
  if (row < 0) row =0;
  if (col < 0) col =0;
  if (row >= hasher->rows) row = hasher->rows-1;
  if (col >= hasher->cols) col = hasher->cols-1;

  return &hasher->hashes[(row*hasher->cols+col)*20];
}

#endif
#if CTX_EVENTS

#if !__COSMOPOLITAN__
#include <termios.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#endif

int ctx_terminal_width (void)
{
  char buf[1024];
  struct termios orig_attr;
  struct termios raw;
  tcgetattr (STDIN_FILENO, &orig_attr);
  raw = orig_attr;
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    return 0;
  fprintf (stderr, "\e[14t");
  //tcflush(STDIN_FILENO, 1);
#if __COSMOPOLITAN__
  /// XXX ?
#else
  tcdrain(STDIN_FILENO);
#endif
  int length = 0;
  usleep (1000 * 60); // to account for possibly lowish latency ssh,
                      // should be made configurable ; perhaps in
                      // an env var
  struct timeval tv = {0,0};
  fd_set rfds;
  
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);
  tv.tv_usec = 1000 * 5;

  for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++)
  {
    length += read (STDIN_FILENO, &buf[length], 1);
  }
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr);
  if (length == -1)
  {
    return 0;
  }
  char *semi = strchr (buf, ';');
  buf[length]=0;
  if (semi) {semi++; semi = strchr (semi, ';');}
  if (semi)
  {
    return atoi(semi + 1);
  }
  return 0;
}

int ctx_terminal_height (void)
{
  char buf[1024];
  struct termios orig_attr;
  struct termios raw;
  tcgetattr (STDIN_FILENO, &orig_attr);
  raw = orig_attr;
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    return 0;
  fprintf (stderr, "\e[14t");
  //tcflush(STDIN_FILENO, 1);
#if !__COSMOPOLITAN__
  tcdrain(STDIN_FILENO);
#endif
  int length = 0;
  usleep (1000 * 60); // to account for possibly lowish latency ssh,
                      // should be made configurable ; perhaps in
                      // an env var
  struct timeval tv = {0,0};
  fd_set rfds;
  
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);
  tv.tv_usec = 1000 * 5;

  for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++)
  {
    length += read (STDIN_FILENO, &buf[length], 1);
  }
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr);
  if (length == -1)
  {
    return 0;
  }
  char *semi = strchr (buf, ';');
  buf[length]=0;
  if (semi)
  {
    return atoi(semi + 1);
  }
  return 0;
}

int ctx_terminal_cols (void)
{
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 80;
  return ws.ws_col;
} 

int ctx_terminal_rows (void)
{
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 25;
  return ws.ws_row;
}





#define DECTCEM_CURSOR_SHOW      "\033[?25h"
#define DECTCEM_CURSOR_HIDE      "\033[?25l"
#define TERMINAL_MOUSE_OFF       "\033[?1000l\033[?1003l"
#define TERMINAL_MOUSE_ON_BASIC  "\033[?1000h"
#define TERMINAL_MOUSE_ON_DRAG   "\033[?1000h\033[?1003h" /* +ON_BASIC for wider */
#define TERMINAL_MOUSE_ON_FULL   "\033[?1000h\033[?1004h" /* compatibility */
#define XTERM_ALTSCREEN_ON       "\033[?47h"
#define XTERM_ALTSCREEN_OFF      "\033[?47l"

/*************************** input handling *************************/

#if !__COSMOPOLITAN__
#include <termios.h>
#include <errno.h>
#include <signal.h>
#endif

#define DELAY_MS  100  

#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif

static int  size_changed = 0;       /* XXX: global state */
static int  signal_installed = 0;   /* XXX: global state */

static const char *mouse_modes[]=
{TERMINAL_MOUSE_OFF,
 TERMINAL_MOUSE_ON_BASIC,
 TERMINAL_MOUSE_ON_DRAG,
 TERMINAL_MOUSE_ON_FULL,
 NULL};

/* note that a nick can have multiple occurences, the labels
 * should be kept the same for all occurences of a combination. */
typedef struct NcKeyCode {
  const char *nick;          /* programmers name for key (combo) */
  const char *label;         /* utf8 label for key */
  const char  sequence[10];  /* terminal sequence */
} NcKeyCode;
static const NcKeyCode keycodes[]={  

  {"up",                  "↑",     "\033[A"},
  {"down",                "↓",     "\033[B"},
  {"right",               "→",     "\033[C"},
  {"left",                "←",     "\033[D"},

  {"shift-up",            "⇧↑",    "\033[1;2A"},
  {"shift-down",          "⇧↓",    "\033[1;2B"},
  {"shift-right",         "⇧→",    "\033[1;2C"},
  {"shift-left",          "⇧←",    "\033[1;2D"},

  {"alt-up",              "^↑",    "\033[1;3A"},
  {"alt-down",            "^↓",    "\033[1;3B"},
  {"alt-right",           "^→",    "\033[1;3C"},
  {"alt-left",            "^←",    "\033[1;3D"},

  {"alt-shift-up",        "alt-s↑", "\033[1;4A"},
  {"alt-shift-down",      "alt-s↓", "\033[1;4B"},
  {"alt-shift-right",     "alt-s→", "\033[1;4C"},
  {"alt-shift-left",      "alt-s←", "\033[1;4D"},

  {"control-up",          "^↑",    "\033[1;5A"},
  {"control-down",        "^↓",    "\033[1;5B"},
  {"control-right",       "^→",    "\033[1;5C"},
  {"control-left",        "^←",    "\033[1;5D"},

  /* putty */
  {"control-up",          "^↑",    "\033OA"},
  {"control-down",        "^↓",    "\033OB"},
  {"control-right",       "^→",    "\033OC"},
  {"control-left",        "^←",    "\033OD"},

  {"control-shift-up",    "^⇧↑",   "\033[1;6A"},
  {"control-shift-down",  "^⇧↓",   "\033[1;6B"},
  {"control-shift-right", "^⇧→",   "\033[1;6C"},
  {"control-shift-left",  "^⇧←",   "\033[1;6D"},

  {"control-up",          "^↑",    "\033Oa"},
  {"control-down",        "^↓",    "\033Ob"},
  {"control-right",       "^→",    "\033Oc"},
  {"control-left",        "^←",    "\033Od"},

  {"shift-up",            "⇧↑",    "\033[a"},
  {"shift-down",          "⇧↓",    "\033[b"},
  {"shift-right",         "⇧→",    "\033[c"},
  {"shift-left",          "⇧←",    "\033[d"},

  {"insert",              "ins",   "\033[2~"},
  {"delete",              "del",   "\033[3~"},
  {"page-up",             "PgUp",  "\033[5~"},
  {"page-down",           "PdDn",  "\033[6~"},
  {"home",                "Home",  "\033OH"},
  {"end",                 "End",   "\033OF"},
  {"home",                "Home",  "\033[H"},
  {"end",                 "End",   "\033[F"},
  {"control-delete",      "^del",  "\033[3;5~"},
  {"shift-delete",        "⇧del",  "\033[3;2~"},
  {"control-shift-delete","^⇧del", "\033[3;6~"},

  {"F1",        "F1",  "\033[11~"},
  {"F2",        "F2",  "\033[12~"},
  {"F3",        "F3",  "\033[13~"},
  {"F4",        "F4",  "\033[14~"},
  {"F1",        "F1",  "\033OP"},
  {"F2",        "F2",  "\033OQ"},
  {"F3",        "F3",  "\033OR"},
  {"F4",        "F4",  "\033OS"},
  {"F5",        "F5",  "\033[15~"},
  {"F6",        "F6",  "\033[16~"},
  {"F7",        "F7",  "\033[17~"},
  {"F8",        "F8",  "\033[18~"},
  {"F9",        "F9",  "\033[19~"},
  {"F9",        "F9",  "\033[20~"},
  {"F10",       "F10", "\033[21~"},
  {"F11",       "F11", "\033[22~"},
  {"F12",       "F12", "\033[23~"},
  {"tab",       "↹",     {9, '\0'}},
  {"shift-tab", "shift+↹",  "\033[Z"},
  {"backspace", "⌫",  {127, '\0'}},
  {"space",     "␣",   " "},
  {"esc",        "␛",  "\033"},
  {"return",    "⏎",  {10,0}},
  {"return",    "⏎",  {13,0}},
  /* this section could be autogenerated by code */
  {"control-a", "^A",  {1,0}},
  {"control-b", "^B",  {2,0}},
  {"control-c", "^C",  {3,0}},
  {"control-d", "^D",  {4,0}},
  {"control-e", "^E",  {5,0}},
  {"control-f", "^F",  {6,0}},
  {"control-g", "^G",  {7,0}},
  {"control-h", "^H",  {8,0}}, /* backspace? */
  {"control-i", "^I",  {9,0}}, /* tab */
  {"control-j", "^J",  {10,0}},
  {"control-k", "^K",  {11,0}},
  {"control-l", "^L",  {12,0}},
  {"control-n", "^N",  {14,0}},
  {"control-o", "^O",  {15,0}},
  {"control-p", "^P",  {16,0}},
  {"control-q", "^Q",  {17,0}},
  {"control-r", "^R",  {18,0}},
  {"control-s", "^S",  {19,0}},
  {"control-t", "^T",  {20,0}},
  {"control-u", "^U",  {21,0}},
  {"control-v", "^V",  {22,0}},
  {"control-w", "^W",  {23,0}},
  {"control-x", "^X",  {24,0}},
  {"control-y", "^Y",  {25,0}},
  {"control-z", "^Z",  {26,0}},
  {"alt-0",     "%0",  "\0330"},
  {"alt-1",     "%1",  "\0331"},
  {"alt-2",     "%2",  "\0332"},
  {"alt-3",     "%3",  "\0333"},
  {"alt-4",     "%4",  "\0334"},
  {"alt-5",     "%5",  "\0335"},
  {"alt-6",     "%6",  "\0336"},
  {"alt-7",     "%7",  "\0337"}, /* backspace? */
  {"alt-8",     "%8",  "\0338"},
  {"alt-9",     "%9",  "\0339"},
  {"alt-+",     "%+",  "\033+"},
  {"alt--",     "%-",  "\033-"},
  {"alt-/",     "%/",  "\033/"},
  {"alt-a",     "%A",  "\033a"},
  {"alt-b",     "%B",  "\033b"},
  {"alt-c",     "%C",  "\033c"},
  {"alt-d",     "%D",  "\033d"},
  {"alt-e",     "%E",  "\033e"},
  {"alt-f",     "%F",  "\033f"},
  {"alt-g",     "%G",  "\033g"},
  {"alt-h",     "%H",  "\033h"}, /* backspace? */
  {"alt-i",     "%I",  "\033i"},
  {"alt-j",     "%J",  "\033j"},
  {"alt-k",     "%K",  "\033k"},
  {"alt-l",     "%L",  "\033l"},
  {"alt-n",     "%N",  "\033m"},
  {"alt-n",     "%N",  "\033n"},
  {"alt-o",     "%O",  "\033o"},
  {"alt-p",     "%P",  "\033p"},
  {"alt-q",     "%Q",  "\033q"},
  {"alt-r",     "%R",  "\033r"},
  {"alt-s",     "%S",  "\033s"},
  {"alt-t",     "%T",  "\033t"},
  {"alt-u",     "%U",  "\033u"},
  {"alt-v",     "%V",  "\033v"},
  {"alt-w",     "%W",  "\033w"},
  {"alt-x",     "%X",  "\033x"},
  {"alt-y",     "%Y",  "\033y"},
  {"alt-z",     "%Z",  "\033z"},
  {"shift-tab", "shift-↹", {27, 9, 0}},
  /* Linux Console  */
  {"home",      "Home", "\033[1~"},
  {"end",       "End",  "\033[4~"},
  {"F1",        "F1",   "\033[[A"},
  {"F2",        "F2",   "\033[[B"},
  {"F3",        "F3",   "\033[[C"},
  {"F4",        "F4",   "\033[[D"},
  {"F5",        "F5",   "\033[[E"},
  {"F6",        "F6",   "\033[[F"},
  {"F7",        "F7",   "\033[[G"},
  {"F8",        "F8",   "\033[[H"},
  {"F9",        "F9",   "\033[[I"},
  {"F10",       "F10",  "\033[[J"},
  {"F11",       "F11",  "\033[[K"},
  {"F12",       "F12",  "\033[[L"}, 
  {"ok",        "",     "\033[0n"},
  {NULL, }
};

static struct termios orig_attr;    /* in order to restore at exit */
static int    nc_is_raw = 0;
static int    atexit_registered = 0;
static int    mouse_mode = NC_MOUSE_NONE;

static void _nc_noraw (void)
{
  if (nc_is_raw && tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr) != -1)
    nc_is_raw = 0;
}

void
nc_at_exit (void)
{
  printf (TERMINAL_MOUSE_OFF);
  printf (XTERM_ALTSCREEN_OFF);
  _nc_noraw();
  fprintf (stdout, "\e[?25h");
  //if (ctx_native_events)
  fprintf (stdout, "\e[?201l");
  fprintf (stdout, "\e[?1049l");
}

static const char *mouse_get_event_int (Ctx *n, int *x, int *y)
{
  static int prev_state = 0;
  const char *ret = "mouse-motion";
  float relx, rely;
  signed char buf[3];
  read (n->mouse_fd, buf, 3);
  relx = buf[1];
  rely = -buf[2];

  n->mouse_x += relx * 0.1;
  n->mouse_y += rely * 0.1;

  if (n->mouse_x < 1) n->mouse_x = 1;
  if (n->mouse_y < 1) n->mouse_y = 1;
  if (n->mouse_x >= n->events.width)  n->mouse_x = n->events.width;
  if (n->mouse_y >= n->events.height) n->mouse_y = n->events.height;

  if (x) *x = n->mouse_x;
  if (y) *y = n->mouse_y;

  if ((prev_state & 1) != (buf[0] & 1))
    {
      if (buf[0] & 1) ret = "mouse-press";
    }
  else if (buf[0] & 1)
    ret = "mouse-drag";

  if ((prev_state & 2) != (buf[0] & 2))
    {
      if (buf[0] & 2) ret = "mouse2-press";
    }
  else if (buf[0] & 2)
    ret = "mouse2-drag";

  if ((prev_state & 4) != (buf[0] & 4))
    {
      if (buf[0] & 4) ret = "mouse1-press";
    }
  else if (buf[0] & 4)
    ret = "mouse1-drag";

  prev_state = buf[0];
  return ret;
}

static const char *mev_type = NULL;
static int         mev_x = 0;
static int         mev_y = 0;
static int         mev_q = 0;

static const char *mouse_get_event (Ctx  *n, int *x, int *y)
{
  if (!mev_q)
    return NULL;
  *x = mev_x;
  *y = mev_y;
  mev_q = 0;
  return mev_type;
}

static int mouse_has_event (Ctx *n)
{
  struct timeval tv;
  int retval;

  if (mouse_mode == NC_MOUSE_NONE)
    return 0;

  if (mev_q)
    return 1;

  if (n->mouse_fd == 0)
    return 0;
  return 0;

  {
    fd_set rfds;
    FD_ZERO (&rfds);
    FD_SET(n->mouse_fd, &rfds);
    tv.tv_sec = 0; tv.tv_usec = 0;
    retval = select (n->mouse_fd+1, &rfds, NULL, NULL, &tv);
  }

  if (retval != 0)
    {
      int nx = 0, ny = 0;
      const char *type = mouse_get_event_int (n, &nx, &ny);

      if ((mouse_mode < NC_MOUSE_DRAG && mev_type && !strcmp (mev_type, "drag")) ||
          (mouse_mode < NC_MOUSE_ALL && mev_type && !strcmp (mev_type, "motion")))
        {
          mev_q = 0;
          return mouse_has_event (n);
        }

      if ((mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse-motion")) ||
         (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse1-drag")) ||
         (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse2-drag")))
        {
          if (nx == mev_x && ny == mev_y)
          {
            mev_q = 0;
            return mouse_has_event (n);
          }
        }
      mev_x = nx;
      mev_y = ny;
      mev_type = type;
      mev_q = 1;
    }
  return retval != 0;
}


static int _nc_raw (void)
{
  struct termios raw;
  if (!isatty (STDIN_FILENO))
    return -1;
  if (!atexit_registered)
    {
      atexit (nc_at_exit);
      atexit_registered = 1;
    }
  if (tcgetattr (STDIN_FILENO, &orig_attr) == -1)
    return -1;
  raw = orig_attr;  /* modify the original mode */
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    return -1;
  nc_is_raw = 1;
#if !__COSMOPOLITAN__
  tcdrain(STDIN_FILENO);
  tcflush(STDIN_FILENO, 1);
#endif
  return 0;
}

static int match_keycode (const char *buf, int length, const NcKeyCode **ret)
{
  int i;
  int matches = 0;

  if (!strncmp (buf, "\033[M", MIN(length,3)))
    {
      if (length >= 6)
        return 9001;
      return 2342;
    }
  for (i = 0; keycodes[i].nick; i++)
    if (!strncmp (buf, keycodes[i].sequence, length))
      {
        matches ++;
        if ((int)strlen (keycodes[i].sequence) == length && ret)
          {
            *ret = &keycodes[i];
            return 1;
          }
      }
  if (matches != 1 && ret)
    *ret = NULL;
  return matches==1?2:matches;
}

static void nc_resize_term (int  dummy)
{
  size_changed = 1;
}

int ctx_nct_has_event (Ctx  *n, int delay_ms)
{
  struct timeval tv;
  int retval;
  fd_set rfds;

  if (size_changed)
    return 1;
  FD_ZERO (&rfds);
  FD_SET (STDIN_FILENO, &rfds);
  tv.tv_sec = 0; tv.tv_usec = delay_ms * 1000; 
  retval = select (1, &rfds, NULL, NULL, &tv);
  if (size_changed)
    return 1;
  return retval == 1 && retval != -1;
}

const char *ctx_nct_get_event (Ctx *n, int timeoutms, int *x, int *y)
{
  unsigned char buf[20];
  int length;


  if (x) *x = -1;
  if (y) *y = -1;

  if (!signal_installed)
    {
      _nc_raw ();
      signal_installed = 1;
      signal (SIGWINCH, nc_resize_term);
    }
  if (mouse_mode) // XXX too often to do it all the time!
    printf("%s", mouse_modes[mouse_mode]);

  {
    int elapsed = 0;
    int got_event = 0;

    do {
      if (size_changed)
        {
          size_changed = 0;
          return "size-changed";
        }
      got_event = mouse_has_event (n);
      if (!got_event)
        got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed));
      if (size_changed)
        {
          size_changed = 0;
          return "size-changed";
        }
      /* only do this if the client has asked for idle events,
       * and perhaps programmed the ms timer?
       */
      elapsed += MIN(DELAY_MS, timeoutms-elapsed);
      if (!got_event && timeoutms && elapsed >= timeoutms)
        return "idle";
    } while (!got_event);
  }

  if (mouse_has_event (n))
    return mouse_get_event (n, x, y);

  for (length = 0; length < 10; length ++)
    if (read (STDIN_FILENO, &buf[length], 1) != -1)
      {
        const NcKeyCode *match = NULL;

        /* special case ESC, so that we can use it alone in keybindings */
        if (length == 0 && buf[0] == 27)
          {
            struct timeval tv;
            fd_set rfds;
            FD_ZERO (&rfds);
            FD_SET (STDIN_FILENO, &rfds);
            tv.tv_sec = 0;
            tv.tv_usec = 1000 * DELAY_MS;
            if (select (1, &rfds, NULL, NULL, &tv) == 0)
              return "esc";
          }

        switch (match_keycode ((const char*)buf, length + 1, &match))
          {
            case 1: /* unique match */
              if (!match)
                return NULL;
              if (!strcmp(match->nick, "ok"))
              {
                ctx_frame_ack = 1;
                return NULL;
              }
              return match->nick;
              break;
            case 9001: /* mouse event */
              if (x) *x = ((unsigned char)buf[4]-32)*1.0;
              if (y) *y = ((unsigned char)buf[5]-32)*1.0;
              switch (buf[3])
                {
                        /* XXX : todo reduce this to less string constants */
                  case 32:  return "mouse-press";
                  case 33:  return "mouse1-press";
                  case 34:  return "mouse2-press";
                  case 40:  return "alt-mouse-press";
                  case 41:  return "alt-mouse1-press";
                  case 42:  return "alt-mouse2-press";
                  case 48:  return "control-mouse-press";
                  case 49:  return "control-mouse1-press";
                  case 50:  return "control-mouse2-press";
                  case 56:  return "alt-control-mouse-press";
                  case 57:  return "alt-control-mouse1-press";
                  case 58:  return "alt-control-mouse2-press";
                  case 64:  return "mouse-drag";
                  case 65:  return "mouse1-drag";
                  case 66:  return "mouse2-drag";
                  case 71:  return "mouse-motion"; /* shift+motion */
                  case 72:  return "alt-mouse-drag";
                  case 73:  return "alt-mouse1-drag";
                  case 74:  return "alt-mouse2-drag";
                  case 75:  return "mouse-motion"; /* alt+motion */
                  case 80:  return "control-mouse-drag";
                  case 81:  return "control-mouse1-drag";
                  case 82:  return "control-mouse2-drag";
                  case 83:  return "mouse-motion"; /* ctrl+motion */
                  case 91:  return "mouse-motion"; /* ctrl+alt+motion */
                  case 95:  return "mouse-motion"; /* ctrl+alt+shift+motion */
                  case 96:  return "scroll-up";
                  case 97:  return "scroll-down";
                  case 100: return "shift-scroll-up";
                  case 101: return "shift-scroll-down";
                  case 104: return "alt-scroll-up";
                  case 105: return "alt-scroll-down";
                  case 112: return "control-scroll-up";
                  case 113: return "control-scroll-down";
                  case 116: return "control-shift-scroll-up";
                  case 117: return "control-shift-scroll-down";
                  case 35: /* (or release) */
                  case 51: /* (or ctrl-release) */
                  case 43: /* (or alt-release) */
                  case 67: return "mouse-motion";
                           /* have a separate mouse-drag ? */
                  default: {
                             static char rbuf[100];
                             sprintf (rbuf, "mouse (unhandled state: %i)", buf[3]);
                             return rbuf;
                           }
                }
            case 0: /* no matches, bail*/
              { 
                static char ret[256];
                if (length == 0 && ctx_utf8_len (buf[0])>1) /* single unicode
                                                               char */
                  {
                    int n_read = 
                    read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1);
                    if (n_read)
                    {
                      buf[ctx_utf8_len(buf[0])]=0;
                      strcpy (ret, (const char*)buf);
                    }
                    return ret;
                  }
                if (length == 0) /* ascii */
                  {
                    buf[1]=0;
                    strcpy (ret, (const char*)buf);
                    return ret;
                  }
                sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'",
                  length>=0? buf[0]: 0, length>=0? buf[0]>31?buf[0]:'?': ' ', 
                  length>=1? buf[1]: 0, length>=1? buf[1]>31?buf[1]:'?': ' ', 
                  length>=2? buf[2]: 0, length>=2? buf[2]>31?buf[2]:'?': ' ', 
                  length>=3? buf[3]: 0, length>=3? buf[3]>31?buf[3]:'?': ' ',
                  length>=4? buf[4]: 0, length>=4? buf[4]>31?buf[4]:'?': ' ',
                  length>=5? buf[5]: 0, length>=5? buf[5]>31?buf[5]:'?': ' ',
                  length>=6? buf[6]: 0, length>=6? buf[6]>31?buf[6]:'?': ' ');
                return ret;
              }
              return NULL;
            default: /* continue */
              break;
          }
      }
    else
      return "key read eek";
  return "fail";
}

int ctx_nct_consume_events (Ctx *ctx)
{
  int ix, iy;
  CtxCtx *ctxctx = (CtxCtx*)ctx->renderer;
  const char *event = NULL;

  {
    float x, y;
    event = ctx_nct_get_event (ctx, 50, &ix, &iy);

    x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width;
    y = (iy - 1.0)       / ctxctx->rows * ctx->events.height;

    if (!strcmp (event, "mouse-press"))
    {
      ctx_pointer_press (ctx, x, y, 0, 0);
      ctxctx->was_down = 1;
    } else if (!strcmp (event, "mouse-release"))
    {
      ctx_pointer_release (ctx, x, y, 0, 0);
      ctxctx->was_down = 0;
    } else if (!strcmp (event, "mouse-motion"))
    {
      //nct_set_cursor_pos (backend->term, ix, iy);
      //nct_flush (backend->term);
      if (ctxctx->was_down)
      {
        ctx_pointer_release (ctx, x, y, 0, 0);
        ctxctx->was_down = 0;
      }
      ctx_pointer_motion (ctx, x, y, 0, 0);
    } else if (!strcmp (event, "mouse-drag"))
    {
      ctx_pointer_motion (ctx, x, y, 0, 0);
    } else if (!strcmp (event, "size-changed"))
    {
#if 0
      int width = nct_sys_terminal_width ();
      int height = nct_sys_terminal_height ();
      nct_set_size (backend->term, width, height);
      width *= CPX;
      height *= CPX;
      free (mrg->glyphs);
      free (mrg->styles);
      free (backend->nct_pixels);
      backend->nct_pixels = calloc (width * height * 4, 1);
      mrg->glyphs = calloc ((width/CPX) * (height/CPX) * 4, 1);
      mrg->styles = calloc ((width/CPX) * (height/CPX) * 1, 1);
      mrg_set_size (mrg, width, height);
      mrg_queue_draw (mrg, NULL);
#endif

    }
    else
    {
      if (!strcmp (event, "esc"))
        ctx_key_press (ctx, 0, "escape", 0);
      else if (!strcmp (event, "space"))
        ctx_key_press (ctx, 0, "space", 0);
      else if (!strcmp (event, "enter"))
        ctx_key_press (ctx, 0, "\n", 0);
      else if (!strcmp (event, "return"))
        ctx_key_press (ctx, 0, "return", 0);
      else if (!strcmp (event, "idle"))
      {
      }
      else
      ctx_key_press (ctx, 0, event, 0);
    }
  }

  return 1;
}

const char *ctx_native_get_event (Ctx *n, int timeoutms)
{
  static unsigned char buf[256];
  int length;

  if (!signal_installed)
    {
      _nc_raw ();
      signal_installed = 1;
      signal (SIGWINCH, nc_resize_term);
    }
//if (mouse_mode) // XXX too often to do it all the time!
//  printf("%s", mouse_modes[mouse_mode]);

    int got_event = 0;
  {
    int elapsed = 0;

    do {
      if (size_changed)
        {
          size_changed = 0;
          return "size-changed";
        }
      got_event = ctx_nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed));
      if (size_changed)
        {
          size_changed = 0;
          return "size-changed";
        }
      /* only do this if the client has asked for idle events,
       * and perhaps programmed the ms timer?
       */
      elapsed += MIN(DELAY_MS, timeoutms-elapsed);
      if (!got_event && timeoutms && elapsed >= timeoutms)
      {
        return "idle";
      }
    } while (!got_event);
  }

  for (length = 0; got_event && length < 200; length ++)
  {
    if (read (STDIN_FILENO, &buf[length], 1) != -1)
      {
         buf[length+1] = 0;
         if (!strcmp ((char*)buf, "\e[0n"))
         {
           ctx_frame_ack = 1;
           return NULL;
         }
         else if (buf[length]=='\n')
         {
           buf[length]=0;
           return (const char*)buf;
         }
      }
      got_event = ctx_nct_has_event (n, 5);
    }
  return NULL;
}

const char *ctx_key_get_label (Ctx  *n, const char *nick)
{
  int j;
  int found = -1;
  for (j = 0; keycodes[j].nick; j++)
    if (found == -1 && !strcmp (keycodes[j].nick, nick))
      return keycodes[j].label;
  return NULL;
}

void _ctx_mouse (Ctx *term, int mode)
{
  //if (term->is_st && mode > 1)
  //  mode = 1;
  if (mode != mouse_mode)
  {
    printf ("%s", mouse_modes[mode]);
    fflush (stdout);
  }
  mouse_mode = mode;
}


#endif

#if !__COSMOPOLITAN__
#include <sys/time.h>
#endif


#define usecs(time)    ((uint64_t)(time.tv_sec - start_time.tv_sec) * 1000000 + time.     tv_usec)

#if !__COSMOPOLITAN__
static struct timeval start_time;

static void
_ctx_init_ticks (void)
{
  static int done = 0;
  if (done)
    return;
  done = 1;
  gettimeofday (&start_time, NULL);
}

static inline unsigned long
_ctx_ticks (void)
{
  struct timeval measure_time;
  gettimeofday (&measure_time, NULL);
  return usecs (measure_time) - usecs (start_time);
}

unsigned long
ctx_ticks (void)
{
  _ctx_init_ticks ();
  return _ctx_ticks ();
}



enum _CtxFlags {
   CTX_FLAG_DIRECT = (1<<0),
};
typedef enum _CtxFlags CtxFlags;


int _ctx_max_threads = 1;
int _ctx_enable_hash_cache = 1;
#if CTX_SHAPE_CACHE
extern int _ctx_shape_cache_enabled;
#endif

#if CTX_THREADS
static mtx_t _ctx_texture_mtx;
#endif

void _ctx_texture_lock (void)
{
#if CTX_THREADS
  mtx_lock (&_ctx_texture_mtx);
#endif
}

void _ctx_texture_unlock (void)
{
#if CTX_THREADS
  mtx_unlock (&_ctx_texture_mtx);
#endif
}


void
ctx_init (int *argc, char ***argv)
{
#if 0
  if (!getenv ("CTX_VERSION"))
  {
    int i;
    char *new_argv[*argc+3];
    new_argv[0] = "ctx";
    for (i = 0; i < *argc; i++)
    {
      new_argv[i+1] = *argv[i];
    }
    new_argv[i+1] = NULL;
    execvp (new_argv[0], new_argv);
    // if this fails .. we continue normal startup
    // and end up in self-hosted braille
  }
#endif
}


#if 0
int ctx_count (Ctx *ctx)
{
  return ctx->drawlist.count;
}
#endif

extern int _ctx_damage_control;


#if CTX_EVENTS

void ctx_list_backends(void)
{
    fprintf (stderr, "possible values for CTX_BACKEND:\n");
    fprintf (stderr, " ctx");
#if CTX_SDL
    fprintf (stderr, " SDL");
#endif
#if CTX_FB
    fprintf (stderr, " fb");
    fprintf (stderr, " drm");
#endif
    fprintf (stderr, " term");
    fprintf (stderr, " termimg");
    fprintf (stderr, "\n");
}

static uint32_t ctx_ms (Ctx *ctx)
{
  return _ctx_ticks () / 1000;
}


static int is_in_ctx (void);
Ctx *ctx_new_ui (int width, int height)
{
#if CTX_TILED
  if (getenv ("CTX_DAMAGE_CONTROL"))
  {
    const char * val = getenv ("CTX_DAMAGE_CONTROL");
    if (!strcmp (val, "0") ||
        !strcmp (val, "off"))
      _ctx_damage_control = 0;
    else
      _ctx_damage_control = 1;
  }
#endif

  if (getenv ("CTX_HASH_CACHE"))
  {
    const char * val = getenv ("CTX_HASH_CACHE");
    if (!strcmp (val, "0"))
      _ctx_enable_hash_cache = 0;
    if (!strcmp (val, "off"))
      _ctx_enable_hash_cache = 0;
  }
#if CTX_SHAPE_CACHE
  if (getenv ("CTX_SHAPE_CACHE"))
  {
    const char * val = getenv ("CTX_SHAPE_CACHE");
    if (!strcmp (val, "0"))
      _ctx_shape_cache_enabled = 0;
    if (!strcmp (val, "off"))
      _ctx_shape_cache_enabled = 0;
  }
#endif

  if (getenv ("CTX_THREADS"))
  {
    int val = atoi (getenv ("CTX_THREADS"));
    _ctx_max_threads = val;
  }
  else
  {
    _ctx_max_threads = 2;
#ifdef _SC_NPROCESSORS_ONLN
    _ctx_max_threads = sysconf (_SC_NPROCESSORS_ONLN) / 2;
#endif
  }
  
#if CTX_THREADS
  mtx_init (&_ctx_texture_mtx, mtx_plain);
#endif

  if (_ctx_max_threads < 1) _ctx_max_threads = 1;
  if (_ctx_max_threads > CTX_MAX_THREADS) _ctx_max_threads = CTX_MAX_THREADS;

  //fprintf (stderr, "ctx using %i threads\n", _ctx_max_threads);
  const char *backend = getenv ("CTX_BACKEND");

  if (backend && !strcmp (backend, ""))
    backend = NULL;
  if (backend && !strcmp (backend, "auto"))
    backend = NULL;
  if (backend && !strcmp (backend, "list"))
  {
    ctx_list_backends ();
    exit (-1);
  }

  Ctx *ret = NULL;

  /* we do the query on auto but not on directly set ctx
   *
   */
  if ((backend && !strcmp(backend, "ctx")) ||
      (backend == NULL && is_in_ctx ()))
  {
    if (!backend || !strcmp (backend, "ctx"))
    {
      // full blown ctx protocol - in terminal or standalone
      ret = ctx_new_ctx (width, height);
    }
  }

#if CTX_SDL
  if (!ret && getenv ("DISPLAY"))
  {
    if ((backend==NULL) || (!strcmp (backend, "SDL")))
      ret = ctx_new_sdl (width, height);
  }
#endif

#if CTX_FB
  if (!ret && !getenv ("DISPLAY"))
  {
    if ((backend==NULL) || (!strcmp (backend, "drm")))
    ret = ctx_new_fb (width, height, 1);

    if (!ret)
    {
      if ((backend==NULL) || (!strcmp (backend, "fb")))
        ret = ctx_new_fb (width, height, 0);
    }
  }
#endif

#if CTX_RASTERIZER
  // braille in terminal
  if (!ret)
  {
    if ((backend==NULL) || (!strcmp (backend, "term")))
    ret = ctx_new_term (width, height);
  }
  if (!ret)
  {
    if ((backend==NULL) || (!strcmp (backend, "termimg")))
    ret = ctx_new_termimg (width, height);
  }
#endif
  if (!ret)
  {
    fprintf (stderr, "no interactive ctx backend\n");
    ctx_list_backends ();
    exit (2);
  }
  ctx_get_event (ret); // enables events
  return ret;
}
#endif
#else
void _ctx_texture_unlock (void)
{
}
void _ctx_texture_lock (void)
{
}

#endif
void _ctx_resized (Ctx *ctx, int width, int height, long time);

void ctx_set_size (Ctx *ctx, int width, int height)
{
#if CTX_EVENTS
  if (ctx->events.width != width || ctx->events.height != height)
  {
    ctx->events.width = width;
    ctx->events.height = height;
    _ctx_resized (ctx, width, height, 0);
  }
#endif
}

#if CTX_EVENTS


static int is_in_ctx (void)
{
  char buf[1024];
  struct termios orig_attr;
  struct termios raw;
  tcgetattr (STDIN_FILENO, &orig_attr);
  raw = orig_attr;
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    return 0;
  fprintf (stderr, "\e[?200$p");
  //tcflush(STDIN_FILENO, 1);
#if !__COSMOPOLITAN__
  tcdrain(STDIN_FILENO);
#endif
  int length = 0;
  usleep (1000 * 60); // to account for possibly lowish latency ssh,
                      // should be made configurable ; perhaps in
                      // an env var
  struct timeval tv = {0,0};
  fd_set rfds;
  
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);
  tv.tv_usec = 1000 * 5;

  for (int n = 0; select(1, &rfds, NULL, NULL, &tv) && n < 20; n++)
  {
    length += read (STDIN_FILENO, &buf[length], 1);
  }
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr);
  if (length == -1)
  {
    return 0;
  }
  char *semi = strchr (buf, ';');
  buf[length]=0;
  if (semi &&  semi[1] == '2')
  {
    return 1;
  }
  return 0;
}

typedef struct CtxIdleCb {
  int (*cb) (Ctx *ctx, void *idle_data);
  void *idle_data;

  void (*destroy_notify)(void *destroy_data);
  void *destroy_data;

  int   ticks_full;
  int   ticks_remaining;
  int   is_idle;
  int   id;
} CtxIdleCb;

void _ctx_events_init (Ctx *ctx)
{
  CtxEvents *events = &ctx->events;
  _ctx_init_ticks ();
  events->tap_delay_min  = 40;
  events->tap_delay_max  = 800;
  events->tap_delay_max  = 8000000; /* quick reflexes needed making it hard for some is an argument against very short values  */

  events->tap_delay_hold = 1000;
  events->tap_hysteresis = 32;  /* XXX: should be ppi dependent */
}


void _ctx_idle_iteration (Ctx *ctx)
{
  static unsigned long prev_ticks = 0;
  CtxList *l;
  CtxList *to_remove = NULL;
  unsigned long ticks = _ctx_ticks ();
  unsigned long tick_delta = (prev_ticks == 0) ? 0 : ticks - prev_ticks;
  prev_ticks = ticks;

  if (!ctx->events.idles)
  {
    return;
  }
  for (l = ctx->events.idles; l; l = l->next)
  {
    CtxIdleCb *item = l->data;

    if (item->ticks_remaining >= 0)
      item->ticks_remaining -= tick_delta;

    if (item->ticks_remaining < 0)
    {
      if (item->cb (ctx, item->idle_data) == 0)
        ctx_list_prepend (&to_remove, item);
      else
        item->ticks_remaining = item->ticks_full;
    }
  }
  for (l = to_remove; l; l = l->next)
  {
    CtxIdleCb *item = l->data;
    if (item->destroy_notify)
      item->destroy_notify (item->destroy_data);
    ctx_list_remove (&ctx->events.idles, l->data);
  }
}


void ctx_add_key_binding_full (Ctx *ctx,
                           const char *key,
                           const char *action,
                           const char *label,
                           CtxCb       cb,
                           void       *cb_data,
                           CtxDestroyNotify destroy_notify,
                           void       *destroy_data)
{
  CtxEvents *events = &ctx->events;
  if (events->n_bindings +1 >= CTX_MAX_KEYBINDINGS)
  {
    fprintf (stderr, "warning: binding overflow\n");
    return;
  }
  events->bindings[events->n_bindings].nick = strdup (key);
  strcpy (events->bindings[events->n_bindings].nick, key);

  if (action)
    events->bindings[events->n_bindings].command = action ? strdup (action) : NULL;
  if (label)
    events->bindings[events->n_bindings].label = label ? strdup (label) : NULL;
  events->bindings[events->n_bindings].cb = cb;
  events->bindings[events->n_bindings].cb_data = cb_data;
  events->bindings[events->n_bindings].destroy_notify = destroy_notify;
  events->bindings[events->n_bindings].destroy_data = destroy_data;
  events->n_bindings++;
}

void ctx_add_key_binding (Ctx *ctx,
                          const char *key,
                          const char *action,
                          const char *label,
                          CtxCb       cb,
                          void       *cb_data)
{
  ctx_add_key_binding_full (ctx, key, action, label, cb, cb_data, NULL, NULL);
}

void ctx_clear_bindings (Ctx *ctx)
{
  CtxEvents *events = &ctx->events;
  int i;
  for (i = 0; events->bindings[i].nick; i ++)
  {
    if (events->bindings[i].destroy_notify)
      events->bindings[i].destroy_notify (events->bindings[i].destroy_data);
    free (events->bindings[i].nick);
    if (events->bindings[i].command)
      free (events->bindings[i].command);
    if (events->bindings[i].label)
      free (events->bindings[i].label);
  }
  memset (&events->bindings, 0, sizeof (events->bindings));
  events->n_bindings = 0;
}

static void
ctx_collect_events (CtxEvent *event, void *data, void *data2);
static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2)
{
  Ctx *ctx = event->ctx;
  CtxEvents *events = &ctx->events;
  int i;
  int handled = 0;

  for (i = events->n_bindings-1; i>=0; i--)
    if (!strcmp (events->bindings[i].nick, event->string))
    {
      if (events->bindings[i].cb)
      {
        events->bindings[i].cb (event, events->bindings[i].cb_data, NULL);
        if (event->stop_propagate)
          return;
        handled = 1;
      }
    }
  if (!handled)
  for (i = events->n_bindings-1; i>=0; i--)
    if (!strcmp (events->bindings[i].nick, "unhandled"))
    {
      if (events->bindings[i].cb)
      {
        events->bindings[i].cb (event, events->bindings[i].cb_data, NULL);
        if (event->stop_propagate)
          return;
      }
    }
  ctx_collect_events (event, data1, data2);
}

CtxBinding *ctx_get_bindings (Ctx *ctx)
{
  return &ctx->events.bindings[0];
}

void ctx_remove_idle (Ctx *ctx, int handle)
{
  CtxList *l;
  CtxList *to_remove = NULL;

  if (!ctx->events.idles)
  {
    return;
  }
  for (l = ctx->events.idles; l; l = l->next)
  {
    CtxIdleCb *item = l->data;
    if (item->id == handle)
      ctx_list_prepend (&to_remove, item);
  }
  for (l = to_remove; l; l = l->next)
  {
    CtxIdleCb *item = l->data;
    if (item->destroy_notify)
      item->destroy_notify (item->destroy_data);
    ctx_list_remove (&ctx->events.idles, l->data);
  }
}

int ctx_add_timeout_full (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data,
                          void (*destroy_notify)(void *destroy_data), void *destroy_data)
{
  CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1);
  item->cb              = idle_cb;
  item->idle_data       = idle_data;
  item->id              = ++ctx->events.idle_id;
  item->ticks_full      = 
  item->ticks_remaining = ms * 1000;
  item->destroy_notify  = destroy_notify;
  item->destroy_data    = destroy_data;
  ctx_list_append (&ctx->events.idles, item);
  return item->id;
}

int ctx_add_timeout (Ctx *ctx, int ms, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data)
{
  return ctx_add_timeout_full (ctx, ms, idle_cb, idle_data, NULL, NULL);
}

int ctx_add_idle_full (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data,
                                 void (*destroy_notify)(void *destroy_data), void *destroy_data)
{
  CtxIdleCb *item = calloc (sizeof (CtxIdleCb), 1);
  item->cb = idle_cb;
  item->idle_data = idle_data;
  item->id = ++ctx->events.idle_id;
  item->ticks_full =
  item->ticks_remaining = -1;
  item->is_idle = 1;
  item->destroy_notify = destroy_notify;
  item->destroy_data = destroy_data;
  ctx_list_append (&ctx->events.idles, item);
  return item->id;
}

int ctx_add_idle (Ctx *ctx, int (*idle_cb)(Ctx *ctx, void *idle_data), void *idle_data)
{
  return ctx_add_idle_full (ctx, idle_cb, idle_data, NULL, NULL);
}

#endif
/* using bigger primes would be a good idea, this falls apart due to rounding
 * when zoomed in close
 */
static inline double ctx_path_hash (void *path)
{
  double ret = 0;
#if 0
  int i;
  cairo_path_data_t *data;
  if (!path)
    return 0.99999;
  for (i = 0; i <path->num_data; i += path->data[i].header.length)
  {
    data = &path->data[i];
    switch (data->header.type) {
      case CAIRO_PATH_MOVE_TO:
        ret *= 17;
        ret += data[1].point.x;
        ret *= 113;
        ret += data[1].point.y;
        break;
      case CAIRO_PATH_LINE_TO:
        ret *= 121;
        ret += data[1].point.x;
        ret *= 1021;
        ret += data[1].point.y;
        break;
      case CAIRO_PATH_CURVE_TO:
        ret *= 3111;
        ret += data[1].point.x;
        ret *= 23;
        ret += data[1].point.y;
        ret *= 107;
        ret += data[2].point.x;
        ret *= 739;
        ret += data[2].point.y;
        ret *= 3;
        ret += data[3].point.x;
        ret *= 51;
        ret += data[3].point.y;
        break;
      case CAIRO_PATH_CLOSE_PATH:
        ret *= 51;
        break;
    }
  }
#endif
  return ret;
}

#if CTX_EVENTS
void _ctx_item_ref (CtxItem *item)
{
  if (item->ref_count < 0)
  {
    fprintf (stderr, "EEEEK!\n");
  }
  item->ref_count++;
}


void _ctx_item_unref (CtxItem *item)
{
  if (item->ref_count <= 0)
  {
    fprintf (stderr, "EEEEK!\n");
    return;
  }
  item->ref_count--;
  if (item->ref_count <=0)
  {
    {
      int i;
      for (i = 0; i < item->cb_count; i++)
      {
        if (item->cb[i].finalize)
          item->cb[i].finalize (item->cb[i].data1, item->cb[i].data2,
                                   item->cb[i].finalize_data);
      }
    }
    if (item->path)
    {
      //cairo_path_destroy (item->path);
    }
    free (item);
  }
}


static int
path_equal (void *path,
            void *path2)
{
  //  XXX
  return 0;
}

void ctx_listen_set_cursor (Ctx      *ctx,
                            CtxCursor cursor)
{
  if (ctx->events.last_item)
  {
    ctx->events.last_item->cursor = cursor;
  }
}

void ctx_listen_full (Ctx     *ctx,
                      float    x,
                      float    y,
                      float    width,
                      float    height,
                      CtxEventType  types,
                      CtxCb    cb,
                      void    *data1,
                      void    *data2,
                      void   (*finalize)(void *listen_data,
                                         void *listen_data2,
                                         void *finalize_data),
                      void    *finalize_data)
{
  if (!ctx->events.frozen)
  {
    CtxItem *item;

    /* early bail for listeners outside screen  */
    /* XXX: fixme respect clipping */
    {
      float tx = x;
      float ty = y;
      float tw = width;
      float th = height;
      _ctx_user_to_device (&ctx->state, &tx, &ty);
      _ctx_user_to_device_distance (&ctx->state, &tw, &th);
      if (ty > ctx->events.height * 2 ||
          tx > ctx->events.width * 2 ||
          tx + tw < 0 ||
          ty + th < 0)
      {
        if (finalize)
          finalize (data1, data2, finalize_data);
        return;
      }
    }

    item = calloc (sizeof (CtxItem), 1);
    item->x0 = x;
    item->y0 = y;
    item->x1 = x + width;
    item->y1 = y + height;
    item->cb[0].types = types;
    item->cb[0].cb = cb;
    item->cb[0].data1 = data1;
    item->cb[0].data2 = data2;
    item->cb[0].finalize = finalize;
    item->cb[0].finalize_data = finalize_data;
    item->cb_count = 1;
    item->types = types;
    //item->path = cairo_copy_path (cr); // XXX
    item->path_hash = ctx_path_hash (item->path);
    ctx_get_matrix (ctx, &item->inv_matrix);
    ctx_matrix_invert (&item->inv_matrix);

    if (ctx->events.items)
    {
      CtxList *l;
      for (l = ctx->events.items; l; l = l->next)
      {
        CtxItem *item2 = l->data;

        /* store multiple callbacks for one entry when the paths
         * are exact matches, reducing per event traversal checks at the
         * cost of a little paint-hit (XXX: is this the right tradeoff,
         * perhaps it is better to spend more time during event processing
         * than during paint?)
         */
        if (item->path_hash == item2->path_hash &&
            path_equal (item->path, item2->path))
        {
          /* found an item, copy over cb data  */
          item2->cb[item2->cb_count] = item->cb[0];
          free (item);
          item2->cb_count++;
          item2->types |= types;
          return;
        }
      }
    }
    item->ref_count       = 1;
    ctx->events.last_item = item;
    ctx_list_prepend_full (&ctx->events.items, item, (void*)_ctx_item_unref, NULL);
  }
}

void ctx_event_stop_propagate (CtxEvent *event)
{
  if (event)
    event->stop_propagate = 1;
}

void ctx_listen (Ctx          *ctx,
                 CtxEventType  types,
                 CtxCb         cb,
                 void*         data1,
                 void*         data2)
{
  float x, y, width, height;
  /* generate bounding box of what to listen for - from current cairo path */
  if (types & CTX_KEY)
  {
    x = 0;
    y = 0;
    width = 0;
    height = 0;
  }
  else
  {
     float ex1,ey1,ex2,ey2;
     ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2);
     x = ex1;
     y = ey1;
     width = ex2 - ex1;
     height = ey2 - ey1;
  }

  if (types == CTX_DRAG_MOTION)
    types = CTX_DRAG_MOTION | CTX_DRAG_PRESS;
  return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, NULL, NULL);
}

void  ctx_listen_with_finalize (Ctx          *ctx,
                                CtxEventType  types,
                                CtxCb         cb,
                                void*         data1,
                                void*         data2,
                      void   (*finalize)(void *listen_data, void *listen_data2,
                                         void *finalize_data),
                      void    *finalize_data)
{
  float x, y, width, height;
  /* generate bounding box of what to listen for - from current cairo path */
  if (types & CTX_KEY)
  {
    x = 0;
    y = 0;
    width = 0;
    height = 0;
  }
  else
  {
     float ex1,ey1,ex2,ey2;
     ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2);
     x = ex1;
     y = ey1;
     width = ex2 - ex1;
     height = ey2 - ey1;
  }

  if (types == CTX_DRAG_MOTION)
    types = CTX_DRAG_MOTION | CTX_DRAG_PRESS;
  return ctx_listen_full (ctx, x, y, width, height, types, cb, data1, data2, finalize, finalize_data);
}


static void ctx_report_hit_region (CtxEvent *event,
                       void     *data,
                       void     *data2)
{
  const char *id = data;

  fprintf (stderr, "hit region %s\n", id);
  // XXX: NYI
}

void ctx_add_hit_region (Ctx *ctx, const char *id)
{
  char *id_copy = strdup (id);
  float x, y, width, height;
  /* generate bounding box of what to listen for - from current cairo path */
  {
     float ex1,ey1,ex2,ey2;
     ctx_path_extents (ctx, &ex1, &ey1, &ex2, &ey2);
     x = ex1;
     y = ey1;
     width = ex2 - ex1;
     height = ey2 - ey1;
  }
  
  return ctx_listen_full (ctx, x, y, width, height,
                          CTX_POINTER, ctx_report_hit_region,
                          id_copy, NULL, (void*)free, NULL);
}

typedef struct _CtxGrab CtxGrab;

struct _CtxGrab
{
  CtxItem *item;
  int      device_no;
  int      timeout_id;
  int      start_time;
  float    x; // for tap and hold
  float    y;
  CtxEventType  type;
};

static void grab_free (Ctx *ctx, CtxGrab *grab)
{
  if (grab->timeout_id)
  {
    ctx_remove_idle (ctx, grab->timeout_id);
    grab->timeout_id = 0;
  }
  _ctx_item_unref (grab->item);
  free (grab);
}

static void device_remove_grab (Ctx *ctx, CtxGrab *grab)
{
  ctx_list_remove (&ctx->events.grabs, grab);
  grab_free (ctx, grab);
}

static CtxGrab *device_add_grab (Ctx *ctx, int device_no, CtxItem *item, CtxEventType type)
{
  CtxGrab *grab = calloc (1, sizeof (CtxGrab));
  grab->item = item;
  grab->type = type;
  _ctx_item_ref (item);
  grab->device_no = device_no;
  ctx_list_append (&ctx->events.grabs, grab);
  return grab;
}

static CtxList *_ctx_device_get_grabs (Ctx *ctx, int device_no)
{
  CtxList *ret = NULL;
  CtxList *l;
  for (l = ctx->events.grabs; l; l = l->next)
  {
    CtxGrab *grab = l->data;
    if (grab->device_no == device_no)
      ctx_list_append (&ret, grab);
  }
  return ret;
}

static void _mrg_restore_path (Ctx *ctx, void *path)  //XXX
{
  //int i;
  //cairo_path_data_t *data;
  //cairo_new_path (cr);
  //cairo_append_path (cr, path);
}

CtxList *_ctx_detect_list (Ctx *ctx, float x, float y, CtxEventType type)
{
  CtxList *a;
  CtxList *ret = NULL;

  if (type == CTX_KEY_DOWN ||
      type == CTX_KEY_UP ||
      type == CTX_KEY_PRESS ||
      type == CTX_MESSAGE ||
      type == (CTX_KEY_DOWN|CTX_MESSAGE) ||
      type == (CTX_KEY_DOWN|CTX_KEY_UP) ||
      type == (CTX_KEY_DOWN|CTX_KEY_UP|CTX_MESSAGE))
  {
    for (a = ctx->events.items; a; a = a->next)
    {
      CtxItem *item = a->data;
      if (item->types & type)
      {
        ctx_list_prepend (&ret, item);
        return ret;
      }
    }
    return NULL;
  }

  for (a = ctx->events.items; a; a = a->next)
  {
    CtxItem *item= a->data;
  
    float u, v;
    u = x;
    v = y;
    _ctx_matrix_apply_transform (&item->inv_matrix, &u, &v);

    if (u >= item->x0 && v >= item->y0 &&
        u <  item->x1 && v <  item->y1 && 
        ((item->types & type) || ((type == CTX_SET_CURSOR) &&
        item->cursor)))
    {
      if (item->path)
      {
        _mrg_restore_path (ctx, item->path);
        if (ctx_in_fill (ctx, u, v))
        {
          ctx_begin_path (ctx);
          ctx_list_prepend (&ret, item);
        }
        ctx_begin_path (ctx);
      }
      else
      {
        ctx_list_prepend (&ret, item);
      }
    }
  }
  return ret;
}

CtxItem *_ctx_detect (Ctx *ctx, float x, float y, CtxEventType type)
{
  CtxList *l = _ctx_detect_list (ctx, x, y, type);
  if (l)
  {
    ctx_list_reverse (&l);
    CtxItem *ret = l->data;
    ctx_list_free (&l);
    return ret;
  }
  return NULL;
}

static int
_ctx_emit_cb_item (Ctx *ctx, CtxItem *item, CtxEvent *event, CtxEventType type, float x, float y)
{
  static CtxEvent s_event;
  CtxEvent transformed_event;
  int i;


  if (!event)
  {
    event = &s_event;
    event->type = type;
    event->x = x;
    event->y = y;
  }
  event->ctx = ctx;
  transformed_event = *event;
  transformed_event.device_x = event->x;
  transformed_event.device_y = event->y;

  {
    float tx, ty;
    tx = transformed_event.x;
    ty = transformed_event.y;
    _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty);
    transformed_event.x = tx;
    transformed_event.y = ty;

    if ((type & CTX_DRAG_PRESS) ||
        (type & CTX_DRAG_MOTION) ||
        (type & CTX_MOTION))   /* probably a worthwhile check for the performance 
                                  benefit
                                */
    {
      tx = transformed_event.start_x;
      ty = transformed_event.start_y;
      _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty);
      transformed_event.start_x = tx;
      transformed_event.start_y = ty;
    }


    tx = transformed_event.delta_x;
    ty = transformed_event.delta_y;
    _ctx_matrix_apply_transform (&item->inv_matrix, &tx, &ty);
    transformed_event.delta_x = tx;
    transformed_event.delta_y = ty;
  }

  transformed_event.state = ctx->events.modifier_state;
  transformed_event.type = type;

  for (i = item->cb_count-1; i >= 0; i--)
  {
    if (item->cb[i].types & type)
    {
      item->cb[i].cb (&transformed_event, item->cb[i].data1, item->cb[i].data2);
      event->stop_propagate = transformed_event.stop_propagate; /* copy back the response */
      if (event->stop_propagate)
        return event->stop_propagate;
    }
  }
  return 0;
}
#endif

#if CTX_EVENTS

#if !__COSMOPOLITAN__
#include <stdatomic.h>
#endif

int ctx_native_events = 0;
#if CTX_SDL
int ctx_sdl_events = 0;
int ctx_sdl_consume_events (Ctx *ctx);
#endif

#if CTX_FB
int ctx_fb_events = 0;
int ctx_fb_consume_events (Ctx *ctx);
#endif

int ctx_nct_consume_events (Ctx *ctx);
int ctx_nct_has_event (Ctx  *n, int delay_ms);
int ctx_ctx_consume_events (Ctx *ctx);



void ctx_consume_events (Ctx *ctx)
{
#if CTX_SDL
  if (ctx_sdl_events)
    ctx_sdl_consume_events (ctx);
  else
#endif
#if CTX_FB
  if (ctx_fb_events)
    ctx_fb_consume_events (ctx);
  else
#endif
  if (ctx_native_events)
    ctx_ctx_consume_events (ctx);
  else
    ctx_nct_consume_events (ctx);
}

int ctx_has_event (Ctx *ctx, int timeout)
{
#if CTX_SDL
  if (ctx_sdl_events)
  {
    return SDL_WaitEventTimeout (NULL, timeout);
  }
  else
#endif
#if CTX_FB
  if (ctx_fb_events)
  {
    return ctx_nct_has_event (ctx, timeout);
  }
  else
#endif
  if (ctx_native_events)
  {
    return ctx_nct_has_event (ctx, timeout);
  }
  else
  {
    return ctx_nct_has_event (ctx, timeout);
  }

  ctx_consume_events (ctx);
  if (ctx->events.events)
    return 1;
  return 0;
}

#if CTX_FB
static int ctx_fb_get_mice_fd (Ctx *ctx);
#endif

void ctx_get_event_fds (Ctx *ctx, int *fd, int *count)
{
#if CTX_SDL
  if (ctx_sdl_events)
  {
    *count = 0;
  }
  else
#endif
#if CTX_FB
  if (ctx_fb_events)
  {
    int mice_fd = ctx_fb_get_mice_fd (ctx);
    fd[0] = STDIN_FILENO;
    if (mice_fd)
    {
      fd[1] = mice_fd;
      *count = 2;
    }
    else
    {
      *count = 1;
    }
  }
  else
#endif
  if (ctx_native_events)
  {
    fd[0] = STDIN_FILENO;
    *count = 1;
  }
  else
  {
    fd[0] = STDIN_FILENO;
    *count = 1;
  }
}

CtxEvent *ctx_get_event (Ctx *ctx)
{
  static CtxEvent event_copy;
  _ctx_idle_iteration (ctx);
  if (!ctx->events.ctx_get_event_enabled)
    ctx->events.ctx_get_event_enabled = 1;

  ctx_consume_events (ctx);

  if (ctx->events.events)
    {
      event_copy = *((CtxEvent*)(ctx->events.events->data));
      ctx_list_remove (&ctx->events.events, ctx->events.events->data);
      return &event_copy;
    }
  return NULL;
}

static int
_ctx_emit_cb (Ctx *ctx, CtxList *items, CtxEvent *event, CtxEventType type, float x, float y)
{
  CtxList *l;
  event->stop_propagate = 0;
  for (l = items; l; l = l->next)
  {
    _ctx_emit_cb_item (ctx, l->data, event, type, x, y);
    if (event->stop_propagate)
      return event->stop_propagate;
  }
  return 0;
}

/*
 * update what is the currently hovered item and returns it.. and the list of hits
 * a well.
 *
 */
static CtxItem *_ctx_update_item (Ctx *ctx, int device_no, float x, float y, CtxEventType type, CtxList **hitlist)
{
  CtxItem *current = NULL;

  CtxList *l = _ctx_detect_list (ctx, x, y, type);
  if (l)
  {
    ctx_list_reverse (&l);
    current = l->data;
  }
  if (hitlist)
    *hitlist = l;
  else
    ctx_list_free (&l);

  if (ctx->events.prev[device_no] == NULL || current == NULL || (current->path_hash != ctx->events.prev[device_no]->path_hash))
  {
// enter/leave should snapshot chain to root
// and compare with previous snapshotted chain to root
// and emit/enter/leave as appropriate..
//
// leave might be registered for emission on enter..emission?


    //int focus_radius = 2;
    if (current)
      _ctx_item_ref (current);

    if (ctx->events.prev[device_no])
    {
      {
#if 0
        CtxIntRectangle rect = {floor(ctx->events.prev[device_no]->x0-focus_radius),
                             floor(ctx->events.prev[device_no]->y0-focus_radius),
                             ceil(ctx->events.prev[device_no]->x1)-floor(ctx->events.prev[device_no]->x0) + focus_radius * 2,
                             ceil(ctx->events.prev[device_no]->y1)-floor(ctx->events.prev[device_no]->y0) + focus_radius * 2};
        mrg_queue_draw (mrg, &rect);
#endif 
      }

      _ctx_emit_cb_item (ctx, ctx->events.prev[device_no], NULL, CTX_LEAVE, x, y);
      _ctx_item_unref (ctx->events.prev[device_no]);
      ctx->events.prev[device_no] = NULL;
    }
    if (current)
    {
#if 0
      {
        CtxIntRectangle rect = {floor(current->x0-focus_radius),
                             floor(current->y0-focus_radius),
                             ceil(current->x1)-floor(current->x0) + focus_radius * 2,
                             ceil(current->y1)-floor(current->y0) + focus_radius * 2};
        mrg_queue_draw (mrg, &rect);
      }
#endif
      _ctx_emit_cb_item (ctx, current, NULL, CTX_ENTER, x, y);
      ctx->events.prev[device_no] = current;
    }
  }
  current = _ctx_detect (ctx, x, y, type);
  //fprintf (stderr, "%p\n", current);
  return current;
}

static int tap_and_hold_fire (Ctx *ctx, void *data)
{
  CtxGrab *grab = data;
  CtxList *list = NULL;
  ctx_list_prepend (&list, grab->item);
  CtxEvent event = {0, };

  event.ctx = ctx;
  event.time = ctx_ms (ctx);

  event.device_x = 
  event.x = ctx->events.pointer_x[grab->device_no];
  event.device_y = 
  event.y = ctx->events.pointer_y[grab->device_no];

  // XXX: x and y coordinates
  int ret = _ctx_emit_cb (ctx, list, &event, CTX_TAP_AND_HOLD,
      ctx->events.pointer_x[grab->device_no], ctx->events.pointer_y[grab->device_no]);

  ctx_list_free (&list);

  grab->timeout_id = 0;

  return 0;

  return ret;
}

int ctx_pointer_drop (Ctx *ctx, float x, float y, int device_no, uint32_t time,
                      char *string)
{
  CtxList *l;
  CtxList *hitlist = NULL;

  ctx->events.pointer_x[device_no] = x;
  ctx->events.pointer_y[device_no] = y;
  if (device_no <= 3)
  {
    ctx->events.pointer_x[0] = x;
    ctx->events.pointer_y[0] = y;
  }

  if (device_no < 0) device_no = 0;
  if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1;
  CtxEvent *event = &ctx->events.drag_event[device_no];

  if (time == 0)
    time = ctx_ms (ctx);

  event->ctx = ctx;
  event->x = x;
  event->y = y;

  event->delta_x = event->delta_y = 0;

  event->device_no = device_no;
  event->string    = string;
  event->time      = time;
  event->stop_propagate = 0;

  _ctx_update_item (ctx, device_no, x, y, CTX_DROP, &hitlist);

  for (l = hitlist; l; l = l?l->next:NULL)
  {
    CtxItem *item = l->data;
    _ctx_emit_cb_item (ctx, item, event, CTX_DROP, x, y);

    if (event->stop_propagate)
    {
      ctx_list_free (&hitlist);
      return 0;
    }
  }

  //mrg_queue_draw (mrg, NULL); /* in case of style change, and more  */
  ctx_list_free (&hitlist);

  return 0;
}

int ctx_pointer_press (Ctx *ctx, float x, float y, int device_no, uint32_t time)
{
  CtxEvents *events = &ctx->events;
  CtxList *hitlist = NULL;
  events->pointer_x[device_no] = x;
  events->pointer_y[device_no] = y;
  if (device_no <= 3)
  {
    events->pointer_x[0] = x;
    events->pointer_y[0] = y;
  }

  if (device_no < 0) device_no = 0;
  if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1;
  CtxEvent *event = &events->drag_event[device_no];

  if (time == 0)
    time = ctx_ms (ctx);

  event->x = event->start_x = event->prev_x = x;
  event->y = event->start_y = event->prev_y = y;

  event->delta_x = event->delta_y = 0;

  event->device_no = device_no;
  event->time      = time;
  event->stop_propagate = 0;

  if (events->pointer_down[device_no] == 1)
  {
    fprintf (stderr, "events thought device %i was already down\n", device_no);
  }
  /* doing just one of these two should be enough? */
  events->pointer_down[device_no] = 1;
  switch (device_no)
  {
    case 1:
      events->modifier_state |= CTX_MODIFIER_STATE_BUTTON1;
      break;
    case 2:
      events->modifier_state |= CTX_MODIFIER_STATE_BUTTON2;
      break;
    case 3:
      events->modifier_state |= CTX_MODIFIER_STATE_BUTTON3;
      break;
    default:
      break;
  }

  CtxGrab *grab = NULL;
  CtxList *l;

  _ctx_update_item (ctx, device_no, x, y, 
      CTX_PRESS | CTX_DRAG_PRESS | CTX_TAP | CTX_TAP_AND_HOLD, &hitlist);

  for (l = hitlist; l; l = l?l->next:NULL)
  {
    CtxItem *item = l->data;
    if (item &&
        ((item->types & CTX_DRAG)||
         (item->types & CTX_TAP) ||
         (item->types & CTX_TAP_AND_HOLD)))
    {
      grab = device_add_grab (ctx, device_no, item, item->types);
      grab->start_time = time;

      if (item->types & CTX_TAP_AND_HOLD)
      {
         grab->timeout_id = ctx_add_timeout (ctx, events->tap_delay_hold, tap_and_hold_fire, grab);
      }
    }
    _ctx_emit_cb_item (ctx, item, event, CTX_PRESS, x, y);
    if (!event->stop_propagate)
      _ctx_emit_cb_item (ctx, item, event, CTX_DRAG_PRESS, x, y);

    if (event->stop_propagate)
    {
      ctx_list_free (&hitlist);
      return 0;
    }
  }

  //events_queue_draw (mrg, NULL); /* in case of style change, and more  */
  ctx_list_free (&hitlist);
  return 0;
}

void _ctx_resized (Ctx *ctx, int width, int height, long time)
{
  CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS);
  CtxEvent event = {0, };

  if (!time)
    time = ctx_ms (ctx);
  
  event.ctx = ctx;
  event.time = time;
  event.string = "resize-event"; /* gets delivered to clients as a key_down event, maybe message shouldbe used instead?
   */

  if (item)
  {
    event.stop_propagate = 0;
    _ctx_emit_cb_item (ctx, item, &event, CTX_KEY_PRESS, 0, 0);
  }

}

int ctx_pointer_release (Ctx *ctx, float x, float y, int device_no, uint32_t time)
{
  CtxEvents *events = &ctx->events;
  if (time == 0)
    time = ctx_ms (ctx);

  if (device_no < 0) device_no = 0;
  if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1;
  CtxEvent *event = &events->drag_event[device_no];

  event->time = time;
  event->x = x;
  event->ctx = ctx;
  event->y = y;
  event->device_no = device_no;
  event->stop_propagate = 0;

  switch (device_no)
  {
    case 1:
      if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON1)
        events->modifier_state -= CTX_MODIFIER_STATE_BUTTON1;
      break;
    case 2:
      if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON2)
        events->modifier_state -= CTX_MODIFIER_STATE_BUTTON2;
      break;
    case 3:
      if (events->modifier_state & CTX_MODIFIER_STATE_BUTTON3)
        events->modifier_state -= CTX_MODIFIER_STATE_BUTTON3;
      break;
    default:
      break;
  }

  //events_queue_draw (mrg, NULL); /* in case of style change */

  if (events->pointer_down[device_no] == 0)
  {
    fprintf (stderr, "device %i already up\n", device_no);
  }
  events->pointer_down[device_no] = 0;

  events->pointer_x[device_no] = x;
  events->pointer_y[device_no] = y;
  if (device_no <= 3)
  {
    events->pointer_x[0] = x;
    events->pointer_y[0] = y;
  }
  CtxList *hitlist = NULL;
  CtxList *grablist = NULL , *g= NULL;
  CtxGrab *grab;

  _ctx_update_item (ctx, device_no, x, y, CTX_RELEASE | CTX_DRAG_RELEASE, &hitlist);
  grablist = _ctx_device_get_grabs (ctx, device_no);

  for (g = grablist; g; g = g->next)
  {
    grab = g->data;

    if (!event->stop_propagate)
    {
      if (grab->item->types & CTX_TAP)
      {
        long delay = time - grab->start_time;

        if (delay > events->tap_delay_min &&
            delay < events->tap_delay_max &&
            (
              (event->start_x - x) * (event->start_x - x) +
              (event->start_y - y) * (event->start_y - y)) < ctx_pow2(events->tap_hysteresis)
            )
        {
          _ctx_emit_cb_item (ctx, grab->item, event, CTX_TAP, x, y);
        }
      }

      if (!event->stop_propagate && grab->item->types & CTX_DRAG_RELEASE)
      {
        _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_RELEASE, x, y);
      }
    }

    device_remove_grab (ctx, grab);
  }

  if (hitlist)
  {
    if (!event->stop_propagate)
      _ctx_emit_cb (ctx, hitlist, event, CTX_RELEASE, x, y);
    ctx_list_free (&hitlist);
  }
  ctx_list_free (&grablist);
  return 0;
}

/*  for multi-touch, we use a list of active grabs - thus a grab corresponds to
 *  a device id. even during drag-grabs events propagate; to stop that stop
 *  propagation.
 */
int ctx_pointer_motion (Ctx *ctx, float x, float y, int device_no, uint32_t time)
{
  CtxList *hitlist = NULL;
  CtxList *grablist = NULL, *g;
  CtxGrab *grab;

  if (device_no < 0) device_no = 0;
  if (device_no >= CTX_MAX_DEVICES) device_no = CTX_MAX_DEVICES-1;
  CtxEvent *event = &ctx->events.drag_event[device_no];

  if (time == 0)
    time = ctx_ms (ctx);

  event->ctx       = ctx;
  event->x         = x;
  event->y         = y;
  event->time      = time;
  event->device_no = device_no;
  event->stop_propagate = 0;
  
  ctx->events.pointer_x[device_no] = x;
  ctx->events.pointer_y[device_no] = y;

  if (device_no <= 3)
  {
    ctx->events.pointer_x[0] = x;
    ctx->events.pointer_y[0] = y;
  }

  grablist = _ctx_device_get_grabs (ctx, device_no);
  _ctx_update_item (ctx, device_no, x, y, CTX_MOTION, &hitlist);

  {
    CtxItem  *cursor_item = _ctx_detect (ctx, x, y, CTX_SET_CURSOR);
    if (cursor_item)
    {
      ctx_set_cursor (ctx, cursor_item->cursor);
    }
    else
    {
      ctx_set_cursor (ctx, CTX_CURSOR_ARROW);
    }
    CtxItem  *hovered_item = _ctx_detect (ctx, x, y, CTX_ANY);
    static CtxItem *prev_hovered_item = NULL;
    if (prev_hovered_item != hovered_item)
    {
      ctx_set_dirty (ctx, 1);
    }
    prev_hovered_item = hovered_item;
  }

  event->delta_x = x - event->prev_x;
  event->delta_y = y - event->prev_y;
  event->prev_x  = x;
  event->prev_y  = y;

  CtxList *remove_grabs = NULL;

  for (g = grablist; g; g = g->next)
  {
    grab = g->data;

    if ((grab->type & CTX_TAP) ||
        (grab->type & CTX_TAP_AND_HOLD))
    {
      if (
          (
            (event->start_x - x) * (event->start_x - x) +
            (event->start_y - y) * (event->start_y - y)) >
              ctx_pow2(ctx->events.tap_hysteresis)
         )
      {
        //fprintf (stderr, "-");
        ctx_list_prepend (&remove_grabs, grab);
      }
      else
      {
        //fprintf (stderr, ":");
      }
    }

    if (grab->type & CTX_DRAG_MOTION)
    {
      _ctx_emit_cb_item (ctx, grab->item, event, CTX_DRAG_MOTION, x, y);
      if (event->stop_propagate)
        break;
    }
  }
  if (remove_grabs)
  {
    for (g = remove_grabs; g; g = g->next)
      device_remove_grab (ctx, g->data);
    ctx_list_free (&remove_grabs);
  }
  if (hitlist)
  {
    if (!event->stop_propagate)
      _ctx_emit_cb (ctx, hitlist, event, CTX_MOTION, x, y);
    ctx_list_free (&hitlist);
  }
  ctx_list_free (&grablist);
  return 0;
}

void ctx_incoming_message (Ctx *ctx, const char *message, long time)
{
  CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_MESSAGE);
  CtxEvent event = {0, };

  if (!time)
    time = ctx_ms (ctx);

  if (item)
  {
    int i;
    event.ctx = ctx;
    event.type = CTX_MESSAGE;
    event.time = time;
    event.string = message;

    fprintf (stderr, "{%s|\n", message);

      for (i = 0; i < item->cb_count; i++)
      {
        if (item->cb[i].types & (CTX_MESSAGE))
        {
          event.state = ctx->events.modifier_state;
          item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2);
          if (event.stop_propagate)
            return;// event.stop_propagate;
        }
      }
  }
}

int ctx_scrolled (Ctx *ctx, float x, float y, CtxScrollDirection scroll_direction, uint32_t time)
{
  CtxList *hitlist = NULL;
  CtxList *l;

  int device_no = 0;
  ctx->events.pointer_x[device_no] = x;
  ctx->events.pointer_y[device_no] = y;

  CtxEvent *event = &ctx->events.drag_event[device_no];  /* XXX: might
                                       conflict with other code
                                       create a sibling member
                                       of drag_event?*/
  if (time == 0)
    time = ctx_ms (ctx);

  event->x         = event->start_x = event->prev_x = x;
  event->y         = event->start_y = event->prev_y = y;
  event->delta_x   = event->delta_y = 0;
  event->device_no = device_no;
  event->time      = time;
  event->stop_propagate = 0;
  event->scroll_direction = scroll_direction;

  _ctx_update_item (ctx, device_no, x, y, CTX_SCROLL, &hitlist);

  for (l = hitlist; l; l = l?l->next:NULL)
  {
    CtxItem *item = l->data;

    _ctx_emit_cb_item (ctx, item, event, CTX_SCROLL, x, y);

    if (event->stop_propagate)
      l = NULL;
  }

  //mrg_queue_draw (mrg, NULL); /* in case of style change, and more  */
  ctx_list_free (&hitlist);
  return 0;
}

static int ctx_str_has_prefix (const char *string, const char *prefix)
{
  for (int i = 0; prefix[i]; i++)
  {
    if (!string[i]) return 0;
    if (string[i] != prefix[i]) return 0;
  }
  return 0;
}

int ctx_key_press (Ctx *ctx, unsigned int keyval,
                   const char *string, uint32_t time)
{
  char event_type[128]="";
  float x, y; int b;
  sscanf (string, "%s %f %f %i", event_type, &x, &y, &b);
  if (!strcmp (event_type, "mouse-motion") ||
      !strcmp (event_type, "mouse-drag"))
    return ctx_pointer_motion (ctx, x, y, b, 0);
  else if (!strcmp (event_type, "mouse-press"))
    return ctx_pointer_press (ctx, x, y, b, 0);
  else if (!strcmp (event_type, "mouse-release"))
    return ctx_pointer_release (ctx, x, y, b, 0);
  //else if (!strcmp (event_type, "keydown"))
  //  return ctx_key_down (ctx, keyval, string + 8, time);
  //else if (!strcmp (event_type, "keyup"))
  //  return ctx_key_up (ctx, keyval, string + 6, time);

  CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_PRESS);
  CtxEvent event = {0,};

  if (time == 0)
    time = ctx_ms (ctx);
  if (item)
  {
    int i;
    event.ctx = ctx;
    event.type = CTX_KEY_PRESS;
    event.unicode = keyval; 
    event.string = strdup(string);
    event.stop_propagate = 0;
    event.time = time;

    for (i = 0; i < item->cb_count; i++)
    {
      if (item->cb[i].types & (CTX_KEY_PRESS))
      {
        event.state = ctx->events.modifier_state;
        item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2);
        if (event.stop_propagate)
        {
          free ((void*)event.string);
          return event.stop_propagate;
        }
      }
    }
    free ((void*)event.string);
  }
  return 0;
}

int ctx_key_down (Ctx *ctx, unsigned int keyval,
                  const char *string, uint32_t time)
{
  CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_DOWN);
  CtxEvent event = {0,};

  if (time == 0)
    time = ctx_ms (ctx);
  if (item)
  {
    int i;
    event.ctx     = ctx;
    event.type    = CTX_KEY_DOWN;
    event.unicode = keyval; 
    event.string  = strdup(string);
    event.stop_propagate = 0;
    event.time    = time;

    for (i = 0; i < item->cb_count; i++)
    {
      if (item->cb[i].types & (CTX_KEY_DOWN))
      {
        event.state = ctx->events.modifier_state;
        item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2);
        if (event.stop_propagate)
        {
          free ((void*)event.string);
          return event.stop_propagate;
        }
      }
    }
    free ((void*)event.string);
  }
  return 0;
}

int ctx_key_up (Ctx *ctx, unsigned int keyval,
                const char *string, uint32_t time)
{
  CtxItem *item = _ctx_detect (ctx, 0, 0, CTX_KEY_UP);
  CtxEvent event = {0,};

  if (time == 0)
    time = ctx_ms (ctx);
  if (item)
  {
    int i;
    event.ctx = ctx;
    event.type = CTX_KEY_UP;
    event.unicode = keyval; 
    event.string = strdup(string);
    event.stop_propagate = 0;
    event.time = time;

    for (i = 0; i < item->cb_count; i++)
    {
      if (item->cb[i].types & (CTX_KEY_UP))
      {
        event.state = ctx->events.modifier_state;
        item->cb[i].cb (&event, item->cb[i].data1, item->cb[i].data2);
        if (event.stop_propagate)
        {
          free ((void*)event.string);
          return event.stop_propagate;
        }
      }
    }
    free ((void*)event.string);
  }
  return 0;

  return 0;
}

void ctx_freeze           (Ctx *ctx)
{
  ctx->events.frozen ++;
}

void ctx_thaw             (Ctx *ctx)
{
  ctx->events.frozen --;
}
int ctx_events_frozen (Ctx *ctx)
{
  return ctx && ctx->events.frozen;
}
void ctx_events_clear_items (Ctx *ctx)
{
  ctx_list_free (&ctx->events.items);
}
int ctx_events_width (Ctx *ctx)
{
  return ctx->events.width;
}
int ctx_events_height (Ctx *ctx)
{
  return ctx->events.height;
}

float ctx_pointer_x (Ctx *ctx)
{
  return ctx->events.pointer_x[0];
}

float ctx_pointer_y (Ctx *ctx)
{
  return ctx->events.pointer_y[0];
}

int ctx_pointer_is_down (Ctx *ctx, int no)
{
  if (no < 0 || no > CTX_MAX_DEVICES) return 0;
  return ctx->events.pointer_down[no];
}

void _ctx_debug_overlays (Ctx *ctx)
{
  CtxList *a;
  ctx_save (ctx);

  ctx_line_width (ctx, 2);
  ctx_rgba (ctx, 0,0,0.8,0.5);
  for (a = ctx->events.items; a; a = a->next)
  {
    float current_x = ctx_pointer_x (ctx);
    float current_y = ctx_pointer_y (ctx);
    CtxItem *item = a->data;
    CtxMatrix matrix = item->inv_matrix;

    _ctx_matrix_apply_transform (&matrix, &current_x, &current_y);

    if (current_x >= item->x0 && current_x < item->x1 &&
        current_y >= item->y0 && current_y < item->y1)
    {
      ctx_matrix_invert (&matrix);
      ctx_set_matrix (ctx, &matrix);
      _mrg_restore_path (ctx, item->path);
      ctx_stroke (ctx);
    }
  }
  ctx_restore (ctx);
}

void ctx_set_render_threads   (Ctx *ctx, int n_threads)
{
  // XXX
}
int ctx_get_render_threads   (Ctx *ctx)
{
  return _ctx_max_threads;
}
void ctx_set_hash_cache (Ctx *ctx, int enable_hash_cache)
{
  _ctx_enable_hash_cache = enable_hash_cache;
}
int ctx_get_hash_cache (Ctx *ctx)
{
  return _ctx_enable_hash_cache;
}

int ctx_is_dirty (Ctx *ctx)
{
  return ctx->dirty;
}
void ctx_set_dirty (Ctx *ctx, int dirty)
{
  ctx->dirty = dirty;
}

/*
 * centralized global API for managing file descriptors that
 * wake us up, this to remove sleeping and polling
 */

#define CTX_MAX_LISTEN_FDS 128 // becomes max clients..

static int _ctx_listen_fd[CTX_MAX_LISTEN_FDS];
static int _ctx_listen_fds    = 0;
static int _ctx_listen_max_fd = 0;

void _ctx_add_listen_fd (int fd)
{
  _ctx_listen_fd[_ctx_listen_fds++]=fd;
  if (fd > _ctx_listen_max_fd)
    _ctx_listen_max_fd = fd;
}

void _ctx_remove_listen_fd (int fd)
{
  for (int i = 0; i < _ctx_listen_fds; i++)
  {
    if (_ctx_listen_fd[i] == fd)
    {
      _ctx_listen_fd[i] = _ctx_listen_fd[_ctx_listen_fds-1];
      _ctx_listen_fds--;
      return;
    }
  }
}

int ctx_input_pending (Ctx *ctx, int timeout)
{
  struct timeval tv;
  fd_set fdset;
  FD_ZERO (&fdset);
  for (int i = 0; i < _ctx_listen_fds; i++)
  {
    FD_SET (_ctx_listen_fd[i], &fdset);
  }
  int input_fds[5];
  int n_fds;
  ctx_get_event_fds (ctx, input_fds, &n_fds);
  for (int i = 0; i < n_fds; i++)
  {
    FD_SET (input_fds[i], &fdset);
  }

  tv.tv_sec = 0;
  tv.tv_usec = timeout;
  tv.tv_sec = timeout / 1000000;
  tv.tv_usec = timeout % 1000000;
  int retval = select (_ctx_listen_max_fd + 1, &fdset, NULL, NULL, &tv);
  if (retval == -1)
  {
    perror ("select");
    return 0;
  }
  return retval;
}

void ctx_sdl_set_title (void *self, const char *new_title);
void ctx_set_title (Ctx *ctx, const char *title)
{
#if CTX_SDL
     // XXX also check we're first/only client?
   if (ctx_renderer_is_sdl (ctx))
     ctx_sdl_set_title (ctx_get_renderer (ctx), title);
#endif
}

#endif
/* the parser comes in the end, nothing in ctx knows about the parser  */

#if CTX_PARSER

/* ctx parser, */

#define CTX_ID_MAXLEN 64 // in use should not be more than 40!
                         // to offer headroom for multiplexing


#define CTX_REPORT_COL_ROW 0

struct
  _CtxParser
{
  Ctx       *ctx;
  int        t_args; // total number of arguments seen for current command
  int        state;
#if CTX_PARSER_FIXED_TEMP
  uint8_t    holding[CTX_PARSER_MAXLEN]; /*  */
#else
  uint8_t   *holding;
#endif
  int        hold_len;
  int        pos;

#if CTX_REPORT_COL_ROW
  int        line; /*  for error reporting */
  int        col;  /*  for error reporting */
#endif
  float      numbers[CTX_PARSER_MAX_ARGS+1];
  int        n_numbers;
  int        decimal;
  CtxCode    command;
  int        expected_args; /* low digits are literal higher values
                               carry special meaning */
  int        n_args;
  int        texture_done;
  uint8_t    texture_id[CTX_ID_MAXLEN]; // used in defineTexture only
  uint64_t   set_key_hash;
  float      pcx;
  float      pcy;
  int        color_components;
  int        color_stroke; // 0 is fill source  1 is stroke source
  CtxColorModel   color_model; // 1 gray 3 rgb 4 cmyk
  float      left_margin; // set by last user provided move_to
  int        width;       // <- maybe should be float
  int        height;
  float      cell_width;
  float      cell_height;
  int        cursor_x;    // <- leaking in from terminal
  int        cursor_y;

  int        translate_origin;

  CtxColorSpace   color_space_slot;

  void (*exit) (void *exit_data);
  void *exit_data;
  int   (*set_prop)(void *prop_data, uint64_t key, const char *data,  int len);
  int   (*get_prop)(void *prop_data, const char *key, char **data, int *len);
  void *prop_data;
  int   prev_byte;
};

void
ctx_parser_set_size (CtxParser *parser,
                 int        width,
                 int        height,
                 float      cell_width,
                 float      cell_height)
{
  if (cell_width > 0)
    parser->cell_width       = cell_width;
  if (cell_height > 0)
    parser->cell_height      = cell_height;
  if (width > 0)
    parser->width            = width;
  if (height > 0)
    parser->height           = height;
}

static CtxParser *
ctx_parser_init (CtxParser *parser,
                 Ctx       *ctx,
                 int        width,
                 int        height,
                 float      cell_width,
                 float      cell_height,
                 int        cursor_x,
                 int        cursor_y,
  int   (*set_prop)(void *prop_data, uint64_t key, const char *data,  int len),
  int   (*get_prop)(void *prop_Data, const char *key, char **data, int *len),
                 void  *prop_data,
                 void (*exit) (void *exit_data),
                 void *exit_data
                )
{
  ctx_memset (parser, 0, sizeof (CtxParser) );
#if CTX_REPORT_COL_ROW
  parser->line             = 1;
#endif
  parser->ctx              = ctx;
  parser->cell_width       = cell_width;
  parser->cell_height      = cell_height;
  parser->cursor_x         = cursor_x;
  parser->cursor_y         = cursor_y;
  parser->width            = width;
  parser->height           = height;
  parser->exit             = exit;
  parser->exit_data        = exit_data;
  parser->color_model      = CTX_RGBA;
  parser->color_stroke     = 0;
  parser->color_components = 4;
  parser->command          = CTX_MOVE_TO;
  parser->set_prop         = set_prop;
  parser->get_prop         = get_prop;
  parser->prop_data        = prop_data;
  return parser;
}

CtxParser *ctx_parser_new (
  Ctx       *ctx,
  int        width,
  int        height,
  float      cell_width,
  float      cell_height,
  int        cursor_x,
  int        cursor_y,
  int   (*set_prop)(void *prop_data, uint64_t key, const char *data,  int len),
  int   (*get_prop)(void *prop_Data, const char *key, char **data, int *len),
  void  *prop_data,
  void (*exit) (void *exit_data),
  void *exit_data)
{
  return ctx_parser_init ( (CtxParser *) ctx_calloc (sizeof (CtxParser), 1),
                           ctx,
                           width, height,
                           cell_width, cell_height,
                           cursor_x, cursor_y, set_prop, get_prop, prop_data,
                           exit, exit_data);
}

void ctx_parser_free (CtxParser *parser)
{
#if !CTX_PARSER_FIXED_TEMP
  if (parser->holding)
    free (parser->holding);
#endif
  free (parser);
}

#define CTX_ARG_COLLECT_NUMBERS             50
#define CTX_ARG_STRING_OR_NUMBER            100
#define CTX_ARG_NUMBER_OF_COMPONENTS        200
#define CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1 201

static int ctx_arguments_for_code (CtxCode code)
{
  switch (code)
    {
      case CTX_SAVE:
      case CTX_START_GROUP:
      case CTX_END_GROUP:
      case CTX_IDENTITY:
      case CTX_CLOSE_PATH:
      case CTX_BEGIN_PATH:
      case CTX_RESET:
      case CTX_FLUSH:
      case CTX_RESTORE:
      case CTX_STROKE:
      case CTX_FILL:
      case CTX_NEW_PAGE:
      case CTX_CLIP:
      case CTX_EXIT:
        return 0;
      case CTX_GLOBAL_ALPHA:
      case CTX_COMPOSITING_MODE:
      case CTX_BLEND_MODE:
      case CTX_FONT_SIZE:
      case CTX_LINE_JOIN:
      case CTX_LINE_CAP:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
      case CTX_IMAGE_SMOOTHING:
      case CTX_SHADOW_BLUR:
      case CTX_SHADOW_OFFSET_X:
      case CTX_SHADOW_OFFSET_Y:
      case CTX_FILL_RULE:
      case CTX_TEXT_ALIGN:
      case CTX_TEXT_BASELINE:
      case CTX_TEXT_DIRECTION:
      case CTX_MITER_LIMIT:
      case CTX_REL_VER_LINE_TO:
      case CTX_REL_HOR_LINE_TO:
      case CTX_HOR_LINE_TO:
      case CTX_VER_LINE_TO:
      case CTX_FONT:
      case CTX_ROTATE:
      case CTX_GLYPH:
        return 1;
      case CTX_TRANSLATE:
      case CTX_REL_SMOOTHQ_TO:
      case CTX_LINE_TO:
      case CTX_MOVE_TO:
      case CTX_SCALE:
      case CTX_REL_LINE_TO:
      case CTX_REL_MOVE_TO:
      case CTX_SMOOTHQ_TO:
        return 2;
      case CTX_LINEAR_GRADIENT:
      case CTX_REL_QUAD_TO:
      case CTX_QUAD_TO:
      case CTX_RECTANGLE:
      case CTX_FILL_RECT:
      case CTX_STROKE_RECT:
      case CTX_REL_SMOOTH_TO:
      case CTX_VIEW_BOX:
      case CTX_SMOOTH_TO:
        return 4;
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
      case CTX_ROUND_RECTANGLE:
        return 5;
      case CTX_ARC:
      case CTX_CURVE_TO:
      case CTX_REL_CURVE_TO:
      case CTX_APPLY_TRANSFORM:
      case CTX_SOURCE_TRANSFORM:
      case CTX_RADIAL_GRADIENT:
        return 6;
      case CTX_STROKE_TEXT:
      case CTX_TEXT:
      case CTX_COLOR_SPACE:
      case CTX_DEFINE_GLYPH:
      case CTX_KERNING_PAIR:
      case CTX_TEXTURE:
      case CTX_DEFINE_TEXTURE:
        return CTX_ARG_STRING_OR_NUMBER;
      case CTX_LINE_DASH: /* append to current dashes for each argument encountered */
        return CTX_ARG_COLLECT_NUMBERS;
      //case CTX_SET_KEY:
      case CTX_COLOR:
      case CTX_SHADOW_COLOR:
        return CTX_ARG_NUMBER_OF_COMPONENTS;
      case CTX_GRADIENT_STOP:
        return CTX_ARG_NUMBER_OF_COMPONENTS_PLUS_1;

        default:
#if 1
        case CTX_SET_RGBA_U8:
        case CTX_NOP:
        case CTX_NEW_EDGE:
        case CTX_EDGE:
        case CTX_EDGE_FLIPPED:
        case CTX_CONT:
        case CTX_DATA:
        case CTX_DATA_REV:
        case CTX_SET_PIXEL:
        case CTX_REL_LINE_TO_X4:
        case CTX_REL_LINE_TO_REL_CURVE_TO:
        case CTX_REL_CURVE_TO_REL_LINE_TO:
        case CTX_REL_CURVE_TO_REL_MOVE_TO:
        case CTX_REL_LINE_TO_X2:
        case CTX_MOVE_TO_REL_LINE_TO:
        case CTX_REL_LINE_TO_REL_MOVE_TO:
        case CTX_FILL_MOVE_TO:
        case CTX_REL_QUAD_TO_REL_QUAD_TO:
        case CTX_REL_QUAD_TO_S16:
        case CTX_STROKE_SOURCE:
#endif
        return 0;
    }
}

static int ctx_parser_set_command (CtxParser *parser, CtxCode code)
{
  if (code < 150 && code >= 32)
  {
  parser->expected_args = ctx_arguments_for_code (code);
  parser->n_args = 0;
  parser->texture_done = 0;
  if (parser->expected_args >= CTX_ARG_NUMBER_OF_COMPONENTS)
    {
      parser->expected_args = (parser->expected_args % 100) + parser->color_components;
    }
  }
  return code;
}

static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke);

static int ctx_parser_resolve_command (CtxParser *parser, const uint8_t *str)
{
  uint64_t ret = str[0]; /* if it is single char it already is the CtxCode */

  /* this is handled outside the hashing to make it possible to be case insensitive
   * with the rest.
   */
  if (str[0] == CTX_SET_KEY && str[1] && str[2] == 0)
  {
    switch (str[1])
    {
      case 'm': return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE);
      case 'B': return ctx_parser_set_command (parser, CTX_BLEND_MODE);
      case 'l': return ctx_parser_set_command (parser, CTX_MITER_LIMIT);
      case 't': return ctx_parser_set_command (parser, CTX_TEXT_ALIGN);
      case 'b': return ctx_parser_set_command (parser, CTX_TEXT_BASELINE);
      case 'd': return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION);
      case 'j': return ctx_parser_set_command (parser, CTX_LINE_JOIN);
      case 'c': return ctx_parser_set_command (parser, CTX_LINE_CAP);
      case 'w': return ctx_parser_set_command (parser, CTX_LINE_WIDTH);
      case 'D': return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET);
      case 'S': return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING);
      case 'C': return ctx_parser_set_command (parser, CTX_SHADOW_COLOR);
      case 's': return ctx_parser_set_command (parser, CTX_SHADOW_BLUR);
      case 'x': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X);
      case 'y': return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y);
      case 'a': return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA);
      case 'f': return ctx_parser_set_command (parser, CTX_FONT_SIZE);
      case 'r': return ctx_parser_set_command (parser, CTX_FILL_RULE);
    }
  }

  if (str[0] && str[1])
    {
      uint64_t str_hash;
      /* trim ctx_ and CTX_ prefix */
      if ( (str[0] == 'c' && str[1] == 't' && str[2] == 'x' && str[3] == '_') ||
           (str[0] == 'C' && str[1] == 'T' && str[2] == 'X' && str[3] == '_') )
        {
          str += 4;
        }
      if ( (str[0] == 's' && str[1] == 'e' && str[2] == 't' && str[3] == '_') )
        { str += 4; }
      str_hash = ctx_strhash ( (char *) str);
      switch (str_hash)
        {
          /* first a list of mappings to one_char hashes, handled in a
           * separate fast path switch without hashing
           */
          case CTX_arcTo:          ret = CTX_ARC_TO; break;
          case CTX_arc:            ret = CTX_ARC; break;
          case CTX_curveTo:        ret = CTX_CURVE_TO; break;
          case CTX_restore:        ret = CTX_RESTORE; break;
          case CTX_stroke:         ret = CTX_STROKE; break;
          case CTX_fill:           ret = CTX_FILL; break;
          case CTX_flush:          ret = CTX_FLUSH; break;
          case CTX_horLineTo:      ret = CTX_HOR_LINE_TO; break;
          case CTX_rotate:         ret = CTX_ROTATE; break;
          case CTX_color:          ret = CTX_COLOR; break;
          case CTX_lineTo:         ret = CTX_LINE_TO; break;
          case CTX_moveTo:         ret = CTX_MOVE_TO; break;
          case CTX_scale:          ret = CTX_SCALE; break;
          case CTX_newPage:        ret = CTX_NEW_PAGE; break;
          case CTX_quadTo:         ret = CTX_QUAD_TO; break;
          case CTX_viewBox:        ret = CTX_VIEW_BOX; break;
          case CTX_smooth_to:      ret = CTX_SMOOTH_TO; break;
          case CTX_smooth_quad_to: ret = CTX_SMOOTHQ_TO; break;
          case CTX_clear:          ret = CTX_COMPOSITE_CLEAR; break;
          case CTX_copy:           ret = CTX_COMPOSITE_COPY; break;
          case CTX_destinationOver:  ret = CTX_COMPOSITE_DESTINATION_OVER; break;
          case CTX_destinationIn:    ret = CTX_COMPOSITE_DESTINATION_IN; break;
          case CTX_destinationOut:   ret = CTX_COMPOSITE_DESTINATION_OUT; break;
          case CTX_sourceOver:       ret = CTX_COMPOSITE_SOURCE_OVER; break;
          case CTX_sourceAtop:       ret = CTX_COMPOSITE_SOURCE_ATOP; break;
          case CTX_destinationAtop:  ret = CTX_COMPOSITE_DESTINATION_ATOP; break;
          case CTX_sourceOut:        ret = CTX_COMPOSITE_SOURCE_OUT; break;
          case CTX_sourceIn:         ret = CTX_COMPOSITE_SOURCE_IN; break;
          case CTX_xor:              ret = CTX_COMPOSITE_XOR; break;
          case CTX_darken:           ret = CTX_BLEND_DARKEN; break;
          case CTX_lighten:          ret = CTX_BLEND_LIGHTEN; break;
          //case CTX_color:          ret = CTX_BLEND_COLOR; break;
          //
          //  XXX check that he special casing for color works
          //      it is the first collision and it is due to our own
          //      color, not w3c for now unique use of it
          //
          case CTX_hue:            ret = CTX_BLEND_HUE; break;
          case CTX_multiply:       ret = CTX_BLEND_MULTIPLY; break;
          case CTX_normal:         ret = CTX_BLEND_NORMAL;break;
          case CTX_screen:         ret = CTX_BLEND_SCREEN;break;
          case CTX_difference:     ret = CTX_BLEND_DIFFERENCE; break;
          case CTX_reset:          ret = CTX_RESET; break;
          case CTX_verLineTo:      ret = CTX_VER_LINE_TO; break;
          case CTX_exit:
          case CTX_done:           ret = CTX_EXIT; break;
          case CTX_closePath:      ret = CTX_CLOSE_PATH; break;
          case CTX_beginPath:
          case CTX_newPath:        ret = CTX_BEGIN_PATH; break;
          case CTX_relArcTo:       ret = CTX_REL_ARC_TO; break;
          case CTX_clip:           ret = CTX_CLIP; break;
          case CTX_relCurveTo:     ret = CTX_REL_CURVE_TO; break;
          case CTX_startGroup:     ret = CTX_START_GROUP; break;
          case CTX_endGroup:       ret = CTX_END_GROUP; break;
          case CTX_save:           ret = CTX_SAVE; break;
          case CTX_translate:      ret = CTX_TRANSLATE; break;
          case CTX_linearGradient: ret = CTX_LINEAR_GRADIENT; break;
          case CTX_relHorLineTo:   ret = CTX_REL_HOR_LINE_TO; break;
          case CTX_relLineTo:      ret = CTX_REL_LINE_TO; break;
          case CTX_relMoveTo:      ret = CTX_REL_MOVE_TO; break;
          case CTX_font:           ret = CTX_FONT; break;
          case CTX_radialGradient:ret = CTX_RADIAL_GRADIENT; break;
          case CTX_gradientAddStop:
          case CTX_addStop:        ret = CTX_GRADIENT_STOP; break;
          case CTX_relQuadTo:      ret = CTX_REL_QUAD_TO; break;
          case CTX_rectangle:
          case CTX_rect:           ret = CTX_RECTANGLE; break;
          case CTX_roundRectangle: ret = CTX_ROUND_RECTANGLE; break;
          case CTX_relSmoothTo:    ret = CTX_REL_SMOOTH_TO; break;
          case CTX_relSmoothqTo:   ret = CTX_REL_SMOOTHQ_TO; break;
          case CTX_strokeText:     ret = CTX_STROKE_TEXT; break;
          case CTX_strokeRect:     ret = CTX_STROKE_RECT; break;
          case CTX_fillRect:       ret = CTX_FILL_RECT; break;
          case CTX_relVerLineTo:   ret = CTX_REL_VER_LINE_TO; break;
          case CTX_text:           ret = CTX_TEXT; break;
          case CTX_identity:       ret = CTX_IDENTITY; break;
          case CTX_transform:      ret = CTX_APPLY_TRANSFORM; break;
          case CTX_sourceTransform: ret = CTX_SOURCE_TRANSFORM; break;
          case CTX_texture:        ret = CTX_TEXTURE; break;
          case CTX_defineTexture:  ret = CTX_DEFINE_TEXTURE; break;
#if 0
          case CTX_rgbSpace:
            return ctx_parser_set_command (parser, CTX_SET_RGB_SPACE);
          case CTX_cmykSpace:
            return ctx_parser_set_command (parser, CTX_SET_CMYK_SPACE);
          case CTX_drgbSpace:
            return ctx_parser_set_command (parser, CTX_SET_DRGB_SPACE);
#endif
          case CTX_defineGlyph:
            return ctx_parser_set_command (parser, CTX_DEFINE_GLYPH);
          case CTX_kerningPair:
            return ctx_parser_set_command (parser, CTX_KERNING_PAIR);

          case CTX_colorSpace:
            return ctx_parser_set_command (parser, CTX_COLOR_SPACE);
          case CTX_fillRule:
            return ctx_parser_set_command (parser, CTX_FILL_RULE);
          case CTX_fontSize:
          case CTX_setFontSize:
            return ctx_parser_set_command (parser, CTX_FONT_SIZE);
          case CTX_compositingMode:
            return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE);

          case CTX_blend:
          case CTX_blending:
          case CTX_blendMode:
            return ctx_parser_set_command (parser, CTX_BLEND_MODE);

          case CTX_miterLimit:
            return ctx_parser_set_command (parser, CTX_MITER_LIMIT);
          case CTX_textAlign:
            return ctx_parser_set_command (parser, CTX_TEXT_ALIGN);
          case CTX_textBaseline:
            return ctx_parser_set_command (parser, CTX_TEXT_BASELINE);
          case CTX_textDirection:
            return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION);
          case CTX_join:
          case CTX_lineJoin:
          case CTX_setLineJoin:
            return ctx_parser_set_command (parser, CTX_LINE_JOIN);
          case CTX_glyph:
            return ctx_parser_set_command (parser, CTX_GLYPH);
          case CTX_cap:
          case CTX_lineCap:
          case CTX_setLineCap:
            return ctx_parser_set_command (parser, CTX_LINE_CAP);
          case CTX_lineDash:
            return ctx_parser_set_command (parser, CTX_LINE_DASH);
          case CTX_lineWidth:
          case CTX_setLineWidth:
            return ctx_parser_set_command (parser, CTX_LINE_WIDTH);
          case CTX_lineDashOffset:
            return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET);
          case CTX_imageSmoothing:
            return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING);
          case CTX_shadowColor:
            return ctx_parser_set_command (parser, CTX_SHADOW_COLOR);
          case CTX_shadowBlur:
            return ctx_parser_set_command (parser, CTX_SHADOW_BLUR);
          case CTX_shadowOffsetX:
            return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X);
          case CTX_shadowOffsetY:
            return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y);
          case CTX_globalAlpha:
            return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA);

          case CTX_strokeSource:
            return ctx_parser_set_command (parser, CTX_STROKE_SOURCE);

          /* strings are handled directly here,
           * instead of in the one-char handler, using return instead of break
           */
          case CTX_gray:
            ctx_parser_set_color_model (parser, CTX_GRAY, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_graya:
            ctx_parser_set_color_model (parser, CTX_GRAYA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_rgb:
            ctx_parser_set_color_model (parser, CTX_RGB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_drgb:
            ctx_parser_set_color_model (parser, CTX_DRGB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_rgba:
            ctx_parser_set_color_model (parser, CTX_RGBA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_drgba:
            ctx_parser_set_color_model (parser, CTX_DRGBA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_cmyk:
            ctx_parser_set_color_model (parser, CTX_CMYK, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_cmyka:
            ctx_parser_set_color_model (parser, CTX_CMYKA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_lab:
            ctx_parser_set_color_model (parser, CTX_LAB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_laba:
            ctx_parser_set_color_model (parser, CTX_LABA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_lch:
            ctx_parser_set_color_model (parser, CTX_LCH, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_lcha:
            ctx_parser_set_color_model (parser, CTX_LCHA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);

          /* and a full repeat of the above, with S for Stroke suffix */
          case CTX_grayS:
            ctx_parser_set_color_model (parser, CTX_GRAY, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_grayaS:
            ctx_parser_set_color_model (parser, CTX_GRAYA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_rgbS:
            ctx_parser_set_color_model (parser, CTX_RGB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_drgbS:
            ctx_parser_set_color_model (parser, CTX_DRGB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_rgbaS:
            ctx_parser_set_color_model (parser, CTX_RGBA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_drgbaS:
            ctx_parser_set_color_model (parser, CTX_DRGBA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_cmykS:
            ctx_parser_set_color_model (parser, CTX_CMYK, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_cmykaS:
            ctx_parser_set_color_model (parser, CTX_CMYKA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_labS:
            ctx_parser_set_color_model (parser, CTX_LAB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_labaS:
            ctx_parser_set_color_model (parser, CTX_LABA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_lchS:
            ctx_parser_set_color_model (parser, CTX_LCH, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case CTX_lchaS:
            ctx_parser_set_color_model (parser, CTX_LCHA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);

          /* words that correspond to low integer constants
          */
          case CTX_nonzero:     return CTX_FILL_RULE_WINDING;
          case CTX_non_zero:    return CTX_FILL_RULE_WINDING;
          case CTX_winding:     return CTX_FILL_RULE_WINDING;
          case CTX_evenOdd:
          case CTX_even_odd:    return CTX_FILL_RULE_EVEN_ODD;
          case CTX_bevel:       return CTX_JOIN_BEVEL;
          case CTX_round:       return CTX_JOIN_ROUND;
          case CTX_miter:       return CTX_JOIN_MITER;
          case CTX_none:        return CTX_CAP_NONE;
          case CTX_square:      return CTX_CAP_SQUARE;
          case CTX_start:       return CTX_TEXT_ALIGN_START;
          case CTX_end:         return CTX_TEXT_ALIGN_END;
          case CTX_left:        return CTX_TEXT_ALIGN_LEFT;
          case CTX_right:       return CTX_TEXT_ALIGN_RIGHT;
          case CTX_center:      return CTX_TEXT_ALIGN_CENTER;
          case CTX_top:         return CTX_TEXT_BASELINE_TOP;
          case CTX_bottom :     return CTX_TEXT_BASELINE_BOTTOM;
          case CTX_middle:      return CTX_TEXT_BASELINE_MIDDLE;
          case CTX_alphabetic:  return CTX_TEXT_BASELINE_ALPHABETIC;
          case CTX_hanging:     return CTX_TEXT_BASELINE_HANGING;
          case CTX_ideographic: return CTX_TEXT_BASELINE_IDEOGRAPHIC;

          case CTX_userRGB:     return CTX_COLOR_SPACE_USER_RGB;
          case CTX_deviceRGB:   return CTX_COLOR_SPACE_DEVICE_RGB;
          case CTX_userCMYK:    return CTX_COLOR_SPACE_USER_CMYK;
          case CTX_deviceCMYK:  return CTX_COLOR_SPACE_DEVICE_CMYK;
#undef STR
#undef LOWER
          default:
            ret = str_hash;
        }
    }
  if (ret == CTX_CLOSE_PATH2)
   {
     ret = CTX_CLOSE_PATH;
   }

  return ctx_parser_set_command (parser, (CtxCode) ret);
}

enum
{
  CTX_PARSER_NEUTRAL = 0,
  CTX_PARSER_NUMBER,
  CTX_PARSER_NEGATIVE_NUMBER,
  CTX_PARSER_WORD,
  CTX_PARSER_COMMENT,
  CTX_PARSER_STRING_APOS,
  CTX_PARSER_STRING_QUOT,
  CTX_PARSER_STRING_APOS_ESCAPED,
  CTX_PARSER_STRING_QUOT_ESCAPED,
  CTX_PARSER_STRING_A85,
  CTX_PARSER_STRING_YENC,
} CTX_STATE;

static void ctx_parser_set_color_model (CtxParser *parser, CtxColorModel color_model, int stroke)
{
  parser->color_model      = color_model;
  parser->color_stroke     = stroke;
  parser->color_components = ctx_color_model_get_components (color_model);
}

static void ctx_parser_get_color_rgba (CtxParser *parser, int offset, float *red, float *green, float *blue, float *alpha)
{
  /* XXX - this function is to be deprecated */
  *alpha = 1.0;
  switch (parser->color_model)
    {
      case CTX_GRAYA:
        *alpha = parser->numbers[offset + 1];
        /* FALLTHROUGH */
      case CTX_GRAY:
        *red = *green = *blue = parser->numbers[offset + 0];
        break;
      default:
      case CTX_LABA: // NYI - needs RGB profile
      case CTX_LCHA: // NYI - needs RGB profile
      case CTX_RGBA:
        *alpha = parser->numbers[offset + 3];
        /* FALLTHROUGH */
      case CTX_LAB: // NYI
      case CTX_LCH: // NYI
      case CTX_RGB:
        *red = parser->numbers[offset + 0];
        *green = parser->numbers[offset + 1];
        *blue = parser->numbers[offset + 2];
        break;
      case CTX_CMYKA:
        *alpha = parser->numbers[offset + 4];
        /* FALLTHROUGH */
      case CTX_CMYK:
        /* should use profile instead  */
        *red = (1.0-parser->numbers[offset + 0]) *
               (1.0 - parser->numbers[offset + 3]);
        *green = (1.0-parser->numbers[offset + 1]) *
                 (1.0 - parser->numbers[offset + 3]);
        *blue = (1.0-parser->numbers[offset + 2]) *
                (1.0 - parser->numbers[offset + 3]);
        break;
    }
}

static void ctx_parser_dispatch_command (CtxParser *parser)
{
  CtxCode cmd = parser->command;
  Ctx *ctx = parser->ctx;

  if (parser->expected_args != CTX_ARG_STRING_OR_NUMBER &&
      parser->expected_args != CTX_ARG_COLLECT_NUMBERS &&
      parser->expected_args != parser->n_numbers)
    {
#if CTX_REPORT_COL_ROW
         fprintf (stderr, "ctx:%i:%i %c got %i instead of %i args\n",
               parser->line, parser->col,
               cmd, parser->n_numbers, parser->expected_args);
#endif
      //return;
    }

#define arg(a)  (parser->numbers[a])
  parser->command = CTX_NOP;
  //parser->n_args = 0;
  switch (cmd)
    {
      default:
        break; // to silence warnings about missing ones
      case CTX_PRESERVE:
        ctx_preserve (ctx);
        break;
      case CTX_FILL:
        ctx_fill (ctx);
        break;
      case CTX_SAVE:
        ctx_save (ctx);
        break;
      case CTX_START_GROUP:
        ctx_start_group (ctx);
        break;
      case CTX_END_GROUP:
        ctx_end_group (ctx);
        break;
      case CTX_STROKE:
        ctx_stroke (ctx);
        break;
      case CTX_STROKE_SOURCE:
        ctx_stroke_source (ctx);
        break;
      case CTX_RESTORE:
        ctx_restore (ctx);
        break;
#if CTX_ENABLE_CM
      case CTX_COLOR_SPACE:
        if (parser->n_numbers == 1)
        {
          parser->color_space_slot = (CtxColorSpace) arg(0);
          parser->command = CTX_COLOR_SPACE; // did this work without?
        }
        else
        {
          ctx_colorspace (ctx, (CtxColorSpace)parser->color_space_slot,
                               parser->holding, parser->pos);
        }
        break;
#endif
      case CTX_KERNING_PAIR:
        switch (parser->n_args)
        {
          case 0:
            parser->numbers[0] = ctx_utf8_to_unichar ((char*)parser->holding);
            break;
          case 1:
            parser->numbers[1] = ctx_utf8_to_unichar ((char*)parser->holding);
            break;
          case 2:
            parser->numbers[2] = strtod ((char*)parser->holding, NULL);
            {
              CtxEntry e = {CTX_KERNING_PAIR, };
              e.data.u16[0] = parser->numbers[0];
              e.data.u16[1] = parser->numbers[1];
              e.data.s32[1] = parser->numbers[2] * 256;
              ctx_process (ctx, &e);
            }
            break;
        }
        parser->command = CTX_KERNING_PAIR;
        parser->n_args ++; // make this more generic?
        break;             
      case CTX_TEXTURE:
        if (parser->texture_done)
        {
        }
        else
        if (parser->n_numbers == 2)
        {
          const char *eid = (char*)parser->holding;
          float x0 = arg(0);
          float x1 = arg(1);
          ctx_texture (ctx, eid, x0, x1);
          parser->texture_done = 1;
        }
        parser->command = CTX_TEXTURE;
        //parser->n_args++;
        break;
      case CTX_DEFINE_TEXTURE:
        if (parser->texture_done)
        {
          if (parser->texture_done++ == 1)
          {
             const char *eid = (char*)parser->texture_id;
             int width  = arg(0);
             int height = arg(1);
             CtxPixelFormat format = (CtxPixelFormat)arg(2);
             int stride = ctx_pixel_format_get_stride (format, width);
             int data_len = stride * height;
             if (format == CTX_FORMAT_YUV420)
                 data_len = height * width + 2*(height/2) * (width/2);


             if (parser->pos != data_len)
             {
             fprintf (stderr, "unexpected datasize for define texture %s %ix%i\n size:%i != expected:%i - start of data: %i %i %i %i\n", eid, width, height,
                               parser->pos,
                               stride * height,
                               parser->holding[0],
                               parser->holding[1],
                               parser->holding[2],
                               parser->holding[3]
                               );
             }
             else
             ctx_define_texture (ctx, eid, width, height, stride, format, parser->holding, NULL);
          }
        }
        else
        {
        switch (parser->n_numbers)
        {
          case 0:
             strncpy ((char*)parser->texture_id, (char*)parser->holding, sizeof(parser->texture_id));
             parser->texture_id[sizeof(parser->texture_id)-1]=0;
             break;
          case 1:
          case 2:
             break;
          case 3:
             parser->texture_done = 1;
             break;
          default:
             fprintf (stderr, "!!%i\n", parser->n_numbers);
             break;
        }
        }
        parser->command = CTX_DEFINE_TEXTURE;
        break;


      case CTX_DEFINE_GLYPH:
        /* XXX : reuse n_args logic - to enforce order */
        if (parser->n_numbers == 1)
        {
          CtxEntry e = {CTX_DEFINE_GLYPH, };
          e.data.u32[0] = parser->color_space_slot;
          e.data.u32[1] = arg(0) * 256;
          ctx_process (ctx, &e);
        }
        else
        {
          int unichar = ctx_utf8_to_unichar ((char*)parser->holding);
          parser->color_space_slot = (CtxColorSpace)unichar;
        }
        parser->command = CTX_DEFINE_GLYPH;
        break;             

      case CTX_COLOR:
        {
          switch (parser->color_model)
            {
              case CTX_GRAY:
              case CTX_GRAYA:
              case CTX_RGB:
              case CTX_RGBA:
              case CTX_DRGB:
              case CTX_DRGBA:
                ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke);
                break;
#if CTX_ENABLE_CMYK
              case CTX_CMYK:
              case CTX_CMYKA:
                ctx_color_raw (ctx, parser->color_model, parser->numbers, parser->color_stroke);
                break;
#else
              /* when there is no cmyk support at all in rasterizer
               * do a naive mapping to RGB on input.
               */
              case CTX_CMYK:
              case CTX_CMYKA:
              case CTX_DCMYKA:
                {
                  float rgba[4] = {1,1,1,1.0f};

                  ctx_cmyk_to_rgb (arg(0), arg(1), arg(2), arg(3), &rgba[0], &rgba[1], &rgba[2]);
                  if (parser->color_model == CTX_CMYKA)
                    { rgba[3] = arg(4); }
                  ctx_color_raw (ctx, CTX_RGBA, rgba, parser->color_stroke);
                }
                break;
#endif
              case CTX_LAB:
              case CTX_LCH:
              default:
                break;
            }
        }
        break;
      case CTX_LINE_DASH:
        if (parser->n_numbers)
        {
          ctx_line_dash (ctx, parser->numbers, parser->n_numbers);
        }
        else
        {
          ctx_line_dash (ctx, NULL, 0);
        }
        //append_dash_val (ctx, arg(0));
        break;
      case CTX_ARC_TO:
        ctx_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4));
        break;
      case CTX_REL_ARC_TO:
        ctx_rel_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4) );
        break;
      case CTX_REL_SMOOTH_TO:
        {
          float cx = parser->pcx;
          float cy = parser->pcy;
          float ax = 2 * ctx_x (ctx) - cx;
          float ay = 2 * ctx_y (ctx) - cy;
          ctx_curve_to (ctx, ax, ay, arg(0) +  cx, arg(1) + cy,
                        arg(2) + cx, arg(3) + cy);
          parser->pcx = arg(0) + cx;
          parser->pcy = arg(1) + cy;
        }
        break;
      case CTX_SMOOTH_TO:
        {
          float ax = 2 * ctx_x (ctx) - parser->pcx;
          float ay = 2 * ctx_y (ctx) - parser->pcy;
          ctx_curve_to (ctx, ax, ay, arg(0), arg(1),
                        arg(2), arg(3) );
          parser->pcx = arg(0);
          parser->pcx = arg(1);
        }
        break;
      case CTX_SMOOTHQ_TO:
        ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0), arg(1) );
        break;
      case CTX_REL_SMOOTHQ_TO:
        {
          float cx = parser->pcx;
          float cy = parser->pcy;
          parser->pcx = 2 * ctx_x (ctx) - parser->pcx;
          parser->pcy = 2 * ctx_y (ctx) - parser->pcy;
          ctx_quad_to (ctx, parser->pcx, parser->pcy, arg(0) +  cx, arg(1) + cy);
        }
        break;
      case CTX_VER_LINE_TO:
        ctx_line_to (ctx, ctx_x (ctx), arg(0) );
        parser->command = CTX_VER_LINE_TO;
        parser->pcx = ctx_x (ctx);
        parser->pcy = ctx_y (ctx);
        break;
      case CTX_HOR_LINE_TO:
        ctx_line_to (ctx, arg(0), ctx_y (ctx) );
        parser->command = CTX_HOR_LINE_TO;
        parser->pcx = ctx_x (ctx);
        parser->pcy = ctx_y (ctx);
        break;
      case CTX_REL_HOR_LINE_TO:
        ctx_rel_line_to (ctx, arg(0), 0.0f);
        parser->command = CTX_REL_HOR_LINE_TO;
        parser->pcx = ctx_x (ctx);
        parser->pcy = ctx_y (ctx);
        break;
      case CTX_REL_VER_LINE_TO:
        ctx_rel_line_to (ctx, 0.0f, arg(0) );
        parser->command = CTX_REL_VER_LINE_TO;
        parser->pcx = ctx_x (ctx);
        parser->pcy = ctx_y (ctx);
        break;
      case CTX_ARC:
        ctx_arc (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        break;
      case CTX_APPLY_TRANSFORM:
        ctx_apply_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        break;
      case CTX_SOURCE_TRANSFORM:
        ctx_source_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        break;
      case CTX_CURVE_TO:
        ctx_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        parser->pcx = arg(2);
        parser->pcy = arg(3);
        parser->command = CTX_CURVE_TO;
        break;
      case CTX_REL_CURVE_TO:
        parser->pcx = arg(2) + ctx_x (ctx);
        parser->pcy = arg(3) + ctx_y (ctx);
        ctx_rel_curve_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        parser->command = CTX_REL_CURVE_TO;
        break;
      case CTX_LINE_TO:
        ctx_line_to (ctx, arg(0), arg(1) );
        parser->command = CTX_LINE_TO;
        parser->pcx = arg(0);
        parser->pcy = arg(1);
        break;
      case CTX_MOVE_TO:
        ctx_move_to (ctx, arg(0), arg(1) );
        parser->command = CTX_LINE_TO;
        parser->pcx = arg(0);
        parser->pcy = arg(1);
        parser->left_margin = parser->pcx;
        break;
      case CTX_FONT_SIZE:
        ctx_font_size (ctx, arg(0) );
        break;
      case CTX_MITER_LIMIT:
        ctx_miter_limit (ctx, arg(0) );
        break;
      case CTX_SCALE:
        ctx_scale (ctx, arg(0), arg(1) );
        break;
      case CTX_QUAD_TO:
        parser->pcx = arg(0);
        parser->pcy = arg(1);
        ctx_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) );
        parser->command = CTX_QUAD_TO;
        break;
      case CTX_REL_QUAD_TO:
        parser->pcx = arg(0) + ctx_x (ctx);
        parser->pcy = arg(1) + ctx_y (ctx);
        ctx_rel_quad_to (ctx, arg(0), arg(1), arg(2), arg(3) );
        parser->command = CTX_REL_QUAD_TO;
        break;
      case CTX_CLIP:
        ctx_clip (ctx);
        break;
      case CTX_TRANSLATE:
        ctx_translate (ctx, arg(0), arg(1) );
        break;
      case CTX_ROTATE:
        ctx_rotate (ctx, arg(0) );
        break;
      case CTX_FONT:
        ctx_font (ctx, (char *) parser->holding);
        break;

      case CTX_STROKE_TEXT:
      case CTX_TEXT:
        if (parser->n_numbers == 1)
          { ctx_rel_move_to (ctx, -parser->numbers[0], 0.0); }  //  XXX : scale by font(size)
        else
          {
            for (char *c = (char *) parser->holding; c; )
              {
                char *next_nl = ctx_strchr (c, '\n');
                if (next_nl)
                  { *next_nl = 0; }
                /* do our own layouting on a per-word basis?, to get justified
                 * margins? then we'd want explict margins rather than the
                 * implicit ones from move_to's .. making move_to work within
                 * margins.
                 */
                if (cmd == CTX_STROKE_TEXT)
                  { ctx_text_stroke (ctx, c); }
                else
                  { ctx_text (ctx, c); }
                if (next_nl)
                  {
                    *next_nl = '\n'; // swap it newline back in
                    ctx_move_to (ctx, parser->left_margin, ctx_y (ctx) +
                                 ctx_get_font_size (ctx) );
                    c = next_nl + 1;
                    if (c[0] == 0)
                      { c = NULL; }
                  }
                else
                  {
                    c = NULL;
                  }
              }
          }
        if (cmd == CTX_STROKE_TEXT)
          { parser->command = CTX_STROKE_TEXT; }
        else
          { parser->command = CTX_TEXT; }
        break;
      case CTX_REL_LINE_TO:
        ctx_rel_line_to (ctx, arg(0), arg(1) );
        parser->pcx += arg(0);
        parser->pcy += arg(1);
        break;
      case CTX_REL_MOVE_TO:
        ctx_rel_move_to (ctx, arg(0), arg(1) );
        parser->pcx += arg(0);
        parser->pcy += arg(1);
        parser->left_margin = ctx_x (ctx);
        break;
      case CTX_LINE_WIDTH:
        ctx_line_width (ctx, arg(0));
        break;
      case CTX_LINE_DASH_OFFSET:
        ctx_line_dash_offset (ctx, arg(0));
        break;
      case CTX_IMAGE_SMOOTHING:
        ctx_image_smoothing (ctx, arg(0));
        break;
      case CTX_SHADOW_COLOR:
        ctx_shadow_rgba (ctx, arg(0), arg(1), arg(2), arg(3));
        break;
      case CTX_SHADOW_BLUR:
        ctx_shadow_blur (ctx, arg(0) );
        break;
      case CTX_SHADOW_OFFSET_X:
        ctx_shadow_offset_x (ctx, arg(0) );
        break;
      case CTX_SHADOW_OFFSET_Y:
        ctx_shadow_offset_y (ctx, arg(0) );
        break;
      case CTX_LINE_JOIN:
        ctx_line_join (ctx, (CtxLineJoin) arg(0) );
        break;
      case CTX_LINE_CAP:
        ctx_line_cap (ctx, (CtxLineCap) arg(0) );
        break;
      case CTX_COMPOSITING_MODE:
        ctx_compositing_mode (ctx, (CtxCompositingMode) arg(0) );
        break;
      case CTX_BLEND_MODE:
        {
          int blend_mode = arg(0);
          if (blend_mode == CTX_COLOR) blend_mode = CTX_BLEND_COLOR;
          ctx_blend_mode (ctx, (CtxBlend)blend_mode);
        }
        break;
      case CTX_FILL_RULE:
        ctx_fill_rule (ctx, (CtxFillRule) arg(0) );
        break;
      case CTX_TEXT_ALIGN:
        ctx_text_align (ctx, (CtxTextAlign) arg(0) );
        break;
      case CTX_TEXT_BASELINE:
        ctx_text_baseline (ctx, (CtxTextBaseline) arg(0) );
        break;
      case CTX_TEXT_DIRECTION:
        ctx_text_direction (ctx, (CtxTextDirection) arg(0) );
        break;
      case CTX_IDENTITY:
        ctx_identity (ctx);
        break;
      case CTX_RECTANGLE:
        ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) );
        break;
      case CTX_FILL_RECT:
        ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) );
        ctx_fill (ctx);
        break;
      case CTX_STROKE_RECT:
        ctx_rectangle (ctx, arg(0), arg(1), arg(2), arg(3) );
        ctx_stroke (ctx);
        break;
      case CTX_ROUND_RECTANGLE:
        ctx_round_rectangle (ctx, arg(0), arg(1), arg(2), arg(3), arg(4));
        break;
      case CTX_VIEW_BOX:
        ctx_view_box (ctx, arg(0), arg(1), arg(2), arg(3) );
        break;
      case CTX_LINEAR_GRADIENT:
        ctx_linear_gradient (ctx, arg(0), arg(1), arg(2), arg(3) );
        break;
      case CTX_RADIAL_GRADIENT:
        ctx_radial_gradient (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) );
        break;
      case CTX_GRADIENT_STOP:
        {
          float red, green, blue, alpha;
          ctx_parser_get_color_rgba (parser, 1, &red, &green, &blue, &alpha);
          ctx_gradient_add_stop (ctx, arg(0), red, green, blue, alpha);
        }
        break;
      case CTX_GLOBAL_ALPHA:
        ctx_global_alpha (ctx, arg(0) );
        break;
      case CTX_BEGIN_PATH:
        ctx_begin_path (ctx);
        break;
      case CTX_GLYPH:
        ctx_glyph (ctx, arg(0), 0);
        break;
      case CTX_CLOSE_PATH:
        ctx_close_path (ctx);
        break;
      case CTX_EXIT:
        if (parser->exit)
          { parser->exit (parser->exit_data);
            return;
          }
        break;
      case CTX_FLUSH:
        //ctx_flush (ctx);
        break;
      case CTX_RESET:
        ctx_reset (ctx);
        if (parser->translate_origin)
        {
          ctx_translate (ctx,
                         (parser->cursor_x-1) * parser->cell_width * 1.0,
                         (parser->cursor_y-1) * parser->cell_height * 1.0);
        }
        break;
    }
#undef arg
//  parser->n_numbers = 0;
}

static inline void ctx_parser_holding_append (CtxParser *parser, int byte)
{
#if !CTX_PARSER_FIXED_TEMP
  if (CTX_UNLIKELY(parser->hold_len < parser->pos + 1 + 1))
  {
    int new_len = parser->hold_len * 2;
    if (new_len < 512) new_len = 512;
    parser->holding = (uint8_t*)realloc (parser->holding, new_len);
    parser->hold_len = new_len;
  }
#endif

  parser->holding[parser->pos++]=byte;
#if CTX_PARSER_FIXED_TEMP
  if (CTX_UNLIKELY(parser->pos > (int) sizeof (parser->holding)-2))
    { parser->pos = sizeof (parser->holding)-2; }
#endif
  parser->holding[parser->pos]=0;
}

static void ctx_parser_transform_percent (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  int big   = parser->width;
  int small = parser->height;
  if (big < small)
    {
      small = parser->width;
      big   = parser->height;
    }
  switch (code)
    {
      case CTX_RADIAL_GRADIENT:
      case CTX_ARC:
        switch (arg_no)
          {
            case 0:
            case 3:
              *value *= (parser->width/100.0);
              break;
            case 1:
            case 4:
              *value *= (parser->height/100.0);
              break;
            case 2:
            case 5:
              *value *= small/100.0;
              break;
          }
        break;
      case CTX_FONT_SIZE:
      case CTX_MITER_LIMIT:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
        {
          *value *= (small/100.0);
        }
        break;
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
        if (arg_no > 3)
          {
            *value *= (small/100.0);
          }
        else
          {
            if (arg_no % 2 == 0)
              { *value  *= ( (parser->width) /100.0); }
            else
              { *value *= ( (parser->height) /100.0); }
          }
        break;
      case CTX_ROUND_RECTANGLE:
        if (arg_no == 4)
        {
          { *value *= ((parser->height)/100.0); }
          return;
        }
        /* FALLTHROUGH */
      default: // even means x coord
        if (arg_no % 2 == 0)
          { *value  *= ((parser->width)/100.0); }
        else
          { *value *= ((parser->height)/100.0); }
        break;
    }
}

static void ctx_parser_transform_percent_height (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  *value *= (parser->height/100.0);
}

static void ctx_parser_transform_percent_width (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  *value *= (parser->height/100.0);
}

static void ctx_parser_transform_cell (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  float small = parser->cell_width;
  if (small > parser->cell_height)
    { small = parser->cell_height; }
  switch (code)
    {
      case CTX_RADIAL_GRADIENT:
      case CTX_ARC:
        switch (arg_no)
          {
            case 0:
            case 3:
              *value *= parser->cell_width;
              break;
            case 1:
            case 4:
              *value *= parser->cell_height;
              break;
            case 2:
            case 5:
              *value *= small; // use height?
              break;
          }
        break;
      case CTX_MITER_LIMIT:
      case CTX_FONT_SIZE:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
        {
          *value *= parser->cell_height;
        }
        break;
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
        if (arg_no > 3)
          {
            *value *= small;
          }
        else
          {
            *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height;
          }
        break;
      case CTX_RECTANGLE:
        if (arg_no % 2 == 0)
          { *value *= parser->cell_width; }
        else
          {
            if (! (arg_no > 1) )
              { (*value) -= 1.0f; }
            *value *= parser->cell_height;
          }
        break;
      default: // even means x coord odd means y coord
        *value *= (arg_no%2==0) ?parser->cell_width:parser->cell_height;
        break;
    }
}

// %h %v %m %M

static void ctx_parser_number_done (CtxParser *parser)
{

}

static void ctx_parser_word_done (CtxParser *parser)
{
  parser->holding[parser->pos]=0;
  //int old_args = parser->expected_args;
  int command = ctx_parser_resolve_command (parser, parser->holding);
  if ((command >= 0 && command < 32)
      || (command > 150) || (command < 0)
      )  // special case low enum values
    {                   // and enum values too high to be
                        // commands - permitting passing words
                        // for strings in some cases
      parser->numbers[parser->n_numbers] = command;

      // trigger transition from number
      parser->state = CTX_PARSER_NUMBER;
      char c = ',';
      ctx_parser_feed_bytes (parser, &c, 1);
    }
  else if (command > 0)
    {
#if 0
      if (old_args == CTX_ARG_COLLECT_NUMBERS ||
          old_args == CTX_ARG_STRING_OR_NUMBER)
      {
        int tmp1 = parser->command;
        int tmp2 = parser->expected_args;
        int tmp3 = parser->n_numbers;
 //     int tmp4 = parser->n_args;
        ctx_parser_dispatch_command (parser);
        parser->command = (CtxCode)tmp1;
        parser->expected_args = tmp2;
        parser->n_numbers = tmp3;
 //     parser->n_args = tmp4;
      }
#endif

      parser->command = (CtxCode) command;
      parser->n_numbers = 0;
      parser->n_args = 0;
      if (parser->expected_args == 0)
        {
          ctx_parser_dispatch_command (parser);
        }
    }
  else
    {
      /* interpret char by char */
      uint8_t buf[16]=" ";
      for (int i = 0; parser->pos && parser->holding[i] > ' '; i++)
        {
          buf[0] = parser->holding[i];
          parser->command = (CtxCode) ctx_parser_resolve_command (parser, buf);
          parser->n_numbers = 0;
          parser->n_args = 0;
          if (parser->command > 0)
            {
              if (parser->expected_args == 0)
                {
                  ctx_parser_dispatch_command (parser);
                }
            }
          else
            {
              ctx_log ("unhandled command '%c'\n", buf[0]);
            }
        }
    }
}

static void ctx_parser_string_done (CtxParser *parser)
{
  if (parser->expected_args == CTX_ARG_STRING_OR_NUMBER)
  {
          /*
    if (parser->state != CTX_PARSER_NUMBER &&
        parser->state != CTX_PARSER_NEGATIVE_NUMBER &&
        parser->state != CTX_PARSER_STRING_A85 &&
        parser->state != CTX_PARSER_STRING_APOS &&
        parser->state != CTX_PARSER_STRING_QUOT
        )
        */
    {
    int tmp1 = parser->command;
    int tmp2 = parser->expected_args;
    int tmp3 = parser->n_numbers;
    int tmp4 = parser->n_args;
    ctx_parser_dispatch_command (parser);
    parser->command = (CtxCode)tmp1;
    parser->expected_args = tmp2;
    parser->n_numbers = tmp3;
    parser->n_args = tmp4;
    }
  }
  else
  {
    ctx_parser_dispatch_command (parser);
  }
}

static inline void ctx_parser_feed_byte (CtxParser *parser, char byte)
{
#if CTX_REPORT_COL_ROW
    if (CTX_UNLIKELY(byte == '\n'))
    {
        parser->col=0;
        parser->line++;
    }
    else
    {
        parser->col++;
    }
#endif

    if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_YENC))
    {
        if (CTX_UNLIKELY((parser->prev_byte == '=') && (byte == 'y')))
        {
          parser->state = CTX_PARSER_NEUTRAL;
                 //   fprintf (stderr, "got %i\n", parser->pos);
          parser->pos = ctx_ydec ((char*)parser->holding, (char*)parser->holding, parser->pos) - 1;
#if 0
          if (parser->pos > 5)
                    fprintf (stderr, "dec got %i %c %c %c %c\n", parser->pos,
                                    parser->holding[0],
                                    parser->holding[1],
                                    parser->holding[2],
                                    parser->holding[3]
                                    );
#endif
          ctx_parser_string_done (parser);
        }
        else
        {
          ctx_parser_holding_append (parser, byte);
        }
        parser->prev_byte = byte;
        return;
    }
    else if (CTX_LIKELY(parser->state == CTX_PARSER_STRING_A85))
    {
        /* since these are our largest bulk transfers, minimize
         * overhead for this case. */
        if (CTX_LIKELY(byte!='~')) 
        {
          ctx_parser_holding_append (parser, byte);
        }
        else
        {
          parser->state = CTX_PARSER_NEUTRAL;
                 //   fprintf (stderr, "got %i\n", parser->pos);
          parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos);
                 //   fprintf (stderr, "dec got %i\n", parser->pos);
          ctx_parser_string_done (parser);
        }
        return;
    }
  switch (parser->state)
    {
      case CTX_PARSER_NEUTRAL:
        switch (byte)
          {
            case  0: case  1: case  2: case  3:  case 4:  case 5:
            case  6: case  7: case  8: case 11: case 12: case 14:
            case 15: case 16: case 17: case 18: case 19: case 20:
            case 21: case 22: case 23: case 24: case 25: case 26:
            case 27: case 28: case 29: case 30: case 31:
              break;
            case ' ': case '\t': case '\r': case '\n':
            case ';': case ',':
            case '(': case ')':
            case '{': case '}':
            //case '=':
              break;
            case '#':
              parser->state = CTX_PARSER_COMMENT;
              break;
            case '\'':
              parser->state = CTX_PARSER_STRING_APOS;
              parser->pos = 0;
              parser->holding[0] = 0;
              break;
            case '=':
              parser->state = CTX_PARSER_STRING_YENC;
              parser->pos = 0;
              parser->holding[0] = 0;
              break;
            case '~':
              parser->state = CTX_PARSER_STRING_A85;
              parser->pos = 0;
              parser->holding[0] = 0;
              break;
            case '"':
              parser->state = CTX_PARSER_STRING_QUOT;
              parser->pos = 0;
              parser->holding[0] = 0;
              break;
            case '-':
              parser->state = CTX_PARSER_NEGATIVE_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->decimal = 0;
              break;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
              parser->state = CTX_PARSER_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->numbers[parser->n_numbers] += (byte - '0');
              parser->decimal = 0;
              break;
            case '.':
              parser->state = CTX_PARSER_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->decimal = 1;
              break;
            default:
              parser->state = CTX_PARSER_WORD;
              parser->pos = 0;
              ctx_parser_holding_append (parser, byte);
              break;
          }
        break;
      case CTX_PARSER_NUMBER:
      case CTX_PARSER_NEGATIVE_NUMBER:
        {
          switch (byte)
            {
              case 0: case 1: case 2: case 3: case 4: case 5:
              case 6: case 7: case 8:
              case 11: case 12: case 14: case 15: case 16:
              case 17: case 18: case 19: case 20: case 21:
              case 22: case 23: case 24: case 25: case 26:
              case 27: case 28: case 29: case 30: case 31:
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              case ' ':
              case '\t':
              case '\r':
              case '\n':
              case ';':
              case ',':
              case '(':
              case ')':
              case '{':
              case '}':
              case '=':
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              case '#':
                parser->state = CTX_PARSER_COMMENT;
                break;
              case '-':
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                parser->state = CTX_PARSER_NEGATIVE_NUMBER;
                parser->numbers[parser->n_numbers+1] = 0;
                parser->n_numbers ++;
                parser->decimal = 0;
                break;
              case '.':
                //if (parser->decimal) // TODO permit .13.32.43 to equivalent to .12 .32 .43
                parser->decimal = 1;
                break;
              case '0': case '1': case '2': case '3': case '4':
              case '5': case '6': case '7': case '8': case '9':
                if (parser->decimal)
                  {
                    parser->decimal *= 10;
                    parser->numbers[parser->n_numbers] += (byte - '0') / (1.0 * parser->decimal);
                  }
                else
                  {
                    parser->numbers[parser->n_numbers] *= 10;
                    parser->numbers[parser->n_numbers] += (byte - '0');
                  }
                break;
              case '@': // cells
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                {
                float fval = parser->numbers[parser->n_numbers];
                ctx_parser_transform_cell (parser, parser->command, parser->n_numbers, &fval);
                parser->numbers[parser->n_numbers]= fval;
                }
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              case '%': // percent of width/height
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                {
                float fval = parser->numbers[parser->n_numbers];
                ctx_parser_transform_percent (parser, parser->command, parser->n_numbers, &fval);
                parser->numbers[parser->n_numbers]= fval;
                }
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              case '^': // percent of height
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                {
                float fval = parser->numbers[parser->n_numbers];
                ctx_parser_transform_percent_height (parser, parser->command, parser->n_numbers, &fval);
                parser->numbers[parser->n_numbers]= fval;
                }
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              case '~': // percent of width
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                {
                float fval = parser->numbers[parser->n_numbers];
                ctx_parser_transform_percent_width (parser, parser->command, parser->n_numbers, &fval);
                parser->numbers[parser->n_numbers]= fval;
                }
                parser->state = CTX_PARSER_NEUTRAL;
                break;
              default:
                if (parser->state == CTX_PARSER_NEGATIVE_NUMBER)
                  { parser->numbers[parser->n_numbers] *= -1; }
                parser->state = CTX_PARSER_WORD;
                parser->pos = 0;
                ctx_parser_holding_append (parser, byte);
                break;
            }
          if ( (parser->state != CTX_PARSER_NUMBER) &&
               (parser->state != CTX_PARSER_NEGATIVE_NUMBER))
            {
              parser->n_numbers ++;
              ctx_parser_number_done (parser);

              if (parser->n_numbers == parser->expected_args ||
                  parser->expected_args == CTX_ARG_COLLECT_NUMBERS ||
                  parser->expected_args == CTX_ARG_STRING_OR_NUMBER)
                {
                  int tmp1 = parser->n_numbers;
                  int tmp2 = parser->n_args;
                  CtxCode tmp3 = parser->command;
                  int tmp4 = parser->expected_args;
                  ctx_parser_dispatch_command (parser);
                  parser->command = tmp3;
                  switch (parser->command)
                  {
                    case CTX_DEFINE_TEXTURE:
                    case CTX_TEXTURE:
                      parser->n_numbers = tmp1;
                      parser->n_args = tmp2;
                      break;
                          default:
                      parser->n_numbers = 0;
                      parser->n_args = 0;
                      break;
                  }
                  parser->expected_args = tmp4;
                }
              if (parser->n_numbers > CTX_PARSER_MAX_ARGS)
                { parser->n_numbers = CTX_PARSER_MAX_ARGS;
                }
            }
        }
        break;
      case CTX_PARSER_WORD:
        switch (byte)
          {
            case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
            case 8: case 11: case 12: case 14: case 15: case 16: case 17:
            case 18: case 19: case 20: case 21: case 22: case 23: case 24:
            case 25: case 26: case 27: case 28: case 29: case 30: case 31:
            case ' ': case '\t': case '\r': case '\n':
            case ';': case ',':
            case '(': case ')': case '=': case '{': case '}':
              parser->state = CTX_PARSER_NEUTRAL;
              break;
            case '#':
              parser->state = CTX_PARSER_COMMENT;
              break;
            case '-':
              parser->state = CTX_PARSER_NEGATIVE_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->decimal = 0;
              break;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
              parser->state = CTX_PARSER_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->numbers[parser->n_numbers] += (byte - '0');
              parser->decimal = 0;
              break;
            case '.':
              parser->state = CTX_PARSER_NUMBER;
              parser->numbers[parser->n_numbers] = 0;
              parser->decimal = 1;
              break;
            default:
              ctx_parser_holding_append (parser, byte);
              break;
          }
        if (parser->state != CTX_PARSER_WORD)
          {
            ctx_parser_word_done (parser);
          }
        break;
      case CTX_PARSER_STRING_A85:
        if (CTX_LIKELY(byte!='~'))
        {
          ctx_parser_holding_append (parser, byte);
        }
        else
        {
          parser->state = CTX_PARSER_NEUTRAL;
                 //   fprintf (stderr, "got %i\n", parser->pos);
          parser->pos = ctx_a85dec ((char*)parser->holding, (char*)parser->holding, parser->pos);
                 //   fprintf (stderr, "dec got %i\n", parser->pos);
          ctx_parser_string_done (parser);
        }
        break;
      case CTX_PARSER_STRING_APOS:
        switch (byte)
          {
            case '\\': parser->state = CTX_PARSER_STRING_APOS_ESCAPED; break;
            case '\'': parser->state = CTX_PARSER_NEUTRAL;
              ctx_parser_string_done (parser);
              break;
            default:
              ctx_parser_holding_append (parser, byte); break;
          }
        break;
      case CTX_PARSER_STRING_APOS_ESCAPED:
        switch (byte)
          {
            case '0': byte = '\0'; break;
            case 'b': byte = '\b'; break;
            case 'f': byte = '\f'; break;
            case 'n': byte = '\n'; break;
            case 'r': byte = '\r'; break;
            case 't': byte = '\t'; break;
            case 'v': byte = '\v'; break;
            default: break;
          }
        ctx_parser_holding_append (parser, byte);
        parser->state = CTX_PARSER_STRING_APOS;
        break;
      case CTX_PARSER_STRING_QUOT_ESCAPED:
        switch (byte)
          {
            case '0': byte = '\0'; break;
            case 'b': byte = '\b'; break;
            case 'f': byte = '\f'; break;
            case 'n': byte = '\n'; break;
            case 'r': byte = '\r'; break;
            case 't': byte = '\t'; break;
            case 'v': byte = '\v'; break;
            default: break;
          }
        ctx_parser_holding_append (parser, byte);
        parser->state = CTX_PARSER_STRING_QUOT;
        break;
      case CTX_PARSER_STRING_QUOT:
        switch (byte)
          {
            case '\\':
              parser->state = CTX_PARSER_STRING_QUOT_ESCAPED;
              break;
            case '"':
              parser->state = CTX_PARSER_NEUTRAL;
              ctx_parser_string_done (parser);
              break;
            default:
              ctx_parser_holding_append (parser, byte);
              break;
          }
        break;
      case CTX_PARSER_COMMENT:
        switch (byte)
          {
            case '\r':
            case '\n':
              parser->state = CTX_PARSER_NEUTRAL;
            default:
              break;
          }
        break;
    }
}

void ctx_parser_feed_bytes (CtxParser *parser, const char *data, int count)
{
  for (int i = 0; i < count; i++)
    ctx_parser_feed_byte (parser, data[i]);
}

void ctx_parse (Ctx *ctx, const char *string)
{
  if (!string)
    return;
  CtxParser *parser = ctx_parser_new (ctx, ctx_width(ctx),
                                           ctx_height(ctx),
                                           ctx_get_font_size(ctx),
                                           ctx_get_font_size(ctx),
                                           0, 0, NULL, NULL, NULL, NULL, NULL);
  ctx_parser_feed_bytes (parser, string, strlen (string));
  ctx_parser_free (parser);
}

#endif

static CtxFont ctx_fonts[CTX_MAX_FONTS];
static int     ctx_font_count = 0;

#if CTX_FONT_ENGINE_STB
static float
ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar);
static float
ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB);
static int
ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke);

CtxFontEngine ctx_font_engine_stb =
{
#if CTX_FONTS_FROM_FILE
  ctx_load_font_ttf_file,
#endif
  ctx_load_font_ttf,
  ctx_glyph_stb,
  ctx_glyph_width_stb,
  ctx_glyph_kern_stb,
};

int
ctx_load_font_ttf (const char *name, const void *ttf_contents, int length)
{
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }
  ctx_fonts[ctx_font_count].type = 1;
  ctx_fonts[ctx_font_count].name = (char *) malloc (strlen (name) + 1);
  ctx_strcpy ( (char *) ctx_fonts[ctx_font_count].name, name);
  if (!stbtt_InitFont (&ctx_fonts[ctx_font_count].stb.ttf_info, ttf_contents, 0) )
    {
      ctx_log ( "Font init failed\n");
      return -1;
    }
  ctx_fonts[ctx_font_count].engine = &ctx_font_engine_stb;
  ctx_font_count ++;
  return ctx_font_count-1;
}

#if CTX_FONTS_FROM_FILE
int
ctx_load_font_ttf_file (const char *name, const char *path)
{
  uint8_t *contents = NULL;
  long length = 0;
  ctx_get_contents (path, &contents, &length);
  if (!contents)
    {
      ctx_log ( "File load failed\n");
      return -1;
    }
  return ctx_load_font_ttf (name, contents, length);
}
#endif

static int
ctx_glyph_stb_find (CtxFont *font, uint32_t unichar)
{
  stbtt_fontinfo *ttf_info = &font->stb.ttf_info;
  int index = font->stb.cache_index;
  if (font->stb.cache_unichar == unichar)
    {
      return index;
    }
  font->stb.cache_unichar = 0;
  index = font->stb.cache_index = stbtt_FindGlyphIndex (ttf_info, unichar);
  font->stb.cache_unichar = unichar;
  return index;
}

static float
ctx_glyph_width_stb (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
  stbtt_fontinfo *ttf_info = &font->stb.ttf_info;
  float font_size          = ctx->state.gstate.font_size;
  float scale              = stbtt_ScaleForPixelHeight (ttf_info, font_size);
  int advance, lsb;
  int glyph = ctx_glyph_stb_find (font, unichar);
  if (glyph==0)
    { return 0.0f; }
  stbtt_GetGlyphHMetrics (ttf_info, glyph, &advance, &lsb);
  return (advance * scale);
}

static float
ctx_glyph_kern_stb (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB)
{
  stbtt_fontinfo *ttf_info = &font->stb.ttf_info;
  float font_size = ctx->state.gstate.font_size;
  float scale = stbtt_ScaleForPixelHeight (ttf_info, font_size);
  int glyphA = ctx_glyph_stb_find (font, unicharA);
  int glyphB = ctx_glyph_stb_find (font, unicharB);
  return stbtt_GetGlyphKernAdvance (ttf_info, glyphA, glyphB) * scale;
}

static int
ctx_glyph_stb (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke)
{
  stbtt_fontinfo *ttf_info = &font->stb.ttf_info;
  int glyph = ctx_glyph_stb_find (font, unichar);
  if (glyph==0)
    { return -1; }
  float font_size = ctx->state.gstate.font_size;
  int   baseline = ctx->state.y;
  float origin_x = ctx->state.x;
  float origin_y = baseline;
  float scale    = stbtt_ScaleForPixelHeight (ttf_info, font_size);;
  stbtt_vertex *vertices = NULL;
  ctx_begin_path (ctx);
  int num_verts = stbtt_GetGlyphShape (ttf_info, glyph, &vertices);
  for (int i = 0; i < num_verts; i++)
    {
      stbtt_vertex *vertex = &vertices[i];
      switch (vertex->type)
        {
          case STBTT_vmove:
            ctx_move_to (ctx,
                         origin_x + vertex->x * scale, origin_y - vertex->y * scale);
            break;
          case STBTT_vline:
            ctx_line_to (ctx,
                         origin_x + vertex->x * scale, origin_y - vertex->y * scale);
            break;
          case STBTT_vcubic:
            ctx_curve_to (ctx,
                          origin_x + vertex->cx  * scale, origin_y - vertex->cy  * scale,
                          origin_x + vertex->cx1 * scale, origin_y - vertex->cy1 * scale,
                          origin_x + vertex->x   * scale, origin_y - vertex->y   * scale);
            break;
          case STBTT_vcurve:
            ctx_quad_to (ctx,
                         origin_x + vertex->cx  * scale, origin_y - vertex->cy  * scale,
                         origin_x + vertex->x   * scale, origin_y - vertex->y   * scale);
            break;
        }
    }
  stbtt_FreeShape (ttf_info, vertices);
  if (stroke)
    {
      ctx_stroke (ctx);
    }
  else
    { ctx_fill (ctx); }
  return 0;
}
#endif

#if CTX_FONT_ENGINE_CTX

/* XXX: todo remove this, and rely on a binary search instead
 */
static int ctx_font_find_glyph_cached (CtxFont *font, uint32_t glyph)
{
  for (int i = 0; i < font->ctx.glyphs; i++)
    {
      if (font->ctx.index[i * 2] == glyph)
        { return font->ctx.index[i * 2 + 1]; }
    }
  return -1;
}

static int ctx_glyph_find_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
  int ret = ctx_font_find_glyph_cached (font, unichar);
  if (ret >= 0) return ret;

  for (int i = 0; i < font->ctx.length; i++)
  {
    CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
    if (entry->code == CTX_DEFINE_GLYPH &&
        entry->data.u32[0] == unichar)
    {
       return i;
       // XXX this could be prone to insertion of valid header
       // data in included bitmaps.. is that an issue?
       //   
    }
  }
  return -1;
}


static float
ctx_glyph_kern_ctx (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB)
{
  float font_size = ctx->state.gstate.font_size;
  int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA);
  if (first_kern < 0) return 0.0;
  for (int i = first_kern + 1; i < font->ctx.length; i++)
    {
      CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
      if (entry->code == CTX_KERNING_PAIR)
        {
          if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB)
            { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; }
        }
      if (entry->code == CTX_DEFINE_GLYPH)
        return 0.0;
    }
  return 0.0;
}
#if 0
static int ctx_glyph_find (Ctx *ctx, CtxFont *font, uint32_t unichar)
{
  for (int i = 0; i < font->ctx.length; i++)
    {
      CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
      if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar)
        { return i; }
    }
  return 0;
}
#endif


static float
ctx_glyph_width_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
  CtxState *state = &ctx->state;
  float font_size = state->gstate.font_size;
  int   start     = ctx_glyph_find_ctx (font, ctx, unichar);
  if (start < 0)
    { return 0.0; }  // XXX : fallback
  for (int i = start; i < font->ctx.length; i++)
    {
      CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
      if (entry->code == CTX_DEFINE_GLYPH)
        if (entry->data.u32[0] == (unsigned) unichar)
          { return (entry->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE); }
    }
  return 0.0;
}

static int
ctx_glyph_drawlist (CtxFont *font, Ctx *ctx, CtxDrawlist *drawlist, uint32_t unichar, int stroke)
{
  CtxState *state = &ctx->state;
  CtxIterator iterator;
  float origin_x = state->x;
  float origin_y = state->y;
  ctx_current_point (ctx, &origin_x, &origin_y);
  int in_glyph = 0;
  float font_size = state->gstate.font_size;
  int start = 0;
  if (font->type == 0)
  {
  start = ctx_glyph_find_ctx (font, ctx, unichar);
  if (start < 0)
    { return -1; }  // XXX : fallback glyph
  }
  ctx_iterator_init (&iterator, drawlist, start, CTX_ITERATOR_EXPAND_BITPACK);
  CtxCommand *command;

  /* XXX :  do a binary search instead of a linear search */
  while ( (command= ctx_iterator_next (&iterator) ) )
    {
      CtxEntry *entry = &command->entry;
      if (in_glyph)
        {
          if (entry->code == CTX_DEFINE_GLYPH)
            {
              if (stroke)
                { ctx_stroke (ctx); }
              else
                {
#if CTX_RASTERIZER
#if CTX_ENABLE_SHADOW_BLUR
      if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow)
      {
        ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer);
        ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1;
      }
      else
#endif
#endif
         ctx_fill (ctx); 
               
                }
              ctx_restore (ctx);
              return 0;
            }
          ctx_process (ctx, entry);
        }
      else if (entry->code == CTX_DEFINE_GLYPH && entry->data.u32[0] == unichar)
        {
          in_glyph = 1;
          ctx_save (ctx);
          ctx_translate (ctx, origin_x, origin_y);
          ctx_move_to (ctx, 0, 0);
          ctx_begin_path (ctx);
          ctx_scale (ctx, font_size / CTX_BAKE_FONT_SIZE,
                     font_size / CTX_BAKE_FONT_SIZE);
        }
    }
  if (stroke)
    { ctx_stroke (ctx);
    }
  else
    { 
    
#if CTX_RASTERIZER
#if CTX_ENABLE_SHADOW_BLUR
      if (ctx->renderer && ((CtxRasterizer*)(ctx->renderer))->in_shadow)
      {
        ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->renderer);
        ((CtxRasterizer*)(ctx->renderer))->in_shadow = 1;
      }
      else
#endif
#endif
      {
         ctx_fill (ctx); 
      }
    }
  ctx_restore (ctx);
  return -1;
}

static int
ctx_glyph_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke)
{
  CtxDrawlist drawlist = { (CtxEntry *) font->ctx.data,
                           font->ctx.length,
                           font->ctx.length, 0, 0
                         };
  return ctx_glyph_drawlist (font, ctx, &drawlist, unichar, stroke);
}

#if 0
uint32_t ctx_glyph_no (Ctx *ctx, int no)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];
  if (no < 0 || no >= font->ctx.glyphs)
    { return 0; }
  return font->ctx.index[no*2];
}
#endif

static void ctx_font_init_ctx (CtxFont *font)
{
  int glyph_count = 0;
  for (int i = 0; i < font->ctx.length; i++)
    {
      CtxEntry *entry = &font->ctx.data[i];
      if (entry->code == CTX_DEFINE_GLYPH)
        { glyph_count ++; }
    }
  font->ctx.glyphs = glyph_count;
#if CTX_DRAWLIST_STATIC
  static uint32_t idx[512]; // one might have to adjust this for
  // larger fonts XXX
  // should probably be made a #define
  font->ctx.index = &idx[0];
#else
  font->ctx.index = (uint32_t *) malloc (sizeof (uint32_t) * 2 * glyph_count);
#endif
  int no = 0;
  for (int i = 0; i < font->ctx.length; i++)
    {
      CtxEntry *entry = &font->ctx.data[i];
      if (entry->code == CTX_DEFINE_GLYPH)
        {
          font->ctx.index[no*2]   = entry->data.u32[0];
          font->ctx.index[no*2+1] = i;
          no++;
        }
    }
}

int
ctx_load_font_ctx (const char *name, const void *data, int length);
#if CTX_FONTS_FROM_FILE
int
ctx_load_font_ctx_file (const char *name, const char *path);
#endif

static CtxFontEngine ctx_font_engine_ctx =
{
#if CTX_FONTS_FROM_FILE
  ctx_load_font_ctx_file,
#endif
  ctx_load_font_ctx,
  ctx_glyph_ctx,
  ctx_glyph_width_ctx,
  ctx_glyph_kern_ctx,
};

int
ctx_load_font_ctx (const char *name, const void *data, int length)
{
  if (length % sizeof (CtxEntry) )
    { return -1; }
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }
  ctx_fonts[ctx_font_count].type = 0;
  ctx_fonts[ctx_font_count].name = name;
  ctx_fonts[ctx_font_count].ctx.data = (CtxEntry *) data;
  ctx_fonts[ctx_font_count].ctx.length = length / sizeof (CtxEntry);
  ctx_font_init_ctx (&ctx_fonts[ctx_font_count]);
  ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx;
  ctx_font_count++;
  return ctx_font_count-1;
}

#if CTX_FONTS_FROM_FILE
int
ctx_load_font_ctx_file (const char *name, const char *path)
{
  uint8_t *contents = NULL;
  long length = 0;
  ctx_get_contents (path, &contents, &length);
  if (!contents)
    {
      ctx_log ( "File load failed\n");
      return -1;
    }
  return ctx_load_font_ctx (name, contents, length);
}
#endif
#endif

#if CTX_FONT_ENGINE_CTX_FS

static float
ctx_glyph_kern_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unicharA, uint32_t unicharB)
{
#if 0
  float font_size = ctx->state.gstate.font_size;
  int first_kern = ctx_glyph_find_ctx (font, ctx, unicharA);
  if (first_kern < 0) return 0.0;
  for (int i = first_kern + 1; i < font->ctx.length; i++)
    {
      CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
      if (entry->code == CTX_KERNING_PAIR)
        {
          if (entry->data.u16[0] == unicharA && entry->data.u16[1] == unicharB)
            { return entry->data.s32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE; }
        }
      if (entry->code == CTX_DEFINE_GLYPH)
        return 0.0;
    }
#endif
  return 0.0;
}

static float
ctx_glyph_width_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
  CtxState *state = &ctx->state;
  char path[1024];
  sprintf (path, "%s/%010p", font->ctx_fs.path, unichar);
  uint8_t *data = NULL;
  long int len_bytes = 0;
  ctx_get_contents (path, &data, &len_bytes);
  float ret = 0.0;
  float font_size = state->gstate.font_size;
  if (data){
    Ctx *glyph_ctx = ctx_new ();
    ctx_parse (glyph_ctx, data);
    for (int i = 0; i < glyph_ctx->drawlist.count; i++)
    {
      CtxEntry *e = &glyph_ctx->drawlist.entries[i];
      if (e->code == CTX_DEFINE_GLYPH)
        ret = e->data.u32[1] / 255.0 * font_size / CTX_BAKE_FONT_SIZE;
    }
    free (data);
    ctx_free (glyph_ctx);
  }
  return ret;
}

static int
ctx_glyph_ctx_fs (CtxFont *font, Ctx *ctx, uint32_t unichar, int stroke)
{
  char path[1024];
  sprintf (path, "file://%s/%010p", font->ctx_fs.path, unichar);
  uint8_t *data = NULL;
  long int len_bytes = 0;
  ctx_get_contents (path, &data, &len_bytes);

  if (data){
    Ctx *glyph_ctx = ctx_new ();
    ctx_parse (glyph_ctx, data);
    int ret = ctx_glyph_drawlist (font, ctx, &(glyph_ctx->drawlist),
                                  unichar, stroke);
    free (data);
    ctx_free (glyph_ctx);
    return ret;
  }
  return -1;
}

int
ctx_load_font_ctx_fs (const char *name, const void *data, int length);

static CtxFontEngine ctx_font_engine_ctx_fs =
{
#if CTX_FONTS_FROM_FILE
  NULL,
#endif
  ctx_load_font_ctx_fs,
  ctx_glyph_ctx_fs,
  ctx_glyph_width_ctx_fs,
  ctx_glyph_kern_ctx_fs,
};

int
ctx_load_font_ctx_fs (const char *name, const void *path, int length) // length is ignored
{
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }

  ctx_fonts[ctx_font_count].type = 42;
  ctx_fonts[ctx_font_count].name = name;
  ctx_fonts[ctx_font_count].ctx_fs.path = strdup (path);
  int path_len = strlen (path);
  if (ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] == '/')
   ctx_fonts[ctx_font_count].ctx_fs.path[path_len-1] = 0;
  ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx_fs;
  ctx_font_count++;
  return ctx_font_count-1;
}

#endif

int
_ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];
  // a begin-path here did not remove stray spikes in terminal
  return font->engine->glyph (font, ctx, unichar, stroke);
}

int
ctx_glyph (Ctx *ctx, uint32_t unichar, int stroke)
{
#if CTX_BACKEND_TEXT
  CtxEntry commands[3]; // 3 to silence incorrect warning from static analysis
  ctx_memset (commands, 0, sizeof (commands) );
  commands[0] = ctx_u32 (CTX_GLYPH, unichar, 0);
  commands[0].data.u8[4] = stroke;
  ctx_process (ctx, commands);
  return 0; // XXX is return value used?
#else
  return _ctx_glyph (ctx, unichar, stroke);
#endif
}

float
ctx_glyph_width (Ctx *ctx, int unichar)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];

  return font->engine->glyph_width (font, ctx, unichar);
}

static float
ctx_glyph_kern (Ctx *ctx, int unicharA, int unicharB)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];
  return font->engine->glyph_kern (font, ctx, unicharA, unicharB);
}

float
ctx_text_width (Ctx        *ctx,
                const char *string)
{
  float sum = 0.0;
  if (!string)
    return 0.0f;
  for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) )
    {
      sum += ctx_glyph_width (ctx, ctx_utf8_to_unichar (utf8) );
    }
  return sum;
}

static void
_ctx_glyphs (Ctx     *ctx,
             CtxGlyph *glyphs,
             int       n_glyphs,
             int       stroke)
{
  for (int i = 0; i < n_glyphs; i++)
    {
      {
        uint32_t unichar = glyphs[i].index;
        ctx_move_to (ctx, glyphs[i].x, glyphs[i].y);
        ctx_glyph (ctx, unichar, stroke);
      }
    }
}

static void
_ctx_text (Ctx        *ctx,
           const char *string,
           int         stroke,
           int         visible)
{
  CtxState *state = &ctx->state;
  float x = ctx->state.x;
  switch ( (int) ctx_state_get (state, CTX_text_align) )
    //switch (state->gstate.text_align)
    {
      case CTX_TEXT_ALIGN_START:
      case CTX_TEXT_ALIGN_LEFT:
        break;
      case CTX_TEXT_ALIGN_CENTER:
        x -= ctx_text_width (ctx, string) /2;
        break;
      case CTX_TEXT_ALIGN_END:
      case CTX_TEXT_ALIGN_RIGHT:
        x -= ctx_text_width (ctx, string);
        break;
    }
  float y = ctx->state.y;
  float baseline_offset = 0.0f;
  switch ( (int) ctx_state_get (state, CTX_text_baseline) )
    {
      case CTX_TEXT_BASELINE_HANGING:
        /* XXX : crude */
        baseline_offset = ctx->state.gstate.font_size  * 0.55;
        break;
      case CTX_TEXT_BASELINE_TOP:
        /* XXX : crude */
        baseline_offset = ctx->state.gstate.font_size  * 0.7;
        break;
      case CTX_TEXT_BASELINE_BOTTOM:
        baseline_offset = -ctx->state.gstate.font_size * 0.1;
        break;
      case CTX_TEXT_BASELINE_ALPHABETIC:
      case CTX_TEXT_BASELINE_IDEOGRAPHIC:
        baseline_offset = 0.0f;
        break;
      case CTX_TEXT_BASELINE_MIDDLE:
        baseline_offset = ctx->state.gstate.font_size * 0.25;
        break;
    }
  float x0 = x;
  for (const char *utf8 = string; *utf8; utf8 = ctx_utf8_skip (utf8, 1) )
    {
      if (*utf8 == '\n')
        {
          y += ctx->state.gstate.font_size * ctx_state_get (state, CTX_line_spacing);
          x = x0;
          if (visible)
            { ctx_move_to (ctx, x, y); }
        }
      else
        {
          uint32_t unichar = ctx_utf8_to_unichar (utf8);
          if (visible)
            {
              ctx_move_to (ctx, x, y + baseline_offset);
              _ctx_glyph (ctx, unichar, stroke);
            }
          const char *next_utf8 = ctx_utf8_skip (utf8, 1);
          if (next_utf8)
            {
              x += ctx_glyph_width (ctx, unichar);
              x += ctx_glyph_kern (ctx, unichar, ctx_utf8_to_unichar (next_utf8) );
            }
          if (visible)
            { ctx_move_to (ctx, x, y); }
        }
    }
  if (!visible)
    { ctx_move_to (ctx, x, y); }
}


CtxGlyph *
ctx_glyph_allocate (int n_glyphs)
{
  return (CtxGlyph *) malloc (sizeof (CtxGlyph) * n_glyphs);
}
void
gtx_glyph_free     (CtxGlyph *glyphs)
{
  free (glyphs);
}

void
ctx_glyphs (Ctx        *ctx,
            CtxGlyph   *glyphs,
            int         n_glyphs)
{
  _ctx_glyphs (ctx, glyphs, n_glyphs, 0);
}

void
ctx_glyphs_stroke (Ctx        *ctx,
                   CtxGlyph   *glyphs,
                   int         n_glyphs)
{
  _ctx_glyphs (ctx, glyphs, n_glyphs, 1);
}

void
ctx_text (Ctx        *ctx,
          const char *string)
{
  if (!string)
    return;
#if CTX_BACKEND_TEXT
  ctx_process_cmd_str (ctx, CTX_TEXT, string, 0, 0);
  _ctx_text (ctx, string, 0, 0);
#else
  _ctx_text (ctx, string, 0, 1);
#endif
}


void
ctx_fill_text (Ctx *ctx, const char *string,
               float x, float y)
{
  ctx_move_to (ctx, x, y);
  ctx_text (ctx, string);
}

void
ctx_text_stroke (Ctx        *ctx,
                 const char *string)
{
  if (!string)
    return;
#if CTX_BACKEND_TEXT
  ctx_process_cmd_str (ctx, CTX_STROKE_TEXT, string, 0, 0);
  _ctx_text (ctx, string, 1, 0);
#else
  _ctx_text (ctx, string, 1, 1);
#endif
}

void
ctx_stroke_text (Ctx *ctx, const char *string,
               float x, float y)
{
  ctx_move_to (ctx, x, y);
  ctx_text_stroke (ctx, string);
}

static int _ctx_resolve_font (const char *name)
{
  for (int i = 0; i < ctx_font_count; i ++)
    {
      if (!ctx_strcmp (ctx_fonts[i].name, name) )
        { return i; }
    }
  for (int i = 0; i < ctx_font_count; i ++)
    {
      if (ctx_strstr (ctx_fonts[i].name, name) )
        { return i; }
    }
  return -1;
}

int ctx_resolve_font (const char *name)
{
  int ret = _ctx_resolve_font (name);
  if (ret >= 0)
    { return ret; }
  if (!ctx_strcmp (name, "regular") )
    {
      int ret = _ctx_resolve_font ("sans");
      if (ret >= 0) { return ret; }
      ret = _ctx_resolve_font ("serif");
      if (ret >= 0) { return ret; }
    }
  return 0;
}

static void ctx_font_setup (void)
{
  static int initialized = 0;
  if (initialized) { return; }
  initialized = 1;
#if CTX_FONT_ENGINE_CTX
  ctx_font_count = 0; // oddly - this is needed in arduino

#if CTX_FONT_ENGINE_CTX_FS
  ctx_load_font_ctx_fs ("sans-ctx", "/tmp/ctx-regular", 0);
#else
#if CTX_FONT_ascii
  ctx_load_font_ctx ("sans-ctx", ctx_font_ascii, sizeof (ctx_font_ascii) );
#endif
#if CTX_FONT_regular
  ctx_load_font_ctx ("sans-ctx", ctx_font_regular, sizeof (ctx_font_regular) );
#endif
#endif

#if CTX_FONT_mono
  ctx_load_font_ctx ("mono-ctx", ctx_font_mono, sizeof (ctx_font_mono) );
#endif
#if CTX_FONT_bold
  ctx_load_font_ctx ("bold-ctx", ctx_font_bold, sizeof (ctx_font_bold) );
#endif
#if CTX_FONT_italic
  ctx_load_font_ctx ("italic-ctx", ctx_font_italic, sizeof (ctx_font_italic) );
#endif
#if CTX_FONT_sans
  ctx_load_font_ctx ("sans-ctx", ctx_font_sans, sizeof (ctx_font_sans) );
#endif
#if CTX_FONT_serif
  ctx_load_font_ctx ("serif-ctx", ctx_font_serif, sizeof (ctx_font_serif) );
#endif
#if CTX_FONT_symbol
  ctx_load_font_ctx ("symbol-ctx", ctx_font_symbol, sizeof (ctx_font_symbol) );
#endif
#if CTX_FONT_emoji
  ctx_load_font_ctx ("emoji-ctx", ctx_font_emoji, sizeof (ctx_font_emoji) );
#endif
#endif

#if NOTO_EMOJI_REGULAR
  ctx_load_font_ttf ("sans-NotoEmoji_Regular", ttf_NotoEmoji_Regular_ttf, ttf_NotoEmoji_Regular_ttf_len);
#endif
#if ROBOTO_LIGHT
  ctx_load_font_ttf ("sans-light-Roboto_Light", ttf_Roboto_Light_ttf, ttf_Roboto_Light_ttf_len);
#endif
#if ROBOTO_REGULAR
  ctx_load_font_ttf ("sans-Roboto_Regular", ttf_Roboto_Regular_ttf, ttf_Roboto_Regular_ttf_len);
#endif
#if ROBOTO_BOLD
  ctx_load_font_ttf ("sans-bold-Roboto_Bold", ttf_Roboto_Bold_ttf, ttf_Roboto_Bold_ttf_len);
#endif
#if DEJAVU_SANS
  ctx_load_font_ttf ("sans-DejaVuSans", ttf_DejaVuSans_ttf, ttf_DejaVuSans_ttf_len);
#endif
#if VERA
  ctx_load_font_ttf ("sans-Vera", ttf_Vera_ttf, ttf_Vera_ttf_len);
#endif
#if UNSCII_16
  ctx_load_font_ttf ("mono-unscii16", ttf_unscii_16_ttf, ttf_unscii_16_ttf_len);
#endif
#if XA000_MONO
  ctx_load_font_ttf ("mono-0xA000", ttf_0xA000_Mono_ttf, ttf_0xA000_Mono_ttf_len);
#endif
#if DEJAVU_SANS_MONO
  ctx_load_font_ttf ("mono-DejaVuSansMono", ttf_DejaVuSansMono_ttf, ttf_DejaVuSansMono_ttf_len);
#endif
#if NOTO_MONO_REGULAR
  ctx_load_font_ttf ("mono-NotoMono_Regular", ttf_NotoMono_Regular_ttf, ttf_NotoMono_Regular_ttf_len);
#endif
}



#if !__COSMOPOLITAN__
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif

//#include "ctx.h"
/* instead of including ctx.h we declare the few utf8
 * functions we use
 */
uint32_t ctx_utf8_to_unichar (const char *input);
int ctx_unichar_to_utf8 (uint32_t  ch, uint8_t  *dest);
int ctx_utf8_strlen (const char *s);

static void ctx_string_init (CtxString *string, int initial_size)
{
  string->allocated_length = initial_size;
  string->length = 0;
  string->utf8_length = 0;
  string->str = (char*)malloc (string->allocated_length + 1);
  string->str[0]='\0';
}

static void ctx_string_destroy (CtxString *string)
{
  if (string->str)
    {
      free (string->str);
      string->str = NULL;
    }
}

void ctx_string_clear (CtxString *string)
{
  string->length = 0;
  string->utf8_length = 0;
  string->str[string->length]=0;
}

static inline void _ctx_string_append_byte (CtxString *string, char  val)
{
  if (CTX_LIKELY((val & 0xC0) != 0x80))
    { string->utf8_length++; }
  if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length))
    {
      char *old = string->str;
      string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2);
      string->str = (char*)realloc (old, string->allocated_length);
    }
  string->str[string->length++] = val;
  string->str[string->length] = '\0';
}

void ctx_string_append_byte (CtxString *string, char  val)
{
  _ctx_string_append_byte (string, val);
}

void ctx_string_append_unichar (CtxString *string, unsigned int unichar)
{
  char *str;
  char utf8[5];
  utf8[ctx_unichar_to_utf8 (unichar, (unsigned char *) utf8)]=0;
  str = utf8;
  while (str && *str)
    {
      _ctx_string_append_byte (string, *str);
      str++;
    }
}

static inline void _ctx_string_append_str (CtxString *string, const char *str)
{
  if (!str) { return; }
  while (*str)
    {
      _ctx_string_append_byte (string, *str);
      str++;
    }
}

void ctx_string_append_utf8char (CtxString *string, const char *str)
{
  if (!str) { return; }
  int len = ctx_utf8_len (*str);
  for (int i = 0; i < len && *str; i++)
    {
      _ctx_string_append_byte (string, *str);
      str++;
    }
}

void ctx_string_append_str (CtxString *string, const char *str)
{
  _ctx_string_append_str (string, str);
}

CtxString *ctx_string_new_with_size (const char *initial, int initial_size)
{
  CtxString *string = (CtxString*)ctx_calloc (sizeof (CtxString), 1);
  ctx_string_init (string, initial_size);
  if (initial)
    { _ctx_string_append_str (string, initial); }
  return string;
}

CtxString *ctx_string_new (const char *initial)
{
  return ctx_string_new_with_size (initial, 8);
}

void ctx_string_append_data (CtxString *string, const char *str, int len)
{
  int i;
  for (i = 0; i<len; i++)
    { _ctx_string_append_byte (string, str[i]); }
}

void ctx_string_append_string (CtxString *string, CtxString *string2)
{
  const char *str = ctx_string_get (string2);
  while (str && *str)
    {
      _ctx_string_append_byte (string, *str);
      str++;
    }
}

const char *ctx_string_get (CtxString *string)
{
  return string->str;
}

int ctx_string_get_utf8length (CtxString *string)
{
  return string->utf8_length;
}

int ctx_string_get_length (CtxString *string)
{
  return string->length;
}

void
ctx_string_free (CtxString *string, int freealloc)
{
  if (freealloc)
    {
      ctx_string_destroy (string);
    }
#if 0
  if (string->is_line)
  {
    VtLine *line = (VtLine*)string;
    if (line->style)
      { free (line->style); }
    if (line->ctx)
      { ctx_free (line->ctx); }
    if (line->ctx_copy)
      { ctx_free (line->ctx_copy); }
  }
#endif
  free (string);
}

char       *ctx_string_dissolve       (CtxString *string)
{
  char *ret = string->str;
  ctx_string_free (string, 0);
  return ret;
}

void
ctx_string_set (CtxString *string, const char *new_string)
{
  ctx_string_clear (string);
  _ctx_string_append_str (string, new_string);
}

static char *ctx_strdup (const char *str)
{
  int len = strlen (str);
  char *ret = (char*)malloc (len + 1);
  memcpy (ret, str, len);
  ret[len]=0;
  return ret;
}

void ctx_string_replace_utf8 (CtxString *string, int pos, const char *new_glyph)
{
#if 1
  int old_len = string->utf8_length;
#else
  int old_len = ctx_utf8_strlen (string->str);// string->utf8_length;
#endif
  if (CTX_LIKELY(pos == old_len))
    {
      _ctx_string_append_str (string, new_glyph);
      return;
    }

  char tmpg[3]=" ";
  int new_len = ctx_utf8_len (*new_glyph);
  if (new_len <= 1 && new_glyph[0] < 32)
    {
      new_len = 1;
      tmpg[0]=new_glyph[0]+64;
      new_glyph = tmpg;
    }
  {
    for (int i = old_len; i <= pos + 2; i++)
      {
        _ctx_string_append_byte (string, ' ');
        old_len++;
      }
  }
  if (string->length + new_len  >= string->allocated_length - 2)
    {
      char *tmp;
      char *defer;
      string->allocated_length = string->length + new_len + 2;
      tmp = (char*) ctx_calloc (string->allocated_length + 1 + 8, 1);
      strcpy (tmp, string->str);
      defer = string->str;
      string->str = tmp;
      free (defer);
    }
  char *p = (char *) ctx_utf8_skip (string->str, pos);
  int prev_len = ctx_utf8_len (*p);
  char *rest;
  if (*p == 0 || * (p+prev_len) == 0)
    {
      rest = ctx_strdup ("");
    }
  else
    {
      if (p + prev_len >= string->length  + string->str)
        { rest = ctx_strdup (""); }
      else
        { rest = ctx_strdup (p + prev_len); }
    }
  memcpy (p, new_glyph, new_len);
  memcpy (p + new_len, rest, strlen (rest) + 1);
  string->length += new_len;
  string->length -= prev_len;
  free (rest);
  //string->length = strlen (string->str);
  //string->utf8_length = ctx_utf8_strlen (string->str);
}

void ctx_string_replace_unichar (CtxString *string, int pos, uint32_t unichar)
{
  uint8_t utf8[8];
  ctx_unichar_to_utf8 (unichar, utf8);
  ctx_string_replace_utf8 (string, pos, (char *) utf8);
}

uint32_t ctx_string_get_unichar (CtxString *string, int pos)
{
  char *p = (char *) ctx_utf8_skip (string->str, pos);
  if (!p)
    { return 0; }
  return ctx_utf8_to_unichar (p);
}


void ctx_string_insert_utf8 (CtxString *string, int pos, const char *new_glyph)
{
  int new_len = ctx_utf8_len (*new_glyph);
  int old_len = string->utf8_length;
  char tmpg[3]=" ";
  if (old_len == pos && 0)
    {
      ctx_string_append_str (string, new_glyph);
      return;
    }
  if (new_len <= 1 && new_glyph[0] < 32)
    {
      tmpg[0]=new_glyph[0]+64;
      new_glyph = tmpg;
    }
  {
    for (int i = old_len; i <= pos; i++)
      {
        _ctx_string_append_byte (string, ' ');
        old_len++;
      }
  }
  if (string->length + new_len + 1  > string->allocated_length)
    {
      char *tmp;
      char *defer;
      string->allocated_length = string->length + new_len + 1;
      tmp = (char*) ctx_calloc (string->allocated_length + 1, 1);
      strcpy (tmp, string->str);
      defer = string->str;
      string->str = tmp;
      free (defer);
    }
  char *p = (char *) ctx_utf8_skip (string->str, pos);
  int prev_len = ctx_utf8_len (*p);
  char *rest;
  if ( (*p == 0 || * (p+prev_len) == 0) && pos != 0)
    {
      rest = ctx_strdup ("");
    }
  else
    {
      rest = ctx_strdup (p);
    }
  memcpy (p, new_glyph, new_len);
  memcpy (p + new_len, rest, strlen (rest) + 1);
  free (rest);
  string->length = strlen (string->str);
  string->utf8_length = ctx_utf8_strlen (string->str);
}

void ctx_string_insert_unichar (CtxString *string, int pos, uint32_t unichar)
{
  uint8_t utf8[5]="";
  utf8[ctx_unichar_to_utf8(unichar, utf8)]=0;
  ctx_string_insert_utf8 (string, pos, (char*)utf8);
}

void ctx_string_remove (CtxString *string, int pos)
{
  int old_len = string->utf8_length;
  {
    for (int i = old_len; i <= pos; i++)
      {
        _ctx_string_append_byte (string, ' ');
        old_len++;
      }
  }
  char *p = (char *) ctx_utf8_skip (string->str, pos);
  int prev_len = ctx_utf8_len (*p);
  char *rest;
  if (!p || *p == 0)
    {
      return;
      rest = ctx_strdup ("");
      prev_len = 0;
    }
  else if (* (p+prev_len) == 0)
  {
      rest = ctx_strdup ("");
  }
  else
    {
      rest = ctx_strdup (p + prev_len);
    }
  strcpy (p, rest);
  string->str[string->length - prev_len] = 0;
  free (rest);
  string->length = strlen (string->str);
  string->utf8_length = ctx_utf8_strlen (string->str);
}

char *ctx_strdup_printf (const char *format, ...)
{
  va_list ap;
  size_t needed;
  char *buffer;
  va_start (ap, format);
  needed = vsnprintf (NULL, 0, format, ap) + 1;
  buffer = (char*)malloc (needed);
  va_end (ap);
  va_start (ap, format);
  vsnprintf (buffer, needed, format, ap);
  va_end (ap);
  return buffer;
}

void ctx_string_append_printf (CtxString *string, const char *format, ...)
{
  va_list ap;
  size_t needed;
  char *buffer;
  va_start (ap, format);
  needed = vsnprintf (NULL, 0, format, ap) + 1;
  buffer = (char*)malloc (needed);
  va_end (ap);
  va_start (ap, format);
  vsnprintf (buffer, needed, format, ap);
  va_end (ap);
  ctx_string_append_str (string, buffer);
  free (buffer);
}

#if CTX_CAIRO

typedef struct _CtxCairo CtxCairo;
struct
  _CtxCairo
{
  CtxImplementation vfuncs;
  Ctx              *ctx;
  cairo_t          *cr;
  cairo_pattern_t  *pat;
  cairo_surface_t  *image;
  int               preserve;
};

static void
ctx_cairo_process (CtxCairo *ctx_cairo, CtxCommand *c)
{
  CtxEntry *entry = (CtxEntry *) &c->entry;
  cairo_t *cr = ctx_cairo->cr;
  switch (entry->code)
    {
      case CTX_LINE_TO:
        cairo_line_to (cr, c->line_to.x, c->line_to.y);
        break;
      case CTX_REL_LINE_TO:
        cairo_rel_line_to (cr, c->rel_line_to.x, c->rel_line_to.y);
        break;
      case CTX_MOVE_TO:
        cairo_move_to (cr, c->move_to.x, c->move_to.y);
        break;
      case CTX_REL_MOVE_TO:
        cairo_rel_move_to (cr, ctx_arg_float (0), ctx_arg_float (1) );
        break;
      case CTX_CURVE_TO:
        cairo_curve_to (cr, ctx_arg_float (0), ctx_arg_float (1),
                        ctx_arg_float (2), ctx_arg_float (3),
                        ctx_arg_float (4), ctx_arg_float (5) );
        break;
      case CTX_REL_CURVE_TO:
        cairo_rel_curve_to (cr,ctx_arg_float (0), ctx_arg_float (1),
                            ctx_arg_float (2), ctx_arg_float (3),
                            ctx_arg_float (4), ctx_arg_float (5) );
        break;
      case CTX_PRESERVE:
        ctx_cairo->preserve = 1;
        break;
      case CTX_QUAD_TO:
        {
          double x0, y0;
          cairo_get_current_point (cr, &x0, &y0);
          float cx = ctx_arg_float (0);
          float cy = ctx_arg_float (1);
          float  x = ctx_arg_float (2);
          float  y = ctx_arg_float (3);
          cairo_curve_to (cr,
                          (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f,
                          (cx * 2 + x) / 3.0f,           (cy * 2 + y) / 3.0f,
                          x,                              y);
        }
        break;
      case CTX_REL_QUAD_TO:
        {
          double x0, y0;
          cairo_get_current_point (cr, &x0, &y0);
          float cx = ctx_arg_float (0) + x0;
          float cy = ctx_arg_float (1) + y0;
          float  x = ctx_arg_float (2) + x0;
          float  y = ctx_arg_float (3) + y0;
          cairo_curve_to (cr,
                          (cx * 2 + x0) / 3.0f, (cy * 2 + y0) / 3.0f,
                          (cx * 2 + x) / 3.0f,           (cy * 2 + y) / 3.0f,
                          x,                              y);
        }
        break;
      /* rotate/scale/translate does not occur in fully minified data stream */
      case CTX_ROTATE:
        cairo_rotate (cr, ctx_arg_float (0) );
        break;
      case CTX_SCALE:
        cairo_scale (cr, ctx_arg_float (0), ctx_arg_float (1) );
        break;
      case CTX_TRANSLATE:
        cairo_translate (cr, ctx_arg_float (0), ctx_arg_float (1) );
        break;
      case CTX_LINE_WIDTH:
        cairo_set_line_width (cr, ctx_arg_float (0) );
        break;
      case CTX_ARC:
#if 0
        fprintf (stderr, "F %2.1f %2.1f %2.1f %2.1f %2.1f %2.1f\n",
                        ctx_arg_float(0),
                        ctx_arg_float(1),
                        ctx_arg_float(2),
                        ctx_arg_float(3),
                        ctx_arg_float(4),
                        ctx_arg_float(5),
                        ctx_arg_float(6));
#endif
        if (ctx_arg_float (5) == 1)
          cairo_arc (cr, ctx_arg_float (0), ctx_arg_float (1),
                     ctx_arg_float (2), ctx_arg_float (3),
                     ctx_arg_float (4) );
        else
          cairo_arc_negative (cr, ctx_arg_float (0), ctx_arg_float (1),
                              ctx_arg_float (2), ctx_arg_float (3),
                              ctx_arg_float (4) );
        break;
      case CTX_SET_RGBA_U8:
        cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ),
                               ctx_u8_to_float (ctx_arg_u8 (1) ),
                               ctx_u8_to_float (ctx_arg_u8 (2) ),
                               ctx_u8_to_float (ctx_arg_u8 (3) ) );
        break;
#if 0
      case CTX_SET_RGBA_STROKE: // XXX : we need to maintain
        //       state for the two kinds
        cairo_set_source_rgba (cr, ctx_arg_u8 (0) /255.0,
                               ctx_arg_u8 (1) /255.0,
                               ctx_arg_u8 (2) /255.0,
                               ctx_arg_u8 (3) /255.0);
        break;
#endif
      case CTX_RECTANGLE:
      case CTX_ROUND_RECTANGLE: // XXX - arcs
        cairo_rectangle (cr, c->rectangle.x, c->rectangle.y,
                         c->rectangle.width, c->rectangle.height);
        break;
      case CTX_SET_PIXEL:
        cairo_set_source_rgba (cr, ctx_u8_to_float (ctx_arg_u8 (0) ),
                               ctx_u8_to_float (ctx_arg_u8 (1) ),
                               ctx_u8_to_float (ctx_arg_u8 (2) ),
                               ctx_u8_to_float (ctx_arg_u8 (3) ) );
        cairo_rectangle (cr, ctx_arg_u16 (2), ctx_arg_u16 (3), 1, 1);
        cairo_fill (cr);
        break;
      case CTX_FILL:
        if (ctx_cairo->preserve)
        {
          cairo_fill_preserve (cr);
          ctx_cairo->preserve = 0;
        }
        else
        {
          cairo_fill (cr);
        }
        break;
      case CTX_STROKE:
        if (ctx_cairo->preserve)
        {
          cairo_stroke_preserve (cr);
          ctx_cairo->preserve = 0;
        }
        else
        {
          cairo_stroke (cr);
        }
        break;
      case CTX_IDENTITY:
        cairo_identity_matrix (cr);
        break;
      case CTX_CLIP:
        if (ctx_cairo->preserve)
        {
          cairo_clip_preserve (cr);
          ctx_cairo->preserve = 0;
        }
        else
        {
          cairo_clip (cr);
        }
        break;
        break;
      case CTX_BEGIN_PATH:
        cairo_new_path (cr);
        break;
      case CTX_CLOSE_PATH:
        cairo_close_path (cr);
        break;
      case CTX_SAVE:
        cairo_save (cr);
        break;
      case CTX_RESTORE:
        cairo_restore (cr);
        break;
      case CTX_FONT_SIZE:
        cairo_set_font_size (cr, ctx_arg_float (0) );
        break;
      case CTX_MITER_LIMIT:
        cairo_set_miter_limit (cr, ctx_arg_float (0) );
        break;
      case CTX_LINE_CAP:
        {
          int cairo_val = CAIRO_LINE_CAP_SQUARE;
          switch (ctx_arg_u8 (0) )
            {
              case CTX_CAP_ROUND:
                cairo_val = CAIRO_LINE_CAP_ROUND;
                break;
              case CTX_CAP_SQUARE:
                cairo_val = CAIRO_LINE_CAP_SQUARE;
                break;
              case CTX_CAP_NONE:
                cairo_val = CAIRO_LINE_CAP_BUTT;
                break;
            }
          cairo_set_line_cap (cr, cairo_val);
        }
        break;
      case CTX_BLEND_MODE:
        {
          // does not map to cairo
        }
        break;
      case CTX_COMPOSITING_MODE:
        {
          int cairo_val = CAIRO_OPERATOR_OVER;
          switch (ctx_arg_u8 (0) )
            {
              case CTX_COMPOSITE_SOURCE_OVER:
                cairo_val = CAIRO_OPERATOR_OVER;
                break;
              case CTX_COMPOSITE_COPY:
                cairo_val = CAIRO_OPERATOR_SOURCE;
                break;
            }
          cairo_set_operator (cr, cairo_val);
        }
      case CTX_LINE_JOIN:
        {
          int cairo_val = CAIRO_LINE_JOIN_ROUND;
          switch (ctx_arg_u8 (0) )
            {
              case CTX_JOIN_ROUND:
                cairo_val = CAIRO_LINE_JOIN_ROUND;
                break;
              case CTX_JOIN_BEVEL:
                cairo_val = CAIRO_LINE_JOIN_BEVEL;
                break;
              case CTX_JOIN_MITER:
                cairo_val = CAIRO_LINE_JOIN_MITER;
                break;
            }
          cairo_set_line_join (cr, cairo_val);
        }
        break;
      case CTX_LINEAR_GRADIENT:
        {
          if (ctx_cairo->pat)
            {
              cairo_pattern_destroy (ctx_cairo->pat);
              ctx_cairo->pat = NULL;
            }
          ctx_cairo->pat = cairo_pattern_create_linear (ctx_arg_float (0), ctx_arg_float (1),
                           ctx_arg_float (2), ctx_arg_float (3) );
          cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 0, 0, 0, 0, 1);
          cairo_pattern_add_color_stop_rgba (ctx_cairo->pat, 1, 1, 1, 1, 1);
          cairo_set_source (cr, ctx_cairo->pat);
        }
        break;
      case CTX_RADIAL_GRADIENT:
        {
          if (ctx_cairo->pat)
            {
              cairo_pattern_destroy (ctx_cairo->pat);
              ctx_cairo->pat = NULL;
            }
          ctx_cairo->pat = cairo_pattern_create_radial (ctx_arg_float (0), ctx_arg_float (1),
                           ctx_arg_float (2), ctx_arg_float (3),
                           ctx_arg_float (4), ctx_arg_float (5) );
          cairo_set_source (cr, ctx_cairo->pat);
        }
        break;
      case CTX_GRADIENT_STOP:
        cairo_pattern_add_color_stop_rgba (ctx_cairo->pat,
                                           ctx_arg_float (0),
                                           ctx_u8_to_float (ctx_arg_u8 (4) ),
                                           ctx_u8_to_float (ctx_arg_u8 (5) ),
                                           ctx_u8_to_float (ctx_arg_u8 (6) ),
                                           ctx_u8_to_float (ctx_arg_u8 (7) ) );
        break;
        // XXX  implement TEXTURE
#if 0
      case CTX_LOAD_IMAGE:
        {
          if (image)
            {
              cairo_surface_destroy (image);
              image = NULL;
            }
          if (pat)
            {
              cairo_pattern_destroy (pat);
              pat = NULL;
            }
          image = cairo_image_surface_create_from_png (ctx_arg_string() );
          cairo_set_source_surface (cr, image, ctx_arg_float (0), ctx_arg_float (1) );
        }
        break;
#endif
      case CTX_TEXT:
        /* XXX: implement some linebreaking/wrap, positioning
         *      behavior here
         */
        cairo_show_text (cr, ctx_arg_string () );
        break;
      case CTX_CONT:
      case CTX_EDGE:
      case CTX_DATA:
      case CTX_DATA_REV:
      case CTX_FLUSH:
        break;
    }
  ctx_process (ctx_cairo->ctx, entry);
}

void ctx_cairo_free (CtxCairo *ctx_cairo)
{
  if (ctx_cairo->pat)
    { cairo_pattern_destroy (ctx_cairo->pat); }
  if (ctx_cairo->image)
    { cairo_surface_destroy (ctx_cairo->image); }
  free (ctx_cairo);
}

void
ctx_render_cairo (Ctx *ctx, cairo_t *cr)
{
  CtxIterator iterator;
  CtxCommand *command;
  CtxCairo    ctx_cairo = {{(void*)ctx_cairo_process, NULL, NULL}, ctx, cr, NULL, NULL};
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    { ctx_cairo_process (&ctx_cairo, command); }
}

Ctx *
ctx_new_for_cairo (cairo_t *cr)
{
  Ctx *ctx = ctx_new ();
  CtxCairo *ctx_cairo = calloc(sizeof(CtxCairo),1);
  ctx_cairo->vfuncs.free = (void*)ctx_cairo_free;
  ctx_cairo->vfuncs.process = (void*)ctx_cairo_process;
  ctx_cairo->ctx = ctx;
  ctx_cairo->cr = cr;

  ctx_set_renderer (ctx, (void*)ctx_cairo);
  return ctx;
}

#endif

#if CTX_EVENTS

static int ctx_find_largest_matching_substring
 (const char *X, const char *Y, int m, int n, int *offsetY, int *offsetX) 
{ 
  int longest_common_suffix[2][n+1];
  int best_length = 0;
  for (int i=0; i<=m; i++)
  {
    for (int j=0; j<=n; j++)
    {
      if (i == 0 || j == 0 || !(X[i-1] == Y[j-1]))
      {
        longest_common_suffix[i%2][j] = 0;
      }
      else
      {
          longest_common_suffix[i%2][j] = longest_common_suffix[(i-1)%2][j-1] + 1;
          if (best_length < longest_common_suffix[i%2][j])
          {
            best_length = longest_common_suffix[i%2][j];
            if (offsetY) *offsetY = j - best_length;
            if (offsetX) *offsetX = i - best_length;
          }
      }
    }
  }
  return best_length;
} 

typedef struct CtxSpan {
  int from_prev;
  int start;
  int length;
} CtxSpan;

#define CHUNK_SIZE 32
#define MIN_MATCH  7        // minimum match length to be encoded
#define WINDOW_PADDING 16   // look-aside amount

#if 0
static void _dassert(int line, int condition, const char *str, int foo, int bar, int baz)
{
  if (!condition)
  {
    FILE *f = fopen ("/tmp/cdebug", "a");
    fprintf (f, "%i: %s    %i %i %i\n", line, str, foo, bar, baz);
    fclose (f);
  }
}
#define dassert(cond, foo, bar, baz) _dassert(__LINE__, cond, #cond, foo, bar ,baz)
#endif
#define dassert(cond, foo, bar, baz)

/* XXX repeated substring matching is slow, we'll be
 * better off with a hash-table with linked lists of
 * matching 3-4 characters in previous.. or even
 * a naive approach that expects rough alignment..
 */
static char *encode_in_terms_of_previous (
                const char *src,  int src_len,
                const char *prev, int prev_len,
                int *out_len,
                int max_ticks)
{
  CtxString *string = ctx_string_new ("");
  CtxList *encoded_list = NULL;

  /* TODO : make expected position offset in prev slide based on
   * matches and not be constant */

  long ticks_start = ctx_ticks ();
  int start = 0;
  int length = CHUNK_SIZE;
  for (start = 0; start < src_len; start += length)
  {
    CtxSpan *span = calloc (sizeof (CtxSpan), 1);
    span->start = start;
    if (start + length > src_len)
      span->length = src_len - start;
    else
      span->length = length;
    span->from_prev = 0;
    ctx_list_append (&encoded_list, span);
  }

  for (CtxList *l = encoded_list; l; l = l->next)
  {
    CtxSpan *span = l->data;
    if (!span->from_prev)
    {
      if (span->length >= MIN_MATCH)
      {
         int prev_pos = 0;
         int curr_pos = 0;
         assert(1);
#if 0
         int prev_start =  0;
         int prev_window_length = prev_len;
#else
         int window_padding = WINDOW_PADDING;
         int prev_start = span->start - window_padding;
         if (prev_start < 0)
           prev_start = 0;

         dassert(span->start>=0 , 0,0,0);

         int prev_window_length = prev_len - prev_start;
         if (prev_window_length > span->length + window_padding * 2 + span->start)
           prev_window_length = span->length + window_padding * 2 + span->start;
#endif
         int match_len = 0;
         if (prev_window_length > 0)
           match_len = ctx_find_largest_matching_substring(prev + prev_start, src + span->start, prev_window_length, span->length, &curr_pos, &prev_pos);
#if 1
         prev_pos += prev_start;
#endif

         if (match_len >= MIN_MATCH)
         {
            int start  = span->start;
            int length = span->length;

            span->from_prev = 1;
            span->start     = prev_pos;
            span->length    = match_len;
            dassert (span->start >= 0, prev_pos, prev_start, span->start);
            dassert (span->length > 0, prev_pos, prev_start, span->length);

            if (curr_pos)
            {
              CtxSpan *prev = calloc (sizeof (CtxSpan), 1);
              prev->start = start;
              prev->length =  curr_pos;
            dassert (prev->start >= 0, prev_pos, prev_start, prev->start);
            dassert (prev->length > 0, prev_pos, prev_start, prev->length);
              prev->from_prev = 0;
              ctx_list_insert_before (&encoded_list, l, prev);
            }


            if (match_len + curr_pos < start + length)
            {
              CtxSpan *next = calloc (sizeof (CtxSpan), 1);
              next->start = start + curr_pos + match_len;
              next->length = (start + length) - next->start;
            dassert (next->start >= 0, prev_pos, prev_start, next->start);
      //    dassert (next->length > 0, prev_pos, prev_start, next->length);
              next->from_prev = 0;
              if (next->length)
              {
                if (l->next)
                  ctx_list_insert_before (&encoded_list, l->next, next);
                else
                  ctx_list_append (&encoded_list, next);
              }
              else
                free (next);
            }

            if (curr_pos) // step one item back for forloop
            {
              CtxList *tmp = encoded_list;
              int found = 0;
              while (!found && tmp && tmp->next)
              {
                if (tmp->next == l)
                {
                  l = tmp;
                  break;
                }
                tmp = tmp->next;
              }
            }
         }
      }
    }

    if (ctx_ticks ()-ticks_start > (unsigned long)max_ticks)
      break;
  }

  /* merge adjecant prev span references  */
  {
    for (CtxList *l = encoded_list; l; l = l->next)
    {
      CtxSpan *span = l->data;
again:
      if (l->next)
      {
        CtxSpan *next_span = l->next->data;
        if (span->from_prev && next_span->from_prev &&
            span->start + span->length == 
            next_span->start)
        {
           span->length += next_span->length;
           ctx_list_remove (&encoded_list, next_span);
           goto again;
        }
      }
    }
  }

  while (encoded_list)
  {
    CtxSpan *span = encoded_list->data;
    if (span->from_prev)
    {
      char ref[128];
      sprintf (ref, "%c%i %i%c", CTX_CODEC_CHAR, span->start, span->length, CTX_CODEC_CHAR);
      ctx_string_append_data (string, ref, strlen(ref));
    }
    else
    {
      for (int i = span->start; i< span->start+span->length; i++)
      {
        if (src[i] == CTX_CODEC_CHAR)
        {
          char bytes[2]={CTX_CODEC_CHAR, CTX_CODEC_CHAR};
          ctx_string_append_data (string, bytes, 2);
        }
        else
        {
          ctx_string_append_data (string, &src[i], 1);
        }
      }
    }
    free (span);
    ctx_list_remove (&encoded_list, span);
  }

  char *ret = string->str;
  if (out_len) *out_len = string->length;
  ctx_string_free (string, 0);
  return ret;
}

#if 0 // for documentation/reference purposes
static char *decode_ctx (const char *encoded, int enc_len, const char *prev, int prev_len, int *out_len)
{
  CtxString *string = ctx_string_new ("");
  char reference[32]="";
  int ref_len = 0;
  int in_ref = 0;
  for (int i = 0; i < enc_len; i++)
  {
    if (encoded[i] == CTX_CODEC_CHAR)
    {
      if (!in_ref)
      {
        in_ref = 1;
      }
      else
      {
        int start = atoi (reference);
        int len = 0;
        if (strchr (reference, ' '))
          len = atoi (strchr (reference, ' ')+1);

        if (start < 0)start = 0;
        if (start >= prev_len)start = prev_len-1;
        if (len + start > prev_len)
          len = prev_len - start;

        if (start == 0 && len == 0)
          ctx_string_append_byte (string, CTX_CODEC_CHAR);
        else
          ctx_string_append_data (string, prev + start, len);
        ref_len = 0;
        in_ref = 0;
      }
    }
    else
    {
      if (in_ref)
      {
        if (ref_len < 16)
        {
          reference[ref_len++] = encoded[i];
          reference[ref_len] = 0;
        }
      }
      else
      ctx_string_append_data (string, &encoded[i], 1);
    }
  }
  char *ret = string->str;
  if (out_len) *out_len = string->length;
  ctx_string_free (string, 0);
  return ret;
}
#endif

#define CTX_START_STRING "U\n"  // or " reset "
#define CTX_END_STRING   "\nX"  // or "\ndone"
#define CTX_END_STRING2  "\n\e"

int ctx_frame_ack = -1;
static char *prev_frame_contents = NULL;
static int   prev_frame_len = 0;

static void ctx_ctx_flush (CtxCtx *ctxctx)
{
#if 0
  FILE *debug = fopen ("/tmp/ctx-debug", "a");
  fprintf (debug, "------\n");
#endif

  if (ctx_native_events)
    fprintf (stdout, "\e[?201h");
  fprintf (stdout, "\e[H\e[?25l\e[?200h");
#if 1
  fprintf (stdout, CTX_START_STRING);
  ctx_render_stream (ctxctx->ctx, stdout, 0);
  fprintf (stdout, CTX_END_STRING);
#else
  {
    int cur_frame_len = 0;
    char *rest = ctx_render_string (ctxctx->ctx, 0, &cur_frame_len);
    char *cur_frame_contents = malloc (cur_frame_len + strlen(CTX_START_STRING) + strlen (CTX_END_STRING) + 1);

    cur_frame_contents[0]=0;
    strcat (cur_frame_contents, CTX_START_STRING);
    strcat (cur_frame_contents, rest);
    strcat (cur_frame_contents, CTX_END_STRING);
    free (rest);
    cur_frame_len += strlen (CTX_START_STRING) + strlen (CTX_END_STRING);

    if (prev_frame_contents && 0)  // XXX : 
    {
      char *encoded;
      int encoded_len = 0;
      //uint64_t ticks_start = ctx_ticks ();

      encoded = encode_in_terms_of_previous (cur_frame_contents, cur_frame_len, prev_frame_contents, prev_frame_len, &encoded_len, 1000 * 10);
//    encoded = strdup (cur_frame_contents);
//    encoded_len = strlen (encoded);
      //uint64_t ticks_end = ctx_ticks ();

      fwrite (encoded, encoded_len, 1, stdout);
//    fwrite (encoded, cur_frame_len, 1, stdout);
#if 0
      fprintf (debug, "---prev-frame(%i)\n%s", (int)strlen(prev_frame_contents), prev_frame_contents);
      fprintf (debug, "---cur-frame(%i)\n%s", (int)strlen(cur_frame_contents), cur_frame_contents);
      fprintf (debug, "---encoded(%.4f %i)---\n%s--------\n",
                      (ticks_end-ticks_start)/1000.0,
                      (int)strlen(encoded), encoded);
#endif
      free (encoded);
    }
    else
    {
      fwrite (cur_frame_contents, cur_frame_len, 1, stdout);
    }

    if (prev_frame_contents)
      free (prev_frame_contents);
    prev_frame_contents = cur_frame_contents;
    prev_frame_len = cur_frame_len;
  }
#endif
#if 0
    fclose (debug);
#endif
  fprintf (stdout, CTX_END_STRING2);

  fprintf (stdout, "\e[5n");
  fflush (stdout);

  ctx_frame_ack = 0;
  do {
     ctx_consume_events (ctxctx->ctx);
  } while (ctx_frame_ack != 1);
}

void ctx_ctx_free (CtxCtx *ctx)
{
  nc_at_exit ();
  free (ctx);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

Ctx *ctx_new_ctx (int width, int height)
{
  float font_size = 12.0;
  Ctx *ctx = ctx_new ();
  CtxCtx *ctxctx = (CtxCtx*)calloc (sizeof (CtxCtx), 1);
  fprintf (stdout, "\e[?1049h");
  fflush (stdout);
  //fprintf (stderr, "\e[H");
  //fprintf (stderr, "\e[2J");
  ctx_native_events = 1;
  if (width <= 0 || height <= 0)
  {
    ctxctx->cols = ctx_terminal_cols ();
    ctxctx->rows = ctx_terminal_rows ();
    width  = ctxctx->width  = ctx_terminal_width ();
    height = ctxctx->height = ctx_terminal_height ();
    font_size = height / ctxctx->rows;
    ctx_font_size (ctx, font_size);
  }
  else
  {
    ctxctx->width  = width;
    ctxctx->height = height;
    ctxctx->cols   = width / 80;
    ctxctx->rows   = height / 24;
  }
  ctxctx->ctx = ctx;
  if (!ctx_native_events)
    _ctx_mouse (ctx, NC_MOUSE_DRAG);
  ctx_set_renderer (ctx, ctxctx);
  ctx_set_size (ctx, width, height);
  ctxctx->flush = (void(*)(void *))ctx_ctx_flush;
  ctxctx->free  = (void(*)(void *))ctx_ctx_free;
  return ctx;
}

void ctx_ctx_pcm (Ctx *ctx);

int ctx_ctx_consume_events (Ctx *ctx)
{
  int ix, iy;
  CtxCtx *ctxctx = (CtxCtx*)ctx->renderer;
  const char *event = NULL;
#if CTX_AUDIO
  ctx_ctx_pcm (ctx);
#endif
  if (ctx_native_events)
    {
      float x = 0, y = 0;
      int b = 0;
      char event_type[128]="";
      event = ctx_native_get_event (ctx, 1000/120);
#if 0
      if(event){
        FILE *file = fopen ("/tmp/log", "a");
        fprintf (file, "[%s]\n", event);
        fclose (file);
      }
#endif
      if (event)
      {
      sscanf (event, "%s %f %f %i", event_type, &x, &y, &b);
      if (!strcmp (event_type, "idle"))
      {
      }
      else if (!strcmp (event_type, "mouse-press"))
      {
        ctx_pointer_press (ctx, x, y, b, 0);
      }
      else if (!strcmp (event_type, "mouse-drag")||
               !strcmp (event_type, "mouse-motion"))
      {
        ctx_pointer_motion (ctx, x, y, b, 0);
      }
      else if (!strcmp (event_type, "mouse-release"))
      {
        ctx_pointer_release (ctx, x, y, b, 0);
      }
      else if (!strcmp (event_type, "message"))
      {
        ctx_incoming_message (ctx, event + strlen ("message"), 0);
      } else if (!strcmp (event, "size-changed"))
      {
        fprintf (stdout, "\e[H\e[2J\e[?25l");
        ctxctx->cols = ctx_terminal_cols ();
        ctxctx->rows = ctx_terminal_rows ();
        ctxctx->width  = ctx_terminal_width ();
        ctxctx->height = ctx_terminal_height ();
        ctx_set_size (ctx, ctxctx->width, ctxctx->height);

        if (prev_frame_contents)
          free (prev_frame_contents);
        prev_frame_contents = NULL;
        prev_frame_len = 0;
        ctx_set_dirty (ctx, 1);
        //ctx_key_press (ctx, 0, "size-changed", 0);
      }
      else if (!strcmp (event_type, "keyup"))
      {
        char buf[4]={ x, 0 };
        ctx_key_up (ctx, (int)x, buf, 0);
      }
      else if (!strcmp (event_type, "keydown"))
      {
        char buf[4]={ x, 0 };
        ctx_key_down (ctx, (int)x, buf, 0);
      }
      else
      {
        ctx_key_press (ctx, 0, event, 0);
      }
      }
    }
  else
    {
      float x, y;
      event = ctx_nct_get_event (ctx, 20, &ix, &iy);

      x = (ix - 1.0 + 0.5) / ctxctx->cols * ctx->events.width;
      y = (iy - 1.0)       / ctxctx->rows * ctx->events.height;

      if (!strcmp (event, "mouse-press"))
      {
        ctx_pointer_press (ctx, x, y, 0, 0);
        ctxctx->was_down = 1;
      } else if (!strcmp (event, "mouse-release"))
      {
        ctx_pointer_release (ctx, x, y, 0, 0);
      } else if (!strcmp (event, "mouse-motion"))
      {
        //nct_set_cursor_pos (backend->term, ix, iy);
        //nct_flush (backend->term);
        if (ctxctx->was_down)
        {
          ctx_pointer_release (ctx, x, y, 0, 0);
          ctxctx->was_down = 0;
        }
        ctx_pointer_motion (ctx, x, y, 0, 0);
      } else if (!strcmp (event, "mouse-drag"))
      {
        ctx_pointer_motion (ctx, x, y, 0, 0);
      } else if (!strcmp (event, "size-changed"))
      {
        fprintf (stdout, "\e[H\e[2J\e[?25l");
        ctxctx->cols = ctx_terminal_cols ();
        ctxctx->rows = ctx_terminal_rows ();
        ctxctx->width  = ctx_terminal_width ();
        ctxctx->height = ctx_terminal_height ();
        ctx_set_size (ctx, ctxctx->width, ctxctx->height);

        if (prev_frame_contents)
           free (prev_frame_contents);
        prev_frame_contents = NULL;
        prev_frame_len = 0;
        ctx_set_dirty (ctx, 1);
        //ctx_key_press (ctx, 0, "size-changed", 0);
      }
      else
      {
        if (!strcmp (event, "esc"))
          ctx_key_press (ctx, 0, "escape", 0);
        else if (!strcmp (event, "space"))
          ctx_key_press (ctx, 0, "space", 0);
        else if (!strcmp (event, "enter")||
                 !strcmp (event, "return"))
          ctx_key_press (ctx, 0, "\n", 0);
        else
        ctx_key_press (ctx, 0, event, 0);
      }
    }

  return 1;
}

int ctx_renderer_is_ctx (Ctx *ctx)
{
  if (ctx->renderer &&
      ctx->renderer->free == (void*)ctx_ctx_free)
          return 1;
  return 0;
}

#endif

#if CTX_TILED
static inline int
ctx_tiled_threads_done (CtxTiled *tiled)
{
  int sum = 0;
  for (int i = 0; i < _ctx_max_threads; i++)
  {
     if (tiled->rendered_frame[i] == tiled->render_frame)
       sum ++;
  }
  return sum;
}

int _ctx_damage_control = 0;

void ctx_tiled_free (CtxTiled *tiled)
{
  tiled->quit = 1;
  mtx_lock (&tiled->mtx);
  cnd_broadcast (&tiled->cond);
  mtx_unlock (&tiled->mtx);

  while (tiled->thread_quit < _ctx_max_threads)
    usleep (1000);

  if (tiled->pixels)
  {
    free (tiled->pixels);
  tiled->pixels = NULL;
  for (int i = 0 ; i < _ctx_max_threads; i++)
  {
    ctx_free (tiled->host[i]);
    tiled->host[i]=NULL;
  }

  ctx_free (tiled->ctx_copy);
  }
  // leak?
}
static unsigned char *sdl_icc = NULL;
static long sdl_icc_length = 0;

inline static void ctx_tiled_flush (CtxTiled *tiled)
{
  if (tiled->shown_frame == tiled->render_frame)
  {
    int dirty_tiles = 0;
    ctx_set_drawlist (tiled->ctx_copy, &tiled->ctx->drawlist.entries[0],
                                           tiled->ctx->drawlist.count * 9);
    if (_ctx_enable_hash_cache)
    {
      Ctx *hasher = ctx_hasher_new (tiled->width, tiled->height,
                        CTX_HASH_COLS, CTX_HASH_ROWS);
      ctx_render_ctx (tiled->ctx_copy, hasher);

      for (int row = 0; row < CTX_HASH_ROWS; row++)
        for (int col = 0; col < CTX_HASH_COLS; col++)
        {
          uint8_t *new_hash = ctx_hasher_get_hash (hasher, col, row);
          if (new_hash && memcmp (new_hash, &tiled->hashes[(row * CTX_HASH_COLS + col) *  20], 20))
          {
            memcpy (&tiled->hashes[(row * CTX_HASH_COLS +  col)*20], new_hash, 20);
            tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1;
            dirty_tiles++;
          }
          else
          {
            tiled->tile_affinity[row * CTX_HASH_COLS + col] = -1;
          }
        }
      free (((CtxHasher*)(hasher->renderer))->hashes);
      ctx_free (hasher);
    }
    else
    {
    for (int row = 0; row < CTX_HASH_ROWS; row++)
      for (int col = 0; col < CTX_HASH_COLS; col++)
        {
          tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1;
          dirty_tiles++;
        }
    }
    int dirty_no = 0;
    if (dirty_tiles)
    for (int row = 0; row < CTX_HASH_ROWS; row++)
      for (int col = 0; col < CTX_HASH_COLS; col++)
      {
        if (tiled->tile_affinity[row * CTX_HASH_COLS + col] != -1)
        {
          tiled->tile_affinity[row * CTX_HASH_COLS + col] = dirty_no * (_ctx_max_threads) / dirty_tiles;
          dirty_no++;
          if (col > tiled->max_col) tiled->max_col = col;
          if (col < tiled->min_col) tiled->min_col = col;
          if (row > tiled->max_row) tiled->max_row = row;
          if (row < tiled->min_row) tiled->min_row = row;
        }
      }

    if (_ctx_damage_control)
    {
      for (int i = 0; i < tiled->width * tiled->height; i++)
      {
        tiled->pixels[i*4+2]  = (tiled->pixels[i*4+2] + 255)/2;
      }
    }

    tiled->render_frame = ++tiled->frame;

#if 0

          //if (tiled->tile_affinity[hno]==no)
          {
            int x0 = ((tiled->width)/CTX_HASH_COLS) * 0;
            int y0 = ((tiled->height)/CTX_HASH_ROWS) * 0;
            int width = tiled->width / CTX_HASH_COLS;
            int height = tiled->height / CTX_HASH_ROWS;
            Ctx *host = tiled->host[0];

            CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer;
            int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green;
            ctx_rasterizer_init (rasterizer,
                                 host, tiled->ctx, &host->state,
                                 &tiled->pixels[tiled->width * 4 * y0 + x0 * 4],
                                 0, 0, 1, 1,
                                 tiled->width*4, CTX_FORMAT_BGRA8,
                                 tiled->antialias);
            ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green;
            if (sdl_icc_length)
              ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length);

            ctx_translate (host, -x0, -y0);
            ctx_render_ctx (tiled->ctx_copy, host);
          }
#endif


    mtx_lock (&tiled->mtx);
    cnd_broadcast (&tiled->cond);
    mtx_unlock (&tiled->mtx);
  }
}

static
void ctx_tiled_render_fun (void **data)
{
  int      no = (size_t)data[0];
  CtxTiled *tiled = data[1];

  while (!tiled->quit)
  {
    Ctx *host = tiled->host[no];

    mtx_lock (&tiled->mtx);
    cnd_wait(&tiled->cond, &tiled->mtx);
    mtx_unlock (&tiled->mtx);

    if (tiled->render_frame != tiled->rendered_frame[no])
    {
      int hno = 0;
      for (int row = 0; row < CTX_HASH_ROWS; row++)
        for (int col = 0; col < CTX_HASH_COLS; col++, hno++)
        {
          if (tiled->tile_affinity[hno]==no)
          {
            int x0 = ((tiled->width)/CTX_HASH_COLS) * col;
            int y0 = ((tiled->height)/CTX_HASH_ROWS) * row;
            int width = tiled->width / CTX_HASH_COLS;
            int height = tiled->height / CTX_HASH_ROWS;

            CtxRasterizer *rasterizer = (CtxRasterizer*)host->renderer;
#if 1 // merge horizontally adjecant tiles of same affinity into one job
            while (col + 1 < CTX_HASH_COLS &&
                   tiled->tile_affinity[hno+1] == no)
            {
              width += tiled->width / CTX_HASH_COLS;
              col++;
              hno++;
            }
#endif
            int swap_red_green = ((CtxRasterizer*)(host->renderer))->swap_red_green;
            ctx_rasterizer_init (rasterizer,
                                 host, tiled->ctx, &host->state,
                                 &tiled->pixels[tiled->width * 4 * y0 + x0 * 4],
                                 0, 0, width, height,
                                 tiled->width*4, CTX_FORMAT_BGRA8,
                                 tiled->antialias);
            ((CtxRasterizer*)(host->renderer))->swap_red_green = swap_red_green;
            if (sdl_icc_length)
              ctx_colorspace (host, CTX_COLOR_SPACE_DEVICE_RGB, sdl_icc, sdl_icc_length);

            ctx_translate (host, -x0, -y0);
            ctx_render_ctx (tiled->ctx_copy, host);
          }
        }
      tiled->rendered_frame[no] = tiled->render_frame;
    }
  }
  tiled->thread_quit++; // need atomic?
}

#endif


#if CTX_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
#endif


#if CTX_FB
  #include <linux/fb.h>
  #include <linux/vt.h>
  #include <linux/kd.h>
  #include <sys/mman.h>
  #include <threads.h>
  #include <libdrm/drm.h>
  #include <libdrm/drm_mode.h>

typedef struct _EvSource EvSource;
 

struct _EvSource
{
  void   *priv; /* private storage  */

  /* returns non 0 if there is events waiting */
  int   (*has_event) (EvSource *ev_source);

  /* get an event, the returned event should be freed by the caller  */
  char *(*get_event) (EvSource *ev_source);

  /* destroy/unref this instance */
  void  (*destroy)   (EvSource *ev_source);

  /* get the underlying fd, useful for using select on  */
  int   (*get_fd)    (EvSource *ev_source);


  void  (*set_coord) (EvSource *ev_source, double x, double y);
  /* set_coord is needed to warp relative cursors into normalized range,
   * like normal mice/trackpads/nipples - to obey edges and more.
   */

  /* if this returns non-0 select can be used for non-blocking.. */
};


typedef struct _CtxFb CtxFb;
struct _CtxFb
{
   CtxTiled tiled;
#if 0
   void (*render) (void *fb, CtxCommand *command);
   void (*reset)  (void *fb);
   void (*flush)  (void *fb);
   char *(*get_clipboard) (void *ctxctx);
   void (*set_clipboard) (void *ctxctx, const char *text);
   void (*free)   (void *fb);
   Ctx          *ctx;
   int           width;
   int           height;
   int           cols; // unused
   int           rows; // unused
   int           was_down;
   uint8_t      *pixels;
   Ctx          *ctx_copy;
   Ctx          *host[CTX_MAX_THREADS];
   CtxAntialias  antialias;
   int           quit;
   _Atomic int   thread_quit;
   int           shown_frame;
   int           render_frame;
   int           rendered_frame[CTX_MAX_THREADS];
   int           frame;
   int           min_col; // hasher cols and rows
   int           min_row;
   int           max_col;
   int           max_row;
   uint8_t       hashes[CTX_HASH_ROWS * CTX_HASH_COLS *  20];
   int8_t        tile_affinity[CTX_HASH_ROWS * CTX_HASH_COLS]; // which render thread no is
                                                           // responsible for a tile
                                                           //


   int           pointer_down[3];
#endif
   int           key_balance;
   int           key_repeat;
   int           lctrl;
   int           lalt;
   int           rctrl;

   uint8_t      *fb;

   int          fb_fd;
   char        *fb_path;
   int          fb_bits;
   int          fb_bpp;
   int          fb_mapped_size;
   struct       fb_var_screeninfo vinfo;
   struct       fb_fix_screeninfo finfo;
   int          vt;
   int          tty;
   int          vt_active;
   EvSource    *evsource[4];
   int          evsource_count;
   int          is_drm;
   cnd_t        cond;
   mtx_t        mtx;
   struct drm_mode_crtc crtc;
};

static char *ctx_fb_clipboard = NULL;
static void ctx_fb_set_clipboard (CtxFb *fb, const char *text)
{
  if (ctx_fb_clipboard)
    free (ctx_fb_clipboard);
  ctx_fb_clipboard = NULL;
  if (text)
  {
    ctx_fb_clipboard = strdup (text);
  }
}

static char *ctx_fb_get_clipboard (CtxFb *sdl)
{
  if (ctx_fb_clipboard) return strdup (ctx_fb_clipboard);
  return strdup ("");
}

#if UINTPTR_MAX == 0xffFFffFF
  #define fbdrmuint_t uint32_t
#elif UINTPTR_MAX == 0xffFFffFFffFFffFF
  #define fbdrmuint_t uint64_t
#endif

void *ctx_fbdrm_new (CtxFb *fb, int *width, int *height)
{
   int got_master = 0;
   fb->fb_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
   if (!fb->fb_fd)
     return NULL;
   static fbdrmuint_t res_conn_buf[20]={0}; // this is static since its contents
                                         // are used by the flip callback
   fbdrmuint_t res_fb_buf[20]={0};
   fbdrmuint_t res_crtc_buf[20]={0};
   fbdrmuint_t res_enc_buf[20]={0};
   struct   drm_mode_card_res res={0};

   if (ioctl(fb->fb_fd, DRM_IOCTL_SET_MASTER, 0))
     goto cleanup;
   got_master = 1;

   if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res))
     goto cleanup;
   res.fb_id_ptr=(fbdrmuint_t)res_fb_buf;
   res.crtc_id_ptr=(fbdrmuint_t)res_crtc_buf;
   res.connector_id_ptr=(fbdrmuint_t)res_conn_buf;
   res.encoder_id_ptr=(fbdrmuint_t)res_enc_buf;
   if(ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETRESOURCES, &res))
      goto cleanup;


   unsigned int i;
   for (i=0;i<res.count_connectors;i++)
   {
     struct drm_mode_modeinfo conn_mode_buf[20]={0};
     fbdrmuint_t conn_prop_buf[20]={0},
                     conn_propval_buf[20]={0},
                     conn_enc_buf[20]={0};

     struct drm_mode_get_connector conn={0};

     conn.connector_id=res_conn_buf[i];

     if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn))
       goto cleanup;

     conn.modes_ptr=(fbdrmuint_t)conn_mode_buf;
     conn.props_ptr=(fbdrmuint_t)conn_prop_buf;
     conn.prop_values_ptr=(fbdrmuint_t)conn_propval_buf;
     conn.encoders_ptr=(fbdrmuint_t)conn_enc_buf;

     if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn))
       goto cleanup;

     //Check if the connector is OK to use (connected to something)
     if (conn.count_encoders<1 || conn.count_modes<1 || !conn.encoder_id || !conn.connection)
       continue;

//------------------------------------------------------------------------------
//Creating a dumb buffer
//------------------------------------------------------------------------------
     struct drm_mode_create_dumb create_dumb={0};
     struct drm_mode_map_dumb    map_dumb={0};
     struct drm_mode_fb_cmd      cmd_dumb={0};
     create_dumb.width  = conn_mode_buf[0].hdisplay;
     create_dumb.height = conn_mode_buf[0].vdisplay;
     create_dumb.bpp   = 32;
     create_dumb.flags = 0;
     create_dumb.pitch = 0;
     create_dumb.size  = 0;
     create_dumb.handle = 0;
     if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) ||
         !create_dumb.handle)
       goto cleanup;

     cmd_dumb.width =create_dumb.width;
     cmd_dumb.height=create_dumb.height;
     cmd_dumb.bpp   =create_dumb.bpp;
     cmd_dumb.pitch =create_dumb.pitch;
     cmd_dumb.depth =24;
     cmd_dumb.handle=create_dumb.handle;
     if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb))
       goto cleanup;

     map_dumb.handle=create_dumb.handle;
     if (ioctl(fb->fb_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb))
       goto cleanup;

     void *base = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED,
                       fb->fb_fd, map_dumb.offset);
     if (!base)
     {
       goto cleanup;
     }
     *width  = create_dumb.width;
     *height = create_dumb.height;

     struct drm_mode_get_encoder enc={0};
     enc.encoder_id=conn.encoder_id;
     if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETENCODER, &enc))
        goto cleanup;

     fb->crtc.crtc_id=enc.crtc_id;
     if (ioctl(fb->fb_fd, DRM_IOCTL_MODE_GETCRTC, &fb->crtc))
        goto cleanup;

     fb->crtc.fb_id=cmd_dumb.fb_id;
     fb->crtc.set_connectors_ptr=(fbdrmuint_t)&res_conn_buf[i];
     fb->crtc.count_connectors=1;
     fb->crtc.mode=conn_mode_buf[0];
     fb->crtc.mode_valid=1;
     return base;
   }
cleanup:
   if (got_master)
     ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0);
   fb->fb_fd = 0;
   return NULL;
}

void ctx_fbdrm_flip (CtxFb *fb)
{
  if (!fb->fb_fd)
    return;
  ioctl(fb->fb_fd, DRM_IOCTL_MODE_SETCRTC, &fb->crtc);
}

void ctx_fbdrm_close (CtxFb *fb)
{
  if (!fb->fb_fd)
    return;
  ioctl(fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0);
  close (fb->fb_fd);
  fb->fb_fd = 0;
}

static void ctx_fb_flip (CtxFb *fb)
{
  if (fb->is_drm)
    ctx_fbdrm_flip (fb);
  else
    ioctl (fb->fb_fd, FBIOPAN_DISPLAY, &fb->vinfo);
}

inline static uint32_t
ctx_swap_red_green2 (uint32_t orig)
{
  uint32_t  green_alpha = (orig & 0xff00ff00);
  uint32_t  red_blue    = (orig & 0x00ff00ff);
  uint32_t  red         = red_blue << 16;
  uint32_t  blue        = red_blue >> 16;
  return green_alpha | red | blue;
}

static int       ctx_fb_cursor_drawn   = 0;
static int       ctx_fb_cursor_drawn_x = 0;
static int       ctx_fb_cursor_drawn_y = 0;
static CtxCursor ctx_fb_cursor_drawn_shape = 0;


#define CTX_FB_HIDE_CURSOR_FRAMES 200

static int ctx_fb_cursor_same_pos = CTX_FB_HIDE_CURSOR_FRAMES;

static inline int ctx_is_in_cursor (int x, int y, int size, CtxCursor shape)
{
  switch (shape)
  {
    case CTX_CURSOR_ARROW:
      if (x > ((size * 4)-y*4)) return 0;
      if (x < y && x > y / 16)
        return 1;
      return 0;

    case CTX_CURSOR_RESIZE_SE:
    case CTX_CURSOR_RESIZE_NW:
    case CTX_CURSOR_RESIZE_SW:
    case CTX_CURSOR_RESIZE_NE:
      {
        float theta = -45.0/180 * M_PI;
        float cos_theta;
        float sin_theta;

        if ((shape == CTX_CURSOR_RESIZE_SW) ||
            (shape == CTX_CURSOR_RESIZE_NE))
        {
          theta = -theta;
          cos_theta = cos (theta);
          sin_theta = sin (theta);
        }
        else
        {
          cos_theta = cos (theta);
          sin_theta = sin (theta);
        }
        int rot_x = x * cos_theta - y * sin_theta;
        int rot_y = y * cos_theta + x * sin_theta;
        x = rot_x;
        y = rot_y;
      }
      /*FALLTHROUGH*/
    case CTX_CURSOR_RESIZE_W:
    case CTX_CURSOR_RESIZE_E:
    case CTX_CURSOR_RESIZE_ALL:
      if (abs (x) < size/2 && abs (y) < size/2)
      {
        if (abs(y) < size/10)
        {
          return 1;
        }
      }
      if ((abs (x) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0)
      {
        if (abs(y) < (size/2.8)-(abs(x) - (size/2)))
          return 1;
      }
      if (shape != CTX_CURSOR_RESIZE_ALL)
        break;
      /* FALLTHROUGH */
    case CTX_CURSOR_RESIZE_S:
    case CTX_CURSOR_RESIZE_N:
      if (abs (y) < size/2 && abs (x) < size/2)
      {
        if (abs(x) < size/10)
        {
          return 1;
        }
      }
      if ((abs (y) - size/ (shape == CTX_CURSOR_RESIZE_ALL?2:2.7)) >= 0)
      {
        if (abs(x) < (size/2.8)-(abs(y) - (size/2)))
          return 1;
      }
      break;
#if 0
    case CTX_CURSOR_RESIZE_ALL:
      if (abs (x) < size/2 && abs (y) < size/2)
      {
        if (abs (x) < size/10 || abs(y) < size/10)
          return 1;
      }
      break;
#endif
    default:
      return (x ^ y) & 1;
  }
  return 0;
}

static void ctx_fb_undraw_cursor (CtxFb *fb)
{
    CtxTiled *tiled = (void*)fb;
    int cursor_size = ctx_height (tiled->ctx) / 28;

    if (ctx_fb_cursor_drawn)
    {
      int no = 0;
      int startx = -cursor_size;
      int starty = -cursor_size;
      if (ctx_fb_cursor_drawn_shape == CTX_CURSOR_ARROW)
        startx = starty = 0;

      for (int y = starty; y < cursor_size; y++)
      for (int x = startx; x < cursor_size; x++, no+=4)
      {
        if (x + ctx_fb_cursor_drawn_x < tiled->width && y + ctx_fb_cursor_drawn_y < tiled->height)
        {
          if (ctx_is_in_cursor (x, y, cursor_size, ctx_fb_cursor_drawn_shape))
          {
            int o = ((ctx_fb_cursor_drawn_y + y) * tiled->width + (ctx_fb_cursor_drawn_x + x)) * 4;
            fb->fb[o+0]^=0x88;
            fb->fb[o+1]^=0x88;
            fb->fb[o+2]^=0x88;
          }
        }
      }

    ctx_fb_cursor_drawn = 0;
    }
}

static void ctx_fb_draw_cursor (CtxFb *fb)
{
    CtxTiled *tiled = (void*)fb;
    int cursor_x    = ctx_pointer_x (tiled->ctx);
    int cursor_y    = ctx_pointer_y (tiled->ctx);
    int cursor_size = ctx_height (tiled->ctx) / 28;
    CtxCursor cursor_shape = tiled->ctx->cursor;
    int no = 0;

    if (cursor_x == ctx_fb_cursor_drawn_x &&
        cursor_y == ctx_fb_cursor_drawn_y &&
        cursor_shape == ctx_fb_cursor_drawn_shape)
      ctx_fb_cursor_same_pos ++;
    else
      ctx_fb_cursor_same_pos = 0;

    if (ctx_fb_cursor_same_pos >= CTX_FB_HIDE_CURSOR_FRAMES)
    {
      if (ctx_fb_cursor_drawn)
        ctx_fb_undraw_cursor (fb);
      return;
    }

    /* no need to flicker when stationary, motion flicker can also be removed
     * by combining the previous and next position masks when a motion has
     * occured..
     */
    if (ctx_fb_cursor_same_pos && ctx_fb_cursor_drawn)
      return;

    ctx_fb_undraw_cursor (fb);

    no = 0;

    int startx = -cursor_size;
    int starty = -cursor_size;

    if (cursor_shape == CTX_CURSOR_ARROW)
      startx = starty = 0;

    for (int y = starty; y < cursor_size; y++)
      for (int x = startx; x < cursor_size; x++, no+=4)
      {
        if (x + cursor_x < tiled->width && y + cursor_y < tiled->height)
        {
          if (ctx_is_in_cursor (x, y, cursor_size, cursor_shape))
          {
            int o = ((cursor_y + y) * tiled->width + (cursor_x + x)) * 4;
            fb->fb[o+0]^=0x88;
            fb->fb[o+1]^=0x88;
            fb->fb[o+2]^=0x88;
          }
        }
      }
    ctx_fb_cursor_drawn = 1;
    ctx_fb_cursor_drawn_x = cursor_x;
    ctx_fb_cursor_drawn_y = cursor_y;
    ctx_fb_cursor_drawn_shape = cursor_shape;
}

static void ctx_fb_show_frame (CtxFb *fb, int block)
{
  CtxTiled *tiled = (void*)fb;
  if (tiled->shown_frame == tiled->render_frame)
  {
    if (block == 0) // consume event call
    {
      ctx_fb_draw_cursor (fb);
      ctx_fb_flip (fb);
    }
    return;
  }

  if (block)
  {
    int count = 0;
    while (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
    {
      usleep (500);
      count ++;
      if (count > 2000)
      {
        tiled->shown_frame = tiled->render_frame;
        return;
      }
    }
  }
  else
  {
    if (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
      return;
  }

    if (fb->vt_active)
    {
       int pre_skip = tiled->min_row * tiled->height/CTX_HASH_ROWS * tiled->width;
       int post_skip = (CTX_HASH_ROWS-tiled->max_row-1) * tiled->height/CTX_HASH_ROWS * tiled->width;

       int rows = ((tiled->width * tiled->height) - pre_skip - post_skip)/tiled->width;

       int col_pre_skip = tiled->min_col * tiled->width/CTX_HASH_COLS;
       int col_post_skip = (CTX_HASH_COLS-tiled->max_col-1) * tiled->width/CTX_HASH_COLS;
       if (_ctx_damage_control)
       {
         pre_skip = post_skip = col_pre_skip = col_post_skip = 0;
       }

       if (pre_skip < 0) pre_skip = 0;
       if (post_skip < 0) post_skip = 0;

     __u32 dummy = 0;

       if (tiled->min_row == 100){
          pre_skip = 0;
          post_skip = 0;
          // not when drm ?
          ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
          ctx_fb_undraw_cursor (fb);
       }
       else
       {

      tiled->min_row = 100;
      tiled->max_row = 0;
      tiled->min_col = 100;
      tiled->max_col = 0;

     // not when drm ?
     ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
     ctx_fb_undraw_cursor (fb);
     switch (fb->fb_bits)
     {
       case 32:
#if 1
         {
           uint8_t *dst = fb->fb + pre_skip * 4;
           uint8_t *src = tiled->pixels + pre_skip * 4;
           int pre = col_pre_skip * 4;
           int post = col_post_skip * 4;
           int core = tiled->width * 4 - pre - post;
           for (int i = 0; i < rows; i++)
           {
             dst  += pre;
             src  += pre;
             memcpy (dst, src, core);
             src  += core;
             dst  += core;
             dst  += post;
             src  += post;
           }
         }
#else
         { int count = tiled->width * tiled->height;
           const uint32_t *src = (void*)tiled->pixels;
           uint32_t *dst = (void*)fb->fb;
           count-= pre_skip;
           src+= pre_skip;
           dst+= pre_skip;
           count-= post_skip;
           while (count -- > 0)
           {
             dst[0] = ctx_swap_red_green2 (src[0]);
             src++;
             dst++;
           }
         }
#endif
         break;
         /* XXX  :  note: converting a scanline (or all) to target and
          * then doing a bulk memcpy be faster (at least with som /dev/fbs)  */
       case 24:
         { int count = tiled->width * tiled->height;
           const uint8_t *src = tiled->pixels;
           uint8_t *dst = fb->fb;
           count-= pre_skip;
           src+= pre_skip * 4;
           dst+= pre_skip * 3;
           count-= post_skip;
           while (count -- > 0)
           {
             dst[0] = src[0];
             dst[1] = src[1];
             dst[2] = src[2];
             dst+=3;
             src+=4;
           }
         }
         break;
       case 16:
         { int count = tiled->width * tiled->height;
           const uint8_t *src = tiled->pixels;
           uint8_t *dst = fb->fb;
           count-= post_skip;
           count-= pre_skip;
           src+= pre_skip * 4;
           dst+= pre_skip * 2;
           while (count -- > 0)
           {
             int big = ((src[0] >> 3)) +
                ((src[1] >> 2)<<5) +
                ((src[2] >> 3)<<11);
             dst[0] = big & 255;
             dst[1] = big >>  8;
             dst+=2;
             src+=4;
           }
         }
         break;
       case 15:
         { int count = tiled->width * tiled->height;
           const uint8_t *src = tiled->pixels;
           uint8_t *dst = fb->fb;
           count-= post_skip;
           count-= pre_skip;
           src+= pre_skip * 4;
           dst+= pre_skip * 2;
           while (count -- > 0)
           {
             int big = ((src[2] >> 3)) +
                       ((src[1] >> 2)<<5) +
                       ((src[0] >> 3)<<10);
             dst[0] = big & 255;
             dst[1] = big >>  8;
             dst+=2;
             src+=4;
           }
         }
         break;
       case 8:
         { int count = tiled->width * tiled->height;
           const uint8_t *src = tiled->pixels;
           uint8_t *dst = fb->fb;
           count-= post_skip;
           count-= pre_skip;
           src+= pre_skip * 4;
           dst+= pre_skip;
           while (count -- > 0)
           {
             dst[0] = ((src[0] >> 5)) +
                      ((src[1] >> 5)<<3) +
                      ((src[2] >> 6)<<6);
             dst+=1;
             src+=4;
           }
         }
         break;
     }
    }
    ctx_fb_cursor_drawn = 0;
    ctx_fb_draw_cursor (fb);
    ctx_fb_flip (fb);
    tiled->shown_frame = tiled->render_frame;
  }
}


#define evsource_has_event(es)   (es)->has_event((es))
#define evsource_get_event(es)   (es)->get_event((es))
#define evsource_destroy(es)     do{if((es)->destroy)(es)->destroy((es));}while(0)
#define evsource_set_coord(es,x,y) do{if((es)->set_coord)(es)->set_coord((es),(x),(y));}while(0)
#define evsource_get_fd(es)      ((es)->get_fd?(es)->get_fd((es)):0)



static int mice_has_event ();
static char *mice_get_event ();
static void mice_destroy ();
static int mice_get_fd (EvSource *ev_source);
static void mice_set_coord (EvSource *ev_source, double x, double y);

static EvSource ctx_ev_src_mice = {
  NULL,
  (void*)mice_has_event,
  (void*)mice_get_event,
  (void*)mice_destroy,
  mice_get_fd,
  mice_set_coord
};

typedef struct Mice
{
  int     fd;
  double  x;
  double  y;
  int     button;
  int     prev_state;
} Mice;

Mice *_mrg_evsrc_coord = NULL;
static int _ctx_mice_fd = 0;

void _mmm_get_coords (Ctx *ctx, double *x, double *y)
{
  if (!_mrg_evsrc_coord)
    return;
  if (x)
    *x = _mrg_evsrc_coord->x;
  if (y)
    *y = _mrg_evsrc_coord->y;
}

static Mice  mice;
static Mice* mrg_mice_this = &mice;

static int mmm_evsource_mice_init ()
{
  unsigned char reset[]={0xff};
  /* need to detect which event */

  mrg_mice_this->prev_state = 0;
  mrg_mice_this->fd = open ("/dev/input/mice", O_RDONLY | O_NONBLOCK);
  if (mrg_mice_this->fd == -1)
  {
    fprintf (stderr, "error opening /dev/input/mice device, maybe add user to input group if such group exist, or otherwise make the rights be satisfied.\n");
    return -1;
  }
  if (write (mrg_mice_this->fd, reset, 1) == -1)
  {
    // might happen if we're a regular user with only read permission
  }
  _ctx_mice_fd = mrg_mice_this->fd;
  _mrg_evsrc_coord = mrg_mice_this;
  return 0;
}

static void mice_destroy ()
{
  if (mrg_mice_this->fd != -1)
    close (mrg_mice_this->fd);
}

static int mice_has_event ()
{
  struct timeval tv;
  int retval;

  if (mrg_mice_this->fd == -1)
    return 0;

  fd_set rfds;
  FD_ZERO (&rfds);
  FD_SET(mrg_mice_this->fd, &rfds);
  tv.tv_sec = 0; tv.tv_usec = 0;
  retval = select (mrg_mice_this->fd+1, &rfds, NULL, NULL, &tv);
  if (retval == 1)
    return FD_ISSET (mrg_mice_this->fd, &rfds);
  return 0;
}

static char *mice_get_event ()
{
  const char *ret = "mouse-motion";
  double relx, rely;
  signed char buf[3];
  int n_read = 0;
  CtxFb *fb = ctx_ev_src_mice.priv;
  CtxTiled *tiled = (void*)fb;
  n_read = read (mrg_mice_this->fd, buf, 3);
  if (n_read == 0)
     return strdup ("");
  relx = buf[1];
  rely = -buf[2];

  if (relx < 0)
  {
    if (relx > -6)
    relx = - relx*relx;
    else
    relx = -36;
  }
  else
  {
    if (relx < 6)
    relx = relx*relx;
    else
    relx = 36;
  }

  if (rely < 0)
  {
    if (rely > -6)
    rely = - rely*rely;
    else
    rely = -36;
  }
  else
  {
    if (rely < 6)
    rely = rely*rely;
    else
    rely = 36;
  }

  mrg_mice_this->x += relx;
  mrg_mice_this->y += rely;

  if (mrg_mice_this->x < 0)
    mrg_mice_this->x = 0;
  if (mrg_mice_this->y < 0)
    mrg_mice_this->y = 0;
  if (mrg_mice_this->x >= tiled->width)
    mrg_mice_this->x = tiled->width -1;
  if (mrg_mice_this->y >= tiled->height)
    mrg_mice_this->y = tiled->height -1;
  int button = 0;
  
  if ((mrg_mice_this->prev_state & 1) != (buf[0] & 1))
    {
      if (buf[0] & 1)
        {
          ret = "mouse-press";
        }
      else
        {
          ret = "mouse-release";
        }
      button = 1;
    }
  else if (buf[0] & 1)
  {
    ret = "mouse-drag";
    button = 1;
  }

  if (!button)
  {
    if ((mrg_mice_this->prev_state & 2) != (buf[0] & 2))
    {
      if (buf[0] & 2)
        {
          ret = "mouse-press";
        }
      else
        {
          ret = "mouse-release";
        }
      button = 3;
    }
    else if (buf[0] & 2)
    {
      ret = "mouse-drag";
      button = 3;
    }
  }

  if (!button)
  {
    if ((mrg_mice_this->prev_state & 4) != (buf[0] & 4))
    {
      if (buf[0] & 4)
        {
          ret = "mouse-press";
        }
      else
        {
          ret = "mouse-release";
        }
      button = 2;
    }
    else if (buf[0] & 4)
    {
      ret = "mouse-drag";
      button = 2;
    }
  }

  mrg_mice_this->prev_state = buf[0];

  {
    char *r = malloc (64);
    sprintf (r, "%s %.0f %.0f %i", ret, mrg_mice_this->x, mrg_mice_this->y, button);
    return r;
  }

  return NULL;
}

static int mice_get_fd (EvSource *ev_source)
{
  return mrg_mice_this->fd;
}

static void mice_set_coord (EvSource *ev_source, double x, double y)
{
  mrg_mice_this->x = x;
  mrg_mice_this->y = y;
}

static EvSource *evsource_mice_new (void)
{
  if (mmm_evsource_mice_init () == 0)
    {
      mrg_mice_this->x = 0;
      mrg_mice_this->y = 0;
      return &ctx_ev_src_mice;
    }
  return NULL;
}

static int evsource_kb_has_event (void);
static char *evsource_kb_get_event (void);
static void evsource_kb_destroy (int sign);
static int evsource_kb_get_fd (void);

/* kept out of struct to be reachable by atexit */
static EvSource ctx_ev_src_kb = {
  NULL,
  (void*)evsource_kb_has_event,
  (void*)evsource_kb_get_event,
  (void*)evsource_kb_destroy,
  (void*)evsource_kb_get_fd,
  NULL
};

static struct termios orig_attr;

static void real_evsource_kb_destroy (int sign)
{
  static int done = 0;

  if (sign == 0)
    return;

  if (done)
    return;
  done = 1;

  switch (sign)
  {
    case  -11:break; /* will be called from atexit with sign==-11 */
    case   SIGSEGV: break;//fprintf (stderr, " SIGSEGV\n");break;
    case   SIGABRT: fprintf (stderr, " SIGABRT\n");break;
    case   SIGBUS:  fprintf (stderr, " SIGBUS\n");break;
    case   SIGKILL: fprintf (stderr, " SIGKILL\n");break;
    case   SIGINT:  fprintf (stderr, " SIGINT\n");break;
    case   SIGTERM: fprintf (stderr, " SIGTERM\n");break;
    case   SIGQUIT: fprintf (stderr, " SIGQUIT\n");break;
    default: fprintf (stderr, "sign: %i\n", sign);
             fprintf (stderr, "%i %i %i %i %i %i %i\n", SIGSEGV, SIGABRT, SIGBUS, SIGKILL, SIGINT, SIGTERM, SIGQUIT);
  }
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr);
  //fprintf (stderr, "evsource kb destroy\n");
}

static void evsource_kb_destroy (int sign)
{
  real_evsource_kb_destroy (-11);
}

static int evsource_kb_init ()
{
//  ioctl(STDIN_FILENO, KDSKBMODE, K_RAW);
  atexit ((void*) real_evsource_kb_destroy);
  signal (SIGSEGV, (void*) real_evsource_kb_destroy);
  signal (SIGABRT, (void*) real_evsource_kb_destroy);
  signal (SIGBUS,  (void*) real_evsource_kb_destroy);
  signal (SIGKILL, (void*) real_evsource_kb_destroy);
  signal (SIGINT,  (void*) real_evsource_kb_destroy);
  signal (SIGTERM, (void*) real_evsource_kb_destroy);
  signal (SIGQUIT, (void*) real_evsource_kb_destroy);

  struct termios raw;
  if (tcgetattr (STDIN_FILENO, &orig_attr) == -1)
    {
      fprintf (stderr, "error initializing keyboard\n");
      return -1;
    }
  raw = orig_attr;

  cfmakeraw (&raw);

  raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
  if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    return 0; // XXX? return other value?

  return 0;
}
static int evsource_kb_has_event (void)
{
  struct timeval tv;
  int retval;

  fd_set rfds;
  FD_ZERO (&rfds);
  FD_SET(STDIN_FILENO, &rfds);
  tv.tv_sec = 0; tv.tv_usec = 0;
  retval = select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv);
  return retval == 1;
}

/* note that a nick can have multiple occurences, the labels
 * should be kept the same for all occurences of a combination.
 *
 * this table is taken from nchanterm.
 */
typedef struct MmmKeyCode {
  char *nick;          /* programmers name for key */
  char  sequence[10];  /* terminal sequence */
} MmmKeyCode;
static const MmmKeyCode ufb_keycodes[]={
  {"up",                  "\e[A"},
  {"down",                "\e[B"},
  {"right",               "\e[C"},
  {"left",                "\e[D"},

  {"shift-up",            "\e[1;2A"},
  {"shift-down",          "\e[1;2B"},
  {"shift-right",         "\e[1;2C"},
  {"shift-left",          "\e[1;2D"},

  {"alt-up",              "\e[1;3A"},
  {"alt-down",            "\e[1;3B"},
  {"alt-right",           "\e[1;3C"},
  {"alt-left",            "\e[1;3D"},
  {"alt-shift-up",         "\e[1;4A"},
  {"alt-shift-down",       "\e[1;4B"},
  {"alt-shift-right",      "\e[1;4C"},
  {"alt-shift-left",       "\e[1;4D"},

  {"control-up",          "\e[1;5A"},
  {"control-down",        "\e[1;5B"},
  {"control-right",       "\e[1;5C"},
  {"control-left",        "\e[1;5D"},

  /* putty */
  {"control-up",          "\eOA"},
  {"control-down",        "\eOB"},
  {"control-right",       "\eOC"},
  {"control-left",        "\eOD"},

  {"control-shift-up",    "\e[1;6A"},
  {"control-shift-down",  "\e[1;6B"},
  {"control-shift-right", "\e[1;6C"},
  {"control-shift-left",  "\e[1;6D"},

  {"control-up",          "\eOa"},
  {"control-down",        "\eOb"},
  {"control-right",       "\eOc"},
  {"control-left",        "\eOd"},

  {"shift-up",            "\e[a"},
  {"shift-down",          "\e[b"},
  {"shift-right",         "\e[c"},
  {"shift-left",          "\e[d"},

  {"insert",              "\e[2~"},
  {"delete",              "\e[3~"},
  {"page-up",             "\e[5~"},
  {"page-down",           "\e[6~"},
  {"home",                "\eOH"},
  {"end",                 "\eOF"},
  {"home",                "\e[H"},
  {"end",                 "\e[F"},
 {"control-delete",       "\e[3;5~"},
  {"shift-delete",        "\e[3;2~"},
  {"control-shift-delete","\e[3;6~"},

  {"F1",         "\e[25~"},
  {"F2",         "\e[26~"},
  {"F3",         "\e[27~"},
  {"F4",         "\e[26~"},


  {"F1",         "\e[11~"},
  {"F2",         "\e[12~"},
  {"F3",         "\e[13~"},
  {"F4",         "\e[14~"},
  {"F1",         "\eOP"},
  {"F2",         "\eOQ"},
  {"F3",         "\eOR"},
  {"F4",         "\eOS"},
  {"F5",         "\e[15~"},
  {"F6",         "\e[16~"},
  {"F7",         "\e[17~"},
  {"F8",         "\e[18~"},
  {"F9",         "\e[19~"},
  {"F9",         "\e[20~"},
  {"F10",        "\e[21~"},
  {"F11",        "\e[22~"},
  {"F12",        "\e[23~"},
  {"tab",         {9, '\0'}},
  {"shift-tab",   {27, 9, '\0'}}, // also generated by alt-tab in linux console
  {"alt-space",   {27, ' ', '\0'}},
  {"shift-tab",   "\e[Z"},
  {"backspace",   {127, '\0'}},
  {"space",       " "},
  {"\e",          "\e"},
  {"return",      {10,0}},
  {"return",      {13,0}},
  /* this section could be autogenerated by code */
  {"control-a",   {1,0}},
  {"control-b",   {2,0}},
  {"control-c",   {3,0}},
  {"control-d",   {4,0}},
  {"control-e",   {5,0}},
  {"control-f",   {6,0}},
  {"control-g",   {7,0}},
  {"control-h",   {8,0}}, /* backspace? */
  {"control-i",   {9,0}},
  {"control-j",   {10,0}},
  {"control-k",   {11,0}},
  {"control-l",   {12,0}},
  {"control-n",   {14,0}},
  {"control-o",   {15,0}},
  {"control-p",   {16,0}},
  {"control-q",   {17,0}},
  {"control-r",   {18,0}},
  {"control-s",   {19,0}},
  {"control-t",   {20,0}},
  {"control-u",   {21,0}},
  {"control-v",   {22,0}},
  {"control-w",   {23,0}},
  {"control-x",   {24,0}},
  {"control-y",   {25,0}},
  {"control-z",   {26,0}},
  {"alt-`",       "\e`"},
  {"alt-0",       "\e0"},
  {"alt-1",       "\e1"},
  {"alt-2",       "\e2"},
  {"alt-3",       "\e3"},
  {"alt-4",       "\e4"},
  {"alt-5",       "\e5"},
  {"alt-6",       "\e6"},
  {"alt-7",       "\e7"}, /* backspace? */
  {"alt-8",       "\e8"},
  {"alt-9",       "\e9"},
  {"alt-+",       "\e+"},
  {"alt--",       "\e-"},
  {"alt-/",       "\e/"},
  {"alt-a",       "\ea"},
  {"alt-b",       "\eb"},
  {"alt-c",       "\ec"},
  {"alt-d",       "\ed"},
  {"alt-e",       "\ee"},
  {"alt-f",       "\ef"},
  {"alt-g",       "\eg"},
  {"alt-h",       "\eh"}, /* backspace? */
  {"alt-i",       "\ei"},
  {"alt-j",       "\ej"},
  {"alt-k",       "\ek"},
  {"alt-l",       "\el"},
  {"alt-n",       "\em"},
  {"alt-n",       "\en"},
  {"alt-o",       "\eo"},
  {"alt-p",       "\ep"},
  {"alt-q",       "\eq"},
  {"alt-r",       "\er"},
  {"alt-s",       "\es"},
  {"alt-t",       "\et"},
  {"alt-u",       "\eu"},
  {"alt-v",       "\ev"},
  {"alt-w",       "\ew"},
  {"alt-x",       "\ex"},
  {"alt-y",       "\ey"},
  {"alt-z",       "\ez"},
  /* Linux Console  */
  {"home",       "\e[1~"},
  {"end",        "\e[4~"},
  {"F1",         "\e[[A"},
  {"F2",         "\e[[B"},
  {"F3",         "\e[[C"},
  {"F4",         "\e[[D"},
  {"F5",         "\e[[E"},
  {"F6",         "\e[[F"},
  {"F7",         "\e[[G"},
  {"F8",         "\e[[H"},
  {"F9",         "\e[[I"},
  {"F10",        "\e[[J"},
  {"F11",        "\e[[K"},
  {"F12",        "\e[[L"},
  {NULL, }
};
static int fb_keyboard_match_keycode (const char *buf, int length, const MmmKeyCode **ret)
{
  int i;
  int matches = 0;

  if (!strncmp (buf, "\e[M", MIN(length,3)))
    {
      if (length >= 6)
        return 9001;
      return 2342;
    }
  for (i = 0; ufb_keycodes[i].nick; i++)
    if (!strncmp (buf, ufb_keycodes[i].sequence, length))
      {
        matches ++;
        if ((int)strlen (ufb_keycodes[i].sequence) == length && ret)
          {
            *ret = &ufb_keycodes[i];
            return 1;
          }
      }
  if (matches != 1 && ret)
    *ret = NULL;
  return matches==1?2:matches;
}

//int is_active (void *host)
//{
//        return 1;
//}

static char *evsource_kb_get_event (void)
{
  unsigned char buf[20];
  int length;


  for (length = 0; length < 10; length ++)
    if (read (STDIN_FILENO, &buf[length], 1) != -1)
      {
        const MmmKeyCode *match = NULL;

        //if (!is_active (ctx_ev_src_kb.priv))
        //  return NULL;

        /* special case ESC, so that we can use it alone in keybindings */
        if (length == 0 && buf[0] == 27)
          {
            struct timeval tv;
            fd_set rfds;
            FD_ZERO (&rfds);
            FD_SET (STDIN_FILENO, &rfds);
            tv.tv_sec = 0;
            tv.tv_usec = 1000 * 120;
            if (select (STDIN_FILENO+1, &rfds, NULL, NULL, &tv) == 0)
              return strdup ("escape");
          }

        switch (fb_keyboard_match_keycode ((void*)buf, length + 1, &match))
          {
            case 1: /* unique match */
              if (!match)
                return NULL;
              return strdup (match->nick);
              break;
            case 0: /* no matches, bail*/
             {
                static char ret[256]="";
                if (length == 0 && ctx_utf8_len (buf[0])>1) /* read a
                                                             * single unicode
                                                             * utf8 character
                                                             */
                  {
                    int bytes = read (STDIN_FILENO, &buf[length+1], ctx_utf8_len(buf[0])-1);
                    if (bytes)
                    {
                      buf[ctx_utf8_len(buf[0])]=0;
                      strcpy (ret, (void*)buf);
                    }
                    return strdup(ret); //XXX: simplify
                  }
                if (length == 0) /* ascii */
                  {
                    buf[1]=0;
                    strcpy (ret, (void*)buf);
                    return strdup(ret);
                  }
                sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'",
                    length >=0 ? buf[0] : 0,
                    length >=0 ? buf[0]>31?buf[0]:'?' : ' ',
                    length >=1 ? buf[1] : 0,
                    length >=1 ? buf[1]>31?buf[1]:'?' : ' ',
                    length >=2 ? buf[2] : 0,
                    length >=2 ? buf[2]>31?buf[2]:'?' : ' ',
                    length >=3 ? buf[3] : 0,
                    length >=3 ? buf[3]>31?buf[3]:'?' : ' ',
                    length >=4 ? buf[4] : 0,
                    length >=4 ? buf[4]>31?buf[4]:'?' : ' ',
                    length >=5 ? buf[5] : 0,
                    length >=5 ? buf[5]>31?buf[5]:'?' : ' ',
                    length >=6 ? buf[6] : 0,
                    length >=6 ? buf[6]>31?buf[6]:'?' : ' '
                    );
                return strdup(ret);
            }
              return NULL;
            default: /* continue */
              break;
          }
      }
    else
      return strdup("key read eek");
  return strdup("fail");
}

static int evsource_kb_get_fd (void)
{
  return STDIN_FILENO;
}


static EvSource *evsource_kb_new (void)
{
  if (evsource_kb_init() == 0)
  {
    return &ctx_ev_src_kb;
  }
  return NULL;
}

static int event_check_pending (CtxFb *fb)
{
  CtxTiled *tiled = (void*)fb;
  int events = 0;
  for (int i = 0; i < fb->evsource_count; i++)
  {
    while (evsource_has_event (fb->evsource[i]))
    {
      char *event = evsource_get_event (fb->evsource[i]);
      if (event)
      {
        if (fb->vt_active)
        {
          ctx_key_press (tiled->ctx, 0, event, 0); // we deliver all events as key-press, the key_press handler disambiguates
          events++;
        }
        free (event);
      }
    }
  }
  return events;
}

int ctx_fb_consume_events (Ctx *ctx)
{
  CtxFb *fb = (void*)ctx->renderer;
  ctx_fb_show_frame (fb, 0);
  event_check_pending (fb);
  return 0;
}

inline static void ctx_fb_reset (CtxFb *fb)
{
  ctx_fb_show_frame (fb, 1);
}

inline static void ctx_fb_flush (CtxFb *fb)
{
  ctx_tiled_flush ((CtxTiled*)fb);
}

void ctx_fb_free (CtxFb *fb)
{
  if (fb->is_drm)
  {
    ctx_fbdrm_close (fb);
  }

  ioctl (0, KDSETMODE, KD_TEXT);
  if (system("stty sane")){};
  ctx_tiled_free ((CtxTiled*)fb);
  //free (fb);
#if CTX_BABL
  babl_exit ();
#endif
}

//static unsigned char *fb_icc = NULL;
//static long fb_icc_length = 0;

int ctx_renderer_is_fb (Ctx *ctx)
{
  if (ctx->renderer &&
      ctx->renderer->free == (void*)ctx_fb_free)
          return 1;
  return 0;
}

static CtxFb *ctx_fb = NULL;
static void vt_switch_cb (int sig)
{
  CtxTiled *tiled = (void*)ctx_fb;
  if (sig == SIGUSR1)
  {
    if (ctx_fb->is_drm)
      ioctl(ctx_fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0);
    ioctl (0, VT_RELDISP, 1);
    ctx_fb->vt_active = 0;
    ioctl (0, KDSETMODE, KD_TEXT);
  }
  else
  {
    ioctl (0, VT_RELDISP, VT_ACKACQ);
    ctx_fb->vt_active = 1;
    // queue draw
    tiled->render_frame = ++tiled->frame;
    ioctl (0, KDSETMODE, KD_GRAPHICS);
    if (ctx_fb->is_drm)
    {
      ioctl(ctx_fb->fb_fd, DRM_IOCTL_SET_MASTER, 0);
      ctx_fb_flip (ctx_fb);
    }
    else
    {
      tiled->ctx->dirty=1;

      for (int row = 0; row < CTX_HASH_ROWS; row++)
      for (int col = 0; col < CTX_HASH_COLS; col++)
      {
        tiled->hashes[(row * CTX_HASH_COLS + col) *  20] += 1;
      }
    }
  }
}

static int ctx_fb_get_mice_fd (Ctx *ctx)
{
  //CtxFb *fb = (void*)ctx->renderer;
  return _ctx_mice_fd;
}

Ctx *ctx_new_fb (int width, int height, int drm)
{
#if CTX_RASTERIZER
  CtxFb *fb = calloc (sizeof (CtxFb), 1);

  CtxTiled *tiled = (void*)fb;
  ctx_fb = fb;
  if (drm)
    fb->fb = ctx_fbdrm_new (fb, &tiled->width, &tiled->height);
  if (fb->fb)
  {
    fb->is_drm         = 1;
    width              = tiled->width;
    height             = tiled->height;
    /*
       we're ignoring the input width and height ,
       maybe turn them into properties - for
       more generic handling.
     */
    fb->fb_mapped_size = tiled->width * tiled->height * 4;
    fb->fb_bits        = 32;
    fb->fb_bpp         = 4;
  }
  else
  {
  fb->fb_fd = open ("/dev/fb0", O_RDWR);
  if (fb->fb_fd > 0)
    fb->fb_path = strdup ("/dev/fb0");
  else
  {
    fb->fb_fd = open ("/dev/graphics/fb0", O_RDWR);
    if (fb->fb_fd > 0)
    {
      fb->fb_path = strdup ("/dev/graphics/fb0");
    }
    else
    {
      free (fb);
      return NULL;
    }
  }

  if (ioctl(fb->fb_fd, FBIOGET_FSCREENINFO, &fb->finfo))
    {
      fprintf (stderr, "error getting fbinfo\n");
      close (fb->fb_fd);
      free (fb->fb_path);
      free (fb);
      return NULL;
    }

   if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &fb->vinfo))
     {
       fprintf (stderr, "error getting fbinfo\n");
      close (fb->fb_fd);
      free (fb->fb_path);
      free (fb);
      return NULL;
     }

//fprintf (stderr, "%s\n", fb->fb_path);
  width = tiled->width = fb->vinfo.xres;
  height = tiled->height = fb->vinfo.yres;

  fb->fb_bits = fb->vinfo.bits_per_pixel;
//fprintf (stderr, "fb bits: %i\n", fb->fb_bits);

  if (fb->fb_bits == 16)
    fb->fb_bits =
      fb->vinfo.red.length +
      fb->vinfo.green.length +
      fb->vinfo.blue.length;

   else if (fb->fb_bits == 8)
  {
    unsigned short red[256],  green[256],  blue[256];
    unsigned short original_red[256];
    unsigned short original_green[256];
    unsigned short original_blue[256];
    struct fb_cmap cmap = {0, 256, red, green, blue, NULL};
    struct fb_cmap original_cmap = {0, 256, original_red, original_green, original_blue, NULL};
    int i;

    /* do we really need to restore it ? */
    if (ioctl (fb->fb_fd, FBIOPUTCMAP, &original_cmap) == -1)
    {
      fprintf (stderr, "palette initialization problem %i\n", __LINE__);
    }

    for (i = 0; i < 256; i++)
    {
      red[i]   = ((( i >> 5) & 0x7) << 5) << 8;
      green[i] = ((( i >> 2) & 0x7) << 5) << 8;
      blue[i]  = ((( i >> 0) & 0x3) << 6) << 8;
    }

    if (ioctl (fb->fb_fd, FBIOPUTCMAP, &cmap) == -1)
    {
      fprintf (stderr, "palette initialization problem %i\n", __LINE__);
    }
  }

  fb->fb_bpp = fb->vinfo.bits_per_pixel / 8;
  fb->fb_mapped_size = fb->finfo.smem_len;
                                              
  fb->fb = mmap (NULL, fb->fb_mapped_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb->fb_fd, 0);
  }
  if (!fb->fb)
    return NULL;
  tiled->pixels = calloc (fb->fb_mapped_size, 1);
  ctx_fb_events = 1;

#if CTX_BABL
  babl_init ();
#endif

  ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);

  tiled->ctx      = ctx_new ();
  tiled->ctx_copy = ctx_new ();
  tiled->width    = width;
  tiled->height   = height;

  ctx_set_renderer (tiled->ctx, fb);
  ctx_set_renderer (tiled->ctx_copy, fb);
  ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx);

  ctx_set_size (tiled->ctx, width, height);
  ctx_set_size (tiled->ctx_copy, width, height);

  tiled->flush = (void*)ctx_fb_flush;
  tiled->reset = (void*)ctx_fb_reset;
  tiled->free  = (void*)ctx_fb_free;
  tiled->set_clipboard = (void*)ctx_fb_set_clipboard;
  tiled->get_clipboard = (void*)ctx_fb_get_clipboard;

  for (int i = 0; i < _ctx_max_threads; i++)
  {
    tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels,
                   tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS,
                   tiled->width * 4, CTX_FORMAT_BGRA8); // this format
                                  // is overriden in  thread
    ((CtxRasterizer*)(tiled->host[i]->renderer))->swap_red_green = 1;
    ctx_set_texture_source (tiled->host[i], tiled->ctx);
  }

  mtx_init (&tiled->mtx, mtx_plain);
  cnd_init (&tiled->cond);

#define start_thread(no)\
  if(_ctx_max_threads>no){ \
    static void *args[2]={(void*)no, };\
    thrd_t tid;\
    args[1]=fb;\
    thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\
  }
  start_thread(0);
  start_thread(1);
  start_thread(2);
  start_thread(3);
  start_thread(4);
  start_thread(5);
  start_thread(6);
  start_thread(7);
  start_thread(8);
  start_thread(9);
  start_thread(10);
  start_thread(11);
  start_thread(12);
  start_thread(13);
  start_thread(14);
  start_thread(15);
#undef start_thread

  ctx_flush (tiled->ctx);

  EvSource *kb = evsource_kb_new ();
  if (kb)
  {
    fb->evsource[fb->evsource_count++] = kb;
    kb->priv = fb;
  }
  EvSource *mice  = evsource_mice_new ();
  if (mice)
  {
    fb->evsource[fb->evsource_count++] = mice;
    mice->priv = fb;
  }

  fb->vt_active = 1;
  ioctl(0, KDSETMODE, KD_GRAPHICS);
  signal (SIGUSR1, vt_switch_cb);
  signal (SIGUSR2, vt_switch_cb);
  struct vt_stat st;
  if (ioctl (0, VT_GETSTATE, &st) == -1)
  {
    ctx_log ("VT_GET_MODE on vt %i failed\n", fb->vt);
    return NULL;
  }

  fb->vt = st.v_active;

  struct vt_mode mode;
  mode.mode   = VT_PROCESS;
  mode.relsig = SIGUSR1;
  mode.acqsig = SIGUSR2;
  if (ioctl (0, VT_SETMODE, &mode) < 0)
  {
    ctx_log ("VT_SET_MODE on vt %i failed\n", fb->vt);
    return NULL;
  }

  return tiled->ctx;
#else
  return NULL;
#endif
}
#else

int ctx_renderer_is_fb (Ctx *ctx)
{
  return 0;
}
#endif
#endif

#if CTX_SDL

/**/

typedef struct _CtxSDL CtxSDL;
struct _CtxSDL
{
   CtxTiled  tiled;
   /* where we diverge from fb*/
   int           key_balance;
   int           key_repeat;
   int           lctrl;
   int           lalt;
   int           rctrl;
   int           lshift;
   int           rshift;

   SDL_Window   *window;
   SDL_Renderer *renderer;
   SDL_Texture  *texture;

// cnd_t  cond;
// mtx_t  mtx;
   int           fullscreen;
};

#include "stb_image_write.h"

void ctx_screenshot (Ctx *ctx, const char *output_path)
{
#if CTX_SCREENSHOT
  int valid = 0;
  CtxSDL *sdl = (void*)ctx->renderer;

  if (ctx_renderer_is_sdl (ctx)) valid = 1;
#if CTX_FB
  if (ctx_renderer_is_fb  (ctx)) valid = 1;
#endif

  if (!valid)
    return;

#if CTX_FB
  // we rely on the same layout
  for (int i = 0; i < sdl->width * sdl->height; i++)
  {
    int tmp = sdl->pixels[i*4];
    sdl->pixels[i*4] = sdl->pixels[i*4 + 2];
    sdl->pixels[i*4 + 2] = tmp;
  }
#endif

  stbi_write_png (output_path, sdl->width, sdl->height, 4, sdl->pixels, sdl->width*4);

#if CTX_FB
  for (int i = 0; i < sdl->width * sdl->height; i++)
  {
    int tmp = sdl->pixels[i*4];
    sdl->pixels[i*4] = sdl->pixels[i*4 + 2];
    sdl->pixels[i*4 + 2] = tmp;
  }
#endif
#endif
}

int ctx_show_fps = 1;
void ctx_sdl_set_title (void *self, const char *new_title)
{
   CtxSDL *sdl = self;
   if (!ctx_show_fps)
   SDL_SetWindowTitle (sdl->window, new_title);
}

static void ctx_sdl_show_frame (CtxSDL *sdl, int block)
{
  CtxTiled *tiled = &sdl->tiled;
  if (tiled->shown_cursor != tiled->ctx->cursor)
  {
    tiled->shown_cursor = tiled->ctx->cursor;
    SDL_Cursor *new_cursor =  NULL;
    switch (tiled->shown_cursor)
    {
      case CTX_CURSOR_UNSET: // XXX: document how this differs from none
                             //      perhaps falling back to arrow?
        break;
      case CTX_CURSOR_NONE:
        new_cursor = NULL;
        break;
      case CTX_CURSOR_ARROW:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
        break;
      case CTX_CURSOR_CROSSHAIR:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
        break;
      case CTX_CURSOR_WAIT:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
        break;
      case CTX_CURSOR_HAND:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
        break;
      case CTX_CURSOR_IBEAM:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
        break;
      case CTX_CURSOR_MOVE:
      case CTX_CURSOR_RESIZE_ALL:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
        break;
      case CTX_CURSOR_RESIZE_N:
      case CTX_CURSOR_RESIZE_S:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
        break;
      case CTX_CURSOR_RESIZE_E:
      case CTX_CURSOR_RESIZE_W:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
        break;
      case CTX_CURSOR_RESIZE_NE:
      case CTX_CURSOR_RESIZE_SW:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
        break;
      case CTX_CURSOR_RESIZE_NW:
      case CTX_CURSOR_RESIZE_SE:
        new_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
        break;
    }
    if (new_cursor)
    {
      SDL_Cursor *old_cursor = SDL_GetCursor();
      SDL_SetCursor (new_cursor);
      SDL_ShowCursor (1);
      if (old_cursor)
        SDL_FreeCursor (old_cursor);
    }
    else
    {
      SDL_ShowCursor (0);
    }
  }

  if (tiled->shown_frame == tiled->render_frame)
  {
    return;
  }

  if (block)
  {
    int count = 0;
    while (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
    {
      usleep (50);
      count ++;
      if (count > 2000)
      {
        tiled->shown_frame = tiled->render_frame;
        return;
      }
    }
  }
  else
  {
    if (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
      return;
  }


  if (tiled->min_row == 100)
  {
  }
  else
  {
#if 1
    int x = tiled->min_col * tiled->width/CTX_HASH_COLS;
    int y = tiled->min_row * tiled->height/CTX_HASH_ROWS;
    int x1 = (tiled->max_col+1) * tiled->width/CTX_HASH_COLS;
    int y1 = (tiled->max_row+1) * tiled->height/CTX_HASH_ROWS;
    int width = x1 - x;
    int height = y1 - y;
#endif
    tiled->min_row = 100;
    tiled->max_row = 0;
    tiled->min_col = 100;
    tiled->max_col = 0;

    SDL_Rect r = {x, y, width, height};
    SDL_UpdateTexture (sdl->texture, &r,
                      //(void*)sdl->pixels,
                      (void*)(tiled->pixels + y * tiled->width * 4 + x * 4),
                      
                      tiled->width * 4);
    SDL_RenderClear (sdl->renderer);
    SDL_RenderCopy (sdl->renderer, sdl->texture, NULL, NULL);
    SDL_RenderPresent (sdl->renderer);


  if (ctx_show_fps)
  {
    static uint64_t prev_time = 0;
    static char tmp_title[1024];
    uint64_t time = ctx_ticks ();
    sprintf (tmp_title, "FPS: %.1f", 1000000.0/  (time - prev_time));
    prev_time = time;
    SDL_SetWindowTitle (sdl->window, tmp_title);
  }
  }
  tiled->shown_frame = tiled->render_frame;
}

static const char *ctx_sdl_keysym_to_name (unsigned int sym, int *r_keycode)
{
  static char buf[16]="";
  buf[ctx_unichar_to_utf8 (sym, (void*)buf)]=0;
  int code = sym;
  const char *name = &buf[0];
   switch (sym)
   {
     case SDLK_RSHIFT: code = 16 ; break;
     case SDLK_LSHIFT: code = 16 ; break;
     case SDLK_LCTRL: code = 17 ; break;
     case SDLK_RCTRL: code = 17 ; break;
     case SDLK_LALT:  code = 18 ; break;
     case SDLK_RALT:  code = 18 ; break;
     case SDLK_CAPSLOCK: name = "capslock"; code = 20 ; break;
     //case SDLK_NUMLOCK: name = "numlock"; code = 144 ; break;
     //case SDLK_SCROLLLOCK: name = "scrollock"; code = 145 ; break;

     case SDLK_F1:     name = "F1"; code = 112; break;
     case SDLK_F2:     name = "F2"; code = 113; break;
     case SDLK_F3:     name = "F3"; code = 114; break;
     case SDLK_F4:     name = "F4"; code = 115; break;
     case SDLK_F5:     name = "F5"; code = 116; break;
     case SDLK_F6:     name = "F6"; code = 117; break;
     case SDLK_F7:     name = "F7"; code = 118; break;
     case SDLK_F8:     name = "F8"; code = 119; break;
     case SDLK_F9:     name = "F9"; code = 120; break;
     case SDLK_F10:    name = "F10"; code = 121; break;
     case SDLK_F11:    name = "F11"; code = 122; break;
     case SDLK_F12:    name = "F12"; code = 123; break;
     case SDLK_ESCAPE: name = "escape"; break;
     case SDLK_DOWN:   name = "down"; code = 40; break;
     case SDLK_LEFT:   name = "left"; code = 37; break;
     case SDLK_UP:     name = "up"; code = 38;  break;
     case SDLK_RIGHT:  name = "right"; code = 39; break;
     case SDLK_BACKSPACE: name = "backspace"; break;
     case SDLK_SPACE:  name = "space"; break;
     case SDLK_TAB:    name = "tab"; break;
     case SDLK_DELETE: name = "delete"; code = 46; break;
     case SDLK_INSERT: name = "insert"; code = 45; break;
     case SDLK_RETURN:
       //if (key_repeat == 0) // return never should repeat
       name = "return";   // on a DEC like terminal
       break;
     case SDLK_HOME:     name = "home"; code = 36; break;
     case SDLK_END:      name = "end"; code = 35; break;
     case SDLK_PAGEDOWN: name = "page-down"; code = 34; break;
     case SDLK_PAGEUP:   name = "page-up"; code = 33; break;
     case ',': code = 188; break;
     case '.': code = 190; break;
     case '/': code = 191; break;
     case '`': code = 192; break;
     case '[': code = 219; break;
     case '\\': code = 220; break;
     case ']':  code = 221; break;
     case '\'': code = 222; break;
     default:
       ;
   }
   if (sym >= 'a' && sym <='z') code -= 32;
   if (r_keycode)
   {
     *r_keycode = code;
   }
   return name;
}

int ctx_sdl_consume_events (Ctx *ctx)
{
  CtxTiled *tiled = (void*)ctx->renderer;
  CtxSDL *sdl = (void*)ctx->renderer;
  SDL_Event event;
  int got_events = 0;

  ctx_sdl_show_frame (sdl, 0);

  while (SDL_PollEvent (&event))
  {
    got_events ++;
    switch (event.type)
    {
      case SDL_MOUSEBUTTONDOWN:
        SDL_CaptureMouse (1);
        ctx_pointer_press (ctx, event.button.x, event.button.y, event.button.button, 0);
        break;
      case SDL_MOUSEBUTTONUP:
        SDL_CaptureMouse (0);
        ctx_pointer_release (ctx, event.button.x, event.button.y, event.button.button, 0);
        break;
      case SDL_MOUSEMOTION:
        //  XXX : look at mask and generate motion for each pressed
        //        button
        ctx_pointer_motion (ctx, event.motion.x, event.motion.y, 1, 0);
        break;
      case SDL_FINGERMOTION:
        ctx_pointer_motion (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height,
            (event.tfinger.fingerId%10) + 4, 0);
        break;
      case SDL_FINGERDOWN:
        {
        static int fdowns = 0;
        fdowns ++;
        if (fdowns > 1) // the very first finger down from SDL seems to be
                        // mirrored as mouse events, later ones not - at
                        // least under wayland
        {
          ctx_pointer_press (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height, 
          (event.tfinger.fingerId%10) + 4, 0);
        }
        }
        break;
      case SDL_FINGERUP:
        ctx_pointer_release (ctx, event.tfinger.x * tiled->width, event.tfinger.y * tiled->height,
          (event.tfinger.fingerId%10) + 4, 0);
        break;
#if 1
      case SDL_TEXTINPUT:
    //  if (!active)
    //    break;
        if (!sdl->lctrl && !sdl->rctrl && !sdl->lalt 
           //&& ( (vt && vt_keyrepeat (vt) ) || (key_repeat==0) )
           )
          {
            const char *name = event.text.text;
            int keycode = 0;
            if (!strcmp (name, " ") ) { name = "space"; }
            if (name[0] && name[1] == 0)
            {
              keycode = name[0];
              keycode = toupper (keycode);
              switch (keycode)
              {
                case '.':  keycode = 190; break;
                case ';':  keycode = 59; break;
                case ',':  keycode = 188; break;
                case '/':  keycode = 191; break;
                case '\'': keycode = 222; break;
                case '`':  keycode = 192; break;
                case '[':  keycode = 219; break;
                case ']':  keycode = 221; break;
                case '\\': keycode = 220; break;
              }
            }
            ctx_key_press (ctx, keycode, name, 0);
            //got_event = 1;
          }
        break;
#endif
      case SDL_KEYDOWN:
        {
          char buf[32] = "";
          const char *name = buf;
          if (!event.key.repeat)
          {
            sdl->key_balance ++;
            sdl->key_repeat = 0;
          }
          else
          {
            sdl->key_repeat ++;
          }
          switch (event.key.keysym.sym)
          {
            case SDLK_LSHIFT: sdl->lshift = 1; break;
            case SDLK_RSHIFT: sdl->rshift = 1; break;
            case SDLK_LCTRL:  sdl->lctrl = 1; break;
            case SDLK_LALT:   sdl->lalt = 1; break;
            case SDLK_RCTRL:  sdl->rctrl = 1; break;
          }
          if (sdl->lshift | sdl->rshift | sdl->lctrl | sdl->lalt | sdl->rctrl)
          {
            ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL|
                                            CTX_MODIFIER_STATE_ALT|
                                            CTX_MODIFIER_STATE_SHIFT);
            if (sdl->lshift | sdl->rshift)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT;
            if (sdl->lctrl | sdl->rctrl)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL;
            if (sdl->lalt)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT;
          }
          int keycode;
          name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode);
          ctx_key_down (ctx, keycode, name, 0);

          if (strlen (name)
              &&(event.key.keysym.mod & (KMOD_CTRL) ||
                 event.key.keysym.mod & (KMOD_ALT) ||
                 strlen (name) >= 2))
          {
            if (event.key.keysym.mod & (KMOD_CTRL) )
              {
                static char buf[64] = "";
                sprintf (buf, "control-%s", name);
                name = buf;
              }
            if (event.key.keysym.mod & (KMOD_ALT) )
              {
                static char buf[128] = "";
                sprintf (buf, "alt-%s", name);
                name = buf;
              }
            if (event.key.keysym.mod & (KMOD_SHIFT) )
              {
                static char buf[196] = "";
                sprintf (buf, "shift-%s", name);
                name = buf;
              }
            if (strcmp (name, "space"))
              {
               ctx_key_press (ctx, keycode, name, 0);
              }
          }
          else
          {
#if 0
             ctx_key_press (ctx, 0, buf, 0);
#endif
          }
        }
        break;
      case SDL_KEYUP:
        {
           sdl->key_balance --;
           switch (event.key.keysym.sym)
           {
             case SDLK_LSHIFT: sdl->lshift = 0; break;
             case SDLK_RSHIFT: sdl->rshift = 0; break;
             case SDLK_LCTRL: sdl->lctrl = 0; break;
             case SDLK_RCTRL: sdl->rctrl = 0; break;
             case SDLK_LALT:  sdl->lalt  = 0; break;
           }

          {
            ctx->events.modifier_state ^= ~(CTX_MODIFIER_STATE_CONTROL|
                                            CTX_MODIFIER_STATE_ALT|
                                            CTX_MODIFIER_STATE_SHIFT);
            if (sdl->lshift | sdl->rshift)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT;
            if (sdl->lctrl | sdl->rctrl)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL;
            if (sdl->lalt)
              ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT;
          }

           int keycode;
           const char *name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode);
           ctx_key_up (ctx, keycode, name, 0);
        }
        break;
      case SDL_QUIT:
        ctx_quit (ctx);
        break;
      case SDL_WINDOWEVENT:
        if (event.window.event == SDL_WINDOWEVENT_RESIZED)
        {
          ctx_sdl_show_frame (sdl, 1);
          int width = event.window.data1;
          int height = event.window.data2;
          SDL_DestroyTexture (sdl->texture);
          sdl->texture = SDL_CreateTexture (sdl->renderer, SDL_PIXELFORMAT_ABGR8888,
                          SDL_TEXTUREACCESS_STREAMING, width, height);
          free (tiled->pixels);
          tiled->pixels = calloc (4, width * height);

          tiled->width  = width;
          tiled->height = height;
          ctx_set_size (tiled->ctx, width, height);
          ctx_set_size (tiled->ctx_copy, width, height);
        }
        break;
    }
  }
  return 1;
}
#else
void ctx_screenshot (Ctx *ctx, const char *path)
{
}
#endif

#if CTX_SDL

static void ctx_sdl_set_clipboard (CtxSDL *sdl, const char *text)
{
  if (text)
    SDL_SetClipboardText (text);
}

static char *ctx_sdl_get_clipboard (CtxSDL *sdl)
{
  return SDL_GetClipboardText ();
}

inline static void ctx_sdl_reset (CtxSDL *sdl)
{
  ctx_sdl_show_frame (sdl, 1);
}

inline static void ctx_sdl_flush (CtxSDL *sdl)
{
  ctx_tiled_flush ((void*)sdl);
  //CtxTiled *tiled = (void*)sdl;
}

void ctx_sdl_free (CtxSDL *sdl)
{

  if (sdl->texture)
  SDL_DestroyTexture (sdl->texture);
  if (sdl->renderer)
  SDL_DestroyRenderer (sdl->renderer);
  if (sdl->window)
  SDL_DestroyWindow (sdl->window);

  ctx_tiled_free ((CtxTiled*)sdl);
#if CTX_BABL
  babl_exit ();
#endif
}


int ctx_renderer_is_sdl (Ctx *ctx)
{
  if (ctx->renderer &&
      ctx->renderer->free == (void*)ctx_sdl_free)
          return 1;
  return 0;
}

void ctx_sdl_set_fullscreen (Ctx *ctx, int val)
{
  CtxSDL *sdl = (void*)ctx->renderer;

  if (val)
  {
    SDL_SetWindowFullscreen (sdl->window, SDL_WINDOW_FULLSCREEN_DESKTOP);
  }
  else
  {
    SDL_SetWindowFullscreen (sdl->window, 0);
  }
  // XXX we're presuming success
  sdl->fullscreen = val;
}
int ctx_sdl_get_fullscreen (Ctx *ctx)
{
  CtxSDL *sdl = (void*)ctx->renderer;
  return sdl->fullscreen;
}


Ctx *ctx_new_sdl (int width, int height)
{
#if CTX_RASTERIZER

  CtxSDL *sdl = (CtxSDL*)calloc (sizeof (CtxSDL), 1);
  CtxTiled *tiled = (void*)sdl;

  ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);
  if (width <= 0 || height <= 0)
  {
    width  = 1920;
    height = 1080;
  }
  sdl->window = SDL_CreateWindow("ctx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);
  //sdl->renderer = SDL_CreateRenderer (sdl->window, -1, SDL_RENDERER_SOFTWARE);
  sdl->renderer = SDL_CreateRenderer (sdl->window, -1, 0);
  if (!sdl->renderer)
  {
     ctx_free (tiled->ctx);
     free (sdl);
     return NULL;
  }
#if CTX_BABL
  babl_init ();
#endif
  sdl->fullscreen = 0;


  ctx_show_fps = getenv ("CTX_SHOW_FPS")!=NULL;

  ctx_sdl_events = 1;
  sdl->texture = SDL_CreateTexture (sdl->renderer,
        SDL_PIXELFORMAT_ABGR8888,
        SDL_TEXTUREACCESS_STREAMING,
        width, height);

  SDL_StartTextInput ();
  SDL_EnableScreenSaver ();

  tiled->ctx      = ctx_new ();
  tiled->ctx_copy = ctx_new ();
  tiled->width    = width;
  tiled->height   = height;
  tiled->cols     = 80;
  tiled->rows     = 20;
  ctx_set_renderer (tiled->ctx, sdl);
  ctx_set_renderer (tiled->ctx_copy, sdl);
  ctx_set_texture_cache (tiled->ctx_copy, tiled->ctx);

  tiled->pixels = (uint8_t*)malloc (width * height * 4);

  ctx_set_size (tiled->ctx,      width, height);
  ctx_set_size (tiled->ctx_copy, width, height);

  tiled->flush = (void*)ctx_sdl_flush;
  tiled->reset = (void*)ctx_sdl_reset;
  tiled->free  = (void*)ctx_sdl_free;
  tiled->set_clipboard = (void*)ctx_sdl_set_clipboard;
  tiled->get_clipboard = (void*)ctx_sdl_get_clipboard;

  for (int i = 0; i < _ctx_max_threads; i++)
  {
    tiled->host[i] = ctx_new_for_framebuffer (tiled->pixels,
                     tiled->width/CTX_HASH_COLS, tiled->height/CTX_HASH_ROWS,
                     tiled->width * 4, CTX_FORMAT_RGBA8);
    ctx_set_texture_source (tiled->host[i], tiled->ctx);
  }

  mtx_init (&tiled->mtx, mtx_plain);
  cnd_init (&tiled->cond);

#define start_thread(no)\
  if(_ctx_max_threads>no){ \
    static void *args[2]={(void*)no, };\
    thrd_t tid;\
    args[1]=sdl;\
    thrd_create (&tid, (void*)ctx_tiled_render_fun, args);\
  }
  start_thread(0);
  start_thread(1);
  start_thread(2);
  start_thread(3);
  start_thread(4);
  start_thread(5);
  start_thread(6);
  start_thread(7);
  start_thread(8);
  start_thread(9);
  start_thread(10);
  start_thread(11);
  start_thread(12);
  start_thread(13);
  start_thread(14);
  start_thread(15);
#undef start_thread

  ctx_flush (tiled->ctx);
  return tiled->ctx;
#else
  return NULL;
#endif
}
#else

int ctx_renderer_is_sdl (Ctx *ctx)
{
  return 0;
}
#endif

#if CTX_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#include <sys/ioctl.h>
#endif

typedef struct CtxTermCell
{
  char    utf8[5];
  uint8_t fg[4];
  uint8_t bg[4];

  char    prev_utf8[5];
  uint8_t prev_fg[4];
  uint8_t prev_bg[4];
} CtxTermCell;

typedef struct CtxTermLine
{
  CtxTermCell *cells;
  int maxcol;
  int size;
} CtxTermLine;

typedef enum
{
  CTX_TERM_ASCII,
  CTX_TERM_ASCII_MONO,
  CTX_TERM_SEXTANT,
  CTX_TERM_BRAILLE_MONO,
  CTX_TERM_BRAILLE,
  CTX_TERM_QUARTER,
} CtxTermMode;

typedef struct _CtxTerm CtxTerm;
struct _CtxTerm
{
   void (*render) (void *term, CtxCommand *command);
   void (*reset)  (void *term);
   void (*flush)  (void *term);
   char *(*get_clipboard) (void *ctxctx);
   void (*set_clipboard) (void *ctxctx, const char *text);
   void (*free)   (void *term);
   Ctx      *ctx;
   int       width;
   int       height;
   int       cols;
   int       rows;
   int       was_down;

   uint8_t  *pixels;

   Ctx      *host;
   CtxList  *lines;
   CtxTermMode mode;
};

static int ctx_term_ch = 8;
static int ctx_term_cw = 8;

void ctx_term_set (CtxTerm *term,
                      int col, int row, const char *utf8,
                      uint8_t *fg, uint8_t *bg)
{
  if (col < 1 || row < 1 || col > term->cols  || row > term->rows) return;
  while (ctx_list_length (term->lines) < row)
  {
    ctx_list_append (&term->lines, calloc (sizeof (CtxTermLine), 1));
  }
  CtxTermLine *line = ctx_list_nth_data (term->lines, row-1);
  assert (line);
  if (line->size < col)
  {
     int new_size = ((col + 128)/128)*128;
     line->cells = realloc (line->cells, sizeof (CtxTermCell) * new_size);
     memset (&line->cells[line->size], 0, sizeof (CtxTermCell) * (new_size - line->size) );
     line->size = new_size;
  }
  if (col > line->maxcol) line->maxcol = col;
  strncpy (line->cells[col-1].utf8, (char*)utf8, 4);
  memcpy  (line->cells[col-1].fg, fg, 4);
  memcpy  (line->cells[col-1].bg, bg, 4);
}

static int _ctx_term256 = 0; // XXX TODO implement autodetect for this
static long _ctx_curfg = -1;
static long _ctx_curbg = -1;

static long ctx_rgb_to_long (int r,int g, int b)
{
  return r * 256 * 256 + g * 256 + b;
}


static void ctx_term_set_fg (int red, int green, int blue)
{
  long lc = ctx_rgb_to_long (red, green, blue);
  if (lc == _ctx_curfg)
    return;
  _ctx_curfg=lc;
  if (_ctx_term256 == 0)
  {
    printf("\e[38;2;%i;%i;%im", red,green,blue);
  }
  else
  {
    int gray = (green /255.0) * 24 + 0.5;
    int r    = (red/255.0)    * 6 + 0.5;
    int g    = (green/255.0)  * 6 + 0.5;
    int b    = (blue/255.0)   * 6 + 0.5;
    if (gray > 23) gray = 23;

    if (r > 5) r = 5;
    if (g > 5) g = 5;
    if (b > 5) b = 5;

    if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66))))
    {
      printf("\e[38;5;%im", 16 + 216 + gray);
    }
    else
      printf("\e[38;5;%im", 16 + r * 6 * 6 + g * 6  + b);
  }
}

static void ctx_term_set_bg(int red, int green, int blue)
{
  long lc = ctx_rgb_to_long (red, green, blue);
//if (lc == _ctx_curbg)
//  return;
  _ctx_curbg=lc;
  if (_ctx_term256 == 0)
  {
    printf("\e[48;2;%i;%i;%im", red,green,blue);
  }
  else
  {
    int gray = (green /255.0) * 24 + 0.5;
    int r    = (red/255.0)    * 6 + 0.5;
    int g    = (green/255.0)  * 6 + 0.5;
    int b    = (blue/255.0)   * 6 + 0.5;
    if (gray > 23) gray = 23;

    if (r > 5) r = 5;
    if (g > 5) g = 5;
    if (b > 5) b = 5;

    if (((int)(r/1.66)== (int)(g/1.66)) && ((int)(g/1.66) == ((int)(b/1.66))))
    {
      printf("\e[48;5;%im", 16 + 216 + gray);
    }
    else
      printf("\e[48;5;%im", 16 + r * 6 * 6 + g * 6  + b);
  }
}

static int _ctx_term_force_full = 0;

void ctx_term_scanout (CtxTerm *term)
{
  int row = 1;
  printf ("\e[H");
//  printf ("\e[?25l");
  printf ("\e[0m");
  for (CtxList *l = term->lines; l; l = l->next)
  {
    CtxTermLine *line = l->data;
    for (int col = 1; col <= line->maxcol; col++)
    {
      CtxTermCell *cell = &line->cells[col-1];

      if (strcmp(cell->utf8, cell->prev_utf8) ||
          memcmp(cell->fg, cell->prev_fg, 3) ||
          memcmp(cell->bg, cell->prev_bg, 3) || _ctx_term_force_full)
      {
        ctx_term_set_fg (cell->fg[0], cell->fg[1], cell->fg[2]);
        ctx_term_set_bg (cell->bg[0], cell->bg[1], cell->bg[2]);
        printf ("%s", cell->utf8);
      }
      else
      {
        // TODO: accumulate succesive such, and compress them
        // into one
        printf ("\e[C");
      }
      strcpy (cell->prev_utf8, cell->utf8);
      memcpy (cell->prev_fg, cell->fg, 3);
      memcpy (cell->prev_bg, cell->bg, 3);
    }
    if (row != term->rows)
      printf ("\n\r");
    row ++;
  }
  printf ("\e[0m");
  //printf ("\e[?25h");
  //
}

// xx
// xx
// xx
//

static inline int _ctx_rgba8_manhattan_diff (const uint8_t *a, const uint8_t *b)
{
  int c;
  int diff = 0;
  for (c = 0; c<3;c++)
    diff += ctx_pow2(a[c]-b[c]);
  return ctx_sqrtf(diff);
  return diff;
}

static void ctx_term_output_buf_half (uint8_t *pixels,
                          int width,
                          int height,
                          CtxTerm *term)
{
  int stride = width * 4;
  const char *sextants[]={
   " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█",

  };
  for (int row = 0; row < height/2; row++)
    {
      for (int col = 0; col < width-3; col++)
        {
          int     unicode = 0;
          int     bitno = 0;
          uint8_t rgba[2][4] = {
                             {255,255,255,0},
                             {0,0,0,0}};
          int i = 0;

          int  rgbasum[2][4] = {0,};
          int  sumcount[2];

          int curdiff = 0;
          /* first find starting point colors */
          for (int yi = 0; yi < ctx_term_ch; yi++)
            for (int xi = 0; xi < ctx_term_cw; xi++, i++)
                {
                  int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4;

                  if (rgba[0][3] == 0)
                  {
                    for (int c = 0; c < 3; c++)
                      rgba[0][c] = pixels[noi + c];
                    rgba[0][3] = 255; // used only as mark of in-use
                  }
                  else
                  {
                    int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]);
                    if (diff > curdiff)
                    {
                      curdiff = diff;
                      for (int c = 0; c < 3; c++)
                        rgba[1][c] = pixels[noi + c];
                    }
                  }

                }

          for (int iters = 0; iters < 1; iters++)
          {
                  i= 0;
          for (int i = 0; i < 4; i ++)
             rgbasum[0][i] = rgbasum[1][i]=0;
          sumcount[0] = sumcount[1] = 0;

          for (int yi = 0; yi < ctx_term_ch; yi++)
            for (int xi = 0; xi < ctx_term_cw; xi++, i++)
                {
                  int noi = (row * ctx_term_ch + yi) * stride + (col*ctx_term_cw+xi) * 4;

                  int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]);
                  int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]);
                  int cluster = 0;
                  if (diff1 <= diff2)
                    cluster = 0;
                  else
                    cluster = 1;
                  sumcount[cluster]++;
                  for (int c = 0; c < 3; c++)
                    rgbasum[cluster][c] += pixels[noi+c];
                }


          if (sumcount[0])
          for (int c = 0; c < 3; c++)
          {
            rgba[0][c] = rgbasum[0][c] / sumcount[0];
          }
          if (sumcount[1])
          for (int c = 0; c < 3; c++)
          {
            rgba[1][c] = rgbasum[1][c] / sumcount[1];
          }
          }

          int pixels_set = 0;
          for (int y = 0; y < ctx_term_ch; y++)
            for (int x = 0; x < ctx_term_cw; x++)
              {
                int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4;
#define CHECK_IS_SET \
      (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \
       _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1]))

                int set = CHECK_IS_SET;
#undef CHECK_IS_SET
                if (set)
                  { unicode |=  (1<< (bitno) ); 
                    pixels_set ++; 
                  }
                bitno++;
              }
           if (pixels_set == 4)
             ctx_term_set (term, col +1, row + 1, " ",
                           rgba[1], rgba[0]);
           else
             ctx_term_set (term, col +1, row + 1, sextants[unicode],
                           rgba[0], rgba[1]);
        }
    }
}

void ctx_term_find_color_pair (CtxTerm *term, int x0, int y0, int w, int h,
                uint8_t rgba[2][4])
        //uint8_t *rgba0, uint8_t *rgba1)
{
int curdiff = 0;
int stride = term->width * 4;
uint8_t *pixels = term->pixels;
/* first find starting point colors */
for (int y = y0; y < y0 + h; y++)
  for (int x = x0; x < x0 + w; x++)
      {
        int noi = (y) * stride + (x) * 4;

        if (rgba[0][3] == 0)
        {
          for (int c = 0; c < 3; c++)
            rgba[0][c] = pixels[noi + c];
          rgba[0][3] = 255; // used only as mark of in-use
        }
        else
        {
          int diff = _ctx_rgba8_manhattan_diff (&pixels[noi], &rgba[0][0]);
          if (diff > curdiff)
          {
            curdiff = diff;
            for (int c = 0; c < 3; c++)
              rgba[1][c] = pixels[noi + c];
          }
        }
      }
          int  rgbasum[2][4] = {0,};
          int  sumcount[2];

          for (int iters = 0; iters < 1; iters++)
          {
          for (int i = 0; i < 4; i ++)
             rgbasum[0][i] = rgbasum[1][i]=0;
          sumcount[0] = sumcount[1] = 0;

          for (int y = y0; y < y0 + h; y++)
            for (int x = x0; x < x0 + w; x++)
                {
                  int noi = (y) * stride + (x) * 4;

                  int diff1 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[0]);
                  int diff2 = _ctx_rgba8_manhattan_diff (&pixels[noi], rgba[1]);
                  int cluster = 0;
                  if (diff1 <= diff2)
                    cluster = 0;
                  else
                    cluster = 1;
                  sumcount[cluster]++;
                  for (int c = 0; c < 3; c++)
                    rgbasum[cluster][c] += pixels[noi+c];
                }


          if (sumcount[0])
          for (int c = 0; c < 3; c++)
          {
            rgba[0][c] = rgbasum[0][c] / sumcount[0];
          }
          if (sumcount[1])
          for (int c = 0; c < 3; c++)
          {
            rgba[1][c] = rgbasum[1][c] / sumcount[1];
          }
          }

}



static void ctx_term_output_buf_quarter (uint8_t *pixels,
                          int width,
                          int height,
                          CtxTerm *term)
{
  int stride = width * 4;
  const char *sextants[]={
   " ","▘","▝","▀","▖","▌", "▞", "▛", "▗", "▚", "▐", "▜","▄","▙","▟","█"

  };
  for (int row = 0; row < height/ctx_term_ch; row++)
    {
      for (int col = 0; col < width /ctx_term_cw; col++)
        {
          int     unicode = 0;
          int     bitno = 0;
          uint8_t rgba[2][4] = {
                             {255,255,255,0},
                             {0,0,0,0}};
          ctx_term_find_color_pair (term, col * ctx_term_cw,
                                    row * ctx_term_ch,
                                    ctx_term_cw,
                                    ctx_term_ch, rgba);

          int pixels_set = 0;
          for (int y = 0; y < 2; y++)
            for (int x = 0; x < ctx_term_cw; x++)
              {
                int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4;
#define CHECK_IS_SET \
      (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \
       _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1]))

                int set = CHECK_IS_SET;
#undef CHECK_IS_SET
                if (set)
                  { unicode |=  (1<< (bitno) ); 
                    pixels_set ++; 
                  }
                bitno++;
              }
           if (pixels_set == 4)
             ctx_term_set (term, col +1, row + 1, " ",
                           rgba[1], rgba[0]);
           else
             ctx_term_set (term, col +1, row + 1, sextants[unicode],
                           rgba[0], rgba[1]);
        }
    }
}


static void ctx_term_output_buf_sextant (uint8_t *pixels,
                          int width,
                          int height,
                          CtxTerm *term)
{
  int stride = width * 4;

  const char *sextants[]={
   " ","🬀","🬁","🬂","🬃","🬄","🬅","🬆","🬇","🬈","🬉","🬊","🬋","🬌","🬍","🬎","🬏","🬐","🬑","🬒","🬓","▌","🬔","🬕","🬖","🬗","🬘","🬙","🬚","🬛","🬜","🬝","🬞","🬟","🬠","🬡","🬢","🬣","🬤","🬥","🬦","🬧","▐","🬨","🬩","🬪","🬫","🬬","🬭","🬮","🬯","🬰","🬱","🬲","🬳","🬴","🬵","🬶","🬷","🬸","🬹","🬺","🬻","█"
  };

  for (int row = 0; row < height/ctx_term_ch; row++)
    {
      for (int col = 0; col < width /ctx_term_cw; col++)
        {
          int     unicode = 0;
          int     bitno = 0;
          uint8_t rgba[2][4] = {
                             {255,255,255,0},
                             {0,0,0,0}};

          ctx_term_find_color_pair (term, col * ctx_term_cw,
                                    row * ctx_term_ch,
                                    ctx_term_cw,
                                    ctx_term_ch, rgba);

          int pixels_set = 0;
          for (int y = 0; y < ctx_term_ch; y++)
            for (int x = 0; x < ctx_term_cw; x++)
              {
                int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4;
#define CHECK_IS_SET \
      (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \
       _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1]))

                int set = CHECK_IS_SET;
#undef CHECK_IS_SET
                if (set)
                  { unicode |=  (1<< (bitno) ); 
                    pixels_set ++; 
                  }
                bitno++;
              }

          if (pixels_set == 6)
            ctx_term_set (term, col +1, row + 1, " ",
                          rgba[1], rgba[0]);
          else
            ctx_term_set (term, col +1, row + 1, sextants[unicode], rgba[0], rgba[1]);
        }
    }
}

static void ctx_term_output_buf_ascii (uint8_t *pixels,
                          int width,
                          int height,
                          CtxTerm *term,
                          int mono)
{
  /* this is a crude ascii-mode built on a quick mapping of sexels to ascii */
  int stride = width * 4;
  const char *sextants[]={
   " ","`","'","^","🬃","`","~","\"","-","\"","'","\"","-","\"","~","^",",",";",
   "=","/","i","[","p","P","z",")","/","7","f",">","/","F",",","\\",":",":",
   "\\","\\","(","T","j","T","]","?","s","\\","<","q","_","=","=","=","c","L",
   "Q","C","a","b","J","]","m","b","d","@"
  };
  uint8_t black[4] = {0,0,0,255};
  for (int row = 0; row < height/ctx_term_ch; row++)
    {
      for (int col = 0; col < width /ctx_term_cw; col++)
        {
          int     unicode = 0;
          int     bitno = 0;
          uint8_t rgba[2][4] = {
                             {255,255,255,0},
                             {0,0,0,0}};

          ctx_term_find_color_pair (term, col * ctx_term_cw,
                                    row * ctx_term_ch,
                                    ctx_term_cw,
                                    ctx_term_ch, rgba);


          if (_ctx_rgba8_manhattan_diff (black, rgba[1]) >
              _ctx_rgba8_manhattan_diff (black, rgba[0]))
          {
            for (int c = 0; c < 4; c ++)
            {
              int tmp = rgba[0][c];
              rgba[0][c] = rgba[1][c];
              rgba[1][c] = tmp;
            }
          }
          if (mono)
          {
            rgba[1][0] = 0;
            rgba[1][1] = 0;
            rgba[1][2] = 0;
          }


          int brightest_dark_diff = _ctx_rgba8_manhattan_diff (black, rgba[0]);

          int pixels_set = 0;
          for (int y = 0; y < ctx_term_ch; y++)
            for (int x = 0; x < ctx_term_cw; x++)
              {
                int no = (row * ctx_term_ch + y) * stride + (col*ctx_term_cw+x) * 4;
#define CHECK_IS_SET \
      (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \
       _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1]))

                int set = CHECK_IS_SET;
#undef CHECK_IS_SET
                if (set)
                  { unicode |=  (1<< (bitno) ); 
                    pixels_set ++; 
                  }
                bitno++;
              }


           if (pixels_set == 6 && brightest_dark_diff < 40)
             ctx_term_set (term, col +1, row + 1, " ",
                           rgba[1], rgba[0]);
           else
             ctx_term_set (term, col +1, row + 1, sextants[unicode],
                           rgba[0], rgba[1]);
        }
    }
}

static void ctx_term_output_buf_braille (uint8_t *pixels,
                          int width,
                          int height,
                          CtxTerm *term,
                          int mono)
{
  int reverse = 0;
  int stride = width * 4;
  uint8_t black[4] = {0,0,0,255};
  for (int row = 0; row < height/ctx_term_ch; row++)
    {
      for (int col = 0; col < width /ctx_term_cw; col++)
        {
          int     unicode = 0;
          int     bitno = 0;
          uint8_t rgba[2][4] = {
                             {255,255,255,0},
                             {0,0,0,0}};

          ctx_term_find_color_pair (term, col * ctx_term_cw,
                                    row * ctx_term_ch,
                                    ctx_term_cw,
                                    ctx_term_ch, rgba);


          /* make darkest consistently be background  */
          if (_ctx_rgba8_manhattan_diff (black, rgba[1]) >
              _ctx_rgba8_manhattan_diff (black, rgba[0]))
          {
            for (int c = 0; c < 4; c ++)
            {
              int tmp = rgba[0][c];
              rgba[0][c] = rgba[1][c];
              rgba[1][c] = tmp;
            }
          }
          if (mono)
          {
            rgba[1][0] = 0;
            rgba[1][1] = 0;
            rgba[1][2] = 0;
          }

          int pixels_set = 0;
          for (int x = 0; x < 2; x++)
            for (int y = 0; y < 3; y++)
              {
                int no = (row * 4 + y) * stride + (col*2+x) * 4;
#define CHECK_IS_SET \
      (_ctx_rgba8_manhattan_diff (&pixels[no], rgba[0])< \
       _ctx_rgba8_manhattan_diff (&pixels[no], rgba[1]))

                int set = CHECK_IS_SET;
                if (reverse) { set = !set; }
                if (set)
                  { unicode |=  (1<< (bitno) ); 
                    pixels_set ++; 
                  }
                bitno++;
              }
          {
            int x = 0;
            int y = 3;
            int no = (row * 4 + y) * stride + (col*2+x) * 4;
            int setA = CHECK_IS_SET;
            no = (row * 4 + y) * stride + (col*2+x+1) * 4;
            int setB = CHECK_IS_SET;

            pixels_set += setA;
            pixels_set += setB;
#undef CHECK_IS_SET
            if (reverse) { setA = !setA; }
            if (reverse) { setB = !setB; }
            if (setA != 0 && setB==0)
              { unicode += 0x2840; }
            else if (setA == 0 && setB)
              { unicode += 0x2880; }
            else if ( (setA != 0) && (setB != 0) )
              { unicode += 0x28C0; }
            else
              { unicode += 0x2800; }
            char utf8[5];
            utf8[ctx_unichar_to_utf8 (unicode, (uint8_t*)utf8)]=0;

#if 0
            if (pixels_set == 8)
            {
              if (rgba[0][0] < 32 && rgba[0][1] < 32 && rgba[0][2] < 32)
              {
                ctx_term_set (term, col +1, row + 1, " ",
                                 rgba[1], rgba[0]);
                continue;
              }
            }
#endif
            {
              ctx_term_set (term, col +1, row + 1, utf8,
                               rgba[0], rgba[1]);
            }
          }
        }
    }
}


inline static void ctx_term_render (void *ctx,
                                       CtxCommand *command)
{
  CtxTerm *term = (void*)ctx;
  /* directly forward */
  ctx_process (term->host, &command->entry);
}

inline static void ctx_term_flush (CtxTerm *term)
{
  int width =  term->width;
  int height = term->height;
  switch (term->mode)
  {
    case CTX_TERM_QUARTER:
       ctx_term_output_buf_quarter (term->pixels,
                                width, height, term);
       break;
    case CTX_TERM_ASCII:
       ctx_term_output_buf_ascii (term->pixels,
                                width, height, term, 0);
       break;
    case CTX_TERM_ASCII_MONO:
       ctx_term_output_buf_ascii (term->pixels,
                                width, height, term, 1);
       break;
    case CTX_TERM_SEXTANT:
       ctx_term_output_buf_sextant (term->pixels,
                                width, height, term);
       break;
    case CTX_TERM_BRAILLE:
       ctx_term_output_buf_braille (term->pixels,
                                width, height, term, 0);
       break;
    case CTX_TERM_BRAILLE_MONO:
       ctx_term_output_buf_braille (term->pixels,
                                width, height, term, 1);
       break;
  }
#if CTX_BRAILLE_TEXT
  CtxRasterizer *rasterizer = (CtxRasterizer*)(term->host->renderer);
  // XXX instead sort and inject along with braille
  //

  //uint8_t rgba_bg[4]={0,0,0,0};
  //uint8_t rgba_fg[4]={255,0,255,255};

  for (CtxList *l = rasterizer->glyphs; l; l = l->next)
  {
    CtxTermGlyph *glyph = l->data;

    uint8_t *pixels = term->pixels;
    long rgb_sum[4]={0,0,0};
    for (int v = 0; v <  ctx_term_ch; v ++)
    for (int u = 0; u <  ctx_term_cw; u ++)
    {
      int i = ((glyph->row-1) * ctx_term_ch + v) * rasterizer->blit_width + 
              ((glyph->col-1) * ctx_term_cw + u);
      for (int c = 0; c < 3; c ++)
        rgb_sum[c] += pixels[i*4+c];
    }
    for (int c = 0; c < 3; c ++)
      glyph->rgba_bg[c] = rgb_sum[c] / (ctx_term_ch * ctx_term_cw);
    char utf8[8];
    utf8[ctx_unichar_to_utf8(glyph->unichar, (uint8_t*)utf8)]=0;
    ctx_term_set (term, glyph->col, glyph->row, 
                     utf8, glyph->rgba_fg, glyph->rgba_bg);
    free (glyph);
  }

  printf ("\e[H");
  printf ("\e[0m");
  ctx_term_scanout (term);
  printf ("\e[0m");
  fflush(NULL);
  while (rasterizer->glyphs)
    ctx_list_remove (&rasterizer->glyphs, rasterizer->glyphs->data);
#endif
}

void ctx_term_free (CtxTerm *term)
{
  while (term->lines)
  {
    free (term->lines->data);
    ctx_list_remove (&term->lines, term->lines->data);
  }
  printf ("\e[?25h"); // cursor on
  nc_at_exit ();
  free (term->pixels);
  ctx_free (term->host);
  free (term);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

int ctx_renderer_is_term (Ctx *ctx)
{
  if (ctx->renderer &&
      ctx->renderer->free == (void*)ctx_term_free)
          return 1;
  return 0;
}

float ctx_term_get_cell_width (Ctx *ctx)
{
  return ctx_term_cw;
}

float ctx_term_get_cell_height (Ctx *ctx)
{
  return ctx_term_ch;
}

Ctx *ctx_new_term (int width, int height)
{
  Ctx *ctx = ctx_new ();
#if CTX_RASTERIZER
  CtxTerm *term = (CtxTerm*)calloc (sizeof (CtxTerm), 1);
 
  const char *mode = getenv ("CTX_TERM_MODE");
  ctx_term_cw = 2;
  ctx_term_ch = 3;

  if (!mode) term->mode = CTX_TERM_SEXTANT;
  else if (!strcmp (mode, "sextant")) term->mode = CTX_TERM_SEXTANT;
  else if (!strcmp (mode, "ascii")) term->mode = CTX_TERM_ASCII_MONO;
  //else if (!strcmp (mode, "ascii-mono")) term->mode = CTX_TERM_ASCII_MONO;
  else if (!strcmp (mode, "quarter")) term->mode = CTX_TERM_QUARTER;
  //else if (!strcmp (mode, "braille")){
  //  term->mode = CTX_TERM_BRAILLE;
  //  ctx_term_ch = 4;
  //}
  else if (!strcmp (mode, "braille")){
    term->mode = CTX_TERM_BRAILLE_MONO;
    ctx_term_ch = 4;
  }
  else {
    fprintf (stderr, "recognized values for CTX_TERM_MODE:\n"
                    " sextant ascii quarter braille\n");
    exit (1);
  }

  mode = getenv ("CTX_TERM_FORCE_FULL");
  if (mode && strcmp (mode, "0") && strcmp (mode, "no"))
    _ctx_term_force_full = 1;

  fprintf (stdout, "\e[?1049h");
  fprintf (stdout, "\e[?25l"); // cursor off

  int maxwidth = ctx_terminal_cols  () * ctx_term_cw;
  int maxheight = (ctx_terminal_rows ()) * ctx_term_ch;
  if (width <= 0 || height <= 0)
  {
    width = maxwidth;
    height = maxheight;
  }
  if (width > maxwidth) width = maxwidth;
  if (height > maxheight) height = maxheight;
  term->ctx = ctx;
  term->width  = width;
  term->height = height;

  term->cols = (width + 1) / ctx_term_cw;
  term->rows = (height + 2) / ctx_term_ch;
  term->lines = 0;
  term->pixels = (uint8_t*)malloc (width * height * 4);
  term->host = ctx_new_for_framebuffer (term->pixels,
                                           width, height,
                                           width * 4, CTX_FORMAT_RGBA8);
#if CTX_BRAILLE_TEXT
  ((CtxRasterizer*)term->host->renderer)->term_glyphs=1;
#endif
  _ctx_mouse (ctx, NC_MOUSE_DRAG);
  ctx_set_renderer (ctx, term);
  ctx_set_size (ctx, width, height);
  ctx_font_size (ctx, ctx_term_ch); 
  term->render = ctx_term_render;
  term->flush = (void(*)(void*))ctx_term_flush;
  term->free  = (void(*)(void*))ctx_term_free;
#endif


  return ctx;
}

#endif

#if CTX_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#include <sys/ioctl.h>
#endif

typedef struct _CtxTermImg CtxTermImg;
struct _CtxTermImg
{
   void (*render)         (void *termimg, CtxCommand *command);
   void (*reset)          (void *termimg);
   void (*flush)          (void *termimg);
   char *(*get_clipboard) (void *ctxctx);
   void (*set_clipboard)  (void *ctxctx, const char *text);
   void (*free)           (void *termimg);
   Ctx      *ctx;
   int       width;
   int       height;
   int       cols;
   int       rows;
   int       was_down;
   // we need to have the above members in that order up to here
   uint8_t  *pixels;
   Ctx      *host;
   CtxList  *lines;
};

inline static void ctx_termimg_render (void       *ctx,
                                       CtxCommand *command)
{
  CtxTermImg *termimg = (void*)ctx;
  /* directly forward */
  ctx_process (termimg->host, &command->entry);
}

inline static void ctx_termimg_flush (CtxTermImg *termimg)
{
  int width =  termimg->width;
  int height = termimg->height;
  if (!termimg->pixels) return;
  char *encoded = malloc (width * height * 3 * 3);
  ctx_bin2base64 (termimg->pixels, width * height * 3,
                  encoded);
  int encoded_len = strlen (encoded);

  int i = 0;

  printf ("\e[H");
  printf ("\e_Gf=24,s=%i,v=%i,t=d,a=T,m=1;\e\\", width, height);
  while (i <  encoded_len)
  {
     if (i + 4096 <  encoded_len)
     {
       printf  ("\e_Gm=1;");
     }
     else
     {
       printf  ("\e_Gm=0;");
     }
     for (int n = 0; n < 4000 && i < encoded_len; n++)
     {
       printf ("%c", encoded[i]);
       i++;
     }
     printf ("\e\\");
  }
  free (encoded);
  
  fflush (NULL);
}

void ctx_termimg_free (CtxTermImg *termimg)
{
  while (termimg->lines)
  {
    free (termimg->lines->data);
    ctx_list_remove (&termimg->lines, termimg->lines->data);
  }
  printf ("\e[?25h"); // cursor on
  nc_at_exit ();
  free (termimg->pixels);
  ctx_free (termimg->host);
  free (termimg);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

int ctx_renderer_is_termimg (Ctx *ctx)
{
  if (ctx->renderer &&
      ctx->renderer->free == (void*)ctx_termimg_free)
          return 1;
  return 0;
}

Ctx *ctx_new_termimg (int width, int height)
{
  Ctx *ctx = ctx_new ();
#if CTX_RASTERIZER
  fprintf (stdout, "\e[?1049h");
  fprintf (stdout, "\e[?25l"); // cursor off
  CtxTermImg *termimg = (CtxTermImg*)calloc (sizeof (CtxTermImg), 1);


  int maxwidth = ctx_terminal_width ();

  int colwidth = maxwidth/ctx_terminal_cols ();
  maxwidth-=colwidth;

  int maxheight = ctx_terminal_height ();
  if (width <= 0 || height <= 0)
  {
    width  = maxwidth;
    height = maxheight;
  }
  if (width > maxwidth) width = maxwidth;
  if (height > maxheight) height = maxheight;
  termimg->ctx = ctx;
  termimg->width  = width;
  termimg->height = height;
  termimg->lines = 0;
  termimg->pixels = (uint8_t*)malloc (width * height * 3);
  termimg->host = ctx_new_for_framebuffer (termimg->pixels,
                                           width, height,
                                           width * 3, CTX_FORMAT_RGB8);
  _ctx_mouse (ctx, NC_MOUSE_DRAG);
  ctx_set_renderer (ctx, termimg);
  ctx_set_size (ctx, width, height);
  ctx_font_size (ctx, 14.0f);
  termimg->render = ctx_termimg_render;
  termimg->flush = (void(*)(void*))ctx_termimg_flush;
  termimg->free  = (void(*)(void*))ctx_termimg_free;
#endif

  return ctx;
}

#endif

#if CTX_FORMATTER

typedef struct _CtxFormatter  CtxFormatter;
struct _CtxFormatter 
{
  void *target; // FILE
  int   longform;
  int   indent;

  void (*add_str)(CtxFormatter *formatter, const char *str, int len);
};

static inline void ctx_formatter_addstr (CtxFormatter *formatter, const char *str, int len)
{
  formatter->add_str (formatter, str, len);
}

static inline void ctx_formatter_addstrf (CtxFormatter *formatter, const char *format, ...)
{
   va_list ap;
   size_t needed;
   char *buffer;
   va_start (ap, format);
   needed = vsnprintf (NULL, 0, format, ap) + 1;
   buffer = (char*) malloc (needed);
   va_end (ap);
   va_start (ap, format);
   vsnprintf (buffer, needed, format, ap);
   va_end (ap);
   ctx_formatter_addstr (formatter, buffer, -1);
   free (buffer);
}

static void _ctx_stream_addstr (CtxFormatter *formatter, const char *str, int len)
{
  if (!str || len == 0)
  {
    return;
  }
  if (len < 0) len = strlen (str);
  fwrite (str, len, 1, (FILE*)formatter->target);
}

void _ctx_string_addstr (CtxFormatter *formatter, const char *str, int len)
{
  if (!str || len == 0)
  {
    return;
  }
  if (len < 0) len = strlen (str);
  ctx_string_append_data ((CtxString*)(formatter->target), str, len);
}


static void _ctx_print_endcmd (CtxFormatter *formatter)
{
  if (formatter->longform)
    {
      ctx_formatter_addstr (formatter, ");\n", 3);
    }
}

static void _ctx_indent (CtxFormatter *formatter)
{
  for (int i = 0; i < formatter->indent; i++)
    { ctx_formatter_addstr (formatter, "  ", 2);
    }
}

const char *_ctx_code_to_name (int code)
{
      switch (code)
        {
          case CTX_REL_LINE_TO_X4:           return "relLinetoX4"; break;
          case CTX_REL_LINE_TO_REL_CURVE_TO: return "relLineToRelCurveTo"; break;
          case CTX_REL_CURVE_TO_REL_LINE_TO: return "relCurveToRelLineTo"; break;
          case CTX_REL_CURVE_TO_REL_MOVE_TO: return "relCurveToRelMoveTo"; break;
          case CTX_REL_LINE_TO_X2:           return "relLineToX2"; break;
          case CTX_MOVE_TO_REL_LINE_TO:      return "moveToRelLineTo"; break;
          case CTX_REL_LINE_TO_REL_MOVE_TO:  return "relLineToRelMoveTo"; break;
          case CTX_FILL_MOVE_TO:             return "fillMoveTo"; break;
          case CTX_REL_QUAD_TO_REL_QUAD_TO:  return "relQuadToRelQuadTo"; break;
          case CTX_REL_QUAD_TO_S16:          return "relQuadToS16"; break;

          case CTX_SET_KEY:              return "setParam"; break;
          case CTX_COLOR:                return "setColor"; break;
          case CTX_DEFINE_GLYPH:         return "defineGlyph"; break;
          case CTX_KERNING_PAIR:         return "kerningPair"; break;
          case CTX_SET_PIXEL:            return "setPixel"; break;
          case CTX_GLOBAL_ALPHA:         return "globalAlpha"; break;
          case CTX_TEXT:                 return "text"; break;
          case CTX_STROKE_TEXT:          return "strokeText"; break;
          case CTX_SAVE:                 return "save"; break;
          case CTX_RESTORE:              return "restore"; break;
          case CTX_STROKE_SOURCE:        return "strokeSource"; break;
          case CTX_NEW_PAGE:             return "newPage"; break;
          case CTX_START_GROUP:          return "startGroup"; break;
          case CTX_END_GROUP:            return "endGroup"; break;
          case CTX_RECTANGLE:            return "rectangle"; break;
          case CTX_ROUND_RECTANGLE:      return "roundRectangle"; break;
          case CTX_LINEAR_GRADIENT:      return "linearGradient"; break;
          case CTX_RADIAL_GRADIENT:      return "radialGradient"; break;
          case CTX_GRADIENT_STOP:        return "gradientAddStop"; break;
          case CTX_VIEW_BOX:             return "viewBox"; break;
          case CTX_MOVE_TO:              return "moveTo"; break;
          case CTX_LINE_TO:              return "lineTo"; break;
          case CTX_BEGIN_PATH:           return "beginPath"; break;
          case CTX_REL_MOVE_TO:          return "relMoveTo"; break;
          case CTX_REL_LINE_TO:          return "relLineTo"; break;
          case CTX_FILL:                 return "fill"; break;
          case CTX_EXIT:                 return "exit"; break;
          case CTX_APPLY_TRANSFORM:      return "transform"; break;
          case CTX_SOURCE_TRANSFORM:     return "sourceTransform"; break;
          case CTX_REL_ARC_TO:           return "relArcTo"; break;
          case CTX_GLYPH:                return "glyph"; break;
          case CTX_TEXTURE:              return "texture"; break;
          case CTX_DEFINE_TEXTURE:       return "defineTexture"; break;
          case CTX_IDENTITY:             return "identity"; break;
          case CTX_CLOSE_PATH:           return "closePath"; break;
          case CTX_PRESERVE:             return "preserve"; break;
          case CTX_FLUSH:                return "flush"; break;
          case CTX_RESET:                return "reset"; break;
          case CTX_FONT:                 return "font"; break;
          case CTX_STROKE:               return "stroke"; break;
          case CTX_CLIP:                 return "clip"; break;
          case CTX_ARC:                  return "arc"; break;
          case CTX_SCALE:                return "scale"; break;
          case CTX_TRANSLATE:            return "translate"; break;
          case CTX_ROTATE:               return "rotate"; break;
          case CTX_ARC_TO:               return "arcTo"; break;
          case CTX_CURVE_TO:             return "curveTo"; break;
          case CTX_REL_CURVE_TO:         return "relCurveTo"; break;
          case CTX_REL_QUAD_TO:          return "relQuadTo"; break;
          case CTX_QUAD_TO:              return "quadTo"; break;
          case CTX_SMOOTH_TO:            return "smoothTo"; break;
          case CTX_REL_SMOOTH_TO:        return "relSmoothTo"; break;
          case CTX_SMOOTHQ_TO:           return "smoothqTo"; break;
          case CTX_REL_SMOOTHQ_TO:       return "relSmoothqTo"; break;
          case CTX_HOR_LINE_TO:          return "horLineTo"; break;
          case CTX_VER_LINE_TO:          return "verLineTo"; break;
          case CTX_REL_HOR_LINE_TO:      return "relHorLineTo"; break;
          case CTX_REL_VER_LINE_TO:      return "relVerLineTo"; break;
          case CTX_COMPOSITING_MODE:     return "compositingMode"; break;
          case CTX_BLEND_MODE:           return "blendMode"; break;
          case CTX_TEXT_ALIGN:           return "textAlign"; break;
          case CTX_TEXT_BASELINE:        return "textBaseline"; break;
          case CTX_TEXT_DIRECTION:       return "textDirection"; break;
          case CTX_FONT_SIZE:            return "fontSize"; break;
          case CTX_MITER_LIMIT:          return "miterLimit"; break;
          case CTX_LINE_JOIN:            return "lineJoin"; break;
          case CTX_LINE_CAP:             return "lineCap"; break;
          case CTX_LINE_WIDTH:           return "lineWidth"; break;
          case CTX_LINE_DASH_OFFSET:     return "lineDashOffset"; break;
          case CTX_IMAGE_SMOOTHING:      return "imageSmoothing"; break;
          case CTX_SHADOW_BLUR:          return "shadowBlur";  break;
          case CTX_FILL_RULE:            return "fillRule"; break;
        }
      return NULL;
}

static void _ctx_print_name (CtxFormatter *formatter, int code)
{
#define CTX_VERBOSE_NAMES 1
#if CTX_VERBOSE_NAMES
  if (formatter->longform)
    {
      const char *name = NULL;
      _ctx_indent (formatter);
      //switch ((CtxCode)code)
      name = _ctx_code_to_name (code);
      if (name)
        {
          ctx_formatter_addstr (formatter, name, -1);
          ctx_formatter_addstr (formatter, " (", 2);
          if (code == CTX_SAVE)
            { formatter->indent ++; }
          else if (code == CTX_RESTORE)
            { formatter->indent --; }
          return;
        }
    }
#endif
  {
    char name[3];
    name[0]=CTX_SET_KEY;
    name[2]='\0';
    switch (code)
      {
        case CTX_GLOBAL_ALPHA:      name[1]='a'; break;
        case CTX_COMPOSITING_MODE:  name[1]='m'; break;
        case CTX_BLEND_MODE:        name[1]='B'; break;
        case CTX_TEXT_ALIGN:        name[1]='t'; break;
        case CTX_TEXT_BASELINE:     name[1]='b'; break;
        case CTX_TEXT_DIRECTION:    name[1]='d'; break;
        case CTX_FONT_SIZE:         name[1]='f'; break;
        case CTX_MITER_LIMIT:       name[1]='l'; break;
        case CTX_LINE_JOIN:         name[1]='j'; break;
        case CTX_LINE_CAP:          name[1]='c'; break;
        case CTX_LINE_WIDTH:        name[1]='w'; break;
        case CTX_LINE_DASH_OFFSET:  name[1]='D'; break;
        case CTX_IMAGE_SMOOTHING:   name[1]='S'; break;
        case CTX_SHADOW_BLUR:       name[1]='s'; break;
        case CTX_SHADOW_COLOR:      name[1]='C'; break;
        case CTX_SHADOW_OFFSET_X:   name[1]='x'; break;
        case CTX_SHADOW_OFFSET_Y:   name[1]='y'; break;
        case CTX_FILL_RULE:         name[1]='r'; break;
        default:
          name[0] = code;
          name[1] = 0;
          break;
      }
    ctx_formatter_addstr (formatter, name, -1);
    if (formatter->longform)
      ctx_formatter_addstr (formatter, " (", 2);
    else
      ctx_formatter_addstr (formatter, " ", 1);
  }
}

static void
ctx_print_entry_enum (CtxFormatter *formatter, CtxEntry *entry, int args)
{
  _ctx_print_name (formatter, entry->code);
  for (int i = 0; i <  args; i ++)
    {
      int val = ctx_arg_u8 (i);
      if (i>0)
        { 
          ctx_formatter_addstr (formatter, " ", 1);
        }
#if CTX_VERBOSE_NAMES
      if (formatter->longform)
        {
          const char *str = NULL;
          switch (entry->code)
            {
              case CTX_TEXT_BASELINE:
                switch (val)
                  {
                    case CTX_TEXT_BASELINE_ALPHABETIC: str = "alphabetic"; break;
                    case CTX_TEXT_BASELINE_TOP:        str = "top";        break;
                    case CTX_TEXT_BASELINE_BOTTOM:     str = "bottom";     break;
                    case CTX_TEXT_BASELINE_HANGING:    str = "hanging";    break;
                    case CTX_TEXT_BASELINE_MIDDLE:     str = "middle";     break;
                    case CTX_TEXT_BASELINE_IDEOGRAPHIC:str = "ideographic";break;
                  }
                break;
              case CTX_TEXT_ALIGN:
                switch (val)
                  {
                    case CTX_TEXT_ALIGN_LEFT:   str = "left"; break;
                    case CTX_TEXT_ALIGN_RIGHT:  str = "right"; break;
                    case CTX_TEXT_ALIGN_START:  str = "start"; break;
                    case CTX_TEXT_ALIGN_END:    str = "end"; break;
                    case CTX_TEXT_ALIGN_CENTER: str = "center"; break;
                  }
                break;
              case CTX_LINE_CAP:
                switch (val)
                  {
                    case CTX_CAP_NONE:   str = "none"; break;
                    case CTX_CAP_ROUND:  str = "round"; break;
                    case CTX_CAP_SQUARE: str = "square"; break;
                  }
                break;
              case CTX_LINE_JOIN:
                switch (val)
                  {
                    case CTX_JOIN_MITER: str = "miter"; break;
                    case CTX_JOIN_ROUND: str = "round"; break;
                    case CTX_JOIN_BEVEL: str = "bevel"; break;
                  }
                break;
              case CTX_FILL_RULE:
                switch (val)
                  {
                    case CTX_FILL_RULE_WINDING:  str = "winding"; break;
                    case CTX_FILL_RULE_EVEN_ODD: str = "evenodd"; break;
                  }
                break;
              case CTX_BLEND_MODE:
                val = ctx_arg_u32 (i);
                switch (val)
                  {
            case CTX_BLEND_NORMAL:      str = "normal"; break;
            case CTX_BLEND_MULTIPLY:    str = "multiply"; break;
            case CTX_BLEND_SCREEN:      str = "screen"; break;
            case CTX_BLEND_OVERLAY:     str = "overlay"; break;
            case CTX_BLEND_DARKEN:      str = "darken"; break;
            case CTX_BLEND_LIGHTEN:     str = "lighten"; break;
            case CTX_BLEND_COLOR_DODGE: str = "colorDodge"; break;
            case CTX_BLEND_COLOR_BURN:  str = "colorBurn"; break;
            case CTX_BLEND_HARD_LIGHT:  str = "hardLight"; break;
            case CTX_BLEND_SOFT_LIGHT:  str = "softLight"; break;
            case CTX_BLEND_DIFFERENCE:  str = "difference"; break;
            case CTX_BLEND_EXCLUSION:   str = "exclusion"; break;
            case CTX_BLEND_HUE:         str = "hue"; break;
            case CTX_BLEND_SATURATION:  str = "saturation"; break;
            case CTX_BLEND_COLOR:       str = "color"; break; 
            case CTX_BLEND_LUMINOSITY:  str = "luminosity"; break;
                  }
                break;
              case CTX_COMPOSITING_MODE:
                val = ctx_arg_u32 (i);
                switch (val)
                  {
              case CTX_COMPOSITE_SOURCE_OVER: str = "sourceOver"; break;
              case CTX_COMPOSITE_COPY: str = "copy"; break;
              case CTX_COMPOSITE_CLEAR: str = "clear"; break;
              case CTX_COMPOSITE_SOURCE_IN: str = "sourceIn"; break;
              case CTX_COMPOSITE_SOURCE_OUT: str = "sourceOut"; break;
              case CTX_COMPOSITE_SOURCE_ATOP: str = "sourceAtop"; break;
              case CTX_COMPOSITE_DESTINATION: str = "destination"; break;
              case CTX_COMPOSITE_DESTINATION_OVER: str = "destinationOver"; break;
              case CTX_COMPOSITE_DESTINATION_IN: str = "destinationIn"; break;
              case CTX_COMPOSITE_DESTINATION_OUT: str = "destinationOut"; break;
              case CTX_COMPOSITE_DESTINATION_ATOP: str = "destinationAtop"; break;
              case CTX_COMPOSITE_XOR: str = "xor"; break;
                  }

               break;
            }
          if (str)
            {
              ctx_formatter_addstr (formatter, str, -1);
            }
          else
            {
              ctx_formatter_addstrf (formatter, "%i", val);
            }
        }
      else
#endif
        {
          ctx_formatter_addstrf (formatter, "%i", val);
        }
    }
  _ctx_print_endcmd (formatter);
}


static void
ctx_print_a85 (CtxFormatter *formatter, uint8_t *data, int length)
{
  char *tmp = (char*)malloc (ctx_a85enc_len (length));
  ctx_a85enc (data, tmp, length);
  ctx_formatter_addstr (formatter, " ~", 2);
  ctx_formatter_addstr (formatter, tmp, -1);
  ctx_formatter_addstr (formatter, "~ ", 2);
  free (tmp);
}

static void
ctx_print_yenc (CtxFormatter *formatter, uint8_t *data, int length)
{
  char *tmp = (char*)malloc (length * 2 + 2);// worst case scenario
  int enclength = ctx_yenc ((char*)data, tmp, length);
  data[enclength]=0;
  ctx_formatter_addstr (formatter, " =", 2);
  ctx_formatter_addstr (formatter, tmp, enclength);
  ctx_formatter_addstr (formatter, "=y ", 2);
  free (tmp);
}

static void
ctx_print_escaped_string (CtxFormatter *formatter, const char *string)
{
  if (!string) { return; }
  for (int i = 0; string[i]; i++)
    {
      switch (string[i])
        {
          case '"':
            ctx_formatter_addstr (formatter, "\\\"", 2);
            break;
          case '\\':
            ctx_formatter_addstr (formatter, "\\\\", 2);
            break;
          case '\n':
            ctx_formatter_addstr (formatter, "\\n", 2);
            break;
          default:
            ctx_formatter_addstr (formatter, &string[i], 1);
        }
    }
}

static void
ctx_print_float (CtxFormatter *formatter, float val)
{
  char temp[128];
  sprintf (temp, "%0.3f", val);
  int j;
  for (j = 0; temp[j]; j++)
    if (j == ',') { temp[j] = '.'; }
  j--;
  if (j>0)
    while (temp[j] == '0')
      {
        temp[j]=0;
        j--;
      }
  if (temp[j]=='.')
    { temp[j]='\0'; }
  ctx_formatter_addstr (formatter, temp, -1);
}

static void
ctx_print_int (CtxFormatter *formatter, int val)
{
  ctx_formatter_addstrf (formatter, "%i", val);
}

static void
ctx_print_entry (CtxFormatter *formatter, CtxEntry *entry, int args)
{
  _ctx_print_name (formatter, entry->code);
  for (int i = 0; i <  args; i ++)
    {
      float val = ctx_arg_float (i);
      if (i>0 && val >= 0.0f)
        {
          if (formatter->longform)
            {
              ctx_formatter_addstr (formatter, ", ", 2);
            }
          else
            {
              if (val >= 0.0f)
                ctx_formatter_addstr (formatter, " ", 1);
            }
        }
      ctx_print_float (formatter, val);
    }
  _ctx_print_endcmd (formatter);
}

static void
ctx_print_glyph (CtxFormatter *formatter, CtxEntry *entry, int args)
{
  _ctx_print_name (formatter, entry->code);
  ctx_formatter_addstrf (formatter, "%i", entry->data.u32[0]);
  _ctx_print_endcmd (formatter);
}

static void
ctx_formatter_process (void *user_data, CtxCommand *c);


static void
ctx_formatter_process (void *user_data, CtxCommand *c)
{
  CtxEntry *entry = &c->entry;
  CtxFormatter *formatter = (CtxFormatter*)user_data;

  switch (entry->code)
  //switch ((CtxCode)(entry->code))
    {
      case CTX_GLYPH:
        ctx_print_glyph (formatter, entry, 1);
        break;
      case CTX_LINE_TO:
      case CTX_REL_LINE_TO:
      case CTX_SCALE:
      case CTX_TRANSLATE:
      case CTX_MOVE_TO:
      case CTX_REL_MOVE_TO:
      case CTX_SMOOTHQ_TO:
      case CTX_REL_SMOOTHQ_TO:
        ctx_print_entry (formatter, entry, 2);
        break;
      case CTX_TEXTURE:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstrf (formatter, "\"");
        ctx_print_escaped_string (formatter, c->texture.eid);
        ctx_formatter_addstrf (formatter, "\", ");
        ctx_print_float (formatter, c->texture.x);
        ctx_formatter_addstrf (formatter, ", ");
        ctx_print_float (formatter, c->texture.y);
        ctx_formatter_addstrf (formatter, " ");
        _ctx_print_endcmd (formatter);
        break;

      case CTX_DEFINE_TEXTURE:
        {
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstrf (formatter, "\"");
        ctx_print_escaped_string (formatter, c->define_texture.eid);
        ctx_formatter_addstrf (formatter, "\", ");
        ctx_print_int (formatter, c->define_texture.width);
        ctx_formatter_addstrf (formatter, ", ");
        ctx_print_int (formatter, c->define_texture.height);
        ctx_formatter_addstrf (formatter, ",%i, ", c->define_texture.format);

        uint8_t *pixel_data = ctx_define_texture_pixel_data (entry);
#if 1

        int stride = ctx_pixel_format_get_stride ((CtxPixelFormat)c->define_texture.format, c->define_texture.width);
        int data_len = stride * c->define_texture.height;
        if (c->define_texture.format == CTX_FORMAT_YUV420)
          data_len = c->define_texture.height * c->define_texture.width +
                       2*(c->define_texture.height/2) * (c->define_texture.width/2);
        //fprintf (stderr, "encoding %i bytes\n", c->define_texture.height *stride);
        //ctx_print_a85 (formatter, pixel_data, c->define_texture.height * stride);
        ctx_print_yenc (formatter, pixel_data, data_len);
#else
        ctx_formatter_addstrf (formatter, "\"");
        ctx_print_escaped_string (formatter, pixel_data);
        ctx_formatter_addstrf (formatter, "\" ");

#endif

        _ctx_print_endcmd (formatter);
        }
        break;

      case CTX_REL_ARC_TO:
      case CTX_ARC_TO:
      case CTX_ROUND_RECTANGLE:
        ctx_print_entry (formatter, entry, 5);
        break;
      case CTX_CURVE_TO:
      case CTX_REL_CURVE_TO:
      case CTX_ARC:
      case CTX_RADIAL_GRADIENT:
      case CTX_APPLY_TRANSFORM:
      case CTX_SOURCE_TRANSFORM:
        ctx_print_entry (formatter, entry, 6);
        break;
      case CTX_QUAD_TO:
      case CTX_RECTANGLE:
      case CTX_REL_QUAD_TO:
      case CTX_LINEAR_GRADIENT:
      case CTX_VIEW_BOX:
      case CTX_SMOOTH_TO:
      case CTX_REL_SMOOTH_TO:
        ctx_print_entry (formatter, entry, 4);
        break;
      case CTX_FONT_SIZE:
      case CTX_MITER_LIMIT:
      case CTX_ROTATE:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
      case CTX_GLOBAL_ALPHA:
      case CTX_SHADOW_BLUR:
      case CTX_SHADOW_OFFSET_X:
      case CTX_SHADOW_OFFSET_Y:
      case CTX_VER_LINE_TO:
      case CTX_HOR_LINE_TO:
      case CTX_REL_VER_LINE_TO:
      case CTX_REL_HOR_LINE_TO:
        ctx_print_entry (formatter, entry, 1);
        break;
#if 0
      case CTX_SET:
        _ctx_print_name (formatter, entry->code);
        switch (c->set.key_hash)
        {
           case CTX_x: ctx_formatter_addstrf (formatter, " 'x' "); break;
           case CTX_y: ctx_formatter_addstrf (formatter, " 'y' "); break;
           case CTX_width: ctx_formatter_addstrf (formatter, " width "); break;
           case CTX_height: ctx_formatter_addstrf (formatter, " height "); break;
           default:
             ctx_formatter_addstrf (formatter, " %d ", c->set.key_hash);
        }
        ctx_formatter_addstrf (formatter, "\"");
        ctx_print_escaped_string (formatter, (char*)c->set.utf8);
        ctx_formatter_addstrf (formatter, "\"");
        _ctx_print_endcmd (formatter);
        break;
#endif
      case CTX_COLOR:
        if (formatter->longform ||  1)
          {
            _ctx_indent (formatter);
            int model = (int) c->set_color.model;
            const char *suffix="";
            if (model & 512)
            {
              model = model & 511;
              suffix = "S";
            }
            switch (model)
              {
                case CTX_GRAY:
                  ctx_formatter_addstrf (formatter, "gray%s ", suffix);
                  ctx_print_float (formatter, c->graya.g);
                  break;
                case CTX_GRAYA:
                  ctx_formatter_addstrf (formatter, "graya%s ", suffix);
                  ctx_print_float (formatter, c->graya.g);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->graya.a);
                  break;
                case CTX_RGBA:
                  if (c->rgba.a != 1.0)
                  {
                    ctx_formatter_addstrf (formatter, "rgba%s ", suffix);
                    ctx_print_float (formatter, c->rgba.r);
                    ctx_formatter_addstrf (formatter, " ");
                    ctx_print_float (formatter, c->rgba.g);
                    ctx_formatter_addstrf (formatter, " ");
                    ctx_print_float (formatter, c->rgba.b);
                    ctx_formatter_addstrf (formatter, " ");
                    ctx_print_float (formatter, c->rgba.a);
                    break;
                  }
                  /* FALLTHROUGH */
                case CTX_RGB:
                  if (c->rgba.r == c->rgba.g && c->rgba.g == c->rgba.b)
                  {
                    ctx_formatter_addstrf (formatter, "gray%s ", suffix);
                    ctx_print_float (formatter, c->rgba.r);
                    ctx_formatter_addstrf (formatter, " ");
                    break;
                  }
                  ctx_formatter_addstrf (formatter, "rgb%s ", suffix);
                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.b);
                  break;
                case CTX_DRGB:
                  ctx_formatter_addstrf (formatter, "drgb%s ", suffix);
                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.b);
                  break;
                case CTX_DRGBA:
                  ctx_formatter_addstrf (formatter, "drgba%s ", suffix);
                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.b);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->rgba.a);
                  break;
                case CTX_CMYK:
                  ctx_formatter_addstrf (formatter, "cmyk%s ", suffix);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.k);
                  break;
                case CTX_CMYKA:
                  ctx_formatter_addstrf (formatter, "cmyka%s ", suffix);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.k);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.a);
                  break;
                case CTX_DCMYK:
                  ctx_formatter_addstrf (formatter, "dcmyk%s ", suffix);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.k);
                  break;
                case CTX_DCMYKA:
                  ctx_formatter_addstrf (formatter, "dcmyka%s ", suffix);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.k);
                  ctx_formatter_addstrf (formatter, " ");
                  ctx_print_float (formatter, c->cmyka.a);
                  break;
              }
          }
        else
          {
            ctx_print_entry (formatter, entry, 1);
          }
        break;
      case CTX_SET_RGBA_U8:
        if (formatter->longform)
          {
            _ctx_indent (formatter);
            ctx_formatter_addstrf (formatter, "rgba (");
          }
        else
          {
            ctx_formatter_addstrf (formatter, "rgba (");
          }
        for (int c = 0; c < 4; c++)
          {
            if (c)
              {
                if (formatter->longform)
                  ctx_formatter_addstrf (formatter, ", ");
                else
                  ctx_formatter_addstrf (formatter, " ");
              }
            ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (c) ) );
          }
        _ctx_print_endcmd (formatter);
        break;
      case CTX_SET_PIXEL:
#if 0
        ctx_set_pixel_u8 (d_ctx,
                          ctx_arg_u16 (2), ctx_arg_u16 (3),
                          ctx_arg_u8 (0),
                          ctx_arg_u8 (1),
                          ctx_arg_u8 (2),
                          ctx_arg_u8 (3) );
#endif
        break;
      case CTX_FILL:
      case CTX_RESET:
      case CTX_STROKE:
      case CTX_IDENTITY:
      case CTX_CLIP:
      case CTX_BEGIN_PATH:
      case CTX_CLOSE_PATH:
      case CTX_SAVE:
      case CTX_PRESERVE:
      case CTX_START_GROUP:
      case CTX_NEW_PAGE:
      case CTX_END_GROUP:
      case CTX_RESTORE:
      case CTX_STROKE_SOURCE:
        ctx_print_entry (formatter, entry, 0);
        break;
      case CTX_TEXT_ALIGN:
      case CTX_TEXT_BASELINE:
      case CTX_TEXT_DIRECTION:
      case CTX_FILL_RULE:
      case CTX_LINE_CAP:
      case CTX_LINE_JOIN:
      case CTX_COMPOSITING_MODE:
      case CTX_BLEND_MODE:
      case CTX_IMAGE_SMOOTHING:
        ctx_print_entry_enum (formatter, entry, 1);
        break;
      case CTX_GRADIENT_STOP:
        _ctx_print_name (formatter, entry->code);
        for (int c = 0; c < 4; c++)
          {
            if (c)
              ctx_formatter_addstrf (formatter, " ");
            ctx_print_float (formatter, ctx_u8_to_float (ctx_arg_u8 (4+c) ) );
          }
        _ctx_print_endcmd (formatter);
        break;
      case CTX_TEXT:
      case CTX_STROKE_TEXT:
      case CTX_FONT:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstrf (formatter, "\"");
        ctx_print_escaped_string (formatter, ctx_arg_string() );
        ctx_formatter_addstrf (formatter, "\"");
        _ctx_print_endcmd (formatter);
        break;
      case CTX_CONT:
      case CTX_EDGE:
      case CTX_DATA:
      case CTX_DATA_REV:
      case CTX_FLUSH:
        break;
      case CTX_KERNING_PAIR:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstrf (formatter, "\"");
        {
           uint8_t utf8[16];
           utf8[ctx_unichar_to_utf8 (c->kern.glyph_before, utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstrf (formatter, "\", \"");
           utf8[ctx_unichar_to_utf8 (c->kern.glyph_after, utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstrf (formatter, "\"");
           sprintf ((char*)utf8, ", %f", c->kern.amount / 256.0);
           ctx_print_escaped_string (formatter, (char*)utf8);
        }
        _ctx_print_endcmd (formatter);
        break;

      case CTX_DEFINE_GLYPH:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstrf (formatter, "\"");
        {
           uint8_t utf8[16];
           utf8[ctx_unichar_to_utf8 (entry->data.u32[0], utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstrf (formatter, "\"");
           sprintf ((char*)utf8, ", %f", entry->data.u32[1]/256.0);
           ctx_print_escaped_string (formatter, (char*)utf8);
        }
        _ctx_print_endcmd (formatter);
        break;
    }
}

void
ctx_render_stream (Ctx *ctx, FILE *stream, int longform)
{
  CtxIterator iterator;
  CtxFormatter formatter;
  formatter.target= stream;
  formatter.longform = longform;
  formatter.indent = 0;
  formatter.add_str = _ctx_stream_addstr;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    { ctx_formatter_process (&formatter, command); }
  fprintf (stream, "\n");
}

char *
ctx_render_string (Ctx *ctx, int longform, int *retlen)
{
  CtxString *string = ctx_string_new ("");
  CtxIterator iterator;
  CtxFormatter formatter;
  formatter.target= string;
  formatter.longform = longform;
  formatter.indent = 0;
  formatter.add_str = _ctx_string_addstr;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    { ctx_formatter_process (&formatter, command); }
  char *ret = string->str;
  if (retlen)
    *retlen = string->length;
  ctx_string_free (string, 0);
  return ret;
}


#endif

#if CTX_EVENTS
int ctx_width (Ctx *ctx)
{
  return ctx->events.width;
}
int ctx_height (Ctx *ctx)
{
  return ctx->events.height;
}
#else
int ctx_width (Ctx *ctx)
{
  return 512;
}
int ctx_height (Ctx *ctx)
{
  return 384;
}
#endif

int ctx_rev (Ctx *ctx)
{
  return ctx->rev;
}

CtxState *ctx_get_state (Ctx *ctx)
{
  return &ctx->state;
}

void ctx_dirty_rect (Ctx *ctx, int *x, int *y, int *width, int *height)
{
  if ( (ctx->state.min_x > ctx->state.max_x) ||
       (ctx->state.min_y > ctx->state.max_y) )
    {
      if (x) { *x = 0; }
      if (y) { *y = 0; }
      if (width) { *width = 0; }
      if (height) { *height = 0; }
      return;
    }
  if (ctx->state.min_x < 0)
    { ctx->state.min_x = 0; }
  if (ctx->state.min_y < 0)
    { ctx->state.min_y = 0; }
  if (x) { *x = ctx->state.min_x; }
  if (y) { *y = ctx->state.min_y; }
  if (width) { *width = ctx->state.max_x - ctx->state.min_x; }
  if (height) { *height = ctx->state.max_y - ctx->state.min_y; }
}

#if CTX_CURRENT_PATH
CtxIterator *
ctx_current_path (Ctx *ctx)
{
  CtxIterator *iterator = &ctx->current_path_iterator;
  ctx_iterator_init (iterator, &ctx->current_path, 0, CTX_ITERATOR_EXPAND_BITPACK);
  return iterator;
}

void
ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2)
{
  float minx = 50000.0;
  float miny = 50000.0;
  float maxx = -50000.0;
  float maxy = -50000.0;
  float x = 0;
  float y = 0;

  CtxIterator *iterator = ctx_current_path (ctx);
  CtxCommand *command;

  while ((command = ctx_iterator_next (iterator)))
  {
     int got_coord = 0;
     switch (command->code)
     {
        // XXX missing many curve types
        case CTX_LINE_TO:
        case CTX_MOVE_TO:
          x = command->move_to.x;
          y = command->move_to.y;
          got_coord++;
          break;
        case CTX_REL_LINE_TO:
        case CTX_REL_MOVE_TO:
          x += command->move_to.x;
          y += command->move_to.y;
          got_coord++;
          break;
        case CTX_CURVE_TO:
          x = command->curve_to.x;
          y = command->curve_to.y;
          got_coord++;
          break;
        case CTX_REL_CURVE_TO:
          x += command->curve_to.x;
          y += command->curve_to.y;
          got_coord++;
          break;
        case CTX_ARC:
          minx = ctx_minf (minx, command->arc.x - command->arc.radius);
          miny = ctx_minf (miny, command->arc.y - command->arc.radius);
          maxx = ctx_maxf (maxx, command->arc.x + command->arc.radius);
          maxy = ctx_maxf (maxy, command->arc.y + command->arc.radius);

          break;
        case CTX_RECTANGLE:
        case CTX_ROUND_RECTANGLE:
          x = command->rectangle.x;
          y = command->rectangle.y;
          minx = ctx_minf (minx, x);
          miny = ctx_minf (miny, y);
          maxx = ctx_maxf (maxx, x);
          maxy = ctx_maxf (maxy, y);

          x += command->rectangle.width;
          y += command->rectangle.height;
          got_coord++;
          break;
     }
    if (got_coord)
    {
      minx = ctx_minf (minx, x);
      miny = ctx_minf (miny, y);
      maxx = ctx_maxf (maxx, x);
      maxy = ctx_maxf (maxy, y);
    }
  }

  if (ex1) *ex1 = minx;
  if (ey1) *ey1 = miny;
  if (ex2) *ex2 = maxx;
  if (ey2) *ey2 = maxy;
}

#else
void
ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2)
{
}
#endif


static inline void
ctx_gstate_push (CtxState *state)
{
  if (state->gstate_no + 1 >= CTX_MAX_STATES)
    { return; }
  state->gstate_stack[state->gstate_no] = state->gstate;
  state->gstate_no++;
  ctx_state_set (state, CTX_new_state, 0.0);
  state->has_clipped=0;
}

static inline void
ctx_gstate_pop (CtxState *state)
{
  if (state->gstate_no <= 0)
    { return; }
  state->gstate = state->gstate_stack[state->gstate_no-1];
  state->gstate_no--;
}

void
ctx_close_path (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_CLOSE_PATH);
}


void
ctx_get_image_data (Ctx *ctx, int sx, int sy, int sw, int sh,
                    CtxPixelFormat format, int dst_stride,
                    uint8_t *dst_data)
{
   if (0)
   {
   }
#if CTX_RASTERIZER
   else if (_ctx_is_rasterizer (ctx))
   {
     CtxRasterizer *rasterizer = (CtxRasterizer*)ctx->renderer;
     if (rasterizer->format->pixel_format == format)
     {
       if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw);
       int bytes_per_pix = rasterizer->format->bpp/8;
       int y = 0;
       for (int v = sy; v < sy + sh; v++, y++)
       {
         int x = 0;
         for (int u = sx; u < sx + sw; u++, x++)
         {
            uint8_t* src_buf = (uint8_t*)rasterizer->buf;
            memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * rasterizer->blit_stride + u * bytes_per_pix], bytes_per_pix);
         }
       }
       return;
     }
   }
#endif
#if CTX_FB
   else if (format == CTX_FORMAT_RGBA8 &&
                   (
                   ctx_renderer_is_fb (ctx)
#if CTX_SDL
                   || ctx_renderer_is_sdl (ctx)
#endif
                   ))
   {
     CtxTiled *tiled = (CtxTiled*)ctx->renderer;
     {
       if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw);
       int bytes_per_pix = 4;
       int y = 0;
       for (int v = sy; v < sy + sh; v++, y++)
       {
         int x = 0;
         for (int u = sx; u < sx + sw; u++, x++)
         {
            uint8_t* src_buf = (uint8_t*)tiled->pixels;
            memcpy (&dst_data[y * dst_stride + x * bytes_per_pix], &src_buf[v * tiled->width * bytes_per_pix + u * bytes_per_pix], bytes_per_pix);
         }
       }
       return;
     }
   }
#endif
}

void
ctx_put_image_data (Ctx *ctx, int w, int h, int stride, int format,
                    uint8_t *data,
                    int ox, int oy,
                    int dirtyX, int dirtyY,
                    int dirtyWidth, int dirtyHeight)
{
   char eid[65]="";
   ctx_save (ctx);
   ctx_identity (ctx);
   ctx_define_texture (ctx, NULL, w, h, stride, format, data, eid);
   if (eid[0])
   {
     // XXX set compositor to source
     ctx_compositing_mode (ctx, CTX_COMPOSITE_COPY);
     ctx_draw_texture_clipped (ctx, eid, ox, oy, w, h, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
   }
   ctx_restore (ctx);
}

/* checking if an eid is valid also sets the frame for it
 */
static int ctx_eid_valid (Ctx *ctx, const char *eid, int *w, int *h)
{
  ctx = ctx->texture_cache;
  CtxList *to_remove = NULL;
  int ret = 0;
  //fprintf (stderr, "{%i}\n", ctx->frame);
  for (CtxList *l = ctx->eid_db; l; l = l->next)
  {
    CtxEidInfo *eid_info = (CtxEidInfo*)l->data;
    if (ctx->frame - eid_info->frame >= 2)
            /* XXX XXX XXX this breaks texture caching since
             *   it is wrong in some cases where more frames
             *   have passed?
             */
    {
      ctx_list_prepend (&to_remove, eid_info);
    }
    else if (!strcmp (eid_info->eid, eid) &&
             ctx->frame - eid_info->frame < 2)
    {
    //fclose (f);
      eid_info->frame = ctx->frame;
      if (w) *w = eid_info->width;
      if (h) *h = eid_info->height;
      ret = 1;
    }
  }
  while (to_remove)
  {
    CtxEidInfo *eid_info = (CtxEidInfo*)to_remove->data;
    //FILE  *f  = fopen ("/tmp/l", "a");
    //fprintf (f, "%i client removing %s\n", getpid(), eid_info->eid);
    //fclose (f);
    free (eid_info->eid);
    free (eid_info);
    ctx_list_remove (&ctx->eid_db, eid_info);
    ctx_list_remove (&to_remove, eid_info);
  }
  return ret;
}

void ctx_texture (Ctx *ctx, const char *eid, float x, float y)
{
  int eid_len = strlen (eid);
  char ascii[41]="";
  //fprintf (stderr, "tx %s\n", eid);
  if (eid_len > 50)
  {
    CtxSHA1 *sha1 = ctx_sha1_new ();
    uint8_t hash[20]="";
    ctx_sha1_process (sha1, (uint8_t*)eid, eid_len);
    ctx_sha1_done (sha1, hash);
    ctx_sha1_free (sha1);
    const char *hex="0123456789abcdef";
    for (int i = 0; i < 20; i ++)
    {
       ascii[i*2]=hex[hash[i]/16];
       ascii[i*2+1]=hex[hash[i]%16];
    }
    ascii[40]=0;
    eid=ascii;
  }

    //FILE  *f = fopen ("/tmp/l", "a");
  if (ctx_eid_valid (ctx, eid, 0, 0))
  {
    ctx_process_cmd_str_float (ctx, CTX_TEXTURE, eid, x, y);
    //fprintf (stderr, "setting texture eid %s\n", eid);
  }
  else
  {
    //fprintf (stderr, "tried setting invalid texture eid %s\n", eid);
  }
    //fclose (f);
}
int
_ctx_frame (Ctx *ctx)
{
   return ctx->frame;
}
int
_ctx_set_frame (Ctx *ctx, int frame)
{
   return ctx->frame = frame;
}

void ctx_define_texture (Ctx *ctx,
                         const char *eid,
                         int width, int height, int stride, int format, void *data,
                         char *ret_eid)
{
  uint8_t hash[20]="";
  char ascii[41]="";
  int dst_stride = width;
  //fprintf (stderr, "df %s\n", eid);

  dst_stride = ctx_pixel_format_get_stride ((CtxPixelFormat)format, width);
  if (stride <= 0)
    stride = dst_stride;

  int data_len;
 
  if (format == CTX_FORMAT_YUV420)
  data_len = width * height + ((width/2) * (height/2)) * 2;
  else
  data_len = height * dst_stride;

  if (eid == NULL)
  {
    CtxSHA1 *sha1 = ctx_sha1_new ();
    uint8_t *src = (uint8_t*)data;
    for (int y = 0; y < height; y++)
    {
       ctx_sha1_process (sha1, src, dst_stride);
       src += stride;
    }
    ctx_sha1_done (sha1, hash);
    ctx_sha1_free (sha1);
    const char *hex="0123456789abcdef";
    for (int i = 0; i < 20; i ++)
    {
       ascii[i*2]  =hex[hash[i]/16];
       ascii[i*2+1]=hex[hash[i]%16];
    }
    ascii[40]=0;
    eid = ascii;
  }

  int eid_len = strlen (eid);

  if (eid_len > 50)
  {
    CtxSHA1 *sha1 = ctx_sha1_new ();
    uint8_t hash[20]="";
    ctx_sha1_process (sha1, (uint8_t*)eid, eid_len);
    ctx_sha1_done (sha1, hash);
    ctx_sha1_free (sha1);
    const char *hex="0123456789abcdef";
    for (int i = 0; i < 20; i ++)
    {
       ascii[i*2]  =hex[hash[i]/16];
       ascii[i*2+1]=hex[hash[i]%16];
    }
    ascii[40]=0;
    eid = ascii;
    eid_len = 40;
  }

  // we now have eid

  if (ctx_eid_valid (ctx, eid, 0, 0))
  {
    ctx_texture (ctx, eid, 0.0, 0.0);
  }
  else

  {
    CtxEntry *commands;
    int command_size = 1 + (data_len+1+1)/9 + 1 + (eid_len+1+1)/9 + 1 +   8;
    if (ctx->renderer && ctx->renderer->process)
    {
       commands = (CtxEntry*)calloc (sizeof (CtxEntry), command_size);
    }
    else
    {
       commands = NULL;
       ctx_drawlist_resize (&ctx->drawlist, ctx->drawlist.count + command_size);
       commands = &(ctx->drawlist.entries[ctx->drawlist.count]);
       memset (commands, 0, sizeof (CtxEntry) * command_size);
    }
    /* bottleneck,  we can avoid copying sometimes - and even when copying
     * we should cut this down to one copy, direct to the drawlist.
     *
     */
    commands[0] = ctx_u32 (CTX_DEFINE_TEXTURE, width, height);
    commands[1].data.u16[0] = format;

    int pos = 2;

    commands[pos].code        = CTX_DATA;
    commands[pos].data.u32[0] = eid_len;
    commands[pos].data.u32[1] = (eid_len+1+1)/9 + 1;
    memcpy ((char *) &commands[pos+1].data.u8[0], eid, eid_len);
    ((char *) &commands[pos+1].data.u8[0])[eid_len]=0;

    pos = 2 + 1 + ctx_conts_for_entry (&commands[2]);
    commands[pos].code        = CTX_DATA;
    commands[pos].data.u32[0] = data_len;
    commands[pos].data.u32[1] = (data_len+1+1)/9 + 1;
    {
      uint8_t *src = (uint8_t*)data;
      uint8_t *dst = &commands[pos+1].data.u8[0];
#if 1
      memcpy (dst, src, data_len);
#else
      for (int y = 0; y < height; y++)
      {
         memcpy (dst, src, dst_stride);
         src += stride;
         dst += dst_stride;
      }
#endif
    }
    ((char *) &commands[pos+1].data.u8[0])[data_len]=0;

    if (ctx->renderer && ctx->renderer->process)
    {
      ctx_process (ctx, commands);
      free (commands);
    }
    else
    {
       ctx->drawlist.count += ctx_conts_for_entry (commands) + 1;
    }

    CtxEidInfo *eid_info = (CtxEidInfo*)calloc (sizeof (CtxEidInfo), 1);
    eid_info->width      = width;
    eid_info->height     = height;
    eid_info->frame      = ctx->texture_cache->frame;
    //fprintf (stderr, "%i\n", eid_info->frame);
    eid_info->eid        = strdup (eid);
    ctx_list_prepend (&ctx->texture_cache->eid_db, eid_info);
  }

  if (ret_eid)
  {
    strcpy (ret_eid, eid);
    ret_eid[64]=0;
  }
}

void
ctx_texture_load (Ctx *ctx, const char *path, int *tw, int *th, char *reid)
{
  const char *eid = path;
  char ascii[41]="";
  int eid_len = strlen (eid);
  if (eid_len > 50)
  {
    CtxSHA1 *sha1 = ctx_sha1_new ();
    uint8_t hash[20]="";
    ctx_sha1_process (sha1, (uint8_t*)eid, eid_len);
    ctx_sha1_done (sha1, hash);
    ctx_sha1_free (sha1);
    const char *hex="0123456789abcdef";
    for (int i = 0; i < 20; i ++)
    {
       ascii[i*2]=hex[hash[i]/16];
       ascii[i*2+1]=hex[hash[i]%16];
    }
    ascii[40]=0;
    eid = ascii;
  }

  if (ctx_eid_valid (ctx, eid , tw, th))
  {
     if (reid)
     {
       strcpy (reid, eid);
     }
     return;
  }

#ifdef STBI_INCLUDE_STB_IMAGE_H
  CtxPixelFormat pixel_format = CTX_FORMAT_RGBA8;
  int w, h, components;
  unsigned char *pixels = NULL;

  if (!strncmp (path, "file://", 7))
  {
    pixels = stbi_load (path + 7, &w, &h, &components, 0);
  }
  else
  {
    unsigned char *data = NULL;
    long length = 0;
    ctx_get_contents (path, &data, &length);
    if (data)
    {
       pixels = stbi_load_from_memory (data, length, &w, &h, &components, 0);
       free (data);
    }
  }

  if (pixels)
  {
    switch (components)
    {
      case 1: pixel_format = CTX_FORMAT_GRAY8;  break;
      case 2: pixel_format = CTX_FORMAT_GRAYA8; break;
      case 3: pixel_format = CTX_FORMAT_RGB8;   break;
      case 4: pixel_format = CTX_FORMAT_RGBA8;  break;
    }
    if (tw) *tw = w;
    if (th) *th = h;
    ctx_define_texture (ctx, eid, w, h, w * components, pixel_format, pixels, reid);
    free (pixels);
  }
  else
  {
    fprintf (stderr, "texture loading problem for %s\n", path);
  }
#endif
}

void
ctx_draw_texture_clipped  (Ctx *ctx, const char *eid,
                           float x, float y,
                           float width, float height,
                           float clip_x, float clip_y,
                           float clip_width, float clip_height)
{
  int tex_width  = 0;
  int tex_height = 0;
  if (ctx_eid_valid (ctx, eid , &tex_width, &tex_height))
  {
    if (width > 0.0 && height > 0.0)
    {
#if 0
      if (clip_width > 0.0f)
      {
        ctx_rectangle (ctx, clip_x, clip_y, clip_width, clip_height);
        ctx_clip (ctx);
      }
#endif
      ctx_rectangle (ctx, x, y, width, height);
      CtxMatrix matrix;
      ctx_matrix_identity (&matrix);
      
      ctx_texture (ctx, eid, 0, 0);// / (width/tex_width), y / (height/tex_height));
      //ctx_rgba (ctx, 1, 0,0,0.5);
#if 1
      if (clip_width > 0.0f)
      {
              // XXX scale is not yet determined to be correct
              // in relation to the translate!
        ctx_matrix_scale (&matrix, clip_width/width, clip_height/height);
        ctx_matrix_translate (&matrix, -clip_x, -clip_y);
      }
      else
      {
        ctx_matrix_scale (&matrix, tex_width/width, tex_height/height);
      }
      ctx_matrix_translate (&matrix, x, y);
#endif
      //ctx_matrix_invert (&matrix);
      ctx_source_transform_matrix (ctx, &matrix);
      //ctx_texture (ctx, eid, x / (width/tex_width), y / (height/tex_height));
      ctx_fill (ctx);
    }
  }
}

void ctx_draw_texture (Ctx *ctx, const char *eid, float x, float y, float w, float h)
{
  ctx_draw_texture_clipped (ctx, eid, x, y, w, h, 0,0,0,0);
}

void ctx_draw_image_clipped (Ctx *ctx, const char *path, float x, float y, float w, float h, float sx, float sy, float swidth, float sheight)
{
  char reteid[65];
  int width, height;
  ctx_texture_load (ctx, path, &width, &height, reteid);
  if (reteid[0])
  {
    ctx_draw_texture_clipped (ctx, reteid, x, y, w, h, sx, sy, swidth, sheight);
  }
}

void
ctx_draw_image (Ctx *ctx, const char *path, float x, float y, float w, float h)
{
  ctx_draw_image_clipped (ctx, path, x, y, w, h, 0,0,0,0);
}

void
ctx_set_pixel_u8 (Ctx *ctx, uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
  CtxEntry command = ctx_u8 (CTX_SET_PIXEL, r, g, b, a, 0, 0, 0, 0);
  command.data.u16[2]=x;
  command.data.u16[3]=y;
  ctx_process (ctx, &command);
}

void
ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1)
{
  CtxEntry command[2]=
  {
    ctx_f (CTX_LINEAR_GRADIENT, x0, y0),
    ctx_f (CTX_CONT,            x1, y1)
  };
  ctx_process (ctx, command);
}

void
ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0, float x1, float y1, float r1)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_RADIAL_GRADIENT, x0, y0),
    ctx_f (CTX_CONT,            r0, x1),
    ctx_f (CTX_CONT,            y1, r1)
  };
  ctx_process (ctx, command);
}

void ctx_preserve (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_PRESERVE);
}

void ctx_fill (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_FILL);
}

void ctx_stroke (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_STROKE);
}


static void ctx_empty (Ctx *ctx)
{
#if CTX_RASTERIZER
  if (ctx->renderer == NULL)
#endif
    {
      ctx->drawlist.count = 0;
      ctx->drawlist.bitpack_pos = 0;
    }
}

void _ctx_set_store_clear (Ctx *ctx)
{
  ctx->transformation |= CTX_TRANSFORMATION_STORE_CLEAR;
}

#if CTX_EVENTS
static void
ctx_event_free (void *event, void *user_data)
{
  free (event);
}

static void
ctx_collect_events (CtxEvent *event, void *data, void *data2)
{
  Ctx *ctx = (Ctx*)data;
  CtxEvent *copy;
  if (event->type == CTX_KEY_PRESS && !strcmp (event->string, "idle"))
    return;
  copy = (CtxEvent*)malloc (sizeof (CtxEvent));
  *copy = *event;
  if (copy->string)
    copy->string = strdup (event->string);
  ctx_list_append_full (&ctx->events.events, copy, ctx_event_free, NULL);
}
#endif

#if CTX_EVENTS
static void _ctx_bindings_key_press (CtxEvent *event, void *data1, void *data2);
#endif

void ctx_reset (Ctx *ctx)
{
        /* we do the callback reset first - maybe we need two cbs,
         * one for before and one after default impl?
         *
         * tiled fb and sdl needs to sync
         */
  if (ctx->renderer && ctx->renderer->reset)
    ctx->renderer->reset (ctx->renderer);

  //CTX_PROCESS_VOID (CTX_RESET);
  //if (ctx->transformation & CTX_TRANSFORMATION_STORE_CLEAR)
  //  { return; }
  ctx_empty (ctx);
  ctx_state_init (&ctx->state);
#if CTX_EVENTS
  ctx_list_free (&ctx->events.items);
  ctx->events.last_item = NULL;

  if (ctx->events.ctx_get_event_enabled)
  {
    ctx_clear_bindings (ctx);
    ctx_listen_full (ctx, 0,0,0,0,
                     CTX_KEY_PRESS, _ctx_bindings_key_press, ctx, ctx,
                     NULL, NULL);

    ctx_listen_full (ctx, 0,0,0,0,
                     CTX_KEY_UP, ctx_collect_events, ctx, ctx,
                     NULL, NULL);
    ctx_listen_full (ctx, 0,0,0,0,
                     CTX_KEY_DOWN, ctx_collect_events, ctx, ctx,
                     NULL, NULL);

    ctx_listen_full (ctx, 0, 0, ctx->events.width, ctx->events.height,
                     (CtxEventType)(CTX_PRESS|CTX_RELEASE|CTX_MOTION),
                     ctx_collect_events, ctx, ctx,
                     NULL, NULL);
  }
#endif
}

void ctx_begin_path (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_BEGIN_PATH);
}

void ctx_clip (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_CLIP);
}

void
ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len);

void ctx_save (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_SAVE);
}
void ctx_restore (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_RESTORE);
}

void ctx_start_group (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_START_GROUP);
}

void ctx_end_group (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_END_GROUP);
}

void ctx_line_width (Ctx *ctx, float x)
{
  if (ctx->state.gstate.line_width != x)
    CTX_PROCESS_F1 (CTX_LINE_WIDTH, x);
}

float ctx_get_miter_limit (Ctx *ctx)
{
  return ctx->state.gstate.miter_limit;
}

float ctx_get_line_dash_offset (Ctx *ctx)
{
  return ctx->state.gstate.line_dash_offset;
}

void ctx_line_dash_offset (Ctx *ctx, float x)
{
  if (ctx->state.gstate.line_dash_offset != x)
    CTX_PROCESS_F1 (CTX_LINE_DASH_OFFSET, x);
}

int ctx_get_image_smoothing (Ctx *ctx)
{
  return ctx->state.gstate.image_smoothing;
}

void ctx_image_smoothing (Ctx *ctx, int enabled)
{
  if (ctx_get_image_smoothing (ctx) != enabled)
    CTX_PROCESS_U8 (CTX_IMAGE_SMOOTHING, enabled);
}


void ctx_line_dash (Ctx *ctx, float *dashes, int count)
{
  ctx_process_cmd_str_with_len (ctx, CTX_LINE_DASH, (char*)(dashes), count, 0, count * 4);
}

void ctx_shadow_blur (Ctx *ctx, float x)
{
#if CTX_ENABLE_SHADOW_BLUR
  if (ctx->state.gstate.shadow_blur != x)
#endif
    CTX_PROCESS_F1 (CTX_SHADOW_BLUR, x);
}

void ctx_shadow_rgba (Ctx *ctx, float r, float g, float b, float a)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_SHADOW_COLOR, CTX_RGBA, r),
    ctx_f (CTX_CONT, g, b),
    ctx_f (CTX_CONT, a, 0)
  };
  ctx_process (ctx, command);
}

void ctx_shadow_offset_x (Ctx *ctx, float x)
{
#if CTX_ENABLE_SHADOW_BLUR
  if (ctx->state.gstate.shadow_offset_x != x)
#endif
    CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_X, x);
}

void ctx_shadow_offset_y (Ctx *ctx, float x)
{
#if CTX_ENABLE_SHADOW_BLUR
  if (ctx->state.gstate.shadow_offset_y != x)
#endif
    CTX_PROCESS_F1 (CTX_SHADOW_OFFSET_Y, x);
}

void
ctx_global_alpha (Ctx *ctx, float global_alpha)
{
  if (ctx->state.gstate.global_alpha_f != global_alpha)
    CTX_PROCESS_F1 (CTX_GLOBAL_ALPHA, global_alpha);
}

float
ctx_get_global_alpha (Ctx *ctx)
{
  return ctx->state.gstate.global_alpha_f;
}

void
ctx_font_size (Ctx *ctx, float x)
{
  CTX_PROCESS_F1 (CTX_FONT_SIZE, x);
}

float ctx_get_font_size  (Ctx *ctx)
{
  return ctx->state.gstate.font_size;
}

void
ctx_miter_limit (Ctx *ctx, float limit)
{
  CTX_PROCESS_F1 (CTX_MITER_LIMIT, limit);
}

float ctx_get_line_width (Ctx *ctx)
{
  return ctx->state.gstate.line_width;
}

void
_ctx_font (Ctx *ctx, const char *name)
{
  ctx->state.gstate.font = ctx_resolve_font (name);
}

#if 0
void
ctx_set (Ctx *ctx, uint64_t key_hash, const char *string, int len)
{
  if (len <= 0) len = strlen (string);
  ctx_process_cmd_str (ctx, CTX_SET, string, key_hash, len);
}

const char *
ctx_get (Ctx *ctx, const char *key)
{
  static char retbuf[32];
  int len = 0;
  CTX_PROCESS_U32(CTX_GET, ctx_strhash (key), 0);
  while (read (STDIN_FILENO, &retbuf[len], 1) != -1)
    {
      if(retbuf[len]=='\n')
        break;
      retbuf[++len]=0;
    }
  return retbuf;
}
#endif

void
ctx_font_family (Ctx *ctx, const char *name)
{
#if CTX_BACKEND_TEXT
  ctx_process_cmd_str (ctx, CTX_FONT, name, 0, 0);
  _ctx_font (ctx, name);
#else
  _ctx_font (ctx, name);
#endif
}

void
ctx_font (Ctx *ctx, const char *family_name)
{
  // should also parse size
  ctx_font_family (ctx, family_name);
}

const char *
ctx_get_font (Ctx *ctx)
{
  return ctx_fonts[ctx->state.gstate.font].name;
}

void ctx_line_to (Ctx *ctx, float x, float y)
{
  if (CTX_UNLIKELY(!ctx->state.has_moved))
    { CTX_PROCESS_F (CTX_MOVE_TO, x, y); }
  else
    { CTX_PROCESS_F (CTX_LINE_TO, x, y); }
}

void ctx_move_to (Ctx *ctx, float x, float y)
{
  CTX_PROCESS_F (CTX_MOVE_TO,x,y);
}

void ctx_curve_to (Ctx *ctx, float x0, float y0,
                   float x1, float y1,
                   float x2, float y2)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_CURVE_TO, x0, y0),
    ctx_f (CTX_CONT,     x1, y1),
    ctx_f (CTX_CONT,     x2, y2)
  };
  ctx_process (ctx, command);
}

void ctx_round_rectangle (Ctx *ctx,
                          float x0, float y0,
                          float w, float h,
                          float radius)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_ROUND_RECTANGLE, x0, y0),
    ctx_f (CTX_CONT,            w, h),
    ctx_f (CTX_CONT,            radius, 0)
  };
  ctx_process (ctx, command);
}

void ctx_view_box (Ctx *ctx,
                   float x0, float y0,
                   float w, float h)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_VIEW_BOX, x0, y0),
    ctx_f (CTX_CONT,     w, h)
  };
  ctx_process (ctx, command);
}

void ctx_rectangle (Ctx *ctx,
                    float x0, float y0,
                    float w, float h)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_RECTANGLE, x0, y0),
    ctx_f (CTX_CONT,      w, h)
  };
  ctx_process (ctx, command);
}

void ctx_rel_line_to (Ctx *ctx, float x, float y)
{
  if (!ctx->state.has_moved)
    { return; }
  CTX_PROCESS_F (CTX_REL_LINE_TO,x,y);
}

void ctx_rel_move_to (Ctx *ctx, float x, float y)
{
  if (!ctx->state.has_moved)
    {
      CTX_PROCESS_F (CTX_MOVE_TO,x,y);
      return;
    }
  CTX_PROCESS_F (CTX_REL_MOVE_TO,x,y);
}

CtxLineJoin ctx_get_line_join (Ctx *ctx)
{
  return ctx->state.gstate.line_join;
}

CtxCompositingMode ctx_get_compositing_mode (Ctx *ctx)
{
  return ctx->state.gstate.compositing_mode;
}

CtxBlend ctx_get_blend_mode (Ctx *ctx)
{
  return ctx->state.gstate.blend_mode;
}

CtxTextAlign ctx_get_text_align  (Ctx *ctx)
{
  return (CtxTextAlign)ctx_state_get (&ctx->state, CTX_text_align);
}

CtxTextBaseline ctx_get_text_baseline (Ctx *ctx)
{
  return (CtxTextBaseline)ctx_state_get (&ctx->state, CTX_text_baseline);
}

CtxLineCap ctx_get_line_cap (Ctx *ctx)
{
  return ctx->state.gstate.line_cap;
}

CtxFillRule ctx_get_fill_rule (Ctx *ctx)
{
  return ctx->state.gstate.fill_rule;
}

void ctx_line_cap (Ctx *ctx, CtxLineCap cap)
{
  if (ctx->state.gstate.line_cap != cap)
    CTX_PROCESS_U8 (CTX_LINE_CAP, cap);
}

void ctx_fill_rule (Ctx *ctx, CtxFillRule fill_rule)
{
  if (ctx->state.gstate.fill_rule != fill_rule)
    CTX_PROCESS_U8 (CTX_FILL_RULE, fill_rule);
}
void ctx_line_join (Ctx *ctx, CtxLineJoin join)
{
  if (ctx->state.gstate.line_join != join)
    CTX_PROCESS_U8 (CTX_LINE_JOIN, join);
}
void ctx_blend_mode (Ctx *ctx, CtxBlend mode)
{
  if (ctx->state.gstate.blend_mode != mode)
    CTX_PROCESS_U32 (CTX_BLEND_MODE, mode, 0);
}
void ctx_compositing_mode (Ctx *ctx, CtxCompositingMode mode)
{
  if (ctx->state.gstate.compositing_mode != mode)
    CTX_PROCESS_U32 (CTX_COMPOSITING_MODE, mode, 0);
}
void ctx_text_align (Ctx *ctx, CtxTextAlign text_align)
{
  CTX_PROCESS_U8 (CTX_TEXT_ALIGN, text_align);
}
void ctx_text_baseline (Ctx *ctx, CtxTextBaseline text_baseline)
{
  CTX_PROCESS_U8 (CTX_TEXT_BASELINE, text_baseline);
}
void ctx_text_direction (Ctx *ctx, CtxTextDirection text_direction)
{
  CTX_PROCESS_U8 (CTX_TEXT_DIRECTION, text_direction);
}

void
ctx_rel_curve_to (Ctx *ctx,
                  float x0, float y0,
                  float x1, float y1,
                  float x2, float y2)
{
  if (!ctx->state.has_moved)
    { return; }
  CtxEntry command[3]=
  {
    ctx_f (CTX_REL_CURVE_TO, x0, y0),
    ctx_f (CTX_CONT, x1, y1),
    ctx_f (CTX_CONT, x2, y2)
  };
  ctx_process (ctx, command);
}

void
ctx_rel_quad_to (Ctx *ctx,
                 float cx, float cy,
                 float x,  float y)
{
  if (!ctx->state.has_moved)
    { return; }
  CtxEntry command[2]=
  {
    ctx_f (CTX_REL_QUAD_TO, cx, cy),
    ctx_f (CTX_CONT, x, y)
  };
  ctx_process (ctx, command);
}

void
ctx_quad_to (Ctx *ctx,
             float cx, float cy,
             float x,  float y)
{
  if (!ctx->state.has_moved)
    { return; }
  CtxEntry command[2]=
  {
    ctx_f (CTX_QUAD_TO, cx, cy),
    ctx_f (CTX_CONT, x, y)
  };
  ctx_process (ctx, command);
}

void ctx_arc (Ctx  *ctx,
              float x0, float y0,
              float radius,
              float angle1, float angle2,
              int   direction)
{
  CtxEntry command[3]=
  {
    ctx_f (CTX_ARC, x0, y0),
    ctx_f (CTX_CONT, radius, angle1),
    ctx_f (CTX_CONT, angle2, direction)
  };
  ctx_process (ctx, command);
}

static int ctx_coords_equal (float x1, float y1, float x2, float y2, float tol)
{
  float dx = x2 - x1;
  float dy = y2 - y1;
  return dx*dx + dy*dy < tol*tol;
}

static float
ctx_point_seg_dist_sq (float x, float y,
                       float vx, float vy, float wx, float wy)
{
  float l2 = ctx_pow2 (vx-wx) + ctx_pow2 (vy-wy);
  if (l2 < 0.0001)
    { return ctx_pow2 (x-vx) + ctx_pow2 (y-vy); }
  float t = ( (x - vx) * (wx - vx) + (y - vy) * (wy - vy) ) / l2;
  t = ctx_maxf (0, ctx_minf (1, t) );
  float ix = vx + t * (wx - vx);
  float iy = vy + t * (wy - vy);
  return ctx_pow2 (x-ix) + ctx_pow2 (y-iy);
}

static void
ctx_normalize (float *x, float *y)
{
  float length = ctx_hypotf ( (*x), (*y) );
  if (length > 1e-6f)
    {
      float r = 1.0f / length;
      *x *= r;
      *y *= r;
    }
}

void
ctx_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius)
{
  // XXX : should partially move into rasterizer to preserve comand
  //       even if an arc preserves all geometry, just to ensure roundtripping
  //       of data
  /* from nanovg - but not quite working ; uncertain if arc or wrong
   * transfusion is the cause.
   */
  float x0 = ctx->state.x;
  float y0 = ctx->state.y;
  float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1;
  int dir;
  if (!ctx->state.has_moved)
    { return; }
  if (1)
    {
      // Handle degenerate cases.
      if (ctx_coords_equal (x0,y0, x1,y1, 0.5f) ||
          ctx_coords_equal (x1,y1, x2,y2, 0.5f) ||
          ctx_point_seg_dist_sq (x1,y1, x0,y0, x2,y2) < 0.5 ||
          radius < 0.5)
        {
          ctx_line_to (ctx, x1,y1);
          return;
        }
    }
  // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2).
  dx0 = x0-x1;
  dy0 = y0-y1;
  dx1 = x2-x1;
  dy1 = y2-y1;
  ctx_normalize (&dx0,&dy0);
  ctx_normalize (&dx1,&dy1);
  a = ctx_acosf (dx0*dx1 + dy0*dy1);
  d = radius / ctx_tanf (a/2.0f);
#if 0
  if (d > 10000.0f)
    {
      ctx_line_to (ctx, x1, y1);
      return;
    }
#endif
  if ( (dx1*dy0 - dx0*dy1) > 0.0f)
    {
      cx = x1 + dx0*d + dy0*radius;
      cy = y1 + dy0*d + -dx0*radius;
      a0 = ctx_atan2f (dx0, -dy0);
      a1 = ctx_atan2f (-dx1, dy1);
      dir = 0;
    }
  else
    {
      cx = x1 + dx0*d + -dy0*radius;
      cy = y1 + dy0*d + dx0*radius;
      a0 = ctx_atan2f (-dx0, dy0);
      a1 = ctx_atan2f (dx1, -dy1);
      dir = 1;
    }
  ctx_arc (ctx, cx, cy, radius, a0, a1, dir);
}

void
ctx_rel_arc_to (Ctx *ctx, float x1, float y1, float x2, float y2, float radius)
{
  x1 += ctx->state.x;
  y1 += ctx->state.y;
  x2 += ctx->state.x;
  y2 += ctx->state.y;
  ctx_arc_to (ctx, x1, y1, x2, y2, radius);
}

void
ctx_exit (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_EXIT);
}

void
ctx_flush (Ctx *ctx)
{
  /* XXX: should be fully moved into the renderers
   *      to permit different behavior and get rid
   *      of the extranous flush() vfunc.
   */
  ctx->rev++;
//  CTX_PROCESS_VOID (CTX_FLUSH);
#if 0
  //printf (" \e[?2222h");
  ctx_drawlist_compact (&ctx->drawlist);
  for (int i = 0; i < ctx->drawlist.count - 1; i++)
    {
      CtxEntry *entry = &ctx->drawlist.entries[i];
      fwrite (entry, 9, 1, stdout);
#if 0
      uint8_t  *buf = (void *) entry;
      for (int j = 0; j < 9; j++)
        { printf ("%c", buf[j]); }
#endif
    }
  printf ("Xx.Xx.Xx.");
  fflush (NULL);
#endif
  if (ctx->renderer && ctx->renderer->flush)
    ctx->renderer->flush (ctx->renderer);
  ctx->frame++;
  if (ctx->texture_cache != ctx)
    ctx->texture_cache->frame++;
  ctx->drawlist.count = 0;
  ctx_state_init (&ctx->state);
}

////////////////////////////////////////

static inline void
ctx_interpret_style (CtxState *state, CtxEntry *entry, void *data)
{
  CtxCommand *c = (CtxCommand *) entry;
  switch (entry->code)
    {
      case CTX_LINE_DASH_OFFSET:
        state->gstate.line_dash_offset = ctx_arg_float (0);
        break;
      case CTX_LINE_WIDTH:
        state->gstate.line_width = ctx_arg_float (0);
        break;
#if CTX_ENABLE_SHADOW_BLUR
      case CTX_SHADOW_BLUR:
        state->gstate.shadow_blur = ctx_arg_float (0);
        break;
      case CTX_SHADOW_OFFSET_X:
        state->gstate.shadow_offset_x = ctx_arg_float (0);
        break;
      case CTX_SHADOW_OFFSET_Y:
        state->gstate.shadow_offset_y = ctx_arg_float (0);
        break;
#endif
      case CTX_LINE_CAP:
        state->gstate.line_cap = (CtxLineCap) ctx_arg_u8 (0);
        break;
      case CTX_FILL_RULE:
        state->gstate.fill_rule = (CtxFillRule) ctx_arg_u8 (0);
        break;
      case CTX_LINE_JOIN:
        state->gstate.line_join = (CtxLineJoin) ctx_arg_u8 (0);
        break;
      case CTX_COMPOSITING_MODE:
        state->gstate.compositing_mode = (CtxCompositingMode) ctx_arg_u32 (0);
        break;
      case CTX_BLEND_MODE:
        state->gstate.blend_mode = (CtxBlend) ctx_arg_u32 (0);
        break;
      case CTX_TEXT_ALIGN:
        ctx_state_set (state, CTX_text_align, ctx_arg_u8 (0) );
        break;
      case CTX_TEXT_BASELINE:
        ctx_state_set (state, CTX_text_baseline, ctx_arg_u8 (0) );
        break;
      case CTX_TEXT_DIRECTION:
        ctx_state_set (state, CTX_text_direction, ctx_arg_u8 (0) );
        break;
      case CTX_GLOBAL_ALPHA:
        state->gstate.global_alpha_u8 = ctx_float_to_u8 (ctx_arg_float (0) );
        state->gstate.global_alpha_f = ctx_arg_float (0);
        break;
      case CTX_FONT_SIZE:
        state->gstate.font_size = ctx_arg_float (0);
        break;
      case CTX_MITER_LIMIT:
        state->gstate.miter_limit = ctx_arg_float (0);
        break;
      case CTX_COLOR_SPACE:
        /* move this out of this function and only do it in rasterizer? XXX */
        ctx_rasterizer_colorspace_icc (state, (CtxColorSpace)c->colorspace.space_slot,
                                              (char*)c->colorspace.data,
                                              c->colorspace.data_len);
        break;
      case CTX_IMAGE_SMOOTHING:
        state->gstate.image_smoothing = c->entry.data.u8[0];
        break;
      case CTX_STROKE_SOURCE:
        state->source = 1;
        break;

      case CTX_COLOR:
        {
          int is_stroke = (state->source != 0);
          CtxSource *source = is_stroke ?
                                &state->gstate.source_stroke:
                                &state->gstate.source_fill;
          state->source = 0;

          source->type = CTX_SOURCE_COLOR;
         
          //float components[5]={c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a};
          switch ( ((int) ctx_arg_float (0)) & 511) // XXX remove 511 after stroke source is complete
            {
              case CTX_RGB:
                ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f);
                break;
              case CTX_RGBA:
                ctx_color_set_rgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
                break;
              case CTX_DRGBA:
                ctx_color_set_drgba (state, &source->color, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
                break;
#if CTX_ENABLE_CMYK
              case CTX_CMYKA:
                ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a);
                break;
              case CTX_CMYK:
                ctx_color_set_cmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f);
                break;
              case CTX_DCMYKA:
                ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, c->cmyka.a);
                break;
              case CTX_DCMYK:
                ctx_color_set_dcmyka (state, &source->color, c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k, 1.0f);
                break;
#endif
              case CTX_GRAYA:
                ctx_color_set_graya (state, &source->color, c->graya.g, c->graya.a);
                break;
              case CTX_GRAY:
                ctx_color_set_graya (state, &source->color, c->graya.g, 1.0f);
                break;
            }
        }
        break;
      case CTX_SET_RGBA_U8:
        //ctx_source_deinit (&state->gstate.source);
        //state->gstate.source_fill.type = CTX_SOURCE_COLOR;
        {
          int is_stroke = (state->source != 0);
          CtxSource *source = is_stroke ?
                                &state->gstate.source_stroke:
                                &state->gstate.source_fill;
          state->source = 0;

          source->type = CTX_SOURCE_COLOR;

          ctx_color_set_RGBA8 (state, &source->color,
                               ctx_arg_u8 (0),
                               ctx_arg_u8 (1),
                               ctx_arg_u8 (2),
                               ctx_arg_u8 (3) );
        }
        //for (int i = 0; i < 4; i ++)
        //  state->gstate.source.color.rgba[i] = ctx_arg_u8(i);
        break;
      //case CTX_TEXTURE:
      //  state->gstate.source.type = CTX_SOURCE_
      //  break;
      case CTX_LINEAR_GRADIENT:
        {
          int is_stroke = (state->source != 0);
          CtxSource *source = is_stroke ?
                                &state->gstate.source_stroke:
                                &state->gstate.source_fill;
          state->source = is_stroke ? 2 : 0;

          float x0 = ctx_arg_float (0);
          float y0 = ctx_arg_float (1);
          float x1 = ctx_arg_float (2);
          float y1 = ctx_arg_float (3);
          float dx, dy, length, start, end;

          length = ctx_hypotf (x1-x0,y1-y0);
          dx = (x1-x0) / length;
          dy = (y1-y0) / length;
          start = (x0 * dx + y0 * dy) / length;
          end =   (x1 * dx + y1 * dy) / length;
          source->linear_gradient.length = length;
          source->linear_gradient.dx = dx;
          source->linear_gradient.dy = dy;
          source->linear_gradient.start = start;
          source->linear_gradient.end = end;
          source->linear_gradient.rdelta = (end-start)!=0.0?1.0f/(end - start):1.0;
          source->type = CTX_SOURCE_LINEAR_GRADIENT;
          source->transform = state->gstate.transform;
          ctx_matrix_invert (&source->transform);
        }
        break;
      case CTX_RADIAL_GRADIENT:
        {
          int is_stroke = (state->source != 0);
          CtxSource *source = is_stroke ?
                                &state->gstate.source_stroke:
                                &state->gstate.source_fill;
          state->source = is_stroke ? 2 : 0;

          float x0 = ctx_arg_float (0);
          float y0 = ctx_arg_float (1);
          float r0 = ctx_arg_float (2);
          float x1 = ctx_arg_float (3);
          float y1 = ctx_arg_float (4);
          float r1 = ctx_arg_float (5);
          source->radial_gradient.x0 = x0;
          source->radial_gradient.y0 = y0;
          source->radial_gradient.r0 = r0;
          source->radial_gradient.x1 = x1;
          source->radial_gradient.y1 = y1;
          source->radial_gradient.r1 = r1;
          source->radial_gradient.rdelta = (r1 - r0) != 0.0 ? 1.0f/(r1-r0):0.0;
          source->type      = CTX_SOURCE_RADIAL_GRADIENT;
          source->transform = state->gstate.transform;
          ctx_matrix_invert (&source->transform);
        }
        break;
    }
}

static inline void
ctx_interpret_transforms (CtxState *state, CtxEntry *entry, void *data)
{
  switch (entry->code)
    {
      case CTX_SAVE:
        ctx_gstate_push (state);
        break;
      case CTX_RESTORE:
        ctx_gstate_pop (state);
        break;
      case CTX_IDENTITY:
        _ctx_matrix_identity (&state->gstate.transform);
        break;
      case CTX_TRANSLATE:
        ctx_matrix_translate (&state->gstate.transform,
                              ctx_arg_float (0), ctx_arg_float (1) );
        break;
      case CTX_SCALE:
        ctx_matrix_scale (&state->gstate.transform,
                          ctx_arg_float (0), ctx_arg_float (1) );
        break;
      case CTX_ROTATE:
        ctx_matrix_rotate (&state->gstate.transform, ctx_arg_float (0) );
        break;
      case CTX_APPLY_TRANSFORM:
        {
          CtxMatrix m;
          ctx_matrix_set (&m,
                          ctx_arg_float (0), ctx_arg_float (1),
                          ctx_arg_float (2), ctx_arg_float (3),
                          ctx_arg_float (4), ctx_arg_float (5) );
          _ctx_matrix_multiply (&state->gstate.transform,
                                &state->gstate.transform, &m); // XXX verify order
        }
#if 0
        ctx_matrix_set (&state->gstate.transform,
                        ctx_arg_float (0), ctx_arg_float (1),
                        ctx_arg_float (2), ctx_arg_float (3),
                        ctx_arg_float (4), ctx_arg_float (5) );
#endif
        break;
    }
}

/*
 * this transforms the contents of entry according to ctx->transformation
 */
static inline void
ctx_interpret_pos_transform (CtxState *state, CtxEntry *entry, void *data)
{
  CtxCommand *c = (CtxCommand *) entry;
  float start_x = state->x;
  float start_y = state->y;
  int had_moved = state->has_moved;
  switch (entry->code)
    {
      case CTX_MOVE_TO:
      case CTX_LINE_TO:
        {
          float x = c->c.x0;
          float y = c->c.y0;
          if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
            {
              _ctx_user_to_device (state, &x, &y);
              ctx_arg_float (0) = x;
              ctx_arg_float (1) = y;
            }
        }
        break;
      case CTX_ARC:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
          {
            float temp;
            _ctx_user_to_device (state, &c->arc.x, &c->arc.y);
            temp = 0;
            _ctx_user_to_device_distance (state, &c->arc.radius, &temp);
          }
        break;
      case CTX_LINEAR_GRADIENT:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
        {
        _ctx_user_to_device (state, &c->linear_gradient.x1, &c->linear_gradient.y1);
        _ctx_user_to_device (state, &c->linear_gradient.x2, &c->linear_gradient.y2);
        }
        break;
      case CTX_RADIAL_GRADIENT:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
        {
          float temp;
          _ctx_user_to_device (state, &c->radial_gradient.x1, &c->radial_gradient.y1);
          temp = 0;
          _ctx_user_to_device_distance (state, &c->radial_gradient.r1, &temp);
          _ctx_user_to_device (state, &c->radial_gradient.x2, &c->radial_gradient.y2);
          temp = 0;
          _ctx_user_to_device_distance (state, &c->radial_gradient.r2, &temp);
        }
        break;
      case CTX_CURVE_TO:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
          {
            for (int c = 0; c < 3; c ++)
              {
                float x = entry[c].data.f[0];
                float y = entry[c].data.f[1];
                _ctx_user_to_device (state, &x, &y);
                entry[c].data.f[0] = x;
                entry[c].data.f[1] = y;
              }
          }
        break;
      case CTX_QUAD_TO:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
          {
            for (int c = 0; c < 2; c ++)
              {
                float x = entry[c].data.f[0];
                float y = entry[c].data.f[1];
                _ctx_user_to_device (state, &x, &y);
                entry[c].data.f[0] = x;
                entry[c].data.f[1] = y;
              }
          }
        break;
      case CTX_REL_MOVE_TO:
      case CTX_REL_LINE_TO:
        if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
          {
            for (int c = 0; c < 1; c ++)
              {
                float x = state->x;
                float y = state->y;
                _ctx_user_to_device (state, &x, &y);
                entry[c].data.f[0] = x;
                entry[c].data.f[1] = y;
              }
            if (entry->code == CTX_REL_MOVE_TO)
              { entry->code = CTX_MOVE_TO; }
            else
              { entry->code = CTX_LINE_TO; }
          }
        break;
      case CTX_REL_CURVE_TO:
        {
          float nx = state->x + ctx_arg_float (4);
          float ny = state->y + ctx_arg_float (5);
          if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
            {
              for (int c = 0; c < 3; c ++)
                {
                  float x = nx + entry[c].data.f[0];
                  float y = ny + entry[c].data.f[1];
                  _ctx_user_to_device (state, &x, &y);
                  entry[c].data.f[0] = x;
                  entry[c].data.f[1] = y;
                }
              entry->code = CTX_CURVE_TO;
            }
        }
        break;
      case CTX_REL_QUAD_TO:
        {
          float nx = state->x + ctx_arg_float (2);
          float ny = state->y + ctx_arg_float (3);
          if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) )
            {
              for (int c = 0; c < 2; c ++)
                {
                  float x = nx + entry[c].data.f[0];
                  float y = ny + entry[c].data.f[1];
                  _ctx_user_to_device (state, &x, &y);
                  entry[c].data.f[0] = x;
                  entry[c].data.f[1] = y;
                }
              entry->code = CTX_QUAD_TO;
            }
        }
        break;
    }
  if ((((Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE))
    {
      int components = 0;
      _ctx_user_to_device (state, &start_x, &start_y);
      switch (entry->code)
        {
          case CTX_MOVE_TO:
            if (had_moved) { components = 1; }
            break;
          case CTX_LINE_TO:
            components = 1;
            break;
          case CTX_CURVE_TO:
            components = 3;
            break;
          case CTX_QUAD_TO:
            components = 2;
            break;
        }
      if (components)
        {
          for (int c = 0; c < components; c++)
            {
              entry[c].data.f[0] -= start_x;
              entry[c].data.f[1] -= start_y;
            }
          switch (entry->code)
            {
              case CTX_MOVE_TO:
                entry[0].code = CTX_REL_MOVE_TO;
                break;
              case CTX_LINE_TO:
                entry[0].code = CTX_REL_LINE_TO;
                break;
                break;
              case CTX_CURVE_TO:
                entry[0].code = CTX_REL_CURVE_TO;
                break;
              case CTX_QUAD_TO:
                entry[0].code = CTX_REL_QUAD_TO;
                break;
            }
        }
    }
}

static inline void
ctx_interpret_pos_bare (CtxState *state, CtxEntry *entry, void *data)
{
  switch (entry->code)
    {
      case CTX_RESET:
        ctx_state_init (state);
        break;
      case CTX_CLIP:
      case CTX_BEGIN_PATH:
      case CTX_FILL:
      case CTX_STROKE:
        state->has_moved = 0;
        break;
      case CTX_MOVE_TO:
      case CTX_LINE_TO:
        state->x = ctx_arg_float (0);
        state->y = ctx_arg_float (1);
        state->has_moved = 1;
        break;
      case CTX_CURVE_TO:
        state->x = ctx_arg_float (4);
        state->y = ctx_arg_float (5);
        state->has_moved = 1;
        break;
      case CTX_QUAD_TO:
        state->x = ctx_arg_float (2);
        state->y = ctx_arg_float (3);
        state->has_moved = 1;
        break;
      case CTX_ARC:
        state->x = ctx_arg_float (0) + ctx_cosf (ctx_arg_float (4) ) * ctx_arg_float (2);
        state->y = ctx_arg_float (1) + ctx_sinf (ctx_arg_float (4) ) * ctx_arg_float (2);
        break;
      case CTX_REL_MOVE_TO:
      case CTX_REL_LINE_TO:
        state->x += ctx_arg_float (0);
        state->y += ctx_arg_float (1);
        break;
      case CTX_REL_CURVE_TO:
        state->x += ctx_arg_float (4);
        state->y += ctx_arg_float (5);
        break;
      case CTX_REL_QUAD_TO:
        state->x += ctx_arg_float (2);
        state->y += ctx_arg_float (3);
        break;
        // XXX missing some smooths
    }
}

static inline void
ctx_interpret_pos (CtxState *state, CtxEntry *entry, void *data)
{
  if ( ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_SCREEN_SPACE) ||
       ( ( (Ctx *) (data) )->transformation & CTX_TRANSFORMATION_RELATIVE) )
    {
      ctx_interpret_pos_transform (state, entry, data);
    }
  ctx_interpret_pos_bare (state, entry, data);
}

#if CTX_BABL
void ctx_colorspace_babl (CtxState   *state,
                          CtxColorSpace  icc_slot,
                          const Babl *space);
#endif

static void
ctx_state_init (CtxState *state)
{
  ctx_memset (state, 0, sizeof (CtxState) );
  state->gstate.global_alpha_u8 = 255;
  state->gstate.global_alpha_f  = 1.0;
  state->gstate.font_size       = 23; // default HTML canvas is 10px sans
  state->gstate.line_width      = 2.0;
  state->gstate.image_smoothing = 1;
  state->gstate.source_stroke.type = CTX_SOURCE_INHERIT_FILL;
  ctx_state_set (state, CTX_line_spacing, 1.0f);
  state->min_x                  = 8192;
  state->min_y                  = 8192;
  state->max_x                  = -8192;
  state->max_y                  = -8192;
  _ctx_matrix_identity (&state->gstate.transform);
#if CTX_CM
#if CTX_BABL
  //ctx_colorspace_babl (state, CTX_COLOR_SPACE_USER_RGB,   babl_space ("sRGB"));
  //ctx_colorspace_babl (state, CTX_COLOR_SPACE_DEVICE_RGB, babl_space ("ACEScg"));
#endif
#endif
}

void _ctx_set_transformation (Ctx *ctx, int transformation)
{
  ctx->transformation = transformation;
}

static void
_ctx_init (Ctx *ctx)
{
  for (int i = 0; i <256;i++)
    ctx_u8_float[i] = i/255.0f;

  ctx_state_init (&ctx->state);

  ctx->renderer = NULL;
#if CTX_CURRENT_PATH
  ctx->current_path.flags |= CTX_DRAWLIST_CURRENT_PATH;
#endif
  //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_SCREEN_SPACE;
  //ctx->transformation |= (CtxTransformation) CTX_TRANSFORMATION_RELATIVE;
#if CTX_BITPACK
  ctx->drawlist.flags |= CTX_TRANSFORMATION_BITPACK;
#endif
  ctx->texture_cache = ctx;
}

static void ctx_setup (void);

#if CTX_DRAWLIST_STATIC
static Ctx ctx_state;
#endif

void ctx_set_renderer (Ctx  *ctx,
                       void *renderer)
{
  if (ctx->renderer && ctx->renderer->free)
    ctx->renderer->free (ctx->renderer);
  ctx->renderer = (CtxImplementation*)renderer;
}

void *ctx_get_renderer (Ctx *ctx)
{
  return ctx->renderer;
}

Ctx *
ctx_new (void)
{
  ctx_setup ();
#if CTX_DRAWLIST_STATIC
  Ctx *ctx = &ctx_state;
#else
  Ctx *ctx = (Ctx *) malloc (sizeof (Ctx) );
#endif
  ctx_memset (ctx, 0, sizeof (Ctx) );
  _ctx_init (ctx);
  return ctx;
}

static inline void
ctx_drawlist_deinit (CtxDrawlist *drawlist)
{
#if !CTX_DRAWLIST_STATIC
  if (drawlist->entries && ! (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) )
    {
      free (drawlist->entries); 
    }
#endif
  drawlist->entries = NULL;
  drawlist->size = 0;
}

static void ctx_deinit (Ctx *ctx)
{
  if (ctx->renderer)
    {
      if (ctx->renderer->free)
        ctx->renderer->free (ctx->renderer);
      ctx->renderer    = NULL;
    }
  ctx_drawlist_deinit (&ctx->drawlist);
#if CTX_CURRENT_PATH
  ctx_drawlist_deinit (&ctx->current_path);
#endif
}

void ctx_free (Ctx *ctx)
{
  if (!ctx)
    { return; }
#if CTX_EVENTS
  ctx_clear_bindings (ctx);
#endif
  ctx_deinit (ctx);
#if !CTX_DRAWLIST_STATIC
  free (ctx);
#endif
}

Ctx *ctx_new_for_drawlist (void *data, size_t length)
{
  Ctx *ctx = ctx_new ();
  ctx->drawlist.flags   |= CTX_DRAWLIST_DOESNT_OWN_ENTRIES;
  ctx->drawlist.entries  = (CtxEntry *) data;
  ctx->drawlist.count    = length / sizeof (CtxEntry);
  return ctx;
}

static void ctx_setup (void)
{
  ctx_font_setup ();
}

void
ctx_render_ctx (Ctx *ctx, Ctx *d_ctx)
{
  CtxIterator iterator;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    {
       ctx_process (d_ctx, &command->entry);
    }
}

void
ctx_render_ctx_textures (Ctx *ctx, Ctx *d_ctx)
{
  CtxIterator iterator;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    {
       switch (command->code)
       {
         default:
                 //fprintf (stderr, "[%c]", command->code);
                 break;
         case CTX_TEXTURE:
             //fprintf (stderr, "t:%s\n", command->texture.eid);
             ctx_process (d_ctx, &command->entry);
             break;
         case CTX_DEFINE_TEXTURE:
             //fprintf (stderr, "d:%s\n", command->define_texture.eid);
             ctx_process (d_ctx, &command->entry);
           break;
       }
    }
}

void ctx_quit (Ctx *ctx)
{
#if CTX_EVENTS
  ctx->quit ++;
#endif
}

int  ctx_has_quit (Ctx *ctx)
{
#if CTX_EVENTS
  return (ctx->quit);
#else
  return 1; 
#endif
}

int ctx_pixel_format_bits_per_pixel (CtxPixelFormat format)
{
  CtxPixelFormatInfo *info = ctx_pixel_format_info (format);
  if (info)
    return info->bpp;
  return -1;
}

int ctx_pixel_format_get_stride (CtxPixelFormat format, int width)
{
  CtxPixelFormatInfo *info = ctx_pixel_format_info (format);
  if (info)
  {
    switch (info->bpp)
    {
      case 0:
      case 1:
        return (width + 7)/8;
      case 2:
        return (width + 3)/4;
      case 4:
        return (width + 1)/2;
      default:
        return width * (info->bpp / 8);
    }
  }
  return width;
}

int ctx_pixel_format_ebpp (CtxPixelFormat format)
{
  CtxPixelFormatInfo *info = ctx_pixel_format_info (format);
  if (info)
    return info->ebpp;
  return -1;
}

int ctx_pixel_format_components (CtxPixelFormat format)
{
  CtxPixelFormatInfo *info = ctx_pixel_format_info (format);
  if (info)
    return info->components;
  return -1;
}

#if CTX_EVENTS
void         ctx_set_cursor (Ctx *ctx, CtxCursor cursor)
{
  if (ctx->cursor != cursor)
  {
    ctx_set_dirty (ctx, 1);
    ctx->cursor = cursor;
  }
}
CtxCursor    ctx_get_cursor (Ctx *ctx)
{
  return ctx->cursor;
}

void ctx_set_clipboard (Ctx *ctx, const char *text)
{
  if (ctx->renderer && ctx->renderer->set_clipboard)
  {
    ctx->renderer->set_clipboard (ctx->renderer, text);
    return;
  }
}

char *ctx_get_clipboard (Ctx *ctx)
{
  if (ctx->renderer && ctx->renderer->get_clipboard)
  {
    return ctx->renderer->get_clipboard (ctx->renderer);
  }
  return strdup ("");
}

void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source)
{
  ((CtxRasterizer*)ctx->renderer)->texture_source = texture_source;
}

void ctx_set_texture_cache (Ctx *ctx, Ctx *texture_cache)
{
  ctx->texture_cache = texture_cache;
}

void ctx_set_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f)
{
  ctx_identity (ctx);
  ctx_apply_transform (ctx, a, b, c, d, e, f);
}

#endif

#if CTX_GET_CONTENTS

#ifndef NO_LIBCURL
#include <curl/curl.h>
static size_t
ctx_string_append_callback (void *contents, size_t size, size_t nmemb, void *userp)
{
  CtxString *string = (CtxString*)userp;
  ctx_string_append_data ((CtxString*)string, contents, size * nmemb);
  return size * nmemb;
}

#endif

int
ctx_get_contents (const char     *uri,
                  unsigned char **contents,
                  long           *length)
{
  char *temp_uri = NULL; // XXX XXX breaks with data uri's
  int   success  = -1;

  if (uri[0] == '/')
  {
    temp_uri = (char*) malloc (strlen (uri) + 8);
    sprintf (temp_uri, "file://%s", uri);
    uri = temp_uri;
  }

  if (strchr (uri, '#'))
   strchr (uri, '#')[0]=0;

  for (CtxList *l = registered_contents; l; l = l->next)
  {
    CtxFileContent *c = (CtxFileContent*)l->data;
    if (!strcmp (c->path, uri))
    {
      contents = malloc (c->length+1);
      contents[c->length]=0;
      if (length) *length = c->length;
      free (temp_uri);
      return 0;
    }
  }

  if (!strncmp (uri, "file://", 5))
  {
    if (strchr (uri, '?'))
     strchr (uri, '?')[0]=0;
  }

  if (!strncmp (uri, "file://", 7))
    success = __ctx_file_get_contents (uri + 7, contents, length);
  else
  {
#ifndef NO_LIBCURL
  CURL *curl = curl_easy_init ();
  CURLcode res;

  curl_easy_setopt(curl, CURLOPT_URL, uri);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    CtxString *string = ctx_string_new ("");

      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ctx_string_append_callback);
   /* we pass our 'chunk' struct to the callback function */
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)string);

  curl_easy_setopt(curl, CURLOPT_USERAGENT, "ctx/0.0");

   res = curl_easy_perform(curl);
  /* check for errors */
  if(res != CURLE_OK) {
          fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));
     curl_easy_cleanup (curl);
  }
  else
  {
     *contents = (unsigned char*)string->str;
     *length = string->length;
     ctx_string_free (string, 0);
     curl_easy_cleanup (curl);
     success = 0;
  }
#else
    success = __ctx_file_get_contents (uri, contents, length);
#endif
  }
  free (temp_uri);
  return success;
}
#endif

#endif // CTX_IMPLEMENTATION
#ifndef __CTX_CLIENTS_H
#define __CTX_CLIENTS_H

typedef enum CtxClientFlags {
  ITK_CLIENT_UI_RESIZABLE = 1<<0,
  ITK_CLIENT_CAN_LAUNCH   = 1<<1,
  ITK_CLIENT_MAXIMIZED    = 1<<2,
  ITK_CLIENT_ICONIFIED    = 1<<3,
  ITK_CLIENT_SHADED       = 1<<4,
  ITK_CLIENT_TITLEBAR     = 1<<5
} CtxClientFlags;

struct _CtxClient {
  VT    *vt;
  Ctx   *ctx;
  char  *title;
  int    x;
  int    y;
  int    width;
  int    height;
  CtxClientFlags flags;
#if 0
  int    shaded;
  int    iconified;
  int    maximized;
  int    resizable;
#endif
  int    unmaximized_x;
  int    unmaximized_y;
  int    unmaximized_width;
  int    unmaximized_height;
  int    do_quit;
  long   drawn_rev;
  int    id;
  int    internal; // render a settings window rather than a vt
#if CTX_THREADS
  mtx_t  mtx;
#endif
#if VT_RECORD
  Ctx   *recording;
#endif
};

typedef struct _CtxClient CtxClient;


extern CtxList *clients;
extern CtxClient *active;
extern CtxClient *active_tab;


int ctx_client_resize (int id, int width, int height);
void ctx_client_maximize (int id);

CtxClient *vt_get_client (VT *vt);
CtxClient *ctx_client_new (Ctx *ctx, const char *commandline,
                       int x, int y, int width, int height,
                       CtxClientFlags flags);
CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags);
int ctx_clients_need_redraw (Ctx *ctx);

extern float ctx_shape_cache_rate;
extern int _ctx_max_threads;

void ctx_client_move (int id, int x, int y);
int ctx_client_resize (int id, int w, int h);
void ctx_client_shade_toggle (int id);
float ctx_client_min_y_pos (Ctx *ctx);
float ctx_client_max_y_pos (Ctx *ctx);

CtxClient *client_by_id (int id);

void ctx_client_remove (Ctx *ctx, CtxClient *client);

int ctx_client_height (int id);

int ctx_client_x (int id);
int ctx_client_y (int id);
void ctx_client_raise_top (int id);
void ctx_client_lower_bottom (int id);
void ctx_client_iconify (int id);
int ctx_client_is_iconified (int id);
void ctx_client_uniconify (int id);
void ctx_client_maximize (int id);
int ctx_client_is_maximized (int id);
void ctx_client_unmaximize (int id);
void ctx_client_maximized_toggle (int id);
void ctx_client_shade (int id);
int ctx_client_is_shaded (int id);
void ctx_client_unshade (int id);
void ctx_client_toggle_maximized (int id);
void ctx_client_shade_toggle (int id);
void ctx_client_move (int id, int x, int y);
int ctx_client_resize (int id, int width, int height);


#endif
#ifndef MRG_UTF8_H
#define MRG_UTF8_H

#if !__COSMOPOLITAN__
#include <string.h>
#include <stdint.h>
#endif

static inline int mrg_utf8_len (const unsigned char first_byte)
{
  if      ((first_byte & 0x80) == 0)
    return 1; /* ASCII */
  else if ((first_byte & 0xE0) == 0xC0)
    return 2;
  else if ((first_byte & 0xF0) == 0xE0)
    return 3;
  else if ((first_byte & 0xF8) == 0xF0)
    return 4;
  return 1;
}

static inline const char *mrg_utf8_skip (const char *s, int utf8_length)
{
   int count;
   if (!s)
     return NULL;
   for (count = 0; *s; s++)
   {
     if ((*s & 0xC0) != 0x80)
       count++;
     if (count == utf8_length+1)
       return s;
   }
   return s;
}

int          mrg_unichar_to_utf8       (unsigned int   ch,
                                        unsigned char *dest);
unsigned int mrg_utf8_to_unichar       (unsigned char *utf8);

//////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.

#define UTF8_ACCEPT 0
#define UTF8_REJECT 1

static const uint8_t utf8d[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
  8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
  0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
  0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
  0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
  1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
  1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
  1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};

static inline uint32_t
utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
  uint32_t type = utf8d[byte];

  *codep = (*state != UTF8_ACCEPT) ?
    (byte & 0x3fu) | (*codep << 6) :
    (0xff >> type) & (byte);

  *state = utf8d[256 + *state*16 + type];
  return *state;
}

#endif
#if CTX_VT

/* mrg - MicroRaptor Gui
 * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef VT_LINE_H
#define VT_LINE_H

#include "ctx.h"

#ifndef CTX_UNLIKELY
#define CTX_UNLIKELY(x)    __builtin_expect(!!(x), 0)
#define CTX_LIKELY(x)      __builtin_expect(!!(x), 1)
#endif
#ifndef CTX_MAX
#define CTX_MAX(a,b) (((a)>(b))?(a):(b))
#endif

typedef struct _VtLine   VtLine;

struct _VtLine
{
  CtxString string;
  /* line extends string, permitting string ops to operate on it  */

  uint64_t *style;
  int       style_size;

  void     *ctx; // each line can have an attached ctx context;
  char     *prev;
  int       prev_length;
  CtxString *frame;

  int       wrapped;

  void     *ctx_copy; // each line can have an attached ctx context;
  // clearing should be brutal enough to unset the context of the current
  // at least in alt-screen mode
  int       double_width;
  int       double_height_top;
  int       double_height_bottom;
  int       contains_proportional;
  float     xscale;
  float     yscale;
  float     y_offset;
  int       in_scrolling_region;

  /*  XXX:  needs refactoring to a CtxList of links/images */
  void     *images[4];
  int       image_col[4];
  float     image_X[4]; // 0.0 - 1.0 offset in cell
  float     image_Y[4];
  int       image_rows[4];
  int       image_cols[4];
  int       image_subx[4];
  int       image_suby[4];
  int       image_subw[4];
  int       image_subh[4];
};


static inline uint64_t vt_line_get_style (VtLine *string, int pos)
{
  if (string->string.is_line==0)
    return 0;
  if (pos < 0 || pos >= string->style_size)
    return 0;
  return string->style[pos];
}

#if !__COSMOPOLITAN__
#include <stdlib.h>
#endif

static inline void vt_line_set_style (VtLine *string, int pos, uint64_t style)
{
  if (string->string.is_line==0)
    return;
  if (pos < 0 || pos >= 512)
    return;
  if (pos >= string->style_size)
    {
      int new_size = pos + 16;
      string->style = realloc (string->style, new_size * sizeof (uint64_t) );
      memset (&string->style[string->style_size], 0, (new_size - string->style_size) * sizeof (uint64_t) );
      string->style_size = new_size;
    }
  string->style[pos] = style;
}

VtLine *vt_line_new_with_size (const char *initial, int initial_size);
VtLine *vt_line_new (const char *initial);

static inline void        vt_line_free           (VtLine *line, int freealloc)
{
  CtxString *string = (CtxString*)line;

#if 1
  //if (string->is_line)
  {
    VtLine *line = (VtLine*)string;
    if (line->frame)
      ctx_string_free (line->frame, 1);
    if (line->style)
      { free (line->style); }
    if (line->ctx)
      { ctx_free (line->ctx); }
    if (line->ctx_copy)
      { ctx_free (line->ctx_copy); }
  }
#endif

  ctx_string_free (string, freealloc);
}
static inline const char *vt_line_get            (VtLine *line)
{
  CtxString *string = (CtxString*)line;
  return ctx_string_get (string);
}
static inline uint32_t    vt_line_get_unichar    (VtLine *line, int pos)
{
  CtxString *string = (CtxString*)line;
  return ctx_string_get_unichar (string, pos);
}
static inline int         vt_line_get_length     (VtLine *line)
{
  CtxString *string = (CtxString*)line;
  return ctx_string_get_length (string);
}
static inline int         vt_line_get_utf8length     (VtLine *line)
{
  CtxString *string = (CtxString*)line;
  return ctx_string_get_utf8length (string);
}
static inline void        vt_line_set            (VtLine *line, const char *new_string)
{
  CtxString *string = (CtxString*)line;
  ctx_string_set (string, new_string);
}
static inline void        vt_line_clear          (VtLine *line)
{
  CtxString *string = (CtxString*)line;
  ctx_string_clear (string);
}
static inline void        vt_line_append_str     (VtLine *line, const char *str)
{
  CtxString *string = (CtxString*)line;
  ctx_string_append_str (string, str);
}

#if 0
static inline void _ctx_string_append_byte (CtxString *string, char  val)
{
  if (CTX_LIKELY((val & 0xC0) != 0x80))
    { string->utf8_length++; }
  if (CTX_UNLIKELY(string->length + 2 >= string->allocated_length))
    {
      char *old = string->str;
      string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2);
      string->str = (char*)realloc (old, string->allocated_length);
    }
  string->str[string->length++] = val;
  string->str[string->length] = '\0';
}
#endif

static inline void        vt_line_append_byte    (VtLine *line, char  val)
{
  CtxString *string = (CtxString*)line;
  _ctx_string_append_byte (string, val);
}
static inline void        vt_line_append_string  (VtLine *line, CtxString *string2)
{
  CtxString *string = (CtxString*)line;
  ctx_string_append_string (string, string2);
}
static inline void        vt_line_append_unichar (VtLine *line, unsigned int unichar)
{
  CtxString *string = (CtxString*)line;
  ctx_string_append_unichar (string, unichar);
}


static inline void vt_line_append_data    (VtLine *line, const char *data, int len)
{
  CtxString *string = (CtxString*)line;
  ctx_string_append_data (string, data, len);
}
static inline void vt_line_append_utf8char (VtLine *line, const char *str)
{
  CtxString *string = (CtxString*)line;
  ctx_string_append_utf8char (string, str);
}
static inline void vt_line_replace_utf8   (VtLine *line, int pos, const char *new_glyph)
{
  CtxString *string = (CtxString*)line;
  ctx_string_replace_utf8 (string, pos, new_glyph);
}
static inline void vt_line_insert_utf8    (VtLine *line, int pos, const char *new_glyph)
{
  CtxString *string = (CtxString*)line;
  ctx_string_insert_utf8 (string, pos, new_glyph);
  int len = vt_line_get_length (line);
  for (int i = pos; i < len; i++)
    vt_line_set_style (line, i, vt_line_get_style (line, i-1));
}

static inline void vt_line_insert_unichar (VtLine *line, int pos, uint32_t new_glyph)
{
  CtxString *string = (CtxString*)line;
  ctx_string_insert_unichar (string, pos, new_glyph);
  int len = vt_line_get_length (line);
  for (int i = 1; i < len; i++)
    vt_line_set_style (line, i, vt_line_get_style (line, i-1));
}
static inline void vt_line_replace_unichar (VtLine *line, int pos, uint32_t unichar)
{
  CtxString *string = (CtxString*)line;
  ctx_string_replace_unichar (string, pos, unichar);
}

static inline void vt_line_remove (VtLine *line, int pos)
{ 
  CtxString *string = (CtxString*)line;
  ctx_string_remove (string, pos);

  for (int i = pos; i < line->style_size-1; i++)
  {
    line->style[i] = line->style[i+1];
  }
}


#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

#endif
/* mrg - MicroRaptor Gui
 * Copyright (c) 2014 Øyvind Kolås <pippin@hodefoting.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */


#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif

#if !__COSMOPOLITAN__
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif

int ctx_unichar_to_utf8 (uint32_t  ch, uint8_t  *dest);
#define mrg_unichar_to_utf8 ctx_unichar_to_utf8
void ctx_string_init (CtxString *string, int initial_size);

VtLine *vt_line_new_with_size (const char *initial, int initial_size)
{
  VtLine *line = calloc (sizeof (VtLine), 1);
  CtxString *string = (CtxString*)line;
  ctx_string_init (string, initial_size);
  if (initial)
    { ctx_string_append_str (string, initial); }
  line->style = calloc (sizeof (uint64_t), initial_size);
  line->style_size = initial_size;
  string->is_line = 1;
  return line;
}

VtLine *vt_line_new (const char *initial)
{
  return vt_line_new_with_size (initial, 8);
}

typedef struct VtPty
{
  int        pty;
  pid_t      pid;
  int        done;
} VtPty;
ssize_t vtpty_read     (void *vtpty, void *buf, size_t count);
ssize_t vtpty_write    (void *vtpty, const void *buf, size_t count);
void    vtpty_resize   (void *vtpty, int cols, int rows,
                        int px_width, int px_height);
int     vtpty_waitdata (void  *vtpty, int timeout);
extern  CtxList *vts;
#define MAX_COLS 2048 // used for tabstops


typedef struct AudioState
{
  int action;
  int samplerate; // 8000
  int channels;   // 1
  int bits;       // 8
  int type;       // 'u'    u-law  f-loat  s-igned u-nsigned
  int buffer_size; // desired size of audiofragment in frames
  // (both for feeding SDL and as desired chunking
  //  size)


  int mic;        // <- should
  //    request permisson,
  //    and if gotten, start streaming
  //    audio packets in the incoming direction
  //
  int encoding;   // 'a' ascci85 'b' base64
  int compression; // '0': none , 'z': zlib   'o': opus(reserved)

  int frames;

  uint8_t *data;
  int      data_size;
} AudioState;

typedef struct GfxState
{
  int action;
  int id;
  int buf_width;
  int buf_height;
  int format;
  int compression;
  int transmission;
  int multichunk;
  int buf_size;
  int x;
  int y;
  int w;
  int h;
  int x_cell_offset;
  int y_cell_offset;
  int columns;
  int rows;
  int z_index;
  int delete;

  uint8_t *data;
  int   data_size;
} GfxState;

struct _VT
{
  VtPty      vtpty;
  int       id;
  unsigned char buf[BUFSIZ]; // need one per vt
  int keyrepeat;
  int       lastx;
  int       lasty;
  int        result;
  long       rev;
  //SDL_Rect   dirty;
  float  dirtpad;
  float  dirtpad1;
  float  dirtpad2;
  float  dirtpad3;

  void  *client;

  ssize_t (*write)   (void *serial_obj, const void *buf, size_t count);
  ssize_t (*read)    (void *serial_obj, void *buf, size_t count);
  int     (*waitdata)(void *serial_obj, int timeout);
  void    (*resize)  (void *serial_obj, int cols, int rows, int px_width, int px_height);


  char     *title;
  void    (*state) (VT *vt, int byte);

  AudioState audio; // < want to move this one level up and share impl
  GfxState   gfx;

  CtxList   *saved_lines;
  int       in_alt_screen;
  int       saved_line_count;
  CtxList   *lines;
  int       line_count;
  CtxList   *scrollback;
  int       scrollback_count;
  int       leds[4];
  uint64_t  cstyle;

  uint8_t   fg_color[3];
  uint8_t   bg_color[3];

  int       in_smooth_scroll;
  int       smooth_scroll;
  float     scroll_offset;
  int       debug;
  int       bell;
  int       origin;
  int       at_line_home;
  int       charset[4];
  int       saved_charset[4];
  int       shifted_in;
  int       reverse_video;
  int       echo;
  int       bracket_paste;
  int       ctx_events;
  int       font_is_mono;
  int       palette_no;
  int       has_blink; // if any of the set characters are blinking
  // updated on each draw of the screen
  
  int can_launch;

  int unit_pixels;
  int mouse;
  int mouse_drag;
  int mouse_all;
  int mouse_decimal;


  uint8_t    utf8_holding[64]; /* only 4 needed for utf8 - but it's purpose
                                 is also overloaded for ctx journal command
                                 buffering , and the bigger sizes for the svg-like
                                 ctx parsing mode */
  int        utf8_expected_bytes;
  int        utf8_pos;


  int        ref_len;
  char       reference[16];
  int        in_prev_match;
  CtxParser *ctxp;
  // text related data
  float      letter_spacing;

  float      word_spacing;
  float      font_stretch;  // horizontal expansion
  float      font_size_adjust;
  // font-variant
  // font-weight
  // text-decoration

  int        encoding;  // 0 = utf8 1=pc vga 2=ascii

  int        local_editing; /* terminal operates without pty  */

  int        insert_mode;
  int        autowrap;
  int        justify;
  float      cursor_x;
  int        cursor_y;
  int        cols;
  int        rows;
  VtLine    *current_line;


  int        cr_on_lf;
  int        cursor_visible;
  int        saved_x;
  int        saved_y;
  uint32_t   saved_style;
  int        saved_origin;
  int        cursor_key_application;
  int        margin_top;
  int        margin_bottom;
  int        margin_left;
  int        margin_right;

  int        left_right_margin_mode;

  int        scrollback_limit;
  float      scroll;
  int        scroll_on_input;
  int        scroll_on_output;

  char       *argument_buf;
  int        argument_buf_len;
  int        argument_buf_cap;
  uint8_t    tabs[MAX_COLS];
  int        inert;

  int        width;
  int        height;

  int        cw; // cell width
  int        ch; // cell height
  float      font_to_cell_scale;
  float      font_size; // when set with set_font_size, cw and ch are recomputed
  float      line_spacing; // using line_spacing
  float      scale_x;
  float      scale_y;

  int        ctx_pos;  // 1 is graphics above text, 0 or -1 is below text
  Ctx       *root_ctx; /* only used for knowledge of top-level dimensions */

  int        blink_state;

  FILE      *log;

  int cursor_down;

  int select_begin_col;
  int select_begin_row;
  int select_start_col;
  int select_start_row;
  int select_end_col;
  int select_end_row;
  int select_begin_x;
  int select_begin_y;
  int select_active;

  int popped;

  /* used to make runs of background on one line be drawn
   * as a single filled rectangle
   */
  int   bg_active;
  float bg_x0;
  float bg_y0;
  float bg_width;
  float bg_height;
  uint8_t bg_rgba[4];
};


VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch);

void vt_open_log (VT *vt, const char *path);

void        vt_set_px_size        (VT *vt, int width, int height);
void        vt_set_term_size      (VT *vt, int cols, int rows);

int         vt_cw                 (VT *vt);
int         vt_ch                 (VT *vt);
void        vt_set_font_size      (VT *vt, float font_size);
float       vt_get_font_size      (VT *vt);
void        vt_set_line_spacing   (VT *vt, float line_spacing);

const char *vt_find_shell_command (void);

int         vt_keyrepeat          (VT *vt);

int         vt_get_result         (VT *vt);
int         vt_is_done            (VT *vt);
int         vt_poll               (VT *vt, int timeout);
long        vt_rev                (VT *vt);
void        vt_destroy            (VT *vt);
int         vt_has_blink (VT *vt);

/* this is how mrg/mmm based key-events are fed into the vt engine
 */
void        vt_feed_keystring     (VT *vt, CtxEvent *event, const char *str);

void        vt_paste              (VT *vt, const char *str);

/* not needed when passing a commandline for command to
 * run, but could be used for injecting commands, or
 * output from stored shell commands/sessions to display
 */
//void        vt_feed_byte          (VT *vt, int byte);

//)#define DEFAULT_SCROLLBACK   (1<<16)
#define DEFAULT_SCROLLBACK   (1<<13)
#define DEFAULT_ROWS         24
#define DEFAULT_COLS         80

int         vt_get_line_count       (VT *vt);

pid_t       vt_get_pid              (VT *vt);

const char *vt_get_line             (VT *vt, int no);

void        vt_set_scrollback_lines (VT *vt, int scrollback_lines);
int         vt_get_scrollback_lines (VT *vt);

void        vt_set_scroll           (VT *vt, int scroll);
int         vt_get_scroll           (VT *vt);

int         vt_get_cols             (VT *vt);
int         vt_get_rows             (VT *vt);

char       *vt_get_selection        (VT *vt);
int         vt_get_cursor_x         (VT *vt);
int         vt_get_cursor_y         (VT *vt);

void        vt_draw                 (VT *vt, Ctx *ctx, double x, double y);
void        vt_register_events      (VT *vt, Ctx *ctx, double x0, double y0);

void        vt_rev_inc              (VT *vt);

int         vt_mic (VT *vt);
void        vt_set_ctx (VT *vt, Ctx *ctx);  /* XXX: rename, this sets the parent/global ctx  */


int         vt_get_local (VT *vt);           // this is a hack for the settings tab
void        vt_set_local (VT *vt, int local);


typedef enum VtMouseEvent
{
  VT_MOUSE_MOTION = 0,
  VT_MOUSE_PRESS,
  VT_MOUSE_DRAG,
  VT_MOUSE_RELEASE,
} VtMouseEvent;

void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y);

static ssize_t vt_write (VT *vt, const void *buf, size_t count)
{
  if (!vt->write) { return 0; }
  return vt->write (&vt->vtpty, buf, count);
}
static ssize_t vt_read (VT *vt, void *buf, size_t count)
{
  if (!vt->read) { return 0; }
  return vt->read (&vt->vtpty, buf, count);
}
static int vt_waitdata (VT *vt, int timeout)
{
  if (!vt->waitdata) { return 0; }
  return vt->waitdata (&vt->vtpty, timeout);
}
static void vt_resize (VT *vt, int cols, int rows, int px_width, int px_height)
{
  if (vt && vt->resize)
    { vt->resize (&vt->vtpty, cols, rows, px_width, px_height); }
}

/* atty - audio interface and driver for terminals
 * Copyright (C) 2020 Øyvind Kolås <pippin@gimp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 
 */


#ifndef NO_SDL
#include <SDL.h>
#include <zlib.h>

static int ydec (const void *srcp, void *dstp, int count)
{
  const char *src = srcp;
  char *dst = dstp;
  int out_len = 0;
  for (int i = 0; i < count; i ++)
  {
    int o = src[i];
    switch (o)
    {
      case '=':
              i++;
              o = src[i];
              o = (o-42-64) % 256;
              break;
      case '\n':
      case '\033':
      case '\r':
      case '\0':
              break;
      default:
              o = (o-42) % 256;
              break;
    }
    dst[out_len++] = o;
  }
  dst[out_len]=0;
  return out_len;
}

#ifndef NO_SDL
static SDL_AudioDeviceID speaker_device = 0;
#endif

//#define AUDIO_CHUNK_SIZE 512

// our pcm queue is currently always 16 bit
// signed stereo

static int16_t pcm_queue[1<<18];
static int     pcm_write_pos = 0;
static int     pcm_read_pos  = 0;

void terminal_queue_pcm (int16_t sample_left, int16_t sample_right)
{
  if (pcm_write_pos >= (1<<18)-1)
  {
    /*  TODO  :  fix cyclic buffer */
    pcm_write_pos = 0;
    pcm_read_pos  = 0;
  }
  pcm_queue[pcm_write_pos++]=sample_left;
  pcm_queue[pcm_write_pos++]=sample_right;
}

float click_volume = 0.05;

void vt_feed_audio (VT *vt, void *samples, int bytes);
int mic_device = 0;   // when non 0 we have an active mic device


/*  https://jonathanhays.me/2018/11/14/mu-law-and-a-law-compression-tutorial/
 */

#if 0
static char MuLawCompressTable[256] =
{
   0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
   4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

unsigned char LinearToMuLawSample(int16_t sample)
{
  const int cBias = 0x84;
  const int cClip = 32635;
  int sign = (sample >> 8) & 0x80;

  if (sign)
    sample = (int16_t)-sample;

  if (sample > cClip)
    sample = cClip;

  sample = (int16_t)(sample + cBias);

  int exponent = (int)MuLawCompressTable[(sample>>7) & 0xFF];
  int mantissa = (sample >> (exponent+3)) & 0x0F;

  int compressedByte = ~ (sign | (exponent << 4) | mantissa);

  return (unsigned char)compressedByte;
}
#endif

void vt_feed_audio (VT *vt, void *samples, int bytes)
{
  char buf[256];
  AudioState *audio = &vt->audio;
  uint8_t *data = samples;
  int frames = bytes / (audio->bits/8) / audio->channels;

  if (audio->compression == 'z')
  {
    uLongf len = compressBound(bytes);
    data = malloc (len);
    int z_result = compress (data, &len, samples, len);
    if (z_result != Z_OK)
    {
      char buf[256]= "\033_Ao=z;zlib error2\033\\";
      vt_write (vt, buf, strlen(buf));
      data = samples;
    }
    else
    {
      bytes = len;
    }
  }

  char *encoded = malloc (bytes * 2);
  encoded[0]=0;
  if (audio->encoding == 'a')
  {
    ctx_a85enc (data, encoded, bytes);
  }
  else /* if (audio->encoding == 'b')  */
  {
    ctx_bin2base64 (data, bytes, encoded);
  }

  sprintf (buf, "\033[_Af=%i;", frames);
  vt_write (vt, buf, strlen (buf));
  vt_write (vt, encoded, strlen(encoded));
  free (encoded);

  if (data != samples)
    free (data);

  //vt_write (vt, samples, bytes);
  buf[0]='\033';
  buf[1]='\\';
  buf[2]=0;
  vt_write (vt, buf, 2);
}

#define MIC_BUF_LEN 40960

uint8_t mic_buf[MIC_BUF_LEN];
int     mic_buf_pos = 0;

static void mic_callback(void*     userdata,
                         uint8_t * stream,
                         int       len)
{
  AudioState *audio = userdata;
  int16_t *sstream = (void*)stream;
  int frames;
  int channels = audio->channels;

  frames = len / 2;

  if (audio->bits == 8)
  {
    if (audio->type == 'u')
    {
      for (int i = 0; i < frames; i++)
      {
        for (int c = 0; c < channels; c++)
        {
          mic_buf[mic_buf_pos++] = LinearToMuLawSample (sstream[i]);
          if (mic_buf_pos >= MIC_BUF_LEN - 4)
            mic_buf_pos = 0;
        }
      }
    }
    else
    {
      for (int i = 0; i < frames; i++)
      {
        for (int c = 0; c <  audio->channels; c++)
        {
          mic_buf[mic_buf_pos++] = (sstream[i]) / 256;
          if (mic_buf_pos >= MIC_BUF_LEN - 4)
            mic_buf_pos = 0;
        }
      }
    }
  }
  else
  {
    for (int i = 0; i < frames; i++)
    {
      for (int c = 0; c < audio->channels; c++)
      {
        *((int16_t*)(&mic_buf[mic_buf_pos])) = (sstream[i]);
        mic_buf_pos+=2;
        if (mic_buf_pos >= MIC_BUF_LEN - 4)
          mic_buf_pos = 0;
      }
    }
  }
}

static long int ticks (void)
{
  struct timeval tp;
  gettimeofday(&tp, NULL);
  return tp.tv_sec * 1000 + tp.tv_usec / 1000;
}

static long int silence_start = 0;

static void sdl_audio_init ()
{
  static int done = 0;
  if (!done)
  {
#ifndef NO_SDL
  if (SDL_Init(SDL_INIT_AUDIO) < 0)
  {
    fprintf (stderr, "sdl audio init fail\n");
  }
#endif
  done = 1;
  }
}

void vt_audio_task (VT *vt, int click)
{
  if (!vt) return;
  AudioState *audio = &vt->audio;
#ifndef NO_SDL

  if (audio->mic)
  {
    if (mic_device == 0)
    {
      SDL_AudioSpec spec_want, spec_got;
      sdl_audio_init ();

      spec_want.freq     = audio->samplerate;
      spec_want.channels = 1;
      spec_want.format   = AUDIO_S16;
      spec_want.samples  = audio->buffer_size;
      spec_want.callback = mic_callback;
      spec_want.userdata = audio;
      mic_device = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, SDL_TRUE), 1, &spec_want, &spec_got, 0);

      SDL_PauseAudioDevice(mic_device, 0);
    }

    if (mic_buf_pos)
    {
      SDL_LockAudioDevice (mic_device);
      vt_feed_audio (vt, mic_buf, mic_buf_pos);
      mic_buf_pos = 0;
      SDL_UnlockAudioDevice (mic_device);
    }
  }
  else
  {
    if (mic_device)
    {
      SDL_PauseAudioDevice(mic_device, 1);
      SDL_CloseAudioDevice(mic_device);
      mic_device = 0;
    }
  }

  int free_frames = audio->buffer_size - SDL_GetQueuedAudioSize(speaker_device);
  int queued = (pcm_write_pos - pcm_read_pos)/2; // 2 for stereo
  //if (free_frames > 6) free_frames -= 4;
  int frames = queued;

  if (frames > free_frames) frames = free_frames;
  if (frames > 0)
  {
    if (speaker_device == 0)
    {
      SDL_AudioSpec spec_want, spec_got;
      sdl_audio_init ();

       spec_want.freq = audio->samplerate;
       if (audio->bits == 8 && audio->type == 'u')
       {
         spec_want.format = AUDIO_S16;
         spec_want.channels = 2;
       }
       else if (audio->bits == 8 && audio->type == 's')
       {
         spec_want.format = AUDIO_S8;
         spec_want.channels = audio->channels;
       }
       else if (audio->bits == 16 && audio->type == 's')
       {
         spec_want.format = AUDIO_S16;
         spec_want.channels = audio->channels;
       }
       else
       {
         spec_want.format = AUDIO_S16; // XXX  : error
         spec_want.channels = audio->channels;
       }

       /* In SDL we always set 16bit stereo, but with the
        * requested sample rate.
        */
      spec_want.format = AUDIO_S16;
      spec_want.channels = 2;

      spec_want.samples = audio->buffer_size;
      spec_want.callback = NULL;

      speaker_device = SDL_OpenAudioDevice (NULL, 0, &spec_want, &spec_got, 0);
      if (!speaker_device){
        fprintf (stderr, "sdl openaudiodevice fail\n");
      }
      SDL_PauseAudioDevice (speaker_device, 0);
    }

#if 0
    {
       int i;
       unsigned char *b = (void*)(&pcm_queue[pcm_read_pos]);
       for (i = 0; i < frames * 4; i++)
       {
         if ((b[i] > ' ') && (b[i] <= '~'))
           fprintf (stderr, "[%c]", b[i]);
         else
           fprintf (stderr, "[%i]", b[i]);
       }
    }
#endif
    SDL_QueueAudio (speaker_device, (void*)&pcm_queue[pcm_read_pos], frames * 4);
    pcm_read_pos += frames*2;
    silence_start = ticks();
  }
  else
  {
    if (speaker_device &&  (ticks() - silence_start >  2000))
    {
      SDL_PauseAudioDevice(speaker_device, 1);
      SDL_CloseAudioDevice(speaker_device);
      speaker_device = 0;
    }
  }
#endif
}

void terminal_queue_pcm (int16_t sample_left, int16_t sample_right);

static unsigned char vt_bell_audio[] = {
#if 1
  0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
  0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
  0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
  0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
  0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
  0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
  0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
  0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
#else
  0x7e, 0xfe, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0xff,
  0xff, 0xfe, 0xfe, 0x7e, 0xff, 0xfe, 0xfd, 0xfd, 0xfe, 0xfe, 0xfd, 0xfd,
  0xfe, 0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d,
  0xfe, 0x7e, 0x7e, 0x7e, 0x7e, 0xfd, 0xfd, 0x7e, 0x7e, 0xfd, 0xfe, 0xfe,
  0xfe, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0xfe, 0xfe, 0xff, 0xfe,
  0xfe, 0xfe, 0x7d, 0x7c, 0xfb, 0xfa, 0xfc, 0xfd, 0xfc, 0x76, 0x75, 0xfa,
  0xfb, 0x7b, 0xfc, 0xef, 0xf6, 0x77, 0x6d, 0x7b, 0xf8, 0x78, 0x78, 0xfa,
  0xf7, 0xfd, 0xfd, 0xfc, 0xfc, 0xfa, 0xf5, 0xf7, 0x7d, 0x7b, 0x78, 0x77,
  0x7c, 0x6f, 0x7b, 0xf5, 0xfb, 0x7b, 0x7c, 0x78, 0x76, 0xea, 0xf2, 0x6d,
  0xfd, 0xed, 0x7a, 0x6d, 0x6e, 0x71, 0xfe, 0x76, 0x6d, 0xfb, 0xef, 0x7e,
  0xfa, 0xef, 0xec, 0xed, 0xf8, 0xf0, 0xea, 0xf9, 0x70, 0x7c, 0x7c, 0x6b,
  0x6d, 0x75, 0xfb, 0xf1, 0xf9, 0xfe, 0xec, 0xea, 0x7c, 0x75, 0xff, 0xfb,
  0x7d, 0x77, 0x7a, 0x71, 0x6e, 0x6c, 0x6e, 0x7b, 0x7e, 0x7a, 0x7c, 0xf4,
  0xf9, 0x7b, 0x7b, 0xfa, 0xfe, 0x73, 0x79, 0xfe, 0x7b, 0x76, 0xfe, 0xf3,
  0xf9, 0x76, 0x77, 0x7e, 0x7e, 0x7d, 0x7c, 0xf9, 0xee, 0xf2, 0x7d, 0xf8,
  0xec, 0xee, 0xf7, 0xfa, 0xf7, 0xf6, 0xfd, 0x77, 0x75, 0x7b, 0xfa, 0xfe,
  0x78, 0x79, 0x7c, 0x76, 0x7e, 0xf7, 0xfb, 0xf5, 0xf6, 0x75, 0x6f, 0x74,
  0x6e, 0x6e, 0x6d, 0x6c, 0x7a, 0xf9, 0x75, 0x77, 0xf4, 0xf0, 0xf0, 0xf1,
  0xef, 0xf3, 0xf6, 0xfd, 0xfc, 0xfb, 0xfd, 0xfc, 0xf6, 0xf8, 0xfb, 0xf9,
  0xfa, 0xfd, 0xfb, 0xfc, 0x7a, 0x7c, 0x77, 0x75, 0x78, 0x7a, 0x7a, 0x78,
  0x7a, 0xfa, 0xf9, 0x7c, 0xff, 0xfb, 0x7d, 0x77, 0x73, 0x6c, 0x6e, 0x7b,
  0xfc, 0xfe, 0x7e, 0xfb, 0xf1, 0xeb, 0xee, 0xf6, 0xf6, 0xef, 0xf7, 0x7c,
  0x76, 0x76, 0x7b, 0x7a, 0x7b, 0x73, 0x73, 0x7c, 0x79, 0x70, 0x79, 0xfb,
  0xfd, 0xf8, 0xf9, 0xfc, 0xfc, 0xf8, 0xfb, 0xff, 0xfc, 0xf9, 0x75, 0x6f,
  0x74, 0xfe, 0xff, 0xfd, 0x7d, 0xf5, 0xef, 0xee, 0xf8, 0xfd, 0xfd, 0xf3,
  0xfa, 0xfe, 0xfe, 0x7c, 0x77, 0x7a, 0xfb, 0x79, 0x7e, 0x7b, 0xfd, 0x6d,
  0xfc, 0x7a, 0xf0, 0x74, 0xee, 0x79, 0xea, 0x79, 0xf9, 0x6d, 0xf7, 0x71,
  0x79, 0x76, 0x7c, 0x77, 0x6f, 0xf3, 0x6c, 0xe8, 0x67, 0xe3, 0x5e, 0xdc,
  0x58, 0xd8, 0x4e, 0xce, 0x46, 0xc5, 0x40, 0x67, 0xba, 0x49, 0xac, 0x26,
  0xba, 0x3e, 0xc5, 0xc8, 0x2b, 0xa8, 0x32, 0xbd, 0xe4, 0x3e, 0xb7, 0x3b,
  0xb7, 0x3a, 0x33, 0xab, 0x3f, 0xc8, 0x46, 0x5f, 0xb7, 0x69, 0xd4, 0x3d,
  0xc0, 0x4c, 0xf2, 0xdb, 0x3b, 0xdd, 0x69, 0xc5, 0x5f, 0xd8, 0xd8, 0xda,
  0xc6, 0x39, 0xba, 0x3f, 0x35, 0xb3, 0x3e, 0xbb, 0x4a, 0x4a, 0xe7, 0x60,
  0xae, 0x2c, 0xcb, 0x53, 0x45, 0xaf, 0x2a, 0xae, 0x3e, 0x4a, 0xae, 0x2a,
  0xad, 0x38, 0xcc, 0xbb, 0x36, 0xae, 0x2c, 0xc6, 0xce, 0x38, 0xb1, 0x2f,
  0xb9, 0x54, 0x7c, 0xb3, 0x28, 0xae, 0x3d, 0xcf, 0xbb, 0x2e, 0xb4, 0x41,
  0xc6, 0x78, 0x39, 0xbc, 0x41, 0xc8, 0x59, 0x5b, 0xc7, 0x43, 0xbc, 0x45,
  0xf3, 0xdc, 0x69, 0xd6, 0x48, 0xc9, 0x4e, 0xd9, 0x59, 0x61, 0xde, 0x4b,
  0xc9, 0x44, 0xc8, 0xf5, 0x43, 0xc5, 0x37, 0xba, 0x65, 0x4d, 0xc8, 0x31,
  0xaf, 0x47, 0xdb, 0xd6, 0x36, 0xad, 0x37, 0xbb, 0x61, 0x3a, 0xae, 0x2d,
  0xb4, 0x47, 0x49, 0xb2, 0x30, 0xac, 0x3a, 0xcd, 0xbc, 0x2e, 0xaf, 0x32,
  0xbd, 0xd7, 0x34, 0xaf, 0x32, 0xbb, 0x55, 0x4a, 0xb4, 0x30, 0xbb, 0x40,
  0xeb, 0xbf, 0x39, 0xba, 0x3a, 0xd6, 0xd3, 0x48, 0xc0, 0x3b, 0xce, 0x5e,
  0xe7, 0xd3, 0x46, 0xcb, 0x4c, 0xce, 0x74, 0x7e, 0x7e, 0x55, 0xcf, 0x44,
  0xc4, 0x5b, 0x7c, 0xd3, 0x3f, 0xbc, 0x44, 0xcb, 0xfa, 0x46, 0xb9, 0x37,
  0xb8, 0x51, 0x54, 0xbe, 0x33, 0xb1, 0x3d, 0xce, 0xc4, 0x34, 0xaf, 0x2f,
  0xbd, 0xf8, 0x37, 0xb0, 0x2d, 0xb1, 0x4c, 0x4a, 0xb3, 0x2c, 0xb0, 0x3c,
  0xe4, 0xbf, 0x2f, 0xaf, 0x35, 0xc0, 0xdb, 0x39, 0xb3, 0x31, 0xbb, 0x5d,
  0x4c, 0xb8, 0x37, 0xb9, 0x48, 0xe8, 0xc7, 0x3d, 0xba, 0x43, 0xce, 0xdd,
  0x52, 0xc6, 0x46, 0xce, 0x55, 0xdf, 0xe8, 0x52, 0xd5, 0x48, 0xca, 0x4d,
  0xef, 0x68, 0x4c, 0xc7, 0x42, 0xc2, 0x49, 0x78, 0xce, 0x3e, 0xb9, 0x3c,
  0xc8, 0xef, 0x43, 0xb7, 0x35, 0xb8, 0x4a, 0x53, 0xb8, 0x32, 0xaf, 0x3b,
  0xde, 0xc1, 0x34, 0xaf, 0x32, 0xc3, 0xde, 0x3b, 0xaf, 0x2e, 0xb6, 0x4e,
  0x48, 0xb4, 0x2e, 0xb2, 0x3d, 0xf0, 0xbf, 0x33, 0xb2, 0x37, 0xc8, 0xd9,
  0x3d, 0xb5, 0x36, 0xbc, 0x56, 0x4f, 0xbc, 0x39, 0xbc, 0x47, 0xf6, 0xcf,
  0x44, 0xbf, 0x46, 0xce, 0x68, 0x5b, 0xd0, 0x4a, 0xcc, 0x4d, 0xd3, 0x60,
  0x6a, 0xcf, 0x49, 0xc8, 0x45, 0xd0, 0x7b, 0x58, 0xc3, 0x3c, 0xbf, 0x48,
  0xe2, 0xc9, 0x3b, 0xb7, 0x39, 0xc5, 0xdb, 0x40, 0xb6, 0x31, 0xb9, 0x50,
  0x50, 0xb9, 0x2f, 0xb3, 0x3b, 0xdc, 0xbf, 0x33, 0xaf, 0x32, 0xc1, 0xd6,
  0x3b, 0xb0, 0x2f, 0xb8, 0x54, 0x4a, 0xb6, 0x30, 0xb4, 0x3f, 0xfd, 0xc0,
  0x36, 0xb5, 0x39, 0xcc, 0xd9, 0x41, 0xb9, 0x39, 0xc2, 0x59, 0x57, 0xc1,
  0x3e, 0xc2, 0x49, 0xe2, 0xd7, 0x4c, 0xcb, 0x47, 0xcf, 0x5b, 0xec, 0xe0,
  0x53, 0xcb, 0x4b, 0xca, 0x55, 0xf6, 0xdb, 0x48, 0xc0, 0x43, 0xc9, 0x5f,
  0x54, 0xc0, 0x3c, 0xbb, 0x43, 0xe8, 0xc8, 0x39, 0xb5, 0x39, 0xc6, 0xde,
  0x3d, 0xb4, 0x32, 0xba, 0x4f, 0x4c, 0xb9, 0x30, 0xb2, 0x3c, 0xec, 0xc1,
  0x33, 0xaf, 0x35, 0xc4, 0xd7, 0x3a, 0xb2, 0x31, 0xba, 0x56, 0x48, 0xb9,
  0x33, 0xb7, 0x44, 0x7e, 0xc3, 0x39, 0xb7, 0x3d, 0xcd, 0xe3, 0x42, 0xbd,
  0x3d, 0xc2, 0x58, 0x5d, 0xcb, 0x43, 0xc4, 0x4c, 0xd8, 0xf8, 0x58, 0xcd,
  0x4c, 0xcb, 0x4e, 0xda, 0x71, 0x5c, 0xcc, 0x46, 0xc4, 0x49, 0xdc, 0xdc,
  0x46, 0xbe, 0x3d, 0xc4, 0x59, 0x53, 0xbe, 0x38, 0xb8, 0x41, 0xe1, 0xc5,
  0x39, 0xb3, 0x38, 0xc4, 0xde, 0x3d, 0xb2, 0x32, 0xb9, 0x4e, 0x4b, 0xb7,
  0x30, 0xb3, 0x3d, 0xf2, 0xbf, 0x33, 0xb1, 0x36, 0xc9, 0xd9, 0x3a, 0xb4,
  0x33, 0xbc, 0x58, 0x49, 0xba, 0x36, 0xb9, 0x46, 0x7e, 0xc8, 0x3c, 0xba,
  0x3f, 0xcd, 0xe8, 0x4b, 0xc1, 0x41, 0xc7, 0x57, 0xfe, 0xd3, 0x4e, 0xc9,
  0x4d, 0xd0, 0x5e, 0x7c, 0xda, 0x4e, 0xca, 0x47, 0xcd, 0x5b, 0x68, 0xcc,
  0x40, 0xbf, 0x42, 0xd2, 0xe4, 0x42, 0xbd, 0x3a, 0xbf, 0x56, 0x50, 0xbd,
  0x36, 0xb6, 0x40, 0xe2, 0xc5, 0x36, 0xb2, 0x37, 0xc5, 0xde, 0x3c, 0xb3,
  0x32, 0xba, 0x52, 0x4a, 0xb7, 0x31, 0xb4, 0x3f, 0xef, 0xbf, 0x34, 0xb2,
  0x39, 0xc8, 0xd3, 0x3c, 0xb6, 0x37, 0xbe, 0x5c, 0x4c, 0xbd, 0x39, 0xbc,
  0x49, 0xf2, 0xcc, 0x3f, 0xbf, 0x44, 0xcf, 0xfd, 0x51, 0xca, 0x48, 0xcb,
  0x54, 0xe4, 0xeb, 0x57, 0xcf, 0x4d, 0xcc, 0x4f, 0xe0, 0xee, 0x51, 0xc7,
  0x44, 0xc6, 0x4f, 0x78, 0xcc, 0x3f, 0xbd, 0x3e, 0xce, 0xe5, 0x42, 0xba,
  0x38, 0xbe, 0x50, 0x4f, 0xbb, 0x35, 0xb6, 0x3e, 0xe8, 0xc2, 0x36, 0xb2,
  0x37, 0xc6, 0xda, 0x3c, 0xb3, 0x32, 0xba, 0x55, 0x4a, 0xb7, 0x33, 0xb5,
  0x41, 0x7e, 0xbf, 0x37, 0xb4, 0x3b, 0xcd, 0xd8, 0x3e, 0xb8, 0x39, 0xc2,
  0x5b, 0x4f, 0xc0, 0x3c, 0xbf, 0x4a, 0xee, 0xd1, 0x47, 0xc5, 0x47, 0xd0,
  0x68, 0x63, 0xd0, 0x4d, 0xcd, 0x4e, 0xd6, 0x67, 0x68, 0xd6, 0x4b, 0xc9,
  0x4a, 0xd1, 0x6e, 0x52, 0xc5, 0x3f, 0xc0, 0x4b, 0xfd, 0xcb, 0x3d, 0xba,
  0x3d, 0xcc, 0xe2, 0x41, 0xb8, 0x37, 0xbc, 0x53, 0x4e, 0xba, 0x34, 0xb6,
  0x3f, 0xee, 0xc1, 0x36, 0xb2, 0x38, 0xc8, 0xd6, 0x3c, 0xb3, 0x34, 0xbc,
  0x58, 0x49, 0xb9, 0x34, 0xb7, 0x44, 0x73, 0xc3, 0x38, 0xb7, 0x3d, 0xce,
  0xd9, 0x40, 0xbc, 0x3c, 0xc5, 0x5e, 0x55, 0xc6, 0x40, 0xc3, 0x4d, 0xe5,
  0xde, 0x4d, 0xca, 0x4b, 0xce, 0x5c, 0xfa, 0xe1, 0x54, 0xcd, 0x4d, 0xcd,
  0x56, 0xf3, 0xd9, 0x4a, 0xc4, 0x44, 0xcb, 0x67, 0x53, 0xc3, 0x3d, 0xbe,
  0x48, 0xf0, 0xca, 0x3c, 0xb8, 0x3b, 0xca, 0xdf, 0x3f, 0xb7, 0x36, 0xbc,
  0x54, 0x4c, 0xb9, 0x34, 0xb6, 0x40, 0xf7, 0xc1, 0x36, 0xb3, 0x39, 0xca,
  0xd6, 0x3c, 0xb4, 0x35, 0xbe, 0x5b, 0x49, 0xba, 0x36, 0xba, 0x47, 0x6f,
  0xc5, 0x3b, 0xba, 0x3f, 0xd2, 0xdd, 0x46, 0xbe, 0x3f, 0xc9, 0x5c, 0x5d,
  0xcc, 0x47, 0xc8, 0x4e, 0xdd, 0xf5, 0x5a, 0xd1, 0x4e, 0xcf, 0x52, 0xde,
  0x7d, 0x5c, 0xcf, 0x49, 0xc9, 0x4d, 0xdd, 0xde, 0x49, 0xc1, 0x3f, 0xc6,
  0x5d, 0x53, 0xc0, 0x3b, 0xbc, 0x46, 0xeb, 0xc8, 0x3b, 0xb7, 0x3b, 0xc8,
  0xde, 0x3e, 0xb6, 0x35, 0xbb, 0x57, 0x4c, 0xb9, 0x34, 0xb6, 0x42, 0xff,
  0xc1, 0x36, 0xb4, 0x3a, 0xcc, 0xd7, 0x3d, 0xb7, 0x37, 0xbf, 0x5e, 0x4a,
  0xbc, 0x38, 0xbc, 0x4a, 0x6e, 0xc9, 0x3e, 0xbe, 0x43, 0xd5, 0xe2, 0x4b,
  0xc5, 0x45, 0xcb, 0x5e, 0x6e, 0xd6, 0x4e, 0xcd, 0x51, 0xd7, 0x65, 0x74,
  0xdc, 0x54, 0xcd, 0x4d, 0xd1, 0x5e, 0x6b, 0xcf, 0x46, 0xc4, 0x47, 0xd7,
  0xe3, 0x48, 0xbe, 0x3d, 0xc3, 0x58, 0x54, 0xbe, 0x39, 0xba, 0x43, 0xee,
  0xc7, 0x3a, 0xb6, 0x3a, 0xc9, 0xdc, 0x3e, 0xb5, 0x35, 0xbd, 0x57, 0x4b,
  0xb9, 0x34, 0xb7, 0x43, 0x6f, 0xc1, 0x38, 0xb6, 0x3c, 0xcf, 0xd3, 0x3e,
  0xb8, 0x3a, 0xc3, 0x64, 0x4c, 0xbe, 0x3c, 0xbf, 0x4d, 0x72, 0xcc, 0x42,
  0xc1, 0x48, 0xd9, 0xed, 0x51, 0xcc, 0x4b, 0xcf, 0x5a, 0xef, 0xe8, 0x59,
  0xd3, 0x50, 0xd1, 0x58, 0xe1, 0xec, 0x56, 0xcc, 0x49, 0xca, 0x55, 0x7c,
  0xcf, 0x44, 0xbf, 0x43, 0xcf, 0xe5, 0x47, 0xbc, 0x3b, 0xbf, 0x56, 0x52,
  0xbe, 0x38, 0xb9, 0x42, 0xee, 0xc6, 0x39, 0xb6, 0x3a, 0xca, 0xdc, 0x3d,
  0xb6, 0x36, 0xbd, 0x59, 0x4a, 0xba, 0x35, 0xb9, 0x45, 0x6b, 0xc2, 0x39,
  0xb8, 0x3d, 0xd2, 0xd5, 0x3f, 0xbb, 0x3c, 0xc6, 0x69, 0x4f, 0xc1, 0x3f,
  0xc3, 0x50, 0x7d, 0xd0, 0x49, 0xc8, 0x4c, 0xd8, 0x7d, 0x5d, 0xd4, 0x4f,
  0xd2, 0x57, 0xde, 0x6e, 0x69, 0xda, 0x50, 0xcd, 0x4e, 0xd6, 0x71, 0x59,
  0xca, 0x44, 0xc5, 0x4d, 0xf3, 0xce, 0x41, 0xbd, 0x3f, 0xcd, 0xe5, 0x45,
  0xbb, 0x3a, 0xbf, 0x55, 0x51, 0xbd, 0x37, 0xb9, 0x43, 0xf1, 0xc5, 0x39,
  0xb6, 0x3b, 0xcc, 0xd9, 0x3d, 0xb7, 0x37, 0xbf, 0x5d, 0x49, 0xbb, 0x37,
  0xba, 0x48, 0x69, 0xc4, 0x3b, 0xba, 0x40, 0xd5, 0xd7, 0x42, 0xbd, 0x3f,
  0xc9, 0x69, 0x52, 0xc7, 0x44, 0xc7, 0x52, 0xfa, 0xda, 0x4f, 0xcd, 0x4f,
  0xd8, 0x66, 0x72, 0xdf, 0x59, 0xd4, 0x52, 0xd6, 0x5d, 0xef, 0xde, 0x4f,
  0xca, 0x49, 0xce, 0x64, 0x5a, 0xc8, 0x40, 0xc1, 0x4a, 0xec, 0xcd, 0x3f,
  0xbc, 0x3e, 0xcc, 0xe5, 0x43, 0xba, 0x39, 0xbe, 0x55, 0x4f, 0xbc, 0x37,
  0xb9, 0x43, 0xf8, 0xc4, 0x39, 0xb6, 0x3b, 0xcd, 0xd7, 0x3e, 0xb7, 0x38,
  0xc0, 0x60, 0x4b, 0xbc, 0x39, 0xbc, 0x4b, 0x6b, 0xc6, 0x3d, 0xbd, 0x43,
  0xd9, 0xda, 0x47, 0xc1, 0x42, 0xcd, 0x66, 0x5a, 0xcd, 0x48, 0xcc, 0x54,
  0xe9, 0xe9, 0x59, 0xd5, 0x54, 0xd5, 0x5c, 0xe4, 0xfb, 0x61, 0xd4, 0x4f,
  0xcd, 0x51, 0xdf, 0xe3, 0x4e, 0xc5, 0x45, 0xca, 0x5e, 0x5b, 0xc6, 0x3e,
  0xbf, 0x48, 0xea, 0xcc, 0x3e, 0xbb, 0x3d, 0xcb, 0xe4, 0x42, 0xba, 0x38,
  0xbe, 0x56, 0x4e, 0xbc, 0x37, 0xb9, 0x44, 0x7b, 0xc4, 0x39, 0xb7, 0x3c,
  0xcf, 0xd7, 0x3e, 0xb9, 0x3a, 0xc2, 0x62, 0x4c, 0xbd, 0x3b, 0xbe, 0x4d,
  0x6b, 0xc9, 0x3f, 0xbf, 0x46, 0xd9, 0xde, 0x4b, 0xc5, 0x47, 0xce, 0x63,
  0x65, 0xd3, 0x4e, 0xcf, 0x55, 0xdf, 0x74, 0x67, 0xdc, 0x55, 0xd3, 0x54,
  0xda, 0x68, 0x67, 0xd5, 0x4c, 0xca, 0x4d, 0xdc, 0xe7, 0x4d, 0xc4, 0x42,
  0xc8, 0x5b, 0x59, 0xc4, 0x3d, 0xbe, 0x47, 0xec, 0xcc, 0x3d, 0xba, 0x3d,
  0xcc, 0xe1, 0x42, 0xba, 0x39, 0xbf, 0x5a, 0x4f, 0xbc, 0x38, 0xba, 0x46,
  0x7d, 0xc5, 0x3b, 0xb9, 0x3e, 0xd0, 0xd8, 0x40, 0xbb, 0x3c, 0xc5, 0x63,
  0x4d, 0xc0, 0x3d, 0xc1, 0x4e, 0x6e, 0xcd, 0x44, 0xc4, 0x49, 0xdb, 0xec,
  0x50, 0xcc, 0x4a, 0xd1, 0x5c, 0x7b, 0xde, 0x56, 0xd2, 0x54, 0xd8, 0x62,
  0xf2, 0xe2, 0x58, 0xcf, 0x4e, 0xd0, 0x5d, 0x72, 0xd3, 0x4a, 0xc5, 0x49,
  0xd6, 0xe8, 0x4b, 0xc0, 0x3f, 0xc5, 0x5b, 0x58, 0xc2, 0x3c, 0xbd, 0x47,
  0xee, 0xca, 0x3d, 0xba, 0x3d, 0xcd, 0xdf, 0x41, 0xba, 0x3a, 0xc0, 0x5b,
  0x4d, 0xbd, 0x39, 0xbc, 0x48, 0x73, 0xc6, 0x3c, 0xbb, 0x3f, 0xd4, 0xd9,
  0x43, 0xbd, 0x3e, 0xc8, 0x67, 0x50, 0xc4, 0x40, 0xc4, 0x51, 0x7b, 0xd1,
  0x4a, 0xc8, 0x4d, 0xd9, 0xf6, 0x5c, 0xd0, 0x51, 0xd2, 0x5c, 0xe8, 0xf2,
  0x62, 0xd8, 0x55, 0xd4, 0x56, 0xe1, 0x7c, 0x59, 0xcf, 0x49, 0xcc, 0x53,
  0x7a, 0xd4, 0x46, 0xc4, 0x45, 0xd5, 0xef, 0x49, 0xc0, 0x3d, 0xc5, 0x57,
  0x55, 0xc2, 0x3b, 0xbd, 0x47, 0xed, 0xc9, 0x3d, 0xb9, 0x3e, 0xcc, 0xdb,
  0x43, 0xb9, 0x3b, 0xc0, 0x61, 0x4f, 0xbd, 0x3b, 0xbc, 0x4b, 0x75, 0xc6,
  0x3d, 0xbc, 0x43, 0xd7, 0xd9, 0x45, 0xbf, 0x40, 0xcc, 0x68, 0x54, 0xc9,
  0x44, 0xca, 0x53, 0x7d, 0xda, 0x4d, 0xce, 0x4f, 0xdb, 0x6d, 0x68, 0xdd,
  0x55, 0xd6, 0x56, 0xdc, 0x67, 0x71, 0xde, 0x53, 0xce, 0x4f, 0xd6, 0x6e,
  0x5c, 0xcc, 0x47, 0xc7, 0x4f, 0xef, 0xd0, 0x45, 0xbf, 0x44, 0xcf, 0xe6,
  0x48, 0xbe, 0x3d, 0xc3, 0x5a, 0x54, 0xc0, 0x3b, 0xbc, 0x47, 0xfa, 0xc9,
  0x3c, 0xba, 0x3e, 0xd0, 0xdc, 0x41, 0xbb, 0x3b, 0xc4, 0x5f, 0x4d, 0xbf,
  0x3c, 0xbf, 0x4c, 0x6d, 0xc9, 0x3f, 0xbe, 0x46, 0xda, 0xdc, 0x49, 0xc3,
  0x45, 0xce, 0x6b, 0x5b, 0xcd, 0x4a, 0xcd, 0x57, 0xee, 0xe4, 0x58, 0xd4,
  0x54, 0xd9, 0x60, 0xf1, 0xed, 0x5f, 0xd7, 0x53, 0xd3, 0x5a, 0xeb, 0xe1,
  0x52, 0xca, 0x4b, 0xce, 0x68, 0x5c, 0xc9, 0x44, 0xc4, 0x4e, 0xec, 0xce,
  0x43, 0xbe, 0x42, 0xcf, 0xe4, 0x47, 0xbd, 0x3c, 0xc3, 0x5b, 0x50, 0xbf,
  0x3b, 0xbd, 0x48, 0x73, 0xc8, 0x3c, 0xbb, 0x3f, 0xd4, 0xda, 0x41, 0xbc,
  0x3d, 0xc7, 0x67, 0x4d, 0xc0, 0x3d, 0xc2, 0x4f, 0x68, 0xcb, 0x42, 0xc2,
  0x4a, 0xdd, 0xdd, 0x4d, 0xc7, 0x4a, 0xd1, 0x6d, 0x65, 0xd3, 0x52, 0xd1,
  0x5b, 0xe3, 0xf8, 0x68, 0xdd, 0x5a, 0xd8, 0x59, 0xde, 0x6d, 0x68, 0xd9,
  0x4e, 0xce, 0x4f, 0xe1, 0xeb, 0x4e, 0xc9, 0x45, 0xcd, 0x5d, 0x58, 0xc9,
  0x3f, 0xc2, 0x4b, 0xf6, 0xce, 0x3f, 0xbd, 0x40, 0xcf, 0xe0, 0x45, 0xbc,
  0x3d, 0xc3, 0x5e, 0x51, 0xbf, 0x3c, 0xbd, 0x4b, 0x7b, 0xc7, 0x3e, 0xbb,
  0x42, 0xd4, 0xd6, 0x45, 0xbd, 0x3f, 0xc9, 0x6f, 0x50, 0xc3, 0x40, 0xc5,
  0x53, 0x6c, 0xce, 0x46, 0xc7, 0x4c, 0xe0, 0xeb, 0x51, 0xce, 0x4d, 0xd7,
  0x5f, 0x6c, 0xe3, 0x57, 0xd9, 0x57, 0xde, 0x64, 0xfd, 0xe9, 0x5b, 0xd5,
  0x52, 0xd5, 0x61, 0x78, 0xd6, 0x4d, 0xc9, 0x4e, 0xd8, 0xe5, 0x4e, 0xc4,
  0x44, 0xc9, 0x5f, 0x5a, 0xc6, 0x3f, 0xc0, 0x4b, 0xf5, 0xcd, 0x3f, 0xbd,
  0x40, 0xd2, 0xdf, 0x43, 0xbd, 0x3c, 0xc6, 0x5e, 0x4e, 0xbf, 0x3b, 0xbf,
  0x4c, 0x6b, 0xc9, 0x3e, 0xbd, 0x44, 0xda, 0xd8, 0x45, 0xbf, 0x42, 0xcc,
  0x75, 0x52, 0xc6, 0x45, 0xc8, 0x58, 0x76, 0xd2, 0x4c, 0xca, 0x51, 0xdd,
  0xed, 0x5d, 0xd3, 0x55, 0xd7, 0x61, 0xec, 0xef, 0x65, 0xdc, 0x58, 0xd7,
  0x5a, 0xe4, 0xfd, 0x5c, 0xd2, 0x4c, 0xcf, 0x57, 0x77, 0xd8, 0x49, 0xc7,
  0x48, 0xd8, 0xeb, 0x4b, 0xc3, 0x40, 0xc8, 0x5d, 0x57, 0xc4, 0x3e, 0xbf,
  0x4b, 0xf8, 0xca, 0x3f, 0xbc, 0x42, 0xd1, 0xd9, 0x45, 0xbc, 0x3e, 0xc6,
  0x6a, 0x4f, 0xbf, 0x3d, 0xc0, 0x4f, 0x67, 0xc9, 0x3f, 0xbf, 0x47, 0xdf,
  0xdb, 0x47, 0xc4, 0x44, 0xd1, 0x6c, 0x53, 0xcc, 0x48, 0xce, 0x58, 0x74,
  0xdc, 0x50, 0xd1, 0x54, 0xdf, 0x74, 0x6a, 0xde, 0x5b, 0xd9, 0x5d, 0xdd,
  0x6e, 0xfc, 0xdd, 0x5a, 0xcf, 0x54, 0xd6, 0x7b, 0x60, 0xcd, 0x4b, 0xc9,
  0x55, 0xf2, 0xd2, 0x48, 0xc3, 0x47, 0xd5, 0xe6, 0x4a, 0xc1, 0x3f, 0xc8,
  0x5d, 0x52, 0xc4, 0x3d, 0xc0, 0x4b, 0x71, 0xcb, 0x3e, 0xbd, 0x41, 0xd7,
  0xdc, 0x43, 0xbe, 0x3e, 0xc9, 0x6a, 0x4e, 0xc1, 0x3e, 0xc3, 0x52, 0x6a,
  0xca, 0x43, 0xc1, 0x4b, 0xdd, 0xda, 0x4c, 0xc6, 0x4a, 0xd1, 0x77, 0x5d,
  0xce, 0x4e, 0xcf, 0x5c, 0xf3, 0xe5, 0x5a, 0xd8, 0x58, 0xdd, 0x62, 0xfb,
  0xf4, 0x5e, 0xdc, 0x54, 0xd9, 0x5a, 0xf6, 0xea, 0x52, 0xce, 0x4c, 0xd4,
  0x67, 0x5c, 0xcc, 0x46, 0xc8, 0x50, 0xf8, 0xd0, 0x45, 0xc0, 0x46, 0xd3,
  0xe1, 0x49, 0xbf, 0x3f, 0xc6, 0x63, 0x54, 0xc1, 0x3e, 0xbf, 0x4d, 0x76,
  0xc9, 0x3f, 0xbd, 0x44, 0xd8, 0xda, 0x44, 0xbe, 0x3f, 0xcb, 0x6b, 0x4e,
  0xc4, 0x3f, 0xc7, 0x53, 0x66, 0xcd, 0x45, 0xc6, 0x4d, 0xe3, 0xdf, 0x4e,
  0xcb, 0x4d, 0xd5, 0x6f, 0x65, 0xd6, 0x55, 0xd4, 0x5e, 0xe5, 0xf1, 0x6b,
  0xdc, 0x5d, 0xd8, 0x5e, 0xdf, 0x79, 0x6d, 0xd9, 0x53, 0xcf, 0x56, 0xe3,
  0xe8, 0x52, 0xcb, 0x49, 0xcf, 0x61, 0x5a, 0xcb, 0x43, 0xc6, 0x4d, 0x7c,
  0xd1, 0x42, 0xc0, 0x43, 0xd6, 0xe3, 0x47, 0xbf, 0x3e, 0xc8, 0x63, 0x51,
  0xc1, 0x3e, 0xc0, 0x4e, 0x6f, 0xc9, 0x3f, 0xbe, 0x47, 0xd9, 0xd7, 0x47,
  0xbf, 0x43, 0xcc, 0x79, 0x51, 0xc6, 0x44, 0xc9, 0x58, 0x6a, 0xd0, 0x49,
  0xca, 0x4f, 0xe6, 0xea, 0x54, 0xd1, 0x50, 0xdb, 0x65, 0x6e, 0xe4, 0x5a,
  0xdc, 0x5a, 0xe1, 0x67, 0xfe, 0xea, 0x5d, 0xd7, 0x55, 0xd8, 0x63, 0x74,
  0xd8, 0x4f, 0xcb, 0x4f, 0xdc, 0xe5, 0x50, 0xc7, 0x47, 0xcc, 0x65, 0x5b,
  0xc8, 0x43, 0xc4, 0x4e, 0xfa, 0xcd, 0x42, 0xbf, 0x44, 0xd6, 0xdf, 0x46,
  0xbf, 0x3f, 0xc9, 0x64, 0x4f, 0xc3, 0x3e, 0xc3, 0x4f, 0x68, 0xcb, 0x40,
  0xc0, 0x48, 0xde, 0xd9, 0x48, 0xc2, 0x45, 0xcf, 0x7b, 0x55, 0xc9, 0x48,
  0xcb, 0x5c, 0x75, 0xd3, 0x4f, 0xcd, 0x56, 0xe0, 0xed, 0x5e, 0xd7, 0x58,
  0xdb, 0x63, 0xef, 0xf5, 0x65, 0xdf, 0x5a, 0xdb, 0x5b, 0xea, 0x7d, 0x5d,
  0xd6, 0x4e, 0xd3, 0x59, 0x73, 0xd9, 0x4b, 0xca, 0x4b, 0xdc, 0xeb, 0x4d,
  0xc6, 0x44, 0xcb, 0x61, 0x59, 0xc7, 0x41, 0xc2, 0x4e, 0xfb, 0xcc, 0x42,
  0xbe, 0x46, 0xd5, 0xdb, 0x47, 0xbe, 0x40, 0xc9, 0x6e, 0x50, 0xc2, 0x3f,
  0xc4, 0x52, 0x69, 0xcb, 0x42, 0xc3, 0x4a, 0xe2, 0xdc, 0x49, 0xc6, 0x48,
  0xd4, 0x71, 0x56, 0xcd, 0x4a, 0xcf, 0x5b, 0x73, 0xdd, 0x53, 0xd3, 0x57,
  0xe2, 0x78, 0x6a, 0xdf, 0x5d, 0xdb, 0x5e, 0xe1, 0x6f, 0x7a, 0xe0, 0x5b,
  0xd4, 0x57, 0xdb, 0x79, 0x5f, 0xd0, 0x4d, 0xcd, 0x58, 0xfd, 0xd6, 0x4a,
  0xc7, 0x4a, 0xd9, 0xe8, 0x4c, 0xc5, 0x42, 0xcb, 0x5f, 0x55, 0xc7, 0x3f,
  0xc4, 0x4e, 0x71, 0xcd, 0x41, 0xbf, 0x46, 0xd9, 0xdb, 0x47, 0xbf, 0x41,
  0xcb, 0x70, 0x51, 0xc4, 0x42, 0xc5, 0x57, 0x6b, 0xcc, 0x46, 0xc4, 0x4d,
  0xe0, 0xdb, 0x4d, 0xc8, 0x4c, 0xd5, 0x78, 0x5d, 0xd1, 0x4f, 0xd3, 0x5d,
  0xfb, 0xe6, 0x5a, 0xda, 0x59, 0xe1, 0x67, 0x7c, 0xf1, 0x5f, 0xde, 0x58,
  0xdc, 0x5e, 0xf9, 0xe8, 0x56, 0xd1, 0x4f, 0xd7, 0x6d, 0x5e, 0xce, 0x4a,
  0xcb, 0x55, 0xf7, 0xd3, 0x49, 0xc4, 0x4a, 0xd7, 0xe3, 0x4c, 0xc2, 0x43,
  0xca, 0x66, 0x56, 0xc5, 0x40, 0xc3, 0x4f, 0x74, 0xcc, 0x42, 0xc0, 0x47,
  0xdb, 0xdc, 0x47, 0xc1, 0x42, 0xce, 0x6e, 0x50, 0xc7, 0x43, 0xc9, 0x57,
  0x67, 0xcf, 0x48, 0xc9, 0x4e, 0xe6, 0xe0, 0x50, 0xcd, 0x4e, 0xd8, 0x6f,
  0x65, 0xd7, 0x55, 0xd7, 0x5f, 0xeb, 0xf3, 0x68, 0xde, 0x5e, 0xdc, 0x60,
  0xe5, 0x7b, 0x6b, 0xdc, 0x56, 0xd4, 0x59, 0xe8, 0xe8, 0x55, 0xcd, 0x4c,
  0xd3, 0x68, 0x5c, 0xcd, 0x47, 0xc9, 0x52, 0xfe, 0xd2, 0x47, 0xc4, 0x48,
  0xd8, 0xe2, 0x4a, 0xc2, 0x42, 0xcb, 0x68, 0x54, 0xc5, 0x40, 0xc4, 0x51,
  0x6e, 0xcc, 0x42, 0xc1, 0x49, 0xdd, 0xda, 0x49, 0xc3, 0x46, 0xcf, 0x7a,
  0x53, 0xc8, 0x46, 0xcb, 0x5b, 0x6a, 0xd2, 0x4b, 0xcc, 0x52, 0xe7, 0xe7,
  0x56, 0xd2, 0x53, 0xdc, 0x6a, 0x6d, 0xe2, 0x5b, 0xdc, 0x5e, 0xe5, 0x6d,
  0x7a, 0xea, 0x5f, 0xda, 0x59, 0xdc, 0x68, 0x70, 0xdb, 0x52, 0xcf, 0x53,
  0xe0, 0xe9, 0x53, 0xcb, 0x4a, 0xcf, 0x66, 0x5c, 0xcb, 0x46, 0xc7, 0x51,
  0xfb, 0xcf, 0x46, 0xc2, 0x48, 0xd8, 0xdf, 0x4a, 0xc2, 0x42, 0xcb, 0x69,
  0x53, 0xc5, 0x41, 0xc5, 0x53, 0x6b, 0xcc, 0x44, 0xc3, 0x4a, 0xe0, 0xdb,
  0x4a, 0xc5, 0x48, 0xd2, 0x78, 0x55, 0xcb, 0x49, 0xce, 0x5c, 0x6e, 0xd7,
  0x4f, 0xcf, 0x56, 0xe6, 0xef, 0x5d, 0xd8, 0x59, 0xdc, 0x67, 0xf6, 0xee,
  0x65, 0xdf, 0x5d, 0xdd, 0x61, 0xec, 0xf4, 0x5f, 0xd8, 0x54, 0xd6, 0x5f,
  0x78, 0xda, 0x4f, 0xcc, 0x4f, 0xde, 0xea, 0x50, 0xc9, 0x48, 0xce, 0x64,
  0x5a, 0xca, 0x45, 0xc7, 0x50, 0x7b, 0xcf, 0x45, 0xc3, 0x48, 0xda, 0xde,
  0x4a, 0xc2, 0x43, 0xcc, 0x6d, 0x53, 0xc6, 0x43, 0xc7, 0x56, 0x6a, 0xcd,
  0x46, 0xc5, 0x4d, 0xe2, 0xdc, 0x4c, 0xc8, 0x4b, 0xd5, 0x7a, 0x59, 0xce,
  0x4d, 0xd0, 0x5e, 0x76, 0xdc, 0x55, 0xd4, 0x5a, 0xe5, 0x7d, 0x68, 0xdf,
  0x5d, 0xde, 0x60, 0xe9, 0x73, 0x70, 0xe4, 0x5c, 0xd9, 0x59, 0xe1, 0x7a,
  0x60, 0xd5, 0x4f, 0xd1, 0x5b, 0x7e, 0xd9, 0x4d, 0xca, 0x4d, 0xdb, 0xe8,
  0x4f, 0xc8, 0x47, 0xcd, 0x66, 0x5a, 0xc9, 0x44, 0xc6, 0x51, 0x78, 0xce,
  0x45, 0xc3, 0x49, 0xdb, 0xdd, 0x49, 0xc3, 0x44, 0xce, 0x6f, 0x53, 0xc7,
  0x44, 0xc9, 0x57, 0x69, 0xce, 0x48, 0xc8, 0x4e, 0xe6, 0xde, 0x4e, 0xcb,
  0x4d, 0xd8, 0x78, 0x5d, 0xd2, 0x50, 0xd5, 0x5f, 0xfc, 0xe4, 0x5c, 0xda,
  0x5c, 0xe2, 0x6e, 0x7d, 0xeb, 0x63, 0xde, 0x5d, 0xde, 0x65, 0xf9, 0xe7,
  0x5a, 0xd5, 0x54, 0xdb, 0x6f, 0x5f, 0xd2, 0x4d, 0xce, 0x58, 0x7d, 0xd7,
  0x4b, 0xc9, 0x4c, 0xdb, 0xe7, 0x4e, 0xc6, 0x46, 0xcd, 0x67, 0x58, 0xc8,
  0x44, 0xc7, 0x53, 0x72, 0xce, 0x45, 0xc3, 0x4a, 0xdd, 0xdc, 0x4a, 0xc4,
  0x46, 0xcf, 0x76, 0x54, 0xc9, 0x46, 0xcb, 0x5a, 0x69, 0xd0, 0x4a, 0xcb,
  0x51, 0xe8, 0xe1, 0x52, 0xce, 0x50, 0xdb, 0x72, 0x63, 0xda, 0x56, 0xda,
  0x5f, 0xf1, 0xf3, 0x65, 0xe0, 0x5e, 0xe0, 0x63, 0xec, 0x7c, 0x6a, 0xde,
  0x59, 0xd8, 0x5c, 0xec, 0xe9, 0x58, 0xd0, 0x4f, 0xd7, 0x6c, 0x5f, 0xcf,
  0x4b, 0xcc, 0x56, 0xfc, 0xd5, 0x4a, 0xc7, 0x4b, 0xdb, 0xe4, 0x4d, 0xc6,
  0x46, 0xcd, 0x69, 0x57, 0xc8, 0x44, 0xc7, 0x54, 0x6e, 0xce, 0x46, 0xc5,
  0x4b, 0xdf, 0xdc, 0x4b, 0xc6, 0x48, 0xd2, 0x79, 0x55, 0xcb, 0x49, 0xcd,
  0x5d, 0x6c, 0xd4, 0x4d, 0xcd, 0x55, 0xe8, 0xe6, 0x58, 0xd3, 0x55, 0xdd,
  0x6e, 0x6d, 0xe0, 0x5d, 0xdd, 0x5f, 0xe9, 0x73, 0x75, 0xe9, 0x60, 0xdd,
  0x5c, 0xe0, 0x6c, 0x6e, 0xde, 0x55, 0xd4, 0x57, 0xe6, 0xeb, 0x56, 0xce,
  0x4d, 0xd4, 0x6a, 0x5e, 0xce, 0x49, 0xcb, 0x55, 0xfe, 0xd3, 0x49, 0xc6,
  0x4b, 0xdb, 0xe2, 0x4c, 0xc5, 0x46, 0xce, 0x6c, 0x56, 0xc8, 0x45, 0xc8,
  0x56, 0x6c, 0xce, 0x47, 0xc6, 0x4d, 0xe3, 0xdd, 0x4c, 0xc8, 0x4a, 0xd5,
  0x7a, 0x57, 0xcd, 0x4b, 0xcf, 0x5e, 0x6d, 0xd8, 0x50, 0xd1, 0x58, 0xe9,
  0xee, 0x5d, 0xd9, 0x5a, 0xde, 0x6a, 0xfe, 0xec, 0x65, 0xe0, 0x5f, 0xe0,
  0x67, 0xf0, 0xf1, 0x63, 0xda, 0x58, 0xda, 0x65, 0x78, 0xdc, 0x53, 0xcf,
  0x54, 0xe1, 0xeb, 0x54, 0xcc, 0x4b, 0xd1, 0x68, 0x5d, 0xcd, 0x48, 0xca,
  0x54, 0x7b, 0xd2, 0x48, 0xc6, 0x4b, 0xdc, 0xe0, 0x4c, 0xc6, 0x47, 0xcf,
  0x6e, 0x55, 0xc9, 0x45, 0xca, 0x58, 0x6b, 0xcf, 0x48, 0xc8, 0x4e, 0xe5,
  0xdd, 0x4e, 0xca, 0x4c, 0xd8, 0x7b, 0x5a, 0xcf, 0x4e, 0xd3, 0x60, 0x73,
  0xdd, 0x56, 0xd6, 0x5b, 0xe8, 0xfc, 0x67, 0xdf, 0x5e, 0xdf, 0x65, 0xed,
  0x7b, 0x6e, 0xe5, 0x5e, 0xdc, 0x5d, 0xe6, 0xff, 0x63, 0xd8, 0x53, 0xd5,
  0x5e, 0x7c, 0xdb, 0x4f, 0xcd, 0x50, 0xde, 0xea, 0x52, 0xcb, 0x4a, 0xcf,
  0x68, 0x5c, 0xcc, 0x47, 0xc9, 0x54, 0x79, 0xd1, 0x48, 0xc6, 0x4b, 0xdd,
  0xdf, 0x4c, 0xc6, 0x47, 0xd0, 0x6f, 0x56, 0xca, 0x47, 0xcb, 0x5a, 0x6b,
  0xd1, 0x4a, 0xca, 0x50, 0xe7, 0xdf, 0x50, 0xcd, 0x4f, 0xda, 0x79, 0x5e,
  0xd5, 0x52, 0xd7, 0x62, 0xff, 0xe4, 0x5c, 0xdb, 0x5d, 0xe5, 0x73, 0x77,
  0xea, 0x64, 0xe0, 0x5f, 0xe2, 0x6b, 0xfe, 0xe8, 0x5c, 0xd8, 0x58, 0xde,
  0x76, 0x63, 0xd5, 0x50, 0xd1, 0x5b, 0xff, 0xda, 0x4e, 0xcb, 0x4f, 0xdd,
  0xe9, 0x50, 0xca, 0x49, 0xcf, 0x69, 0x5b, 0xcb, 0x47, 0xc9, 0x55, 0x75,
  0xd1, 0x48, 0xc7, 0x4c, 0xde, 0xde, 0x4c, 0xc7, 0x49, 0xd2, 0x76, 0x57,
  0xcb, 0x49, 0xcd, 0x5c, 0x6b, 0xd3, 0x4c, 0xcd, 0x54, 0xe9, 0xe3, 0x54,
  0xcf, 0x52, 0xdc, 0x75, 0x64, 0xda, 0x58, 0xdb, 0x63, 0xf4, 0xee, 0x65,
  0xe0, 0x5f, 0xe3, 0x68, 0xf1, 0xf9, 0x6a, 0xe0, 0x5d, 0xdc, 0x60, 0xef,
  0xeb, 0x5b, 0xd5, 0x54, 0xda, 0x6d, 0x62, 0xd3, 0x4e, 0xcf, 0x59, 0xfd,
  0xd9, 0x4d, 0xca, 0x4e, 0xdd, 0xe8, 0x4f, 0xc9, 0x49, 0xcf, 0x69, 0x5a,
  0xcb, 0x47, 0xca, 0x56, 0x73, 0xd1, 0x49, 0xc7, 0x4d, 0xdf, 0xdf, 0x4d,
  0xc8, 0x4a, 0xd4, 0x77, 0x58, 0xcd, 0x4b, 0xcf, 0x5d, 0x6d, 0xd6, 0x4e,
  0xcf, 0x56, 0xea, 0xe9, 0x59, 0xd5, 0x56, 0xde, 0x6f, 0x6d, 0xdf, 0x5d,
  0xdd, 0x62, 0xeb, 0x7c, 0x71, 0xe8, 0x62, 0xdf, 0x60, 0xe5, 0x73, 0x6f,
  0xdf, 0x5a, 0xd7, 0x5c, 0xe9, 0xec, 0x5a, 0xd1, 0x50, 0xd7, 0x6c, 0x61,
  0xd1, 0x4c, 0xcd, 0x58, 0xfd, 0xd7, 0x4c, 0xc9, 0x4d, 0xdd, 0xe7, 0x4f,
  0xc9, 0x49, 0xcf, 0x6b, 0x59, 0xcb, 0x47, 0xcb, 0x57, 0x6f, 0xd1, 0x49,
  0xc9, 0x4e, 0xe2, 0xdf, 0x4e, 0xca, 0x4c, 0xd6, 0x78, 0x5a, 0xcf, 0x4d,
  0xd1, 0x5f, 0x70, 0xda, 0x52, 0xd3, 0x59, 0xe9, 0xef, 0x5e, 0xda, 0x5a,
  0xdf, 0x6b, 0x7b, 0xeb, 0x63, 0xe1, 0x61, 0xe5, 0x6b, 0xfa, 0xf0, 0x64,
  0xdd, 0x5b, 0xde, 0x68, 0x75, 0xdf, 0x56, 0xd4, 0x58, 0xe4, 0xed, 0x58,
  0xcf, 0x4e, 0xd5, 0x6a, 0x60, 0xcf, 0x4b, 0xcc, 0x57, 0xfd, 0xd6, 0x4b,
  0xc9, 0x4d, 0xdd, 0xe5, 0x4f, 0xc9, 0x49, 0xd0, 0x6d, 0x5a, 0xcc, 0x48,
  0xcc, 0x59, 0x6f, 0xd3, 0x4b, 0xca, 0x4f, 0xe5, 0xe1, 0x50, 0xcc, 0x4e,
  0xd9, 0x77, 0x5d, 0xd2, 0x4f, 0xd5, 0x60, 0x77, 0xde, 0x57, 0xd7, 0x5c,
  0xe8, 0xfa, 0x67, 0xdf, 0x5e, 0xe0, 0x67, 0xf0, 0xfc, 0x6d, 0xe5, 0x5f,
  0xdf, 0x61, 0xeb, 0xfb, 0x65, 0xdb, 0x57, 0xd9, 0x61, 0x7c, 0xde, 0x54,
  0xd0, 0x54, 0xe1, 0xed, 0x56, 0xcd, 0x4d, 0xd3, 0x69, 0x5f, 0xce, 0x4b,
  0xcc, 0x57, 0x7d, 0xd5, 0x4b, 0xc9, 0x4e, 0xde, 0xe4, 0x4f, 0xc9, 0x4a,
  0xd2, 0x6f, 0x5a, 0xcc, 0x4a, 0xcd, 0x5b, 0x6f, 0xd4, 0x4c, 0xcc, 0x52,
  0xe7, 0xe4, 0x53, 0xce, 0x50, 0xdb, 0x76, 0x60, 0xd6, 0x54, 0xd8, 0x62,
  0xff, 0xe5, 0x5d, 0xdc, 0x5e, 0xe7, 0x75, 0x73, 0xe9, 0x64, 0xe2, 0x63,
  0xe7, 0x6f, 0x7b, 0xe9, 0x5f, 0xdb, 0x5c, 0xe2, 0x78, 0x65, 0xd9, 0x54,
  0xd6, 0x5e, 0xfe, 0xdd, 0x51, 0xce, 0x52, 0xdf, 0xec, 0x54, 0xcd, 0x4c,
  0xd2, 0x69, 0x5d, 0xce, 0x4a, 0xcc, 0x57, 0x7a, 0xd5, 0x4b, 0xc9, 0x4e,
  0xdf, 0xe3, 0x4f, 0xca, 0x4b, 0xd4, 0x70, 0x5a, 0xcd, 0x4b, 0xce, 0x5c,
  0x6f, 0xd6, 0x4e, 0xce, 0x55, 0xe8, 0xe7, 0x57, 0xd2, 0x54, 0xdd, 0x73,
  0x66, 0xdb, 0x59, 0xdb, 0x63, 0xf5, 0xee, 0x65, 0xe0, 0x60, 0xe5, 0x6b,
  0xf7, 0xf6, 0x69, 0xe2, 0x5f, 0xdf, 0x65, 0xf5, 0xeb, 0x5d, 0xd8, 0x58,
  0xdd, 0x71, 0x65, 0xd7, 0x51, 0xd2, 0x5c, 0xfd, 0xdb, 0x4f, 0xcd, 0x50,
  0xde, 0xea, 0x53, 0xcc, 0x4c, 0xd2, 0x6a, 0x5d, 0xce, 0x4a, 0xcc, 0x58,
  0x78, 0xd4, 0x4b, 0xca, 0x4f, 0xe1, 0xe3, 0x4f, 0xcb, 0x4c, 0xd6, 0x74,
  0x5b, 0xcf, 0x4d, 0xd0, 0x5e, 0x70, 0xd9, 0x50, 0xd0, 0x58, 0xea, 0xeb,
  0x5b, 0xd6, 0x58, 0xdf, 0x6f, 0x6d, 0xe1, 0x5d, 0xde, 0x64, 0xed, 0xff,
  0x6e, 0xe8, 0x63, 0xe2, 0x64, 0xe9, 0x77, 0x6e, 0xe2, 0x5c, 0xdb, 0x5e,
  0xec, 0xed, 0x5c, 0xd5, 0x54, 0xda, 0x6d, 0x64, 0xd5, 0x4f, 0xd0, 0x5a,
  0xfd, 0xda, 0x4e, 0xcc, 0x4f, 0xde, 0xe9, 0x52, 0xcb, 0x4b, 0xd3, 0x6c,
  0x5c, 0xce, 0x4a, 0xcd, 0x5a, 0x74, 0xd5, 0x4c, 0xcb, 0x50, 0xe4, 0xe3,
  0x51, 0xcc, 0x4e, 0xd8, 0x77, 0x5c, 0xd1, 0x4f, 0xd4, 0x60, 0x72, 0xdc,
  0x55, 0xd5, 0x5b, 0xeb, 0xef, 0x5f, 0xdb, 0x5c, 0xe1, 0x6d, 0x7a, 0xeb,
  0x65, 0xe3, 0x64, 0xe8, 0x6e, 0xfc, 0xef, 0x66, 0xdf, 0x5e, 0xe0, 0x6b,
  0x76, 0xe0, 0x59, 0xd7, 0x5a, 0xe8, 0xee, 0x5a, 0xd2, 0x51, 0xd8, 0x6c,
  0x62, 0xd3, 0x4e, 0xcf, 0x5a, 0xff, 0xd9, 0x4e, 0xcc, 0x4f, 0xdf, 0xe7,
  0x51, 0xcb, 0x4c, 0xd4, 0x6e, 0x5c, 0xce, 0x4b, 0xce, 0x5b, 0x71, 0xd5,
  0x4d, 0xcd, 0x53, 0xe7, 0xe3, 0x53, 0xce, 0x50, 0xdb, 0x78, 0x5e, 0xd5,
  0x52, 0xd7, 0x63, 0x78, 0xdf, 0x5a, 0xd9, 0x5e, 0xea, 0xf9, 0x69, 0xe1,
  0x60, 0xe3, 0x6a, 0xf2, 0xfa, 0x6e, 0xe7, 0x63, 0xe1, 0x65, 0xed, 0xfa,
  0x67, 0xdd, 0x5a, 0xdc, 0x65, 0x7b, 0xdf, 0x57, 0xd4, 0x57, 0xe4, 0xee,
  0x59, 0xd0, 0x4f, 0xd6, 0x6b, 0x60, 0xd2, 0x4d, 0xce, 0x59, 0x7d, 0xd8,
  0x4d, 0xcc, 0x4f, 0xe0, 0xe7, 0x51, 0xcc, 0x4c, 0xd5, 0x6f, 0x5b, 0xce,
  0x4c, 0xcf, 0x5c, 0x70, 0xd7, 0x4e, 0xce, 0x55, 0xe9, 0xe5, 0x56, 0xd1,
  0x53, 0xdd, 0x78, 0x62, 0xd9, 0x57, 0xda, 0x66, 0x7e, 0xe6, 0x5e, 0xde,
  0x60, 0xe9, 0x78, 0x73, 0xea, 0x66, 0xe4, 0x66, 0xea, 0x71, 0x7a, 0xea,
  0x61, 0xde, 0x5e, 0xe5, 0x7a, 0x67, 0xdb, 0x57, 0xd8, 0x60, 0x7e, 0xde,
  0x54, 0xd1, 0x55, 0xe2, 0xed, 0x57, 0xcf, 0x4e, 0xd6, 0x6b, 0x5f, 0xd0,
  0x4c, 0xce, 0x5a, 0x7a, 0xd8, 0x4d, 0xcc, 0x50, 0xe3, 0xe5, 0x51, 0xcc,
  0x4d, 0xd7, 0x74, 0x5c, 0xcf, 0x4d, 0xd1, 0x5e, 0x6f, 0xd9, 0x50, 0xd0,
  0x58, 0xea, 0xe7, 0x59, 0xd4, 0x57, 0xde, 0x77, 0x68, 0xdd, 0x5b, 0xdd,
  0x66, 0xf7, 0xee, 0x67, 0xe3, 0x64, 0xe7, 0x6d, 0xf9, 0xf5, 0x6b, 0xe5,
  0x62, 0xe2, 0x68, 0xf6, 0xed, 0x5f, 0xdb, 0x5a, 0xdf, 0x72, 0x67, 0xd9,
  0x54, 0xd5, 0x5e, 0xfd, 0xdd, 0x52, 0xcf, 0x53, 0xe1, 0xed, 0x56, 0xce,
  0x4e, 0xd5, 0x6b, 0x5e, 0xd0, 0x4c, 0xce, 0x5a, 0x77, 0xd7, 0x4d, 0xcc,
  0x52, 0xe4, 0xe5, 0x53, 0xcd, 0x4e, 0xd8, 0x76, 0x5d, 0xd1, 0x4f, 0xd3,
  0x5f, 0x71, 0xdb, 0x53, 0xd4, 0x5a, 0xec, 0xec, 0x5d, 0xd9, 0x5a, 0xe1,
  0x72, 0x6e, 0xe3, 0x5f, 0xe0, 0x66, 0xef, 0xfe, 0x6f, 0xe9, 0x66, 0xe5,
  0x67, 0xec, 0x79, 0x70, 0xe5, 0x5e, 0xdd, 0x60, 0xee, 0xee, 0x5e, 0xd8,
  0x57, 0xdc, 0x6f, 0x67, 0xd8, 0x52, 0xd3, 0x5d, 0xfd, 0xdc, 0x51, 0xce,
  0x53, 0xe0, 0xeb, 0x55, 0xce, 0x4e, 0xd6, 0x6d, 0x5e, 0xd0, 0x4c, 0xcf,
  0x5b, 0x75, 0xd8, 0x4e, 0xcd, 0x53, 0xe6, 0xe5, 0x54, 0xce, 0x50, 0xda,
  0x78, 0x5e, 0xd4, 0x51, 0xd6, 0x63, 0x74, 0xdd, 0x57, 0xd7, 0x5d, 0xec,
  0xf0, 0x61, 0xdd, 0x5e, 0xe4, 0x6f, 0x7a, 0xec, 0x67, 0xe5, 0x67, 0xea,
  0x70, 0xfe, 0xf0, 0x68, 0xe2, 0x61, 0xe3, 0x6d, 0x77, 0xe4, 0x5c, 0xda,
  0x5d, 0xea, 0xef, 0x5d, 0xd6, 0x54, 0xdb, 0x6c, 0x65, 0xd6, 0x50, 0xd2,
  0x5c, 0xfe, 0xdc, 0x50, 0xce, 0x52, 0xe1, 0xea, 0x55, 0xcd, 0x4e, 0xd6,
  0x6e, 0x5e, 0xd0, 0x4d, 0xcf, 0x5d, 0x74, 0xd8, 0x4f, 0xce, 0x55, 0xe8,
  0xe6, 0x56, 0xd0, 0x53, 0xdc, 0x78, 0x60, 0xd7, 0x55, 0xd9, 0x65, 0x78,
  0xe2, 0x5b, 0xdb, 0x5f, 0xec, 0xfa, 0x69, 0xe3, 0x62, 0xe6, 0x6b, 0xf6,
  0xfa, 0x6e, 0xe9, 0x66, 0xe5, 0x68, 0xee, 0xfb, 0x69, 0xdf, 0x5d, 0xde,
  0x68, 0x7c, 0xe3, 0x5a, 0xd7, 0x5a, 0xe6, 0xef, 0x5c, 0xd3, 0x52, 0xd9,
  0x6c, 0x64, 0xd5, 0x4f, 0xd1, 0x5b, 0xff, 0xdb, 0x4f, 0xce, 0x53, 0xe2,
  0xe9, 0x55, 0xce, 0x4e, 0xd7, 0x70, 0x5e, 0xd1, 0x4e, 0xd1, 0x5e, 0x73,
  0xd9, 0x50, 0xd0, 0x58, 0xea, 0xe7, 0x58, 0xd3, 0x56, 0xde, 0x78, 0x64,
  0xdb, 0x59, 0xdc, 0x67, 0x7d, 0xe8, 0x5f, 0xdf, 0x62, 0xeb, 0x79, 0x72,
  0xeb, 0x67, 0xe7, 0x68, 0xec, 0x74, 0x79, 0xec, 0x64, 0xe0, 0x60, 0xe8,
  0x7a, 0x6a, 0xde, 0x5a, 0xdb, 0x63, 0xfe, 0xe1, 0x58, 0xd4, 0x58, 0xe4,
  0xef, 0x5a, 0xd2, 0x51, 0xd8, 0x6c, 0x63, 0xd4, 0x4f, 0xd0, 0x5c, 0x7d,
  0xda, 0x4f, 0xce, 0x53, 0xe3, 0xe8, 0x55, 0xce, 0x4f, 0xd9, 0x73, 0x5e,
  0xd2, 0x4f, 0xd4, 0x5f, 0x72, 0xdb, 0x53, 0xd3, 0x5a, 0xeb, 0xea, 0x5b,
  0xd7, 0x59, 0xe0, 0x75, 0x6a, 0xde, 0x5d, 0xde, 0x68, 0xf9, 0xef, 0x67,
  0xe5, 0x65, 0xe9, 0x6f, 0xfb, 0xf5, 0x6c, 0xe7, 0x64, 0xe5, 0x6a, 0xf8,
  0xee, 0x62, 0xdd, 0x5d, 0xe2, 0x73, 0x6a, 0xdc, 0x57, 0xd8, 0x5f, 0xfb,
  0xe0, 0x56, 0xd2, 0x57, 0xe2, 0xee, 0x59, 0xd1, 0x50, 0xd8, 0x6d, 0x62,
  0xd3, 0x4e, 0xd0, 0x5c, 0x7a, 0xda, 0x4f, 0xce, 0x54, 0xe5, 0xe8, 0x56,
  0xcf, 0x50, 0xda, 0x75, 0x5f, 0xd4, 0x51, 0xd6, 0x62, 0x74, 0xdd, 0x56,
  0xd6, 0x5c, 0xec, 0xed, 0x5e, 0xdb, 0x5c, 0xe3, 0x73, 0x6e, 0xe5, 0x60,
  0xe3, 0x68, 0xf1, 0xfd, 0x6f, 0xeb, 0x67, 0xe7, 0x69, 0xee, 0x7a, 0x71,
  0xe7, 0x61, 0xdf, 0x64, 0xef, 0xef, 0x60, 0xda, 0x5a, 0xde, 0x70, 0x69,
  0xda, 0x55, 0xd6, 0x5e, 0xfb, 0xde, 0x55, 0xd1, 0x56, 0xe2, 0xed, 0x59,
  0xd0, 0x50, 0xd8, 0x6d, 0x60, 0xd3, 0x4f, 0xd1, 0x5d, 0x79, 0xda, 0x50,
  0xcf, 0x56, 0xe7, 0xe8, 0x57, 0xd1, 0x53, 0xdc, 0x77, 0x60, 0xd7, 0x54,
  0xd8, 0x64, 0x76, 0xdf, 0x59, 0xd9, 0x5e, 0xed, 0xf2, 0x64, 0xde, 0x5f,
  0xe5, 0x6f, 0x79, 0xec, 0x68, 0xe7, 0x68, 0xec, 0x73, 0x7e, 0xf1, 0x6a,
  0xe5, 0x63, 0xe7, 0x6f, 0x77, 0xe7, 0x5e, 0xdc, 0x5e, 0xeb, 0xf2, 0x5f,
  0xd9, 0x57, 0xdc, 0x6d, 0x68, 0xd9, 0x53, 0xd5, 0x5d, 0xfc, 0xde, 0x53,
  0xd0, 0x55, 0xe3, 0xed, 0x58, 0xd0, 0x50, 0xd8, 0x6e, 0x60, 0xd3, 0x4f,
  0xd2, 0x5e, 0x77, 0xdb, 0x52, 0xd1, 0x57, 0xe8, 0xe9, 0x59, 0xd3, 0x55,
  0xdd, 0x78, 0x64, 0xd9, 0x57, 0xdb, 0x66, 0x7b, 0xe4, 0x5d, 0xdc, 0x61,
  0xec, 0xfa, 0x6b, 0xe4, 0x64, 0xe7, 0x6d, 0xf7, 0xf8, 0x6f, 0xea, 0x68,
  0xe8, 0x6a, 0xf2, 0xfa, 0x6b, 0xe3, 0x5f, 0xe1, 0x69, 0x7c, 0xe6, 0x5c,
  0xda, 0x5c, 0xe9, 0xf3, 0x5e, 0xd7, 0x55, 0xdb, 0x6c, 0x67, 0xd8, 0x52,
  0xd4, 0x5d, 0xfd, 0xdd, 0x53, 0xd0, 0x55, 0xe3, 0xec, 0x58, 0xd0, 0x50,
  0xd9, 0x6f, 0x61, 0xd4, 0x50, 0xd4, 0x5f, 0x77, 0xdc, 0x54, 0xd3, 0x59,
  0xea, 0xea, 0x5b, 0xd6, 0x58, 0xdf, 0x77, 0x67, 0xdc, 0x5a, 0xdd, 0x68,
  0xfe, 0xea, 0x62, 0xdf, 0x64, 0xeb, 0x7b, 0x73, 0xeb, 0x68, 0xe8, 0x6a,
  0xee, 0x77, 0x79, 0xed, 0x67, 0xe3, 0x64, 0xeb, 0x7c, 0x6b, 0xe0, 0x5c,
  0xdd, 0x65, 0xfe, 0xe5, 0x5a, 0xd8, 0x5a, 0xe6, 0xf3, 0x5d, 0xd5, 0x54,
  0xda, 0x6d, 0x67, 0xd7, 0x51, 0xd3, 0x5d, 0xfe, 0xdd, 0x53, 0xd0, 0x56,
  0xe5, 0xeb, 0x58, 0xd1, 0x52, 0xda, 0x71, 0x61, 0xd6, 0x52, 0xd6, 0x61,
  0x77, 0xdd, 0x56, 0xd5, 0x5b, 0xeb, 0xec, 0x5d, 0xd9, 0x5a, 0xe2, 0x75,
  0x6b, 0xe0, 0x5e, 0xe0, 0x68, 0xf9, 0xf0, 0x68, 0xe5, 0x66, 0xeb, 0x70,
  0xfe, 0xf4, 0x6c, 0xe9, 0x67, 0xe8, 0x6d, 0xfa, 0xef, 0x65, 0xdf, 0x5f,
  0xe6, 0x75, 0x6b, 0xde, 0x5a, 0xdb, 0x62, 0xfc, 0xe3, 0x59, 0xd6, 0x59,
  0xe5, 0xf2, 0x5c, 0xd4, 0x53, 0xda, 0x6d, 0x65, 0xd7, 0x51, 0xd4, 0x5e,
  0x7e, 0xdd, 0x53, 0xd1, 0x57, 0xe6, 0xeb, 0x59, 0xd2, 0x53, 0xdc, 0x75,
  0x62, 0xd7, 0x54, 0xd8, 0x64, 0x78, 0xdf, 0x59, 0xd8, 0x5d, 0xec, 0xee,
  0x60, 0xdc, 0x5d, 0xe4, 0x74, 0x70, 0xe6, 0x63, 0xe3, 0x6a, 0xf2, 0xfc,
  0x70, 0xeb, 0x69, 0xe9, 0x6b, 0xf0, 0x7d, 0x72, 0xe9, 0x64, 0xe3, 0x67,
  0xf2, 0xf2, 0x63, 0xdd, 0x5c, 0xe1, 0x70, 0x6b, 0xdd, 0x58, 0xd9, 0x5f,
  0xfb, 0xe2, 0x58, 0xd4, 0x58, 0xe5, 0xf1, 0x5b, 0xd4, 0x53, 0xda, 0x6d,
  0x64, 0xd6, 0x51, 0xd4, 0x5e, 0x7b, 0xdd, 0x54, 0xd2, 0x58, 0xe8, 0xeb,
  0x5a, 0xd4, 0x55, 0xdd, 0x76, 0x64, 0xd9, 0x57, 0xda, 0x65, 0x79, 0xe2,
  0x5b, 0xdb, 0x5f, 0xed, 0xf3, 0x66, 0xdf, 0x61, 0xe7, 0x71, 0x7a, 0xed,
  0x69, 0xe8, 0x6a, 0xed, 0x76, 0x7e, 0xf1, 0x6b, 0xe7, 0x66, 0xea, 0x71,
  0x77, 0xe9, 0x60, 0xde, 0x61, 0xed, 0xf5, 0x61, 0xdb, 0x5a, 0xde, 0x6e,
  0x6b, 0xdc, 0x57, 0xd8, 0x5f, 0xfb, 0xe1, 0x57, 0xd4, 0x57, 0xe5, 0xef,
  0x5b, 0xd3, 0x53, 0xda, 0x6e, 0x64, 0xd7, 0x52, 0xd5, 0x5f, 0x7b, 0xdd,
  0x55, 0xd3, 0x59, 0xe9, 0xeb, 0x5b, 0xd6, 0x58, 0xde, 0x77, 0x67, 0xdb,
  0x59, 0xdc, 0x68, 0x7d, 0xe6, 0x5f, 0xde, 0x63, 0xed, 0xfb, 0x6b, 0xe6,
  0x65, 0xe8, 0x6e, 0xfa, 0xf8, 0x6e, 0xeb, 0x69, 0xea, 0x6c, 0xf5, 0xfa,
  0x6c, 0xe5, 0x61, 0xe4, 0x6c, 0x7c, 0xe8, 0x5e, 0xdc, 0x5e, 0xea, 0xf4,
  0x60, 0xd9, 0x58, 0xdd, 0x6e, 0x6a, 0xdb, 0x55, 0xd7, 0x5e, 0xfc, 0xdf,
  0x56, 0xd3, 0x58, 0xe5, 0xee, 0x5b, 0xd3, 0x54, 0xdb, 0x6f, 0x64, 0xd7,
  0x53, 0xd7, 0x60, 0x79, 0xde, 0x56, 0xd5, 0x5b, 0xeb, 0xed, 0x5d, 0xd8,
  0x5a, 0xe1, 0x76, 0x69, 0xde, 0x5c, 0xde, 0x69, 0xfd, 0xeb, 0x64, 0xe2,
  0x66, 0xed, 0x7c, 0x74, 0xec, 0x6a, 0xe9, 0x6c, 0xef, 0x7a, 0x78, 0xee,
  0x68, 0xe6, 0x67, 0xed, 0x7c, 0x6d, 0xe3, 0x5e, 0xdf, 0x68, 0xfe, 0xe7,
  0x5d, 0xda, 0x5d, 0xe8, 0xf4, 0x5f, 0xd8, 0x57, 0xdc, 0x6e, 0x69, 0xda,
  0x55, 0xd6, 0x5e, 0xfd, 0xdf, 0x56, 0xd3, 0x58, 0xe6, 0xed, 0x5a, 0xd4,
  0x54, 0xdc, 0x72, 0x64, 0xd8, 0x55, 0xd8, 0x63, 0x78, 0xdf, 0x58, 0xd8,
  0x5d, 0xed, 0xee, 0x5f, 0xdb, 0x5c, 0xe4, 0x76, 0x6d, 0xe2, 0x5f, 0xe2,
  0x6a, 0xfa, 0xf1, 0x6a, 0xe7, 0x68, 0xec, 0x72, 0x7e, 0xf5, 0x6e, 0xea,
  0x69, 0xea, 0x6e, 0xfc, 0xf0, 0x68, 0xe2, 0x62, 0xe7, 0x77, 0x6d, 0xe0,
  0x5d, 0xdd, 0x65, 0xfb, 0xe6, 0x5b, 0xd9, 0x5b, 0xe7, 0xf4, 0x5e, 0xd7,
  0x56, 0xdc, 0x6e, 0x68, 0xd9, 0x54, 0xd6, 0x5f, 0xfe, 0xdf, 0x56, 0xd4,
  0x59, 0xe7, 0xed, 0x5b, 0xd5, 0x56, 0xdd, 0x73, 0x65, 0xda, 0x57, 0xda,
  0x65, 0x79, 0xe2, 0x5b, 0xda, 0x5f, 0xed, 0xf1, 0x63, 0xde, 0x5f, 0xe6,
  0x75, 0x72, 0xe8, 0x64, 0xe6, 0x6b, 0xf4, 0xfb, 0x70, 0xec, 0x6a, 0xeb,
  0x6d, 0xf3, 0x7e, 0x72, 0xeb, 0x66, 0xe5, 0x69, 0xf5, 0xf3, 0x66, 0xdf,
  0x5f, 0xe4, 0x73, 0x6d, 0xdf, 0x5b, 0xdc, 0x63, 0xfb, 0xe5, 0x5a, 0xd7,
  0x5a, 0xe6, 0xf3, 0x5d, 0xd7, 0x56, 0xdc, 0x6e, 0x67, 0xd9, 0x55, 0xd7,
  0x5f, 0x7e, 0xdf, 0x57, 0xd5, 0x5a, 0xe9, 0xed, 0x5c, 0xd6, 0x58, 0xde,
  0x76, 0x67, 0xdb, 0x59, 0xdc, 0x66, 0x7b, 0xe5, 0x5d, 0xdc, 0x61, 0xee,
  0xf6, 0x67, 0xe2, 0x62, 0xe8, 0x71, 0x7a, 0xee, 0x69, 0xe9, 0x6b, 0xef,
  0x78, 0x7c, 0xf0, 0x6c, 0xe9, 0x69, 0xec, 0x76, 0x77, 0xea, 0x63, 0xe1,
  0x65, 0xee, 0xf4, 0x65, 0xdd, 0x5d, 0xe1, 0x70, 0x6d, 0xde, 0x59, 0xda,
  0x61, 0xfb, 0xe4, 0x59, 0xd7, 0x5a, 0xe6, 0xf2, 0x5d, 0xd6, 0x56, 0xdc,
  0x6e, 0x67, 0xd9, 0x55, 0xd8, 0x61, 0x7c, 0xdf, 0x58, 0xd6, 0x5b, 0xea,
  0xee, 0x5d, 0xd8, 0x59, 0xe0, 0x76, 0x69, 0xdd, 0x5b, 0xde, 0x68, 0x7d,
  0xe9, 0x5f, 0xdf, 0x63, 0xee, 0xfc, 0x6c, 0xe7, 0x66, 0xe9, 0x6f, 0xfb,
  0xf7, 0x6e, 0xec, 0x6a, 0xec, 0x6e, 0xf8, 0xf9, 0x6d, 0xe7, 0x65, 0xe7,
  0x6e, 0x7c, 0xea, 0x61, 0xde, 0x61, 0xec, 0xf6, 0x64, 0xdc, 0x5b, 0xdf,
  0x6f, 0x6c, 0xdd, 0x58, 0xd9, 0x61, 0xfb, 0xe3, 0x59, 0xd6, 0x5a, 0xe7,
  0xf1, 0x5d, 0xd6, 0x56, 0xdd, 0x6f, 0x67, 0xda, 0x56, 0xd9, 0x62, 0x7c,
  0xe0, 0x59, 0xd8, 0x5d, 0xeb, 0xee, 0x5f, 0xda, 0x5b, 0xe2, 0x76, 0x6b,
  0xe0, 0x5d, 0xe0, 0x6a, 0xfe, 0xed, 0x65, 0xe4, 0x67, 0xee, 0x7b, 0x73,
  0xed, 0x6a, 0xeb, 0x6d, 0xf2, 0x7b, 0x77, 0xee, 0x6a, 0xe9, 0x6a, 0xef,
  0x7e, 0x6e, 0xe6, 0x61, 0xe2, 0x6a, 0xfe, 0xe9, 0x5f, 0xdc, 0x5f, 0xea,
  0xf7, 0x62, 0xdb, 0x5a, 0xde, 0x6e, 0x6c, 0xdc, 0x58, 0xd9, 0x60, 0xfc,
  0xe2, 0x59, 0xd6, 0x5a, 0xe7, 0xef, 0x5d, 0xd7, 0x57, 0xde, 0x72, 0x67,
  0xdb, 0x57, 0xda, 0x64, 0x7b, 0xe2, 0x5b, 0xda, 0x5e, 0xed, 0xf0, 0x61,
  0xdd, 0x5e, 0xe5, 0x77, 0x6e, 0xe4, 0x61, 0xe3, 0x6c, 0xf9, 0xf1, 0x6b,
  0xe8, 0x6a, 0xed, 0x75, 0x7e, 0xf5, 0x6e, 0xec, 0x6b, 0xec, 0x71, 0xfe,
  0xf1, 0x69, 0xe5, 0x65, 0xea, 0x78, 0x6e, 0xe4, 0x5e, 0xdf, 0x67, 0xfc,
  0xe9, 0x5e, 0xdb, 0x5d, 0xe9, 0xf6, 0x61, 0xda, 0x59, 0xde, 0x6e, 0x6b,
  0xdc, 0x57, 0xd9, 0x61, 0xfd, 0xe2, 0x59, 0xd7, 0x5b, 0xe9, 0xef, 0x5d,
  0xd8, 0x58, 0xdf, 0x73, 0x68, 0xdc, 0x59, 0xdc, 0x66, 0x7b, 0xe4, 0x5c,
  0xdc, 0x60, 0xee, 0xf4, 0x65, 0xdf, 0x60, 0xe7, 0x75, 0x73, 0xe9, 0x66,
  0xe7, 0x6c, 0xf5, 0xfa, 0x71, 0xed, 0x6b, 0xec, 0x6e, 0xf6, 0xfe, 0x73,
  0xec, 0x68, 0xe9, 0x6b, 0xf7, 0xf5, 0x68, 0xe2, 0x61, 0xe7, 0x73, 0x6e,
  0xe2, 0x5d, 0xde, 0x65, 0xfa, 0xe8, 0x5d, 0xda, 0x5d, 0xe8, 0xf6, 0x5f,
  0xd9, 0x59, 0xdd, 0x6f, 0x6a, 0xdc, 0x58, 0xd9, 0x62, 0xfe, 0xe2, 0x59,
  0xd7, 0x5c, 0xe9, 0xef, 0x5e, 0xd9, 0x5a, 0xdf, 0x75, 0x69, 0xdd, 0x5b,
  0xdd, 0x68, 0x7d, 0xe7, 0x5f, 0xde, 0x63, 0xee, 0xf7, 0x69, 0xe4, 0x64,
  0xe9, 0x72, 0x7b, 0xee, 0x6b, 0xea, 0x6c, 0xf0, 0x79, 0x7b, 0xf2, 0x6d,
  0xeb, 0x6b, 0xee, 0x76, 0x78, 0xec, 0x66, 0xe4, 0x67, 0xf0, 0xf7, 0x67,
  0xdf, 0x5e, 0xe4, 0x70, 0x6e, 0xe0, 0x5c, 0xdc, 0x63, 0xfa, 0xe7, 0x5c,
  0xd9, 0x5c, 0xe8, 0xf5, 0x5f, 0xd9, 0x58, 0xde, 0x6f, 0x6a, 0xdc, 0x58,
  0xda, 0x62, 0x7e, 0xe3, 0x5a, 0xd9, 0x5d, 0xeb, 0xf0, 0x5f, 0xdb, 0x5b,
  0xe2, 0x74, 0x6b, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xea, 0x63, 0xe1, 0x65,
  0xee, 0xfd, 0x6e, 0xe8, 0x68, 0xea, 0x6f, 0xfc, 0xf8, 0x6f, 0xed, 0x6c,
  0xed, 0x70, 0xf9, 0xf9, 0x6e, 0xe9, 0x68, 0xe9, 0x6f, 0x7c, 0xec, 0x64,
  0xe1, 0x64, 0xed, 0xf8, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6e, 0xdf, 0x5b,
  0xdc, 0x63, 0xfb, 0xe6, 0x5b, 0xd9, 0x5c, 0xe8, 0xf4, 0x5f, 0xd9, 0x59,
  0xde, 0x70, 0x6a, 0xdc, 0x59, 0xdb, 0x64, 0x7e, 0xe3, 0x5b, 0xda, 0x5e,
  0xec, 0xf0, 0x61, 0xdc, 0x5d, 0xe4, 0x75, 0x6d, 0xe2, 0x5f, 0xe1, 0x6b,
  0xfb, 0xee, 0x67, 0xe5, 0x68, 0xee, 0x7c, 0x75, 0xed, 0x6b, 0xec, 0x6e,
  0xf4, 0x7d, 0x77, 0xef, 0x6c, 0xea, 0x6b, 0xf1, 0xff, 0x6f, 0xe8, 0x64,
  0xe6, 0x6c, 0xff, 0xeb, 0x62, 0xdf, 0x61, 0xec, 0xf8, 0x65, 0xdd, 0x5c,
  0xe0, 0x6f, 0x6d, 0xdf, 0x5a, 0xdb, 0x63, 0xfa, 0xe6, 0x5b, 0xd9, 0x5c,
  0xe8, 0xf3, 0x5f, 0xda, 0x59, 0xdf, 0x71, 0x6a, 0xdd, 0x59, 0xdc, 0x65,
  0x7d, 0xe5, 0x5d, 0xdc, 0x5f, 0xed, 0xf3, 0x64, 0xde, 0x5f, 0xe6, 0x75,
  0x70, 0xe6, 0x63, 0xe5, 0x6c, 0xf8, 0xf3, 0x6c, 0xe9, 0x6a, 0xed, 0x76,
  0x7e, 0xf5, 0x6e, 0xec, 0x6c, 0xee, 0x74, 0xfe, 0xf2, 0x6b, 0xe8, 0x68,
  0xec, 0x7a, 0x6f, 0xe6, 0x61, 0xe2, 0x6a, 0xfc, 0xea, 0x60, 0xdd, 0x5f,
  0xea, 0xf8, 0x64, 0xdc, 0x5b, 0xdf, 0x6e, 0x6d, 0xde, 0x5a, 0xdb, 0x63,
  0xfb, 0xe5, 0x5b, 0xd9, 0x5d, 0xea, 0xf3, 0x5f, 0xda, 0x5a, 0xe0, 0x72,
  0x6b, 0xde, 0x5b, 0xdd, 0x67, 0x7e, 0xe7, 0x5e, 0xdd, 0x62, 0xee, 0xf6,
  0x68, 0xe1, 0x62, 0xe8, 0x74, 0x75, 0xeb, 0x68, 0xe8, 0x6c, 0xf6, 0xfb,
  0x72, 0xed, 0x6d, 0xed, 0x70, 0xf7, 0xfe, 0x74, 0xed, 0x6a, 0xea, 0x6d,
  0xf7, 0xf6, 0x6a, 0xe5, 0x64, 0xe9, 0x75, 0x70, 0xe5, 0x5f, 0xdf, 0x67,
  0xfa, 0xea, 0x5f, 0xdc, 0x5e, 0xe9, 0xf8, 0x63, 0xdc, 0x5b, 0xdf, 0x6f,
  0x6c, 0xde, 0x5a, 0xdb, 0x63, 0xfc, 0xe6, 0x5c, 0xda, 0x5e, 0xea, 0xf3,
  0x61, 0xdb, 0x5c, 0xe2, 0x74, 0x6c, 0xdf, 0x5d, 0xdf, 0x69, 0x7e, 0xe9,
  0x61, 0xdf, 0x64, 0xef, 0xfa, 0x6b, 0xe6, 0x65, 0xea, 0x72, 0x7b, 0xef,
  0x6c, 0xeb, 0x6d, 0xf1, 0x7a, 0x7a, 0xf3, 0x6e, 0xec, 0x6c, 0xef, 0x78,
  0x78, 0xed, 0x68, 0xe7, 0x6a, 0xf2, 0xf7, 0x6a, 0xe2, 0x61, 0xe6, 0x72,
  0x70, 0xe3, 0x5e, 0xde, 0x66, 0xfa, 0xe9, 0x5e, 0xdc, 0x5e, 0xe9, 0xf8,
  0x62, 0xdb, 0x5b, 0xdf, 0x6f, 0x6c, 0xde, 0x5a, 0xdc, 0x64, 0xfd, 0xe6,
  0x5c, 0xdb, 0x5e, 0xeb, 0xf4, 0x62, 0xdd, 0x5d, 0xe3, 0x74, 0x6d, 0xe2,
  0x5e, 0xe1, 0x69, 0xfd, 0xed, 0x65, 0xe3, 0x67, 0xee, 0xfe, 0x6f, 0xea,
  0x69, 0xeb, 0x70, 0xfb, 0xf7, 0x70, 0xed, 0x6d, 0xee, 0x72, 0xfa, 0xf9,
  0x6f, 0xeb, 0x6a, 0xeb, 0x71, 0x7c, 0xed, 0x67, 0xe3, 0x66, 0xee, 0xf9,
  0x69, 0xe0, 0x5f, 0xe4, 0x71, 0x71, 0xe2, 0x5d, 0xdd, 0x65, 0xf8, 0xe9,
  0x5e, 0xdb, 0x5e, 0xe8, 0xf7, 0x63, 0xdb, 0x5b, 0xdf, 0x70, 0x6d, 0xde,
  0x5b, 0xdc, 0x65, 0xfc, 0xe7, 0x5d, 0xdc, 0x5f, 0xec, 0xf5, 0x64, 0xde,
  0x5e, 0xe5, 0x73, 0x6f, 0xe5, 0x60, 0xe3, 0x6a, 0xfc, 0xf0, 0x68, 0xe7,
  0x69, 0xef, 0x7b, 0x75, 0xee, 0x6c, 0xec, 0x6f, 0xf5, 0x7d, 0x77, 0xef,
  0x6d, 0xec, 0x6d, 0xf3, 0xff, 0x70, 0xea, 0x66, 0xe8, 0x6e, 0xfe, 0xed,
  0x65, 0xe1, 0x64, 0xed, 0xfa, 0x68, 0xdf, 0x5e, 0xe2, 0x6f, 0x6f, 0xe1,
  0x5d, 0xdd, 0x65, 0xf9, 0xe9, 0x5e, 0xdb, 0x5e, 0xe9, 0xf6, 0x63, 0xdc,
  0x5b, 0xe0, 0x71, 0x6d, 0xdf, 0x5c, 0xdd, 0x66, 0xfe, 0xe9, 0x5f, 0xdd,
  0x61, 0xed, 0xf7, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x73, 0xe8, 0x64, 0xe6,
  0x6c, 0xf8, 0xf5, 0x6c, 0xeb, 0x6b, 0xee, 0x76, 0x7d, 0xf4, 0x6f, 0xed,
  0x6d, 0xef, 0x75, 0x7e, 0xf4, 0x6c, 0xe9, 0x6a, 0xee, 0x7a, 0x71, 0xe9,
  0x64, 0xe5, 0x6b, 0xfc, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfa, 0x67, 0xde,
  0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd, 0x64, 0xf9, 0xe8, 0x5e, 0xdb,
  0x5e, 0xea, 0xf6, 0x64, 0xdc, 0x5c, 0xe1, 0x72, 0x6d, 0xe0, 0x5d, 0xde,
  0x68, 0xfd, 0xea, 0x61, 0xdf, 0x63, 0xed, 0xf9, 0x6a, 0xe4, 0x63, 0xe9,
  0x74, 0x77, 0xec, 0x69, 0xe9, 0x6d, 0xf6, 0xfd, 0x73, 0xee, 0x6d, 0xee,
  0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6c, 0xec, 0x6f, 0xf9, 0xf6, 0x6c, 0xe8,
  0x66, 0xeb, 0x76, 0x72, 0xe8, 0x62, 0xe2, 0x69, 0xfa, 0xec, 0x62, 0xde,
  0x60, 0xea, 0xfb, 0x66, 0xde, 0x5d, 0xe1, 0x6f, 0x6f, 0xe0, 0x5c, 0xdd,
  0x65, 0xfa, 0xe9, 0x5e, 0xdc, 0x5f, 0xeb, 0xf7, 0x64, 0xdd, 0x5d, 0xe3,
  0x72, 0x6e, 0xe2, 0x5e, 0xe0, 0x69, 0xfc, 0xec, 0x64, 0xe1, 0x65, 0xef,
  0xfc, 0x6c, 0xe7, 0x66, 0xeb, 0x72, 0x7c, 0xf1, 0x6c, 0xec, 0x6d, 0xf2,
  0x7b, 0x7a, 0xf2, 0x6e, 0xed, 0x6e, 0xf1, 0x7a, 0x79, 0xee, 0x6a, 0xe9,
  0x6c, 0xf4, 0xf9, 0x6b, 0xe5, 0x64, 0xe8, 0x73, 0x73, 0xe6, 0x60, 0xe0,
  0x68, 0xf9, 0xec, 0x61, 0xde, 0x5f, 0xea, 0xfa, 0x66, 0xde, 0x5d, 0xe1,
  0x6f, 0x6f, 0xe1, 0x5c, 0xdd, 0x66, 0xfb, 0xe9, 0x5e, 0xdd, 0x5f, 0xec,
  0xf7, 0x65, 0xde, 0x5e, 0xe5, 0x74, 0x6f, 0xe5, 0x60, 0xe2, 0x6a, 0xfc,
  0xee, 0x67, 0xe5, 0x68, 0xef, 0xfe, 0x71, 0xeb, 0x6a, 0xec, 0x72, 0xfc,
  0xf8, 0x71, 0xee, 0x6e, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xed, 0x6c, 0xed,
  0x73, 0x7d, 0xef, 0x69, 0xe6, 0x68, 0xef, 0xfa, 0x6b, 0xe3, 0x62, 0xe6,
  0x72, 0x73, 0xe5, 0x5f, 0xdf, 0x67, 0xf8, 0xeb, 0x60, 0xdd, 0x5f, 0xea,
  0xfb, 0x65, 0xde, 0x5d, 0xe2, 0x6f, 0x6e, 0xe1, 0x5d, 0xde, 0x66, 0xfd,
  0xea, 0x5f, 0xde, 0x61, 0xed, 0xf8, 0x67, 0xe0, 0x60, 0xe7, 0x74, 0x71,
  0xe8, 0x63, 0xe5, 0x6c, 0xfa, 0xf1, 0x6b, 0xe8, 0x6a, 0xef, 0x7c, 0x77,
  0xef, 0x6d, 0xed, 0x71, 0xf5, 0x7e, 0x79, 0xf0, 0x6e, 0xed, 0x6f, 0xf4,
  0xfe, 0x73, 0xeb, 0x69, 0xe9, 0x6f, 0xfd, 0xee, 0x67, 0xe4, 0x66, 0xed,
  0xfb, 0x6a, 0xe2, 0x60, 0xe5, 0x70, 0x72, 0xe5, 0x5e, 0xdf, 0x66, 0xf8,
  0xeb, 0x5f, 0xdd, 0x5f, 0xea, 0xfa, 0x65, 0xde, 0x5d, 0xe3, 0x6f, 0x6e,
  0xe2, 0x5d, 0xdf, 0x67, 0xfc, 0xeb, 0x60, 0xdf, 0x63, 0xed, 0xf9, 0x69,
  0xe3, 0x63, 0xe9, 0x74, 0x75, 0xea, 0x67, 0xe8, 0x6d, 0xf9, 0xf7, 0x6e,
  0xec, 0x6c, 0xef, 0x78, 0x7e, 0xf5, 0x71, 0xee, 0x6e, 0xf0, 0x77, 0xfe,
  0xf4, 0x6e, 0xeb, 0x6c, 0xef, 0x7b, 0x74, 0xea, 0x67, 0xe7, 0x6d, 0xfb,
  0xee, 0x66, 0xe1, 0x64, 0xec, 0xfc, 0x69, 0xe1, 0x5f, 0xe4, 0x6f, 0x72,
  0xe4, 0x5e, 0xdf, 0x66, 0xf9, 0xeb, 0x5f, 0xdd, 0x5f, 0xeb, 0xf9, 0x66,
  0xde, 0x5e, 0xe3, 0x71, 0x6f, 0xe4, 0x5f, 0xe0, 0x69, 0xfc, 0xec, 0x63,
  0xe1, 0x65, 0xef, 0xfb, 0x6b, 0xe6, 0x65, 0xea, 0x74, 0x79, 0xed, 0x6a,
  0xea, 0x6e, 0xf6, 0xfc, 0x73, 0xef, 0x6e, 0xef, 0x73, 0xf9, 0xfd, 0x75,
  0xef, 0x6d, 0xed, 0x70, 0xfa, 0xf7, 0x6e, 0xe9, 0x69, 0xec, 0x77, 0x74,
  0xe9, 0x65, 0xe5, 0x6b, 0xfa, 0xee, 0x64, 0xe0, 0x63, 0xec, 0xfc, 0x69,
  0xe0, 0x5f, 0xe3, 0x6f, 0x72, 0xe4, 0x5e, 0xdf, 0x66, 0xfa, 0xeb, 0x60,
  0xde, 0x60, 0xeb, 0xf9, 0x67, 0xdf, 0x5f, 0xe5, 0x72, 0x70, 0xe5, 0x60,
  0xe2, 0x6a, 0xfc, 0xee, 0x66, 0xe4, 0x67, 0xef, 0xfd, 0x6e, 0xe9, 0x68,
  0xec, 0x73, 0x7d, 0xf3, 0x6e, 0xed, 0x6e, 0xf3, 0x7b, 0x7b, 0xf5, 0x70,
  0xee, 0x6f, 0xf3, 0x7a, 0x7a, 0xef, 0x6c, 0xeb, 0x6d, 0xf5, 0xf9, 0x6d,
  0xe7, 0x67, 0xea, 0x75, 0x75, 0xe8, 0x63, 0xe3, 0x69, 0xf8, 0xed, 0x64,
  0xdf, 0x62, 0xeb, 0xfc, 0x69, 0xdf, 0x5f, 0xe3, 0x70, 0x71, 0xe3, 0x5e,
  0xdf, 0x67, 0xfa, 0xeb, 0x61, 0xdf, 0x61, 0xed, 0xfa, 0x68, 0xe1, 0x60,
  0xe7, 0x73, 0x71, 0xe7, 0x62, 0xe5, 0x6b, 0xfb, 0xf0, 0x69, 0xe7, 0x69,
  0xef, 0x7e, 0x72, 0xed, 0x6b, 0xed, 0x72, 0xfc, 0xf9, 0x72, 0xef, 0x6e,
  0xf1, 0x74, 0xfc, 0xf9, 0x71, 0xee, 0x6d, 0xee, 0x75, 0x7d, 0xf0, 0x6b,
  0xe8, 0x6b, 0xf0, 0xfa, 0x6d, 0xe6, 0x64, 0xe8, 0x72, 0x75, 0xe8, 0x62,
  0xe2, 0x69, 0xf8, 0xed, 0x63, 0xdf, 0x61, 0xeb, 0xfc, 0x68, 0xdf, 0x5f,
  0xe4, 0x70, 0x71, 0xe4, 0x5f, 0xe0, 0x68, 0xfb, 0xec, 0x62, 0xdf, 0x63,
  0xed, 0xfa, 0x69, 0xe3, 0x62, 0xe8, 0x74, 0x74, 0xea, 0x65, 0xe7, 0x6c,
  0xfa, 0xf4, 0x6c, 0xea, 0x6b, 0xef, 0x7a, 0x78, 0xf0, 0x6e, 0xee, 0x71,
  0xf6, 0xff, 0x79, 0xf2, 0x6f, 0xee, 0x70, 0xf5, 0xfd, 0x74, 0xed, 0x6b,
  0xec, 0x70, 0xfe, 0xf0, 0x69, 0xe7, 0x68, 0xef, 0xfd, 0x6c, 0xe5, 0x62,
  0xe7, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x62, 0xdf, 0x61,
  0xeb, 0xfb, 0x68, 0xe0, 0x5f, 0xe4, 0x70, 0x71, 0xe5, 0x5f, 0xe1, 0x69,
  0xfb, 0xed, 0x64, 0xe1, 0x65, 0xee, 0xfb, 0x6b, 0xe5, 0x65, 0xe9, 0x74,
  0x78, 0xec, 0x69, 0xe9, 0x6e, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xef, 0x77,
  0x7e, 0xf6, 0x72, 0xef, 0x70, 0xf2, 0x78, 0x7e, 0xf6, 0x6f, 0xed, 0x6d,
  0xf0, 0x7b, 0x75, 0xed, 0x69, 0xe9, 0x6e, 0xfb, 0xf0, 0x68, 0xe5, 0x66,
  0xee, 0xfd, 0x6b, 0xe4, 0x61, 0xe6, 0x70, 0x74, 0xe7, 0x60, 0xe1, 0x67,
  0xf8, 0xed, 0x63, 0xdf, 0x62, 0xeb, 0xfb, 0x69, 0xe0, 0x5f, 0xe5, 0x71,
  0x72, 0xe6, 0x61, 0xe2, 0x6a, 0xfa, 0xee, 0x66, 0xe3, 0x67, 0xee, 0xfc,
  0x6e, 0xe8, 0x67, 0xeb, 0x74, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfd,
  0x75, 0xf0, 0x6f, 0xef, 0x74, 0xfa, 0xfd, 0x75, 0xf0, 0x6e, 0xef, 0x73,
  0xfb, 0xf8, 0x6e, 0xeb, 0x6a, 0xee, 0x78, 0x76, 0xeb, 0x67, 0xe7, 0x6c,
  0xfa, 0xf0, 0x67, 0xe3, 0x65, 0xed, 0xfe, 0x6b, 0xe3, 0x61, 0xe5, 0x6f,
  0x74, 0xe7, 0x60, 0xe1, 0x67, 0xf9, 0xee, 0x63, 0xdf, 0x62, 0xec, 0xfc,
  0x69, 0xe2, 0x60, 0xe6, 0x72, 0x73, 0xe8, 0x63, 0xe4, 0x6b, 0xfa, 0xef,
  0x68, 0xe5, 0x69, 0xef, 0xfe, 0x70, 0xea, 0x6a, 0xec, 0x74, 0x7e, 0xf3,
  0x6f, 0xed, 0x6f, 0xf3, 0x7c, 0x7b, 0xf4, 0x71, 0xef, 0x71, 0xf4, 0x7c,
  0x7a, 0xf0, 0x6e, 0xec, 0x6f, 0xf6, 0xfa, 0x6f, 0xea, 0x69, 0xeb, 0x76,
  0x77, 0xeb, 0x66, 0xe5, 0x6b, 0xf8, 0xef, 0x67, 0xe2, 0x64, 0xed, 0xfe,
  0x6b, 0xe3, 0x60, 0xe5, 0x6f, 0x74, 0xe7, 0x60, 0xe1, 0x68, 0xf9, 0xee,
  0x63, 0xe1, 0x63, 0xed, 0xfd, 0x6a, 0xe4, 0x62, 0xe8, 0x72, 0x75, 0xe9,
  0x65, 0xe6, 0x6c, 0xfa, 0xf2, 0x6b, 0xe8, 0x6a, 0xef, 0x7d, 0x74, 0xed,
  0x6d, 0xed, 0x73, 0xfb, 0xf9, 0x74, 0xef, 0x70, 0xf1, 0x77, 0xfd, 0xfa,
  0x73, 0xef, 0x6e, 0xef, 0x76, 0x7d, 0xf2, 0x6c, 0xeb, 0x6c, 0xf2, 0xfc,
  0x6e, 0xe9, 0x67, 0xea, 0x72, 0x77, 0xea, 0x65, 0xe4, 0x6a, 0xf8, 0xef,
  0x66, 0xe2, 0x63, 0xec, 0xff, 0x6b, 0xe3, 0x60, 0xe5, 0x70, 0x75, 0xe7,
  0x61, 0xe2, 0x69, 0xf9, 0xee, 0x65, 0xe2, 0x64, 0xed, 0xfd, 0x6c, 0xe5,
  0x64, 0xe9, 0x73, 0x77, 0xeb, 0x67, 0xe8, 0x6d, 0xf9, 0xf6, 0x6d, 0xeb,
  0x6c, 0xef, 0x7a, 0x7a, 0xf1, 0x6f, 0xee, 0x72, 0xf7, 0xff, 0x78, 0xf3,
  0x70, 0xef, 0x72, 0xf7, 0xfe, 0x75, 0xee, 0x6c, 0xed, 0x72, 0xfd, 0xf2,
  0x6b, 0xe8, 0x6a, 0xef, 0xfd, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x78, 0xea,
  0x63, 0xe4, 0x69, 0xf7, 0xef, 0x66, 0xe1, 0x64, 0xec, 0xff, 0x6b, 0xe3,
  0x61, 0xe6, 0x70, 0x75, 0xe8, 0x62, 0xe3, 0x6a, 0xf9, 0xef, 0x67, 0xe3,
  0x66, 0xee, 0xfe, 0x6d, 0xe7, 0x66, 0xea, 0x74, 0x7a, 0xee, 0x6a, 0xea,
  0x6e, 0xf7, 0xfa, 0x70, 0xee, 0x6e, 0xf1, 0x77, 0x7e, 0xf7, 0x72, 0xf0,
  0x71, 0xf4, 0x79, 0x7d, 0xf6, 0x71, 0xee, 0x6f, 0xf2, 0x7c, 0x77, 0xee,
  0x6b, 0xeb, 0x6f, 0xfb, 0xf2, 0x6b, 0xe7, 0x68, 0xee, 0xff, 0x6e, 0xe6,
  0x64, 0xe7, 0x70, 0x78, 0xea, 0x63, 0xe3, 0x69, 0xf7, 0xef, 0x66, 0xe2,
  0x64, 0xec, 0xfe, 0x6b, 0xe4, 0x62, 0xe6, 0x71, 0x76, 0xe9, 0x63, 0xe4,
  0x6a, 0xf9, 0xf1, 0x68, 0xe5, 0x67, 0xef, 0x7e, 0x6f, 0xea, 0x68, 0xec,
  0x74, 0x7c, 0xf1, 0x6c, 0xec, 0x6f, 0xf6, 0xfe, 0x76, 0xf1, 0x6f, 0xf0,
  0x75, 0xfb, 0xfd, 0x76, 0xf1, 0x6f, 0xef, 0x74, 0xfb, 0xf9, 0x70, 0xec,
  0x6d, 0xee, 0x79, 0x78, 0xed, 0x6a, 0xe9, 0x6d, 0xf9, 0xf2, 0x6a, 0xe6,
  0x67, 0xed, 0x7e, 0x6d, 0xe6, 0x63, 0xe7, 0x70, 0x78, 0xe9, 0x63, 0xe3,
  0x69, 0xf7, 0xf0, 0x66, 0xe2, 0x64, 0xec, 0xfe, 0x6c, 0xe5, 0x63, 0xe7,
  0x71, 0x77, 0xea, 0x65, 0xe6, 0x6b, 0xf8, 0xf3, 0x6a, 0xe8, 0x69, 0xef,
  0x7e, 0x72, 0xec, 0x6b, 0xed, 0x74, 0xfe, 0xf5, 0x6f, 0xee, 0x6f, 0xf4,
  0x7c, 0x7b, 0xf5, 0x71, 0xf0, 0x72, 0xf6, 0x7b, 0x79, 0xf3, 0x6e, 0xee,
  0x6f, 0xf7, 0xfb, 0x6f, 0xec, 0x6b, 0xed, 0x76, 0x79, 0xed, 0x68, 0xe7,
  0x6c, 0xf7, 0xf2, 0x69, 0xe5, 0x66, 0xec, 0x7e, 0x6e, 0xe5, 0x63, 0xe7,
  0x6f, 0x78, 0xe9, 0x63, 0xe4, 0x69, 0xf7, 0xf0, 0x67, 0xe3, 0x65, 0xed,
  0xff, 0x6d, 0xe6, 0x65, 0xe9, 0x73, 0x78, 0xeb, 0x67, 0xe7, 0x6c, 0xf8,
  0xf5, 0x6d, 0xea, 0x6b, 0xf0, 0x7c, 0x76, 0xef, 0x6d, 0xee, 0x72, 0xfc,
  0xfb, 0x74, 0xf2, 0x70, 0xf3, 0x76, 0xfe, 0xfb, 0x75, 0xf0, 0x6f, 0xf1,
  0x77, 0x7d, 0xf4, 0x6e, 0xec, 0x6d, 0xf3, 0xfe, 0x70, 0xeb, 0x69, 0xeb,
  0x73, 0x7a, 0xed, 0x67, 0xe7, 0x6b, 0xf7, 0xf4, 0x69, 0xe5, 0x66, 0xec,
  0x7d, 0x6d, 0xe5, 0x63, 0xe6, 0x6f, 0x79, 0xea, 0x64, 0xe3, 0x6a, 0xf6,
  0xf0, 0x68, 0xe4, 0x66, 0xed, 0xff, 0x6e, 0xe7, 0x66, 0xea, 0x73, 0x7a,
  0xed, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a,
  0xf3, 0x6f, 0xf0, 0x72, 0xf8, 0x7e, 0x78, 0xf5, 0x70, 0xf1, 0x72, 0xf9,
  0xff, 0x75, 0xf0, 0x6d, 0xef, 0x72, 0xfd, 0xf4, 0x6d, 0xeb, 0x6b, 0xf1,
  0xff, 0x70, 0xea, 0x67, 0xea, 0x72, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6,
  0xf3, 0x68, 0xe4, 0x66, 0xec, 0x7d, 0x6e, 0xe6, 0x64, 0xe7, 0x70, 0x79,
  0xea, 0x65, 0xe4, 0x6b, 0xf6, 0xf2, 0x69, 0xe5, 0x68, 0xed, 0xff, 0x6f,
  0xe9, 0x68, 0xeb, 0x73, 0x7c, 0xef, 0x6c, 0xeb, 0x6e, 0xf6, 0xfc, 0x72,
  0xee, 0x6e, 0xf1, 0x77, 0xfe, 0xf8, 0x73, 0xf2, 0x71, 0xf5, 0x79, 0x7d,
  0xf7, 0x72, 0xef, 0x6f, 0xf4, 0x7b, 0x77, 0xf0, 0x6c, 0xec, 0x6f, 0xfb,
  0xf5, 0x6c, 0xe9, 0x6a, 0xef, 0x7d, 0x6f, 0xe9, 0x66, 0xe9, 0x70, 0x7a,
  0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf3, 0x69, 0xe4, 0x65, 0xec, 0x7d, 0x6e,
  0xe7, 0x64, 0xe8, 0x70, 0x79, 0xeb, 0x66, 0xe6, 0x6b, 0xf7, 0xf4, 0x6b,
  0xe7, 0x69, 0xee, 0x7d, 0x72, 0xeb, 0x6a, 0xec, 0x73, 0xff, 0xf2, 0x6e,
  0xed, 0x6f, 0xf5, 0xfe, 0x78, 0xf2, 0x70, 0xf1, 0x75, 0xfb, 0xfd, 0x77,
  0xf3, 0x70, 0xf1, 0x75, 0xfb, 0xfa, 0x73, 0xee, 0x6e, 0xf0, 0x78, 0x79,
  0xef, 0x6b, 0xeb, 0x6e, 0xf8, 0xf5, 0x6c, 0xe8, 0x69, 0xee, 0x7c, 0x6f,
  0xe9, 0x66, 0xe8, 0x6f, 0x7a, 0xec, 0x66, 0xe5, 0x6a, 0xf6, 0xf4, 0x69,
  0xe5, 0x66, 0xed, 0x7c, 0x6e, 0xe7, 0x65, 0xe9, 0x71, 0x7a, 0xec, 0x68,
  0xe7, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6b, 0xef, 0x7c, 0x75, 0xed, 0x6c,
  0xed, 0x74, 0xfd, 0xf7, 0x71, 0xef, 0x70, 0xf4, 0x7c, 0x7c, 0xf6, 0x74,
  0xf1, 0x73, 0xf6, 0x7d, 0x7b, 0xf3, 0x70, 0xef, 0x71, 0xf7, 0xfd, 0x72,
  0xee, 0x6c, 0xee, 0x75, 0x7a, 0xef, 0x6a, 0xea, 0x6d, 0xf7, 0xf6, 0x6b,
  0xe7, 0x68, 0xed, 0x7b, 0x6f, 0xe8, 0x66, 0xe9, 0x6f, 0x7b, 0xec, 0x66,
  0xe5, 0x6a, 0xf6, 0xf4, 0x6a, 0xe6, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x66,
  0xe9, 0x72, 0x7a, 0xee, 0x69, 0xe9, 0x6d, 0xf8, 0xf8, 0x6e, 0xeb, 0x6c,
  0xf0, 0x7b, 0x78, 0xf0, 0x6e, 0xef, 0x74, 0xfa, 0xfb, 0x76, 0xf1, 0x71,
  0xf3, 0x78, 0xfd, 0xfa, 0x76, 0xf1, 0x71, 0xf3, 0x78, 0x7e, 0xf5, 0x6f,
  0xed, 0x6f, 0xf4, 0xfe, 0x72, 0xec, 0x6b, 0xec, 0x74, 0x7c, 0xee, 0x6a,
  0xe8, 0x6c, 0xf6, 0xf5, 0x6b, 0xe7, 0x67, 0xed, 0x7c, 0x6f, 0xe9, 0x65,
  0xe9, 0x6f, 0x7a, 0xed, 0x66, 0xe6, 0x6a, 0xf6, 0xf5, 0x6a, 0xe7, 0x68,
  0xee, 0x7c, 0x70, 0xea, 0x68, 0xeb, 0x72, 0x7c, 0xef, 0x6b, 0xeb, 0x6e,
  0xf7, 0xfa, 0x70, 0xee, 0x6d, 0xf1, 0x79, 0x7b, 0xf5, 0x70, 0xf0, 0x74,
  0xf7, 0x7e, 0x7a, 0xf4, 0x73, 0xf1, 0x75, 0xf9, 0xfe, 0x77, 0xf1, 0x6f,
  0xef, 0x74, 0xfc, 0xf6, 0x6f, 0xec, 0x6d, 0xf1, 0x7e, 0x73, 0xeb, 0x6a,
  0xeb, 0x73, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6, 0x6b, 0xe7, 0x67,
  0xed, 0x7b, 0x6f, 0xe9, 0x66, 0xe9, 0x6f, 0x7b, 0xed, 0x67, 0xe7, 0x6b,
  0xf7, 0xf6, 0x6b, 0xe8, 0x68, 0xee, 0x7c, 0x72, 0xeb, 0x69, 0xec, 0x73,
  0x7d, 0xf2, 0x6d, 0xec, 0x6f, 0xf7, 0xfc, 0x75, 0xef, 0x6f, 0xf1, 0x78,
  0xfe, 0xf9, 0x75, 0xf2, 0x73, 0xf5, 0x7a, 0x7e, 0xf8, 0x74, 0xf1, 0x71,
  0xf4, 0x7c, 0x79, 0xf1, 0x6e, 0xee, 0x71, 0xfb, 0xf7, 0x6e, 0xeb, 0x6b,
  0xf0, 0x7c, 0x72, 0xeb, 0x69, 0xea, 0x71, 0x7d, 0xee, 0x68, 0xe7, 0x6b,
  0xf5, 0xf6, 0x6b, 0xe7, 0x68, 0xed, 0x7c, 0x70, 0xe9, 0x67, 0xe9, 0x70,
  0x7b, 0xed, 0x68, 0xe8, 0x6c, 0xf7, 0xf6, 0x6c, 0xe9, 0x6a, 0xef, 0x7b,
  0x74, 0xed, 0x6b, 0xed, 0x73, 0xff, 0xf5, 0x6f, 0xee, 0x6f, 0xf6, 0x7e,
  0x79, 0xf3, 0x71, 0xf2, 0x76, 0xfb, 0xfd, 0x78, 0xf5, 0x72, 0xf3, 0x76,
  0xfc, 0xfa, 0x74, 0xef, 0x6f, 0xf2, 0x79, 0x7a, 0xf1, 0x6d, 0xec, 0x6f,
  0xf9, 0xf7, 0x6d, 0xea, 0x6b, 0xee, 0x7c, 0x72, 0xeb, 0x68, 0xea, 0x70,
  0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf4, 0xf6, 0x6c, 0xe7, 0x68, 0xed, 0x7b,
  0x71, 0xea, 0x67, 0xea, 0x71, 0x7c, 0xee, 0x69, 0xe9, 0x6c, 0xf6, 0xf8,
  0x6e, 0xeb, 0x6b, 0xf0, 0x7a, 0x76, 0xef, 0x6d, 0xee, 0x73, 0xfd, 0xf9,
  0x72, 0xf0, 0x70, 0xf5, 0x7b, 0x7c, 0xf7, 0x73, 0xf3, 0x74, 0xf8, 0x7c,
  0x7b, 0xf5, 0x71, 0xf1, 0x72, 0xf9, 0xfd, 0x74, 0xee, 0x6d, 0xef, 0x77,
  0x7c, 0xf1, 0x6c, 0xeb, 0x6e, 0xf7, 0xf7, 0x6d, 0xe9, 0x6a, 0xee, 0x7c,
  0x72, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe7, 0x6b, 0xf5, 0xf6,
  0x6c, 0xe7, 0x69, 0xed, 0x7c, 0x72, 0xea, 0x68, 0xeb, 0x71, 0x7d, 0xef,
  0x6b, 0xea, 0x6d, 0xf7, 0xfa, 0x6f, 0xec, 0x6d, 0xf1, 0x7a, 0x7a, 0xf2,
  0x6f, 0xef, 0x74, 0xfa, 0xfc, 0x76, 0xf3, 0x72, 0xf4, 0x78, 0xfe, 0xfb,
  0x75, 0xf3, 0x72, 0xf4, 0x79, 0x7e, 0xf7, 0x70, 0xef, 0x70, 0xf5, 0xfe,
  0x75, 0xee, 0x6c, 0xee, 0x75, 0x7d, 0xf0, 0x6c, 0xea, 0x6d, 0xf7, 0xf8,
  0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x73, 0xea, 0x68, 0xea, 0x70, 0x7d, 0xee,
  0x69, 0xe8, 0x6b, 0xf6, 0xf7, 0x6c, 0xe8, 0x69, 0xee, 0x7b, 0x73, 0xec,
  0x6a, 0xec, 0x71, 0x7d, 0xf2, 0x6c, 0xec, 0x6e, 0xf7, 0xfc, 0x73, 0xee,
  0x6e, 0xf1, 0x79, 0x7c, 0xf6, 0x72, 0xf1, 0x74, 0xf9, 0x7e, 0x7a, 0xf6,
  0x73, 0xf3, 0x75, 0xfa, 0xfe, 0x78, 0xf3, 0x70, 0xf1, 0x76, 0xfd, 0xf7,
  0x70, 0xed, 0x6e, 0xf3, 0x7e, 0x75, 0xed, 0x6c, 0xed, 0x73, 0x7e, 0xf0,
  0x6b, 0xea, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9, 0x69, 0xee, 0x7b, 0x72, 0xeb,
  0x68, 0xea, 0x70, 0x7d, 0xee, 0x69, 0xe9, 0x6c, 0xf5, 0xf8, 0x6d, 0xea,
  0x6a, 0xef, 0x7b, 0x75, 0xed, 0x6b, 0xed, 0x72, 0xff, 0xf4, 0x6e, 0xed,
  0x6f, 0xf6, 0xfe, 0x75, 0xf1, 0x70, 0xf3, 0x78, 0xfe, 0xf9, 0x75, 0xf3,
  0x73, 0xf6, 0x7b, 0x7d, 0xf9, 0x74, 0xf2, 0x72, 0xf7, 0x7c, 0x7a, 0xf3,
  0x6f, 0xef, 0x73, 0xfb, 0xf8, 0x6f, 0xed, 0x6d, 0xf1, 0x7c, 0x75, 0xed,
  0x6b, 0xec, 0x73, 0x7d, 0xef, 0x6b, 0xe9, 0x6c, 0xf6, 0xf8, 0x6d, 0xe9,
  0x69, 0xee, 0x7a, 0x73, 0xeb, 0x69, 0xeb, 0x71, 0x7e, 0xef, 0x6b, 0xe9,
  0x6d, 0xf6, 0xf8, 0x6e, 0xea, 0x6c, 0xef, 0x7c, 0x77, 0xee, 0x6d, 0xee,
  0x74, 0xfe, 0xf7, 0x70, 0xef, 0x70, 0xf6, 0x7d, 0x78, 0xf5, 0x71, 0xf4,
  0x76, 0xfb, 0xfe, 0x78, 0xf6, 0x73, 0xf5, 0x76, 0xfd, 0xfb, 0x75, 0xf2,
  0x70, 0xf3, 0x7a, 0x7b, 0xf3, 0x6e, 0xed, 0x71, 0xf9, 0xf8, 0x6f, 0xec,
  0x6c, 0xef, 0x7c, 0x75, 0xec, 0x6a, 0xec, 0x71, 0x7e, 0xf0, 0x6a, 0xe9,
  0x6c, 0xf6, 0xf9, 0x6d, 0xe9, 0x69, 0xee, 0x7a, 0x73, 0xec, 0x69, 0xeb,
  0x71, 0x7e, 0xf0, 0x6b, 0xea, 0x6d, 0xf6, 0xfa, 0x6f, 0xec, 0x6d, 0xf0,
  0x7b, 0x79, 0xf0, 0x6e, 0xef, 0x75, 0xfc, 0xf9, 0x74, 0xf1, 0x72, 0xf6,
  0x7b, 0x7c, 0xf8, 0x75, 0xf4, 0x74, 0xf8, 0x7d, 0x7c, 0xf7, 0x73, 0xf2,
  0x74, 0xf9, 0xfd, 0x76, 0xf0, 0x6f, 0xf0, 0x77, 0x7d, 0xf3, 0x6d, 0xed,
  0x6f, 0xf8, 0xfa, 0x6f, 0xeb, 0x6b, 0xef, 0x7b, 0x75, 0xec, 0x6a, 0xeb,
  0x71, 0x7e, 0xf0, 0x6a, 0xe9, 0x6c, 0xf5, 0xf9, 0x6d, 0xea, 0x6a, 0xee,
  0x7a, 0x74, 0xed, 0x6a, 0xec, 0x71, 0x7e, 0xf2, 0x6c, 0xec, 0x6e, 0xf5,
  0xfc, 0x72, 0xed, 0x6e, 0xf1, 0x79, 0x7b, 0xf3, 0x70, 0xef, 0x74, 0xfa,
  0xfc, 0x78, 0xf3, 0x73, 0xf4, 0x79, 0xfc, 0xfa, 0x77, 0xf3, 0x74, 0xf4,
  0x7a, 0xfe, 0xf7, 0x73, 0xef, 0x71, 0xf7, 0x7e, 0x76, 0xef, 0x6e, 0xef,
  0x75, 0x7d, 0xf3, 0x6d, 0xec, 0x6e, 0xf7, 0xfa, 0x6e, 0xeb, 0x6b, 0xef,
  0x79, 0x74, 0xed, 0x69, 0xeb, 0x6f, 0x7e, 0xf1, 0x6a, 0xea, 0x6c, 0xf6,
  0xfa, 0x6e, 0xeb, 0x6a, 0xef, 0x7a, 0x76, 0xee, 0x6b, 0xed, 0x72, 0xfe,
  0xf4, 0x6e, 0xed, 0x6f, 0xf6, 0xfd, 0x75, 0xef, 0x6f, 0xf1, 0x79, 0x7e,
  0xf6, 0x75, 0xf1, 0x76, 0xf8, 0xfd, 0x7a, 0xf5, 0x74, 0xf2, 0x76, 0xf8,
  0x7e, 0x7a, 0xf6, 0x71, 0xec, 0x76, 0xfa, 0x7e, 0x7b, 0xf9, 0x72, 0xf5,
  0x7d, 0xfb, 0xfb, 0x76, 0xf9, 0x75, 0x7b, 0xf1, 0x77, 0xfe, 0x7a, 0xfb,
  0x7e, 0x73, 0xfa, 0x7e, 0xfb, 0x7d, 0x77, 0xf7, 0x7e, 0x7c, 0xfe, 0x78,
  0xfa, 0x7e, 0xff, 0x7e, 0x76, 0xf9, 0xfd, 0xf9, 0xfe, 0x75, 0xf1, 0x76,
  0x7c, 0xfc, 0x7a, 0xf3, 0x70, 0xfd, 0x7e, 0x7d, 0xf3, 0x70, 0xf7, 0x78,
  0x7e, 0xf9, 0x72, 0xef, 0x77, 0xfc, 0xfd, 0x75, 0xee, 0x72, 0xf7, 0xfb
#endif
};

static short MuLawDecompressTable[256] =
{
     -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
     -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
     -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
     -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
      -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
      -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
      -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
      -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
      -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
      -1372, -1308, -1244, -1180, -1116, -1052,  -988,  -924,
       -876,  -844,  -812,  -780,  -748,  -716,  -684,  -652,
       -620,  -588,  -556,  -524,  -492,  -460,  -428,  -396,
       -372,  -356,  -340,  -324,  -308,  -292,  -276,  -260,
       -244,  -228,  -212,  -196,  -180,  -164,  -148,  -132,
       -120,  -112,  -104,   -96,   -88,   -80,   -72,   -64,
        -56,   -48,   -40,   -32,   -24,   -16,    -8,     -1,
      32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
      23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
      15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
      11900, 11388, 10876, 10364,  9852,  9340,  8828,  8316,
       7932,  7676,  7420,  7164,  6908,  6652,  6396,  6140,
       5884,  5628,  5372,  5116,  4860,  4604,  4348,  4092,
       3900,  3772,  3644,  3516,  3388,  3260,  3132,  3004,
       2876,  2748,  2620,  2492,  2364,  2236,  2108,  1980,
       1884,  1820,  1756,  1692,  1628,  1564,  1500,  1436,
       1372,  1308,  1244,  1180,  1116,  1052,   988,   924,
        876,   844,   812,   780,   748,   716,   684,   652,
        620,   588,   556,   524,   492,   460,   428,   396,
        372,   356,   340,   324,   308,   292,   276,   260,
        244,   228,   212,   196,   180,   164,   148,   132,
        120,   112,   104,    96,    88,    80,    72,    64,
         56,    48,    40,    32,    24,    16,     8,     0
};


void vt_bell (VT *vt)
{
  if (vt->bell < 2)
    return;
  for (int i = 0; i < (int)sizeof (vt_bell_audio); i++)
  {
    int16_t val = MuLawDecompressTable[vt_bell_audio[i]] * vt->bell / 8;
    terminal_queue_pcm (val, val);
  }
}


void terminal_queue_pcm (int16_t sample_left, int16_t sample_right);

void vt_audio (VT *vt, const char *command)
{
  AudioState *audio = &vt->audio;
  // the simplest form of audio is raw audio
  // _As=8000,c=2,b=8,e=u
  //
  // multiple voices:
  //   ids to queue - store samples as images...
  //
  // reusing samples
  //   .. pitch bend and be able to do a mod player?
  const char *payload = NULL;
  char key = 0;
  int  value;
  int  pos = 1;

  audio->frames=0;
  audio->action='t';

  int configure = 0;
  while (command[pos] != ';')
  {
    pos ++; // G or ,
    if (command[pos] == ';') break;
    key = command[pos]; pos++;
    if (command[pos] == ';') break;
    pos ++; // =
    if (command[pos] == ';') break;

    if (command[pos] >= '0' && command[pos] <= '9')
      value = atoi(&command[pos]);
    else
      value = command[pos];
    while (command[pos] &&
           command[pos] != ',' &&
           command[pos] != ';') pos++;
    
    if (value=='?')
    {
      char buf[256];
      const char *range="";
      switch (key)
      {
        case 's':range="8000,16000,24000,48000";break;
        case 'b':range="8,16";break;
        case 'B':range="512-65536";break;
        case 'c':range="1";break;
        case 'T':range="u,s,f";break;
        case 'e':range="b,a";break;
        case 'o':range="z,0";break;
        case 'a':range="t,q";break;
        default:range="unknown";break;
      }
      sprintf (buf, "\033_A%c=?;%s\033\\", key, range);
      vt_write (vt, buf, strlen(buf));
      return;
    }

    switch (key)
    {
      case 's': audio->samplerate = value; configure = 1; break;
      case 'b': audio->bits = value; configure = 1; break;
      case 'B': audio->buffer_size = value; configure = 1; break;
      case 'c': audio->channels = value; configure = 1; break;
      case 'a': audio->action = value; configure = 1; break;
      case 'T': audio->type = value; configure = 1; break;
      case 'f': audio->frames = value; configure = 1; break;
      case 'e': audio->encoding = value; configure = 1; break;
      case 'o': audio->compression = value; configure = 1; break;
      case 'm': 
        audio->mic = value?1:0;
        break;
    }

    if (configure)
    {
      /* these are the specific sample rates supported by opus,
       * instead of enabling anything SDL supports, the initial
       * implementation limits itself to the opus sample rates
       */
      if (audio->samplerate <= 8000)
      {
        audio->samplerate = 8000;
      }
      else if (audio->samplerate <= 16000)
      {
        audio->samplerate = 16000;
      }
      else if (audio->samplerate <= 24000)
      {
        audio->samplerate = 24000;
      }
      else
      {
        audio->samplerate = 48000;
      }

      if (audio->bits != 8 && audio->bits != 16)
        audio->bits = 8;

      if (audio->buffer_size > 2048)
        audio->buffer_size = 2048;
      else if (audio->buffer_size < 512)
        audio->buffer_size = 512;

      switch (audio->type)
      {
        case 'u':
        case 's':
        case 'f':
          break;
        default:
          audio->type = 's';
      }

      /* only 1 and 2 channels supported */
      if (audio->channels <= 0 || audio->channels > 2)
      {
        audio->channels = 1;
      }
    }
  }
  
  if (audio->frames ||  audio->action != 'd')
  {
  payload = &command[pos+1];

  // accumulate incoming data
  {
     int chunk_size = strlen (payload);
     int old_size = audio->data_size;
     if (audio->data == NULL)
     {
       audio->data_size = chunk_size;
       audio->data = malloc (audio->data_size + 1);
     }
     else
     {
       audio->data_size += chunk_size;
       audio->data = realloc (audio->data, audio->data_size + 1);
     }
     memcpy (audio->data + old_size, payload, chunk_size);
     audio->data[audio->data_size]=0;
  }

    if (audio->frames)
    switch (audio->encoding)
    {
      case 'y':
        audio->data_size = ydec (audio->data, audio->data, audio->data_size);
      break;
      case 'a':
      {
        int bin_length = audio->data_size;
        if (bin_length)
        {
        uint8_t *data2 = malloc ((unsigned int)ctx_a85len ((char*)audio->data, audio->data_size) + 1);
        // a85len is inaccurate but gives an upper bound,
        // should be fixed.
        bin_length = ctx_a85dec ((char*)audio->data,
                                 (void*)data2,
                                 bin_length);
        free (audio->data);
        audio->data = data2;
        audio->data_size = bin_length;
        }
      }
      break;

      case 'b':
      {
        int bin_length = audio->data_size;
        uint8_t *data2 = malloc (audio->data_size);
        bin_length = ctx_base642bin ((char*)audio->data,
                                     &bin_length,
                                     data2);
        memcpy (audio->data, data2, bin_length + 1);
        audio->data_size = bin_length;
        free (data2);
      }
      break;
    }

    if (audio->frames)
    switch (audio->compression)
    {
      case 'z':
    {
      unsigned long actual_uncompressed_size = audio->frames * audio->bits/8 * audio->channels + 512;
      unsigned char *data2 = malloc (actual_uncompressed_size);
      /* if a buf size is set (rather compression, but
       * this works first..) then */
      int z_result = uncompress (data2, &actual_uncompressed_size,
                                 audio->data,
                                 audio->data_size);
      if (z_result != Z_OK)
      {
       // fprintf (stderr, "[z error %i %i]", __LINE__, z_result);
      }

#if 0
      // XXX : we seem to get buf-error (-5) here, which indicates not enough
      //       space in output buffer, which is odd
      //
      //       it is non fatal though so we ignore it and use the validly
      //       decompressed bits.
      {
        char buf[256];
        sprintf (buf, "\e_Ao=z;zlib error1 %i\e\\", z_result);
        vt_write (vt, buf, strlen(buf));
        //goto cleanup;
      }
#endif
      free (audio->data);
      audio->data = data2;
      audio->data_size = actual_uncompressed_size;
    }

        break;
      case 'o':
        break;
      default:
        break;
    }

    if (audio->frames == 0)
    {
      /* implicit frame count */
      audio->frames = audio->data_size /
                                (audio->bits/8) /
                                   audio->channels;
    }


#if 0
    if (audio->format == 100/* opus */)
    {
      int channels;
      uint8_t *new_data = NULL;//stbi_load_from_memory (audio->data, audio->data_size, &audio->buf_width, &audio->buf_height, &channels, 4);

      if (!new_data)
      {
        char buf[256]= "\e_Gf=100;audio decode error\e\\";
        vt_write (vt, buf, strlen(buf));
        goto cleanup;
      }
      audio->format = 32;
      free (audio->data);
      audio->data = new_data;
      audio->data_size = audio->buf_width * audio->buf_height * 4;
    }
#endif

  switch (audio->action)
  {
    case 't': // transfer
       if (audio->type == 'u') // implied 8bit
       {
         if (audio->channels == 2)
         {
           for (int i = 0; i < audio->frames; i++)
           {
             int val_left = MuLawDecompressTable[audio->data[i*2]];
             int val_right = MuLawDecompressTable[audio->data[i*2+1]];
             terminal_queue_pcm (val_left, val_right);
           }
         }
         else
         {
           for (int i = 0; i < audio->frames; i++)
           {
             int val = MuLawDecompressTable[audio->data[i]];
             terminal_queue_pcm (val, val);
           }
         }
       }
       else if (audio->type == 's')
       {
         if (audio->bits == 8)
         {
           if (audio->channels == 2)
           {
             for (int i = 0; i < audio->frames; i++)
             {
               int val_left = 256*((int8_t*)(audio->data))[i*2];
               int val_right = 256*((int8_t*)(audio->data))[i*2+1];
               terminal_queue_pcm (val_left, val_right);
             }
           }
           else
           {
             for (int i = 0; i < audio->frames; i++)
             {
               int val = 256*((int8_t*)(audio->data))[i];
               terminal_queue_pcm (val, val);
             }
           }
         }
         else
         {
           if (audio->channels == 2)
           {
             for (int i = 0; i < audio->frames; i++)
             {
               int val_left = ((int16_t*)(audio->data))[i*2];
               int val_right = ((int16_t*)(audio->data))[i*2+1];
               terminal_queue_pcm (val_left, val_right);
             }
           }
           else
           {
             for (int i = 0; i < audio->frames; i++)
             {
               int val = ((int16_t*)(audio->data))[i];
               terminal_queue_pcm (val, val);
             }
           }
         }
       }
       free (audio->data);
       audio->data = NULL;
       audio->data_size=0;
       break;
    case 'q': // query
       {
         char buf[512];
         sprintf (buf, "\033_As=%i,b=%i,c=%i,T=%c,B=%i,e=%c,o=%c;OK\033\\",
      audio->samplerate, audio->bits, audio->channels, audio->type,
      audio->buffer_size,
      audio->encoding?audio->encoding:'0',
      audio->compression?audio->compression:'0'
      /*audio->transmission*/);

         vt_write (vt, buf, strlen(buf));
       }
      break;
  }
  }

//cleanup:
    if (audio->data)
      free (audio->data);
    audio->data = NULL;
    audio->data_size=0;
}
#endif
/* DEC terminals/xterm family terminal with ANSI, utf8, vector graphics and
 * audio.
 *
 * Copyright (c) 2014, 2016, 2018, 2020 Øyvind Kolås <pippin@gimp.org>
 *
 * Adhering to the standards with modern extensions.
 *
 * Features:
 *     vt100 - 101 points on scoresheet
 *     UTF8, cp437
 *     dim, bold, strikethrough, underline, italic, reverse
 *     ANSI colors, 256 colors (non-redefineable), 24bit color
 *     realtime audio transmission
 *     raster sprites (kitty spec)
 *     vector graphics
 *     vt320 - horizontal margins
 *     proportional fonts
 *
 *     BBS/ANSI-art mode
 *
 * 8bit clean
 *
 *
 * Todo:
 *     DECCIR - cursor state report https://vt100.net/docs/vt510-rm/DECCIR.html
 *     make absolute positioning take proportional into account
 *     HTML / PNG / SVG / PDF export of scrollback / screen
 *     sixels
 *
 */

#define _GNU_SOURCE
#define _BSD_SOURCE
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 // for posix_openpt
#endif

#if !__COSMOPOLITAN__
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <assert.h>

#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <zlib.h>
#endif

#include "ctx.h"


#define CTX_VT_USE_FRAMEDIFF 0  // is a larger drain than neccesary when everything is per-byte?
                                // is anyways currently disabled also in ctx

//#define STB_IMAGE_IMPLEMENTATION
//#include "stb_image.h"

//#include "vt-line.h"
//#include "vt.h"
//#include "ctx-clients.h"



#define VT_LOG_INFO     (1<<0)
#define VT_LOG_CURSOR   (1<<1)
#define VT_LOG_COMMAND  (1<<2)
#define VT_LOG_WARNING  (1<<3)
#define VT_LOG_ERROR    (1<<4)
#define VT_LOG_INPUT    (1<<5)
#define VT_LOG_ALL       0xff

static int vt_log_mask = VT_LOG_INPUT;
//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR;// | VT_LOG_COMMAND;// | VT_LOG_INFO | VT_LOG_COMMAND;
//static int vt_log_mask = VT_LOG_WARNING | VT_LOG_ERROR | VT_LOG_INFO | VT_LOG_COMMAND | VT_LOG_INPUT;
//static int vt_log_mask = VT_LOG_ALL;

#if 0
#define vt_log(domain, fmt, ...)

#define VT_input(str, ...)
#define VT_info(str, ...)
#define VT_command(str, ...)
#define VT_cursor(str, ...)
#define VT_warning(str, ...)
#define VT_error(str, ...)
#else
#define vt_log(domain, line, a...) \
        do {fprintf (stderr, "%i %s ", line, domain);fprintf(stderr, ##a);fprintf(stderr, "\n");}while(0)
#define VT_info(a...) if (vt_log_mask & VT_LOG_INFO) vt_log ("INFO  ", __LINE__, ##a)
#define VT_input(a...) if (vt_log_mask & VT_LOG_INPUT) vt_log ("INPUT ", __LINE__, ##a)
#define VT_command(a...) if (vt_log_mask & VT_LOG_COMMAND) vt_log ("CMD   ", __LINE__, ##a)
#define VT_cursor(a...) if (vt_log_mask & VT_LOG_CURSOR) vt_log ("CURSOR",__LINE__, ##a)
#define VT_warning(a...) if (vt_log_mask & VT_LOG_WARNING) vt_log ("WARN  ",__LINE__, ##a)
#define VT_error(a...) if (vt_log_mask & VT_LOG_ERROR) vt_log ("ERROR",__LINE__, ##a)

#endif

#ifndef MIN
#define MIN(a,b)  ((a)<(b)?(a):(b))
#endif

static void vt_state_neutral      (VT *vt, int byte);
static void vt_state_esc          (VT *vt, int byte);
static void vt_state_osc          (VT *vt, int byte);
static void vt_state_apc          (VT *vt, int byte);
static void vt_state_apc_generic  (VT *vt, int byte);
static void vt_state_sixel        (VT *vt, int byte);
static void vt_state_esc_sequence (VT *vt, int byte);
static void vt_state_esc_foo      (VT *vt, int byte);
static void vt_state_swallow      (VT *vt, int byte);
static void vt_state_ctx          (VT *vt, int byte);
static void vt_state_vt52         (VT *vt, int byte);

#if 0
/* barebones linked list */

typedef struct _CtxList CtxList;
struct _CtxList
{
  void *data;
  CtxList *next;
};

static inline int ctx_list_length (CtxList *list)
{
  int length = 0;
  for (CtxList *l = list; l; l = l->next, length++);
  return length;
}

static inline void ctx_list_prepend (CtxList **list, void *data)
{
  CtxList *new_=calloc (sizeof (CtxList), 1);
  new_->next = *list;
  new_->data = data;
  *list = new_;
}

static inline void *ctx_list_last (CtxList *list)
{
  if (list)
    {
      CtxList *last;
      for (last = list; last->next; last=last->next);
      return last->data;
    }
  return NULL;
}

static inline void ctx_list_append (CtxList **list, void *data)
{
  CtxList *new_= calloc (sizeof (CtxList), 1);
  new_->data=data;
  if (*list)
    {
      CtxList *last;
      for (last = *list; last->next; last=last->next);
      last->next = new_;
      return;
    }
  *list = new_;
  return;
}

static inline void ctx_list_remove (CtxList **list, void *data)
{
  CtxList *iter, *prev = NULL;
  if ( (*list)->data == data)
    {
      prev = (void *) (*list)->next;
      free (*list);
      *list = prev;
      return;
    }
  for (iter = *list; iter; iter = iter->next)
    if (iter->data == data)
      {
        prev->next = iter->next;
        free (iter);
        break;
      }
    else
      { prev = iter; }
}

static inline void
ctx_list_insert_before (CtxList **list, CtxList *sibling,
                       void *data)
{
  if (*list == NULL || *list == sibling)
    {
      ctx_list_prepend (list, data);
    }
  else
    {
      CtxList *prev = NULL;
      for (CtxList *l = *list; l; l=l->next)
        {
          if (l == sibling)
            { break; }
          prev = l;
        }
      if (prev)
        {
          CtxList *new_=calloc (sizeof (CtxList), 1);
          new_->next = sibling;
          new_->data = data;
          prev->next=new_;
        }
    }
}
#endif


typedef enum
{
  STYLE_REVERSE         = 1 << 0,
  STYLE_BOLD            = 1 << 1,
  STYLE_BLINK           = 1 << 2,
  STYLE_UNDERLINE       = 1 << 3,
  STYLE_DIM             = 1 << 4,
  STYLE_HIDDEN          = 1 << 5,
  STYLE_ITALIC          = 1 << 6,
  STYLE_UNDERLINE_VAR   = 1 << 7,
  STYLE_STRIKETHROUGH   = 1 << 8,
  STYLE_OVERLINE        = 1 << 9,
  STYLE_BLINK_FAST      = 1 << 10,
  STYLE_PROPORTIONAL    = 1 << 11,
  STYLE_FG_COLOR_SET    = 1 << 12,
  STYLE_BG_COLOR_SET    = 1 << 13,
  STYLE_FG24_COLOR_SET  = 1 << 14,
  STYLE_BG24_COLOR_SET  = 1 << 15,
  //STYLE_NONERASABLE     = 1 << 16  // needed for selective erase
} TerminalStyle;

typedef struct Image
{
  int kitty_format;
  int width;
  int height;
  int id;
  int eid_no;
  int size;
  uint8_t *data;
} Image;

#define MAX_IMAGES 128

static Image image_db[MAX_IMAGES]= {{0,},};

static Image *image_query (int id)
{
  for (int i = 0; i < MAX_IMAGES; i++)
    {
      Image *image = &image_db[i];
      if (image->id == id)
        { return image; }
    }
  return NULL;
}

static int image_eid_no = 0;

static Image *image_add (int width,
                         int height,
                         int id,
                         int format,
                         int size,
                         uint8_t *data)
{
  // look for id if id is not 0
  Image *image;
  for (int i = 0; i < MAX_IMAGES; i++)
    {
      image = &image_db[i];
      if (image->data == NULL)
        { break; }
    }
  if (image->data)
    {
      // not a good eviction strategy
      image = &image_db[random() %MAX_IMAGES];
    }
  if (image->data)
    { free (image->data); }
  image->kitty_format = format;
  image->width  = width;
  image->height = height;
  image->id     = id;
  image->size   = size;
  image->data   = data;
  image->eid_no = image_eid_no++;
  return image;
}



void vtpty_resize (void *data, int cols, int rows, int px_width, int px_height)
{
  VtPty *vtpty = data;
  struct winsize ws;
  ws.ws_row = rows;
  ws.ws_col = cols;
  ws.ws_xpixel = px_width;
  ws.ws_ypixel = px_height;
  ioctl (vtpty->pty, TIOCSWINSZ, &ws);
}

ssize_t vtpty_write (void *data, const void *buf, size_t count)
{
  VtPty *vtpty = data;
  return write (vtpty->pty, buf, count);
}

ssize_t vtpty_read (void  *data, void *buf, size_t count)
{
  VtPty *vtpty = data;
  return read (vtpty->pty, buf, count);
}

int vtpty_waitdata (void  *data, int timeout)
{
  VtPty *vtpty = data;
  struct timeval tv;
  fd_set fdset;
  FD_ZERO (&fdset);
  FD_SET (vtpty->pty, &fdset);
  tv.tv_sec = 0;
  tv.tv_usec = timeout;
  tv.tv_sec  = timeout / 1000000;
  tv.tv_usec = timeout % 1000000;
  if (select (vtpty->pty+1, &fdset, NULL, NULL, &tv) == -1)
    {
      perror ("select");
      return 0;
    }
  if (FD_ISSET (vtpty->pty, &fdset) )
    {
      return 1;
    }
  return 0;
}


/* on current line */
static int vt_col_to_pos (VT *vt, int col)
{
  int pos = col;
  if (vt->current_line->contains_proportional)
    {
      Ctx *ctx = ctx_new ();
      ctx_font (ctx, "regular");
      ctx_font_size (ctx, vt->font_size);
      int x = 0;
      pos = 0;
      int prev_prop = 0;
      while (x <= col * vt->cw)
        {
          if (vt_line_get_style (vt->current_line, pos) & STYLE_PROPORTIONAL)
            {
              x += ctx_glyph_width (ctx, vt_line_get_unichar (vt->current_line, pos) );
              prev_prop = 1;
            }
          else
            {
              if (prev_prop)
                {
                  int new_cw = vt->cw - ( (x % vt->cw) );
                  if (new_cw < vt->cw*3/2)
                    { new_cw += vt->cw; }
                  x += new_cw;
                }
              else
                {
                  x += vt->cw;
                }
              prev_prop = 0;
            }
          pos ++;
        }
      pos --;
      ctx_free (ctx);
    }
  return pos;
}

static int vt_margin_left (VT *vt)
{
  int left = vt->left_right_margin_mode?vt->margin_left:1;
  return vt_col_to_pos (vt, left);
}

#define VT_MARGIN_LEFT vt_margin_left(vt)

static int vt_margin_right (VT *vt)
{
  int right = vt->left_right_margin_mode?vt->margin_right:vt->cols;
  return vt_col_to_pos (vt, right);
}

#define VT_MARGIN_RIGHT vt_margin_right(vt)


void vt_rev_inc (VT *vt)
{
  if (vt)
    vt->rev++;
}

long vt_rev (VT *vt)
{
  return vt?vt->rev:0;
}

static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence);
int vt_set_prop (VT *vt, uint32_t key_hash, const char *val);
uint64_t ctx_strhash (const char *utf8);

static void vt_set_title (VT *vt, const char *new_title)
{
  if (vt->inert) return;

  if (vt->title)
    { free (vt->title); }
  vt->title = strdup (new_title);
  vt_set_prop (vt, ctx_strhash ("title"), (char*)new_title);
}

const char *vt_get_title (VT *vt)
{
  return vt->title;
}

static void vt_run_command (VT *vt, const char *command, const char *term);
static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence);
static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence);
static void _vt_move_to (VT *vt, int y, int x);

static void vtcmd_clear (VT *vt, const char *sequence)
{
  while (vt->lines)
    {
      vt_line_free (vt->lines->data, 1);
      ctx_list_remove (&vt->lines, vt->lines->data);
    }
  vt->lines = NULL;
  vt->line_count = 0;

  if (1)
  { /* TODO: detect if this is neccesary.. due to images present
             in lines in scrollback */
    for (int i=0; i<vt->rows; i++)
    {
      vt->current_line = vt_line_new_with_size ("", vt->cols);
      ctx_list_prepend (&vt->scrollback, vt->current_line);
      vt->scrollback_count++;
    }
  }

  /* populate lines */
  for (int i=0; i<vt->rows; i++)
    {
      vt->current_line = vt_line_new_with_size ("", vt->cols);
      ctx_list_prepend (&vt->lines, vt->current_line);
      vt->line_count++;
    }
}

#define set_fg_rgb(r, g, b) \
    vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\
    vt->cstyle |=  ((uint64_t)(r)<<16);\
    vt->cstyle |=  ((uint64_t)(g)<<(16+8));\
    vt->cstyle |=  ((uint64_t)(b)<<(16+8+8));\
    vt->cstyle |= STYLE_FG_COLOR_SET;\
    vt->cstyle |= STYLE_FG24_COLOR_SET;\

#define set_bg_rgb(r, g, b) \
    vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\
    vt->cstyle |=  ((uint64_t)(r)<<40);\
    vt->cstyle |=  ((uint64_t)(g)<<(40+8));\
    vt->cstyle |=  ((uint64_t)(b)<<(40+8+8));\
    vt->cstyle |= STYLE_BG_COLOR_SET;\
    vt->cstyle |= STYLE_BG24_COLOR_SET;\

#define set_fg_idx(idx) \
    vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<16));\
    vt->cstyle ^= (vt->cstyle & STYLE_FG24_COLOR_SET);\
    vt->cstyle |=  ((idx)<<16);\
    vt->cstyle |= STYLE_FG_COLOR_SET;

#define set_bg_idx(idx) \
    vt->cstyle ^= (vt->cstyle & (((uint64_t)((1l<<24)-1))<<40));\
    vt->cstyle ^= (vt->cstyle & STYLE_BG24_COLOR_SET);\
    vt->cstyle |= ((int64_t)(idx)<<40) ;\
    vt->cstyle |= STYLE_BG_COLOR_SET;


static void _vt_compute_cw_ch (VT *vt)
{
  vt->cw = (vt->font_size / vt->line_spacing * vt->scale_x) + 0.99;
  vt->ch = vt->font_size;
}

static void vtcmd_set_132_col (VT  *vt, int set)
{
  // this should probably force the window as well
  if (set == 0 && vt->scale_x == 1.0f) return;
  if (set == 1 && vt->scale_x != 1.0f) return;
  if (set) // 132 col
    {
      vt->scale_x = 74.0/132.0; // show all - po
      //vt->scale_x = 80.0/132.0;
      vt->scale_y = 1.0;
      _vt_compute_cw_ch (vt);
      vt_set_term_size (vt, vt->cols * 132/80.0, vt->rows);
    }
  else // 80 col
    {
      vt->scale_x = 1.0;
      vt->scale_y = 1.0;
      _vt_compute_cw_ch (vt);
      vt_set_term_size (vt, vt->cols * 80/132.0, vt->rows);
    }
}

static void vt_line_feed (VT *vt);
static void vt_carriage_return (VT *vt);

static int vt_trimlines (VT *vt, int max);
static void vtcmd_reset_to_initial_state (VT *vt, const char *sequence)
{
  VT_info ("reset %s", sequence);
  if (getenv ("VT_DEBUG") )
    { vt->debug = 1; }
  vtcmd_clear (vt, sequence);
  vt->encoding = 0;
  vt->bracket_paste = 0;
  vt->ctx_events = 0;
  vt->cr_on_lf = 0;
  vtcmd_set_top_and_bottom_margins (vt, "[r");
  vtcmd_set_left_and_right_margins (vt, "[s");
  vt->autowrap               = 1;
  vt->justify                = 0;
  vt->cursor_visible         = 1;
  vt->charset[0]             = 0;
  vt->charset[1]             = 0;
  vt->charset[2]             = 0;
  vt->charset[3]             = 0;
  vt->bell                   = 3;
  vt->scale_x                = 1.0;
  vt->scale_y                = 1.0;
  vt->saved_x                = 1;
  vt->saved_y                = 1;
  vt->saved_style            = 1;
  vt->reverse_video          = 0;
  vt->cstyle                 = 0;
  vt->keyrepeat              = 1;
  vt->cursor_key_application = 0;
  vt->argument_buf_len       = 0;
  vt->argument_buf[0]        = 0;
  vt->vtpty.done             = 0;
  vt->result                 = -1;
  vt->state                  = vt_state_neutral;
  vt->scroll_on_output       = 0;
  vt->scroll_on_input        = 1;
  vt->unit_pixels            = 0;
  vt->mouse                  = 0;
  vt->mouse_drag             = 0;
  vt->mouse_all              = 0;
  vt->mouse_decimal          = 0;
  _vt_compute_cw_ch (vt);
  for (int i = 0; i < MAX_COLS; i++)
    { vt->tabs[i] = i % 8 == 0? 1 : 0; }
  _vt_move_to (vt, vt->margin_top, vt->cursor_x);
  vt_carriage_return (vt);
  //if (vt->ctx)
  //  { ctx_reset (vt->ctx); }
  vt->audio.bits = 8;
  vt->audio.channels = 1;
  vt->audio.type = 'u';
  vt->audio.samplerate = 8000;
  vt->audio.buffer_size = 1024;
  vt->audio.encoding = 'a';
  vt->audio.compression = '0';
  vt->audio.mic = 0;
  while (vt->scrollback)
    {
      vt_line_free (vt->scrollback->data, 1);
      ctx_list_remove (&vt->scrollback, vt->scrollback->data);
    }
  vt->scrollback_count = 0;
}

void vt_set_font_size (VT *vt, float font_size)
{
  vt->font_size = font_size;
  _vt_compute_cw_ch (vt);
}

float       vt_get_font_size      (VT *vt)
{
  return vt->font_size;
}

void vt_set_line_spacing (VT *vt, float line_spacing)
{
  vt->line_spacing = line_spacing;
  _vt_compute_cw_ch (vt);
}

VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch)
{
  VT *vt                 = calloc (sizeof (VT), 1);
  vt->id                 = id;
  vt->lastx              = -1;
  vt->lasty              = -1;
  vt->state              = vt_state_neutral;
  vt->smooth_scroll      = 0;
  vt->can_launch         = can_launch;
  vt->scroll_offset      = 0.0;
  vt->waitdata           = vtpty_waitdata;
  vt->read               = vtpty_read;
  vt->write              = vtpty_write;
  vt->resize             = vtpty_resize;
  vt->font_to_cell_scale = 0.98;
  vt->cursor_visible     = 1;
  vt->lines              = NULL;
  vt->line_count         = 0;
  vt->current_line       = NULL;
  vt->cols               = 0;
  vt->rows               = 0;

  vt->scrollback_limit   = DEFAULT_SCROLLBACK;
  vt->argument_buf_len   = 0;
  vt->argument_buf_cap   = 64;
  vt->argument_buf       = malloc (vt->argument_buf_cap);
  vt->argument_buf[0]    = 0;
  vt->vtpty.done         = 0;
  vt->result             = -1;
  vt->line_spacing       = 1.0;
  vt->scale_x            = 1.0;
  vt->scale_y            = 1.0;
  vt_set_font_size (vt, font_size);
  vt_set_line_spacing (vt, line_spacing);
  if (command)
    {
      vt_run_command (vt, command, NULL);
    }
  if (width <= 0) width = 640;
  if (height <= 0) width = 480;
  vt_set_px_size (vt, width, height);

  vt->fg_color[0] = 216;
  vt->fg_color[1] = 216;
  vt->fg_color[2] = 216;
  vt->bg_color[0] = 0;
  vt->bg_color[1] = 0;
  vt->bg_color[2] = 0;
  vtcmd_reset_to_initial_state (vt, NULL);
  //vt->ctx = ctx_new ();
  ctx_list_prepend (&vts, vt);
  return vt;
}

int vt_cw (VT *vt)
{
  return vt->cw;
}

int vt_ch (VT *vt)
{
  return vt->ch;
}

static int vt_trimlines (VT *vt, int max)
{
  CtxList *chop_point = NULL;
  CtxList *l;
  int i;
  if (vt->line_count < max)
    { 
      return 0;
    }
  for (l = vt->lines, i = 0; l && i < max-1; l = l->next, i++);
  if (l)
    {
      chop_point = l->next;
      l->next = NULL;
    }
  while (chop_point)
    {
      if (vt->in_alt_screen)
        {
          vt_line_free (chop_point->data, 1);
        }
      else
        {
          ctx_list_prepend (&vt->scrollback, chop_point->data);
          vt->scrollback_count ++;
        }
      ctx_list_remove (&chop_point, chop_point->data);
      vt->line_count--;
    }
  if (vt->scrollback_count > vt->scrollback_limit + 1024)
    {
      CtxList *l = vt->scrollback;
      int no = 0;
      while (l && no < vt->scrollback_limit)
        {
          l = l->next;
          no++;
        }
      chop_point = NULL;
      if (l)
        {
          chop_point = l->next;
          l->next = NULL;
        }
      while (chop_point)
        {
          vt_line_free (chop_point->data, 1);
          ctx_list_remove (&chop_point, chop_point->data);
          vt->scrollback_count --;
        }
    }
  return 0;
}

static void vt_rewrap_pair (VT *vt, VtLine *topline, VtLine *bottomline, int max_col)
{
  int toplen = 0;

  while ((toplen = vt_line_get_utf8length (topline)) > max_col)
  {
     uint32_t unichar = vt_line_get_unichar (topline, toplen-1);
     uint32_t style =  vt_line_get_style (topline, toplen-1);
     vt_line_insert_unichar (bottomline, 0, unichar);
     vt_line_remove (topline, toplen-1);
     vt_line_set_style (bottomline, 0, style);
  }

  while (vt_line_get_length (bottomline) &&
         (toplen = vt_line_get_utf8length (topline)) < max_col)
  {
     uint32_t unichar = vt_line_get_unichar (bottomline, 0);
     uint32_t style =  vt_line_get_style (bottomline, 0);
     vt_line_append_unichar (topline, unichar);
     vt_line_set_style (topline, toplen, style);
     vt_line_remove (bottomline, 0);
  }
}

static void vt_rewrap (VT *vt, int max_col)
{
  if (max_col < 8) max_col = 8;
  CtxList *list = NULL;

  for (CtxList *l = vt->lines; l;)
  {
    CtxList *next = l->next;
    ctx_list_prepend (&list, l->data);
    ctx_list_remove (&vt->lines, l->data);
    l = next;
  }
  for (CtxList *l = vt->scrollback; l;)
  {
    CtxList *next = l->next;
    ctx_list_prepend (&list, l->data);
    ctx_list_remove (&vt->scrollback, l->data);
    l = next;
  }

  for (CtxList *l = list; l; l = l->next)
    {
      VtLine *line = l->data;
      VtLine *next = l->next ?l->next->data:NULL;

      if (vt_line_get_utf8length (line) >= max_col || (next && next->wrapped))
      {
        if (!next)
        {
          ctx_list_append (&list, vt_line_new (""));
          next = l->next->data;
          next->wrapped = 1;
        }
        else if (!next->wrapped)
        {
          ctx_list_insert_before (&list, l->next, vt_line_new (""));
          next = l->next->data;
          next->wrapped = 1;
        } 
        vt_rewrap_pair (vt, line, next, max_col);
        if (vt_line_get_utf8length (next) == 0)
          ctx_list_remove (&list, l->next->data);
      }
    }

  int rows = vt->rows;
  int total_rows = ctx_list_length (list);

  int scrollback_rows = total_rows - rows;

  int c = 0;
  CtxList *l;
  for (l = list; l && c < scrollback_rows;)
  {
    CtxList *next = l->next;
    ctx_list_prepend (&vt->scrollback, l->data);
    ctx_list_remove (&list, l->data);
    l = next;
    c++;
  }
  for (; l ;)
  {
    CtxList *next = l->next;
    ctx_list_prepend (&vt->lines, l->data);
    ctx_list_remove (&list, l->data);
    l = next;
    c++;
  }
}

void vt_set_term_size (VT *vt, int icols, int irows)
{
  if (vt->rows == irows && vt->cols == icols)
    return;


  if (vt->state == vt_state_ctx)
  {
    // we should queue a pending resize instead,
    // .. or set a flag indicating that the last
    // rendered frame is discarded?
    return;
  }

  if(1)vt_rewrap (vt, icols);

  while (irows > vt->rows)
    {
      if (vt->scrollback_count)
        {
          vt->scrollback_count--;
          ctx_list_append (&vt->lines, vt->scrollback->data);
          ctx_list_remove (&vt->scrollback, vt->scrollback->data);
          vt->cursor_y++;
        }
      else
        {
          ctx_list_prepend (&vt->lines, vt_line_new_with_size ("", vt->cols) );
        }
      vt->line_count++;
      vt->rows++;
    }
  while (irows < vt->rows)
    {
      vt->cursor_y--;
      vt->rows--;
    }
  vt->rows = irows;
  vt->cols = icols;
  vt_resize (vt, vt->cols, vt->rows, vt->width, vt->height);
  vt_trimlines (vt, vt->rows);
  vt->margin_top     = 1;
  vt->margin_left    = 1;
  vt->margin_bottom  = vt->rows;
  vt->margin_right   = vt->cols;
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  vt->rev++;
  VT_info ("resize %i %i", irows, icols);
  if (vt->ctxp)
    ctx_parser_free (vt->ctxp);
  vt->ctxp = NULL;
}

void vt_set_px_size (VT *vt, int width, int height)
{
  int cols = width / vt->cw;
  int rows = height / vt->ch;
  vt->width = width;
  vt->height = height;
  vt_set_term_size (vt, cols, rows);
}



static void vt_argument_buf_reset (VT *vt, const char *start)
{
  if (start)
    {
      strcpy (vt->argument_buf, start);
      vt->argument_buf_len = strlen (start);
    }
  else
    { vt->argument_buf[vt->argument_buf_len=0]=0; }
}

static inline void vt_argument_buf_add (VT *vt, int ch)
{
  if (vt->argument_buf_len + 1 >= 1024 * 1024 * 16)
    return; 
  //
  if (vt->argument_buf_len + 1 >=
      vt->argument_buf_cap)
    {
      vt->argument_buf_cap = vt->argument_buf_cap * 2;
      vt->argument_buf = realloc (vt->argument_buf, vt->argument_buf_cap);
    }
  vt->argument_buf[vt->argument_buf_len] = ch;
  vt->argument_buf[++vt->argument_buf_len] = 0;
}

static void
_vt_move_to (VT *vt, int y, int x)
{
  int i;
  x = x < 1 ? 1 : (x > vt->cols ? vt->cols : x);
  y = y < 1 ? 1 : (y > vt->rows ? vt->rows : y);
  vt->at_line_home = 0;
  vt->cursor_x = x;
  vt->cursor_y = y;
  i = vt->rows - y;
  CtxList *l;
  for (l = vt->lines; l && i >= 1; l = l->next, i--);
  if (l)
    {
      vt->current_line = l->data;
    }
  else
    {
      for (; i > 0; i--)
        {
          vt->current_line = vt_line_new_with_size ("", vt->cols);
          ctx_list_append (&vt->lines, vt->current_line);
          vt->line_count++;
        }
    }
  VT_cursor ("%i,%i (_vt_move_to)", y, x);
  vt->rev++;
}

static void vt_scroll (VT *vt, int amount);

static void _vt_add_str (VT *vt, const char *str)
{
  int logical_margin_right = VT_MARGIN_RIGHT;
  if (vt->cstyle & STYLE_PROPORTIONAL)
    { vt->current_line->contains_proportional = 1; }
  if (vt->cursor_x > logical_margin_right)
    {
      if (vt->autowrap)
        {
          int chars = 0;
          int old_x = vt->cursor_x;
          VtLine *old_line = vt->current_line;
          if (vt->justify && str[0] != ' ')
            {
              while (old_x-1-chars >1 && vt_line_get_unichar (vt->current_line,
                     old_x-1-chars) !=' ')
                {
                  chars++;
                }
              chars--;
              if (chars > (vt->margin_right - vt->margin_left) * 3 / 2)
                { chars = 0; }
            }
          if (vt->cursor_y == vt->margin_bottom)
            {
              vt_scroll (vt, -1);
            }
          else
            {
              _vt_move_to (vt, vt->cursor_y+1, 1);
            }
          vt->current_line->wrapped=1;
          vt_carriage_return (vt);
          for (int i = 0; i < chars; i++)
            {
              vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle);
              vt_line_replace_unichar (vt->current_line, vt->cursor_x - 1,
                                         vt_line_get_unichar (old_line, old_x-1-chars+i) );
              vt->cursor_x++;
            }
          for (int i = 0; i < chars; i++)
            {
              vt_line_replace_unichar (old_line, old_x-1-chars+i, ' ');
            }
          if (str[0] == ' ')
            return;
        }
      else
        {
          vt->cursor_x = logical_margin_right;
        }
    }
  if (vt->insert_mode)
    {
      vt_line_insert_utf8 (vt->current_line, vt->cursor_x - 1, str);
      while (vt->current_line->string.utf8_length > logical_margin_right)
        { vt_line_remove (vt->current_line, logical_margin_right); }
    }
  else
    {
      vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1, str);
    }
  vt_line_set_style (vt->current_line, vt->cursor_x-1, vt->cstyle);
  vt->cursor_x += 1;
  vt->at_line_home = 0;
  vt->rev++;
}

static void _vt_backspace (VT *vt)
{
  if (vt->current_line)
    {
      vt->cursor_x --;
      if (vt->cursor_x == VT_MARGIN_RIGHT) { vt->cursor_x--; }
      if (vt->cursor_x < VT_MARGIN_LEFT)
        {
          vt->cursor_x = VT_MARGIN_LEFT;
          vt->at_line_home = 1;
        }
      VT_cursor ("backspace");
    }
  vt->rev++;
}

static void vtcmd_set_top_and_bottom_margins (VT *vt, const char *sequence)
{
  int top = 1, bottom = vt->rows;
  /* w3m issues this; causing reset of cursor position, why it is issued
   * is unknown 
   */
  if (!strcmp (sequence, "[?1001r"))
    return;
  if (strlen (sequence) > 2)
    {
      sscanf (sequence, "[%i;%ir", &top, &bottom);
    }
  VT_info ("margins: %i %i", top, bottom);
  if (top <1) { top = 1; }
  if (top > vt->rows) { top = vt->rows; }
  if (bottom > vt->rows) { bottom = vt->rows; }
  if (bottom < top) { bottom = top; }
  vt->margin_top = top;
  vt->margin_bottom = bottom;
#if 0
  _vt_move_to (vt, top, 1);
#endif
  vt_carriage_return (vt);
  VT_cursor ("%i, %i (home)", top, 1);
}
static void vtcmd_save_cursor_position (VT *vt, const char *sequence);

static void vtcmd_set_left_and_right_margins (VT *vt, const char *sequence)
{
  int left = 1, right = vt->cols;
  if (!vt->left_right_margin_mode)
    {
      vtcmd_save_cursor_position (vt, sequence);
      return;
    }
  if (strlen (sequence) > 2)
    {
      sscanf (sequence, "[%i;%is", &left, &right);
    }
  VT_info ("hor margins: %i %i", left, right);
  if (left <1) { left = 1; }
  if (left > vt->cols) { left = vt->cols; }
  if (right > vt->cols) { right = vt->cols; }
  if (right < left) { right = left; }
  vt->margin_left = left;
  vt->margin_right = right;
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  vt_carriage_return (vt);
  //VT_cursor ("%i, %i (home)", left, 1);
}

static inline int parse_int (const char *arg, int def_val)
{
  if (!isdigit (arg[1]) || strlen (arg) == 2)
    { return def_val; }
  return atoi (arg+1);
}


static void vtcmd_set_line_home (VT *vt, const char *sequence)
{
  int val = parse_int (sequence, 1);
  char buf[256];
  vt->left_right_margin_mode = 1;
  sprintf (buf, "[%i;%it", val, vt->margin_right);
  vtcmd_set_left_and_right_margins (vt, buf);
}

static void vtcmd_set_line_limit (VT *vt, const char *sequence)
{
  int val = parse_int (sequence, 0);
  char buf[256];
  vt->left_right_margin_mode = 1;
  if (val < vt->margin_left) { val = vt->margin_left; }
  sprintf (buf, "[%i;%it", vt->margin_left, val);
  vtcmd_set_left_and_right_margins (vt, buf);
}

static void vt_scroll (VT *vt, int amount)
{
  int remove_no, insert_before;
  VtLine *string = NULL;
  if (amount == 0) { amount = 1; }
  if (amount < 0)
    {
      remove_no = vt->margin_top;
      insert_before = vt->margin_bottom;
    }
  else
    {
      remove_no = vt->margin_bottom;
      insert_before = vt->margin_top;
    }
  CtxList *l;
  int i;
  for (i=vt->rows, l = vt->lines; i > 0 && l; l=l->next, i--)
    {
      if (i == remove_no)
        {
          string = l->data;
          ctx_list_remove (&vt->lines, string);
          break;
        }
    }
  if (string)
    {
      if (!vt->in_alt_screen &&
          (vt->margin_top == 1 && vt->margin_bottom == vt->rows) )
        {
          ctx_list_prepend (&vt->scrollback, string);
          vt->scrollback_count ++;
        }
      else
        {
          vt_line_free (string, 1);
        }
    }
  string = vt_line_new_with_size ("", vt->cols/4);
  if (amount > 0 && vt->margin_top == 1)
    {
      ctx_list_append (&vt->lines, string);
    }
  else
    {
      for (i=vt->rows, l = vt->lines; l; l=l->next, i--)
        {
          if (i == insert_before)
            {
              ctx_list_insert_before (&vt->lines, l, string);
              break;
            }
        }
      if (i != insert_before)
        {
          ctx_list_append (&vt->lines, string);
        }
    }
  vt->current_line = string;
  /* not updating line count since we should always remove one and add one */
  if (vt->smooth_scroll)
    {
      if (amount < 0)
        {
          vt->scroll_offset = -1.0;
          vt->in_smooth_scroll = -1;
        }
      else
        {
          vt->scroll_offset = 1.0;
          vt->in_smooth_scroll = 1;
        }
    }

  {
    vt->select_begin_row += amount;
    vt->select_end_row += amount;
    vt->select_start_row += amount;
  }
}

typedef struct Sequence
{
  const char *prefix;
  char        suffix;
  void (*vtcmd) (VT *vt, const char *sequence);
  uint32_t    compat;
} Sequence;

static void vtcmd_cursor_position (VT *vt, const char *sequence)
{
  int y = 1, x = 1;
  const char *semi;
  if (sequence[0] != 'H' && sequence[0] != 'f')
    {
      y = parse_int (sequence, 1);
      if ( (semi = strchr (sequence, ';') ) )
        {
          x = parse_int (semi, 1);
        }
    }
  if (x == 0) { x = 1; }
  if (y == 0) { y = 1; }
  if (vt->origin)
    {
      y += vt->margin_top - 1;
      _vt_move_to (vt, y, vt->cursor_x);
      x += VT_MARGIN_LEFT - 1;
    }
  VT_cursor ("%i %i CUP", y, x);
  _vt_move_to (vt, y, x);
}


static void vtcmd_horizontal_position_absolute (VT *vt, const char *sequence)
{
  int x = parse_int (sequence, 1);
  if (x<=0) { x = 1; }
  _vt_move_to (vt, vt->cursor_y, x);
}

static void vtcmd_goto_row (VT *vt, const char *sequence)
{
  int y = parse_int (sequence, 1);
  _vt_move_to (vt, y, vt->cursor_x);
}

static void vtcmd_cursor_forward (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      vt->cursor_x++;
    }
  if (vt->cursor_x > VT_MARGIN_RIGHT)
    { vt->cursor_x = VT_MARGIN_RIGHT; }
}

static void vtcmd_cursor_backward (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      vt->cursor_x--;
    }
  if (vt->cursor_x < VT_MARGIN_LEFT)
    {
      vt->cursor_x = VT_MARGIN_LEFT; // should this wrap??
      vt->at_line_home = 1;
    }
}

static void vtcmd_reverse_index (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      if (vt->cursor_y == vt->margin_top)
        {
          vt_scroll (vt, 1);
          _vt_move_to (vt, vt->margin_top, vt->cursor_x);
        }
      else
        {
          _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x);
        }
    }
}

static void vtcmd_cursor_up (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      if (vt->cursor_y == vt->margin_top)
        {
          //_vt_move_to (vt, 1, vt->cursor_x);
        }
      else
        {
          _vt_move_to (vt, vt->cursor_y-1, vt->cursor_x);
        }
    }
}

static void vtcmd_back_index (VT *vt, const char *sequence)
{
  // XXX implement
}

static void vtcmd_forward_index (VT *vt, const char *sequence)
{
  // XXX implement
}

static void vtcmd_index (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      if (vt->cursor_y == vt->margin_bottom)
        {
          vt_scroll (vt, -1);
          _vt_move_to (vt, vt->margin_bottom, vt->cursor_x);
        }
      else
        {
          _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x);
        }
    }
}

static void vtcmd_cursor_down (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n==0) { n = 1; }
  for (int i = 0; i < n; i++)
    {
      if (vt->cursor_y >= vt->margin_bottom)
        {
          _vt_move_to (vt, vt->margin_bottom, vt->cursor_x);
        }
      else
        {
          _vt_move_to (vt, vt->cursor_y + 1, vt->cursor_x);
        }
    }
}

static void vtcmd_next_line (VT *vt, const char *sequence)
{
  vtcmd_index (vt, sequence);
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  vt_carriage_return (vt);
  vt->cursor_x = VT_MARGIN_LEFT;
}

static void vtcmd_cursor_preceding_line (VT *vt, const char *sequence)
{
  vtcmd_cursor_up (vt, sequence);
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  vt->cursor_x = VT_MARGIN_LEFT;
}

static void vtcmd_erase_in_line (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 0);
  switch (n)
    {
      case 0: // clear to end of line
        {
          char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1);
          if (p) { *p = 0; }
          // XXX : this is chopping lines
          for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++)
            { vt_line_set_style (vt->current_line, col - 1, vt->cstyle); }
          vt->current_line->string.length = strlen (vt->current_line->string.str);
          vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str);
        }
        break;
      case 1: // clear from beginning to cursor
        {
          for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++)
            {
              vt_line_replace_utf8 (vt->current_line, col-1, " ");
            }
          for (int col = VT_MARGIN_LEFT; col <= vt->cursor_x; col++)
            { vt_line_set_style (vt->current_line, col-1, vt->cstyle); }
          vt->current_line->string.length = strlen (vt->current_line->string.str);
          vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str); // should be a nop
        }
        break;
      case 2: // clear entire line
        for (int col = VT_MARGIN_LEFT; col <= VT_MARGIN_RIGHT; col++)
          { vt_line_set_style (vt->current_line, col-1, vt->cstyle); }
        vt_line_set (vt->current_line, ""); // XXX not all
        break;
    }
}

static void vtcmd_erase_in_display (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 0);
  switch (n)
    {
      case 0: // clear to end of screen
        {
          char *p = (char *) mrg_utf8_skip (vt->current_line->string.str, vt->cursor_x-1);
          if (p) { *p = 0; }
          vt->current_line->string.length = strlen (vt->current_line->string.str);
          vt->current_line->string.utf8_length = ctx_utf8_strlen (vt->current_line->string.str);
        }
        for (int col = vt->cursor_x; col <= VT_MARGIN_RIGHT; col++)
          { vt_line_set_style (vt->current_line, col-1, vt->cstyle); }
        {
          CtxList *l;
          int no = vt->rows;
          for (l = vt->lines; l->data != vt->current_line; l = l->next, no--)
            {
              VtLine *buf = l->data;
              buf->string.str[0] = 0;
              buf->string.length = 0;
              buf->string.utf8_length = 0;
              for (int col = 1; col <= vt->cols; col++)
                { vt_line_set_style (buf, col-1, vt->cstyle); }
            }
        }
        break;
      case 1: // clear from beginning to cursor
        {
          for (int col = 1; col <= vt->cursor_x; col++)
            {
              vt_line_replace_utf8 (vt->current_line, col-1, " ");
              vt_line_set_style (vt->current_line, col-1, vt->cstyle);
            }
        }
        {
          CtxList *l;
          int there_yet = 0;
          int no = vt->rows;
          for (l = vt->lines; l; l = l->next, no--)
            {
              VtLine *buf = l->data;
              if (there_yet)
                {
                  buf->string.str[0] = 0;
                  buf->string.length = 0;
                  buf->string.utf8_length = 0;
                  for (int col = 1; col <= vt->cols; col++)
                    { vt_line_set_style (buf, col-1, vt->cstyle); }
                }
              if (buf == vt->current_line)
                {
                  there_yet = 1;
                }
            }
        }
        break;
      case 3: // also clear scrollback
        while (vt->scrollback)
        {
           vt_line_free (vt->scrollback->data, 1);
           ctx_list_remove (&vt->scrollback, vt->scrollback->data);
         }
        vt->scrollback_count = 0;
        /* FALLTHROUGH */
      case 2: // clear entire screen but keep cursor;
        {
          int tx = vt->cursor_x;
          int ty = vt->cursor_y;
          vtcmd_clear (vt, "");
          _vt_move_to (vt, ty, tx);
          for (CtxList *l = vt->lines; l; l = l->next)
            {
              VtLine *line = l->data;
              for (int col = 1; col <= vt->cols; col++)
                { vt_line_set_style (line, col-1, vt->cstyle); }
            }
        }
        break;
    }
}

static void vtcmd_screen_alignment_display (VT *vt, const char *sequence)
{
  for (int y = 1; y <= vt->rows; y++)
    {
      _vt_move_to (vt, y, 1);
      for (int x = 1; x <= vt->cols; x++)
        {
          _vt_add_str (vt, "E");
        }
    }
}

#if 0
static int find_idx (int r, int g, int b)
{
  r = r / 255.0 * 5;
  g = g / 255.0 * 5;
  b = b / 255.0 * 5;
  return 16 + r * 6 * 6 + g * 6 + b;
}
#endif

static void vtcmd_set_graphics_rendition (VT *vt, const char *sequence)
{
  const char *s = sequence;
  if (s[0]) { s++; }
  while (s && *s)
    {
      int n = parse_int (s - 1, 0); // works until color
      // both fg and bg could be set in 256 color mode FIXME
      //
      /* S_GR@38@Set forground color@foo bar baz@ */
      if (n == 38) // set foreground
        {
          s = strchr (s, ';');
          if (!s)
            {
              VT_warning ("incomplete [38m expected ;  %s", sequence);
              return;
            }
          n = parse_int (s, 0);
          if (n == 5)
            {
              s++;
              if (strchr (s, ';') )
                { s = strchr (s, ';'); }
              else
                { s = strchr (s, ':'); }
              if (s)
                {
                  n = parse_int (s, 0);
                  set_fg_idx (n);
                  s++;
                  while (*s && *s >= '0' && *s <='9') { s++; }
                }
            }
          else if (n == 2)
            {
              int r = 0, g = 0, b = 0;
              s++;
              if (strchr (s, ';') )
                {
                  s = strchr (s, ';');
                  if (s)
                    { sscanf (s, ";%i;%i;%i", &r, &g, &b); }
                }
              else
                {
                  s = strchr (s, ':');
                  if (s)
                    { sscanf (s, ":%i:%i:%i", &r, &g, &b); }
                }
              if (s)
              for (int i = 0; i < 3; i++)
                {
                  if (*s)
                    {
                      s++;
                      while (*s && *s >= '0' && *s <='9') { s++; }
                    }
                }
              set_fg_rgb (r,g,b);
            }
          else
            {
              VT_warning ("unhandled %s %i", sequence, n);
              return;
            }
          //return; // XXX we should continue, and allow further style set after complex color
        }
      else if (n == 48) // set background
        {
          s = strchr (s, ';');
          if (!s)
            {
              VT_warning ("incomplete [38m expected ;  %s", sequence);
              return;
            }
          n = parse_int (s, 0);
          if (n == 5)
            {
              s++;
              if (strchr (s, ';') )
                { s = strchr (s, ';'); }
              else
                { s = strchr (s, ':'); }
              if (s)
                { n = parse_int (s, 0); }
              set_bg_idx (n);
              if (s)
                {
                  s++;
                  while (*s && *s >= '0' && *s <='9') { s++; }
                }
            }
          else if (n == 2)
            {
              int r = 0, g = 0, b = 0;
              s++;
              if (strchr (s, ';') )
                {
                  s = strchr (s, ';');
                  if (s)
                    { sscanf (s, ";%i;%i;%i", &r, &g, &b); }
                }
              else
                {
                  s = strchr (s, ':');
                  if (s)
                    { sscanf (s, ":%i:%i:%i", &r, &g, &b); }
                }
              if (s)
              for (int i = 0; i < 3; i++)
                {
                  s++;
                  while (*s >= '0' && *s <='9') { s++; }
                }
              set_bg_rgb (r,g,b);
            }
          else
            {
              VT_warning ("unhandled %s %i", sequence, n);
              return;
            }
          //return; // we XXX should continue, and allow further style set after complex color
        }
      else
        switch (n)
          {
            case 0: /* SGR@0@Style reset@@ */
              if (vt->cstyle & STYLE_PROPORTIONAL)
                { vt->cstyle = STYLE_PROPORTIONAL; }
              else
                { vt->cstyle = 0; }
              break;
            case 1: /* SGR@@Bold@@ */
              vt->cstyle |= STYLE_BOLD;
              break;
            case 2: /* SGR@@Dim@@ */
              vt->cstyle |= STYLE_DIM;
              break;
            case 3: /* SGR@@Italic@@ */
              vt->cstyle |= STYLE_ITALIC;
              break;
            case 4: /* SGR@@Underscore@@ */
                    /* SGR@4:2@Double underscore@@ */
                    /* SGR@4:3@Curvy underscore@@ */
              if (s[1] == ':')
                {
                  switch (s[2])
                    {
                      case '0':
                        break;
                      case '1':
                        vt->cstyle |= STYLE_UNDERLINE;
                        break;
                      case '2':
                        vt->cstyle |= STYLE_UNDERLINE|
                                      STYLE_UNDERLINE_VAR;
                        break;
                      default:
                      case '3':
                        vt->cstyle |= STYLE_UNDERLINE_VAR;
                        break;
                    }
                }
              else
                {
                  vt->cstyle |= STYLE_UNDERLINE;
                }
              break;
            case 5: /* SGR@@Blink@@ */
              vt->cstyle |= STYLE_BLINK;
              break;
            case 6: /* SGR@@Blink Fast@@ */
              vt->cstyle |= STYLE_BLINK_FAST;
              break;
            case 7: /* SGR@@Reverse@@ */
              vt->cstyle |= STYLE_REVERSE;
              break;
            case 8: /* SGR@@Hidden@@ */
              vt->cstyle |= STYLE_HIDDEN;
              break;
            case 9: /* SGR@@Strikethrough@@ */
              vt->cstyle |= STYLE_STRIKETHROUGH;
              break;
            case 10: /* SGR@@Font 0@@ */
              break;
            case 11: /* SGR@@Font 1@@ */
              break;
            case 12: /* SGR@@Font 2(ignored)@@ */
            case 13: /* SGR@@Font 3(ignored)@@ */
            case 14: /* SGR@@Font 4(ignored)@@ */
              break;
            case 22: /* SGR@@Bold off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_BOLD);
              vt->cstyle ^= (vt->cstyle & STYLE_DIM);
              break;
            case 23: /* SGR@@Italic off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_ITALIC);
              break;
            case 24: /* SGR@@Underscore off@@ */
              vt->cstyle ^= (vt->cstyle & (STYLE_UNDERLINE|STYLE_UNDERLINE_VAR) );
              break;
            case 25: /* SGR@@Blink off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_BLINK);
              vt->cstyle ^= (vt->cstyle & STYLE_BLINK_FAST);
              break;
            case 26: /* SGR@@Proportional spacing @@ */
              vt->cstyle |= STYLE_PROPORTIONAL;
              break;
            case 27: /* SGR@@Reverse off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_REVERSE);
              break;
            case 28: /* SGR@@Hidden off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_HIDDEN);
              break;
            case 29: /* SGR@@Strikethrough off@@ */
              vt->cstyle ^= (vt->cstyle & STYLE_STRIKETHROUGH);
              break;
            case 30: /* SGR@@black text color@@ */
              set_fg_idx (0);
              break;
            case 31: /* SGR@@red text color@@ */
              set_fg_idx (1);
              break;
            case 32: /* SGR@@green text color@@ */
              set_fg_idx (2);
              break;
            case 33: /* SGR@@yellow text color@@ */
              set_fg_idx (3);
              break;
            case 34: /* SGR@@blue text color@@ */
              set_fg_idx (4);
              break;
            case 35: /* SGR@@magenta text color@@ */
              set_fg_idx (5);
              break;
            case 36: /* SGR@@cyan text color@@ */
              set_fg_idx (6);
              break;
            case 37: /* SGR@@light gray text color@@ */
              set_fg_idx (7);
              break;
          /* SGR@38;5;Pn@256 color index foreground color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6  RGB cube and in the end a grayscale without white and black.@ */
              /* SGR@38;2;Pr;Pg;Pb@24 bit RGB foreground color each of Pr Pg and Pb have 0-255 range@@ */
            case 39: /* SGR@@default text color@@ */
              set_fg_idx (vt->reverse_video?0:15);
              vt->cstyle ^= (vt->cstyle & STYLE_FG_COLOR_SET);
              break;
            case 40: /* SGR@@black background color@@ */
              set_bg_idx (0);
              break;
            case 41: /* SGR@@red background color@@ */
              set_bg_idx (1);
              break;
            case 42: /* SGR@@green background color@@ */
              set_bg_idx (2);
              break;
            case 43: /* SGR@@yellow background color@@ */
              set_bg_idx (3);
              break;
            case 44: /* SGR@@blue background color@@ */
              set_bg_idx (4);
              break;
            case 45: /* SGR@@magenta background color@@ */
              set_bg_idx (5);
              break;
            case 46: /* SGR@@cyan background color@@ */
              set_bg_idx (6);
              break;
            case 47: /* SGR@@light gray background color@@ */
              set_bg_idx (7);
              break;

          /* SGR@48;5;Pn@256 color index background color@where Pn is 0-15 is system colors 16-(16+6*6*6) is a 6x6x6  RGB cube and in the end a grayscale without white and black.@ */
          /* SGR@48;2;Pr;Pg;Pb@24 bit RGB background color@Where Pr Pg and Pb have 0-255 range@ */

            case 49: /* SGR@@default background color@@ */
              set_bg_idx (vt->reverse_video?15:0);
              vt->cstyle ^= (vt->cstyle & STYLE_BG_COLOR_SET);
              break;
            case 50: /* SGR@@Proportional spacing off @@ */
              vt->cstyle ^= (vt->cstyle & STYLE_PROPORTIONAL);
              break;
            // 51 : framed
            // 52 : encircled
            case 53: /* SGR@@Overlined@@ */
              vt->cstyle |= STYLE_OVERLINE;
              break;
            case 55: /* SGR@@Not Overlined@@ */
              vt->cstyle ^= (vt->cstyle&STYLE_OVERLINE);
              break;
            case 90: /* SGR@@dark gray text color@@ */
              set_fg_idx (8);
              break;
            case 91: /* SGR@@light red text color@@ */
              set_fg_idx (9);
              break;
            case 92: /* SGR@@light green text color@@ */
              set_fg_idx (10);
              break;
            case 93: /* SGR@@light yellow text color@@ */
              set_fg_idx (11);
              break;
            case 94: /* SGR@@light blue text color@@ */
              set_fg_idx (12);
              break;
            case 95: /* SGR@@light magenta text color@@ */
              set_fg_idx (13);
              break;
            case 96: /* SGR@@light cyan text color@@ */
              set_fg_idx (14);
              break;
            case 97: /* SGR@@white text color@@ */
              set_fg_idx (15);
              break;
            case 100: /* SGR@@dark gray background color@@ */
              set_bg_idx (8);
              break;
            case 101: /* SGR@@light red background color@@ */
              set_bg_idx (9);
              break;
            case 102: /* SGR@@light green background color@@ */
              set_bg_idx (10);
              break;
            case 103: /* SGR@@light yellow background color@@ */
              set_bg_idx (11);
              break;
            case 104: /* SGR@@light blue background color@@ */
              set_bg_idx (12);
              break;
            case 105: /* SGR@@light magenta background color@@ */
              set_bg_idx (13);
              break;
            case 106: /* SGR@@light cyan background color@@ */
              set_bg_idx (14);
              break;
            case 107: /* SGR@@white background color@@ */
              set_bg_idx (15);
              break;
            default:
              VT_warning ("unhandled style code %i in sequence \\033%s\n", n, sequence);
              return;
          }
      while (s && *s && *s != ';') { s++; }
      if (s && *s == ';') { s++; }
    }
}

static void vtcmd_ignore (VT *vt, const char *sequence)
{
  VT_info ("ignoring sequence %s", sequence);
}

static void vtcmd_clear_all_tabs (VT *vt, const char *sequence)
{
  memset (vt->tabs, 0, sizeof (vt->tabs) );
}

static void vtcmd_clear_current_tab (VT *vt, const char *sequence)
{
  vt->tabs[ (int) (vt->cursor_x-1)] = 0;
}

static void vtcmd_horizontal_tab_set (VT *vt, const char *sequence)
{
  vt->tabs[ (int) vt->cursor_x-1] = 1;
}

static void vtcmd_save_cursor_position (VT *vt, const char *sequence)
{
  vt->saved_x = vt->cursor_x;
  vt->saved_y = vt->cursor_y;
}

static void vtcmd_restore_cursor_position (VT *vt, const char *sequence)
{
  _vt_move_to (vt, vt->saved_y, vt->saved_x);
}


static void vtcmd_save_cursor (VT *vt, const char *sequence)
{
  vt->saved_style   = vt->cstyle;
  vt->saved_origin  = vt->origin;
  vtcmd_save_cursor_position (vt, sequence);
  for (int i = 0; i < 4; i++)
    { vt->saved_charset[i] = vt->charset[i]; }
}

static void vtcmd_restore_cursor (VT *vt, const char *sequence)
{
  vtcmd_restore_cursor_position (vt, sequence);
  vt->cstyle  = vt->saved_style;
  vt->origin  = vt->saved_origin;
  for (int i = 0; i < 4; i++)
    { vt->charset[i] = vt->saved_charset[i]; }
}

static void vtcmd_erase_n_chars (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  while (n--)
    {
      vt_line_replace_utf8 (vt->current_line, vt->cursor_x - 1 + n, " ");
      vt_line_set_style (vt->current_line, vt->cursor_x + n - 1, vt->cstyle);
    }
}

static void vtcmd_delete_n_chars (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  int count = n;
  while (count--)
    {
      vt_line_remove (vt->current_line, vt->cursor_x - 1);
    }
}

static void vtcmd_delete_n_lines (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  for (int a = 0; a < n; a++)
    {
      int i;
      CtxList *l;
      VtLine *string = vt->current_line;
      vt_line_set (string, "");
      ctx_list_remove (&vt->lines, vt->current_line);
      for (i=vt->rows, l = vt->lines; l; l=l->next, i--)
        {
          if (i == vt->margin_bottom)
            {
              vt->current_line = string;
              ctx_list_insert_before (&vt->lines, l, string);
              break;
            }
        }
      _vt_move_to (vt, vt->cursor_y, vt->cursor_x); // updates current_line
    }
}

static void vtcmd_insert_character (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  while (n--)
    {
      vt_line_insert_utf8 (vt->current_line, vt->cursor_x-1, " ");
    }
  while (vt->current_line->string.utf8_length > vt->cols)
    { vt_line_remove (vt->current_line, vt->cols); }
  // XXX update style
}

static void vtcmd_scroll_up (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n == 0) { n = 1; }
  while (n--)
    { vt_scroll (vt, -1); }
}

static void vtcmd_scroll_down (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n == 0) { n = 1; }
  while (n--)
    { vt_scroll (vt, 1); }
}

static void vtcmd_insert_blank_lines (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  if (n == 0) { n = 1; }
  {
    int st = vt->margin_top;
    int sb = vt->margin_bottom;
    vt->margin_top = vt->cursor_y;
    while (n--)
      {
        vt_scroll (vt, 1);
      }
    vt->margin_top = st;
    vt->margin_bottom = sb;
  }
}

static void vtcmd_set_default_font (VT *vt, const char *sequence)
{
  vt->charset[0] = 0;
}

static void vtcmd_set_alternate_font (VT *vt, const char *sequence)
{
  vt->charset[0] = 1;
}


static void vt_ctx_exit (void *data)
{
  VT *vt = data;
  vt->state = vt_state_neutral;
  vt->rev ++;
  if (!vt->current_line)
    return;
#if 0
  fprintf (stderr, "\n");
  if (vt->current_line->prev)
  fprintf (stderr, "---prev(%i)----\n%s", (int)strlen(vt->current_line->prev),vt->current_line->prev);
  fprintf (stderr, "---new(%i)----\n%s", (int)strlen(vt->current_line->frame->str),vt->current_line->frame->str);
  fprintf (stderr, "--------\n");
#endif

#if CTX_VT_USE_FRAME_DIFF
  if (vt->current_line->prev)
    free (vt->current_line->prev);
  vt->current_line->prev = NULL;
  if (vt->current_line->frame)
  {
    vt->current_line->prev = vt->current_line->frame->str;
    vt->current_line->prev_length = vt->current_line->frame->length;

    ctx_string_free (vt->current_line->frame, 0);
    vt->current_line->frame = NULL;
  }
#endif

  void *tmp = vt->current_line->ctx;
  vt->current_line->ctx = vt->current_line->ctx_copy;
  vt->current_line->ctx_copy = tmp;

  _ctx_set_frame (vt->current_line->ctx, _ctx_frame (vt->current_line->ctx) + 1);
  _ctx_set_frame (vt->current_line->ctx_copy, _ctx_frame (vt->current_line->ctx));
#if 1
  if (vt->ctxp) // XXX: ugly hack to aid double buffering
    ((void**)vt->ctxp)[0]= vt->current_line->ctx;
#endif

  //ctx_parser_free (vt->ctxp);
  //vt->ctxp = NULL;
}
#if 0
#define CTX_x            CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0)
#define CTX_y            CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0)
#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0)
#define CTX_lower        CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0)
#define CTX_raise        CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0)
#define CTX_raise_top    CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0)
#define CTX_terminate    CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0)
#define CTX_maximize     CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0)
#define CTX_unmaximize   CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0)
#define CTX_width        CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0)
#define CTX_title        CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0)
#define CTX_action       CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0)
#define CTX_height       CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0)
#endif


static int vt_get_prop (VT *vt, const char *key, const char **val, int *len)
{
#if 0
  uint32_t key_hash = ctx_strhash (key);
  char str[4096]="";
  fprintf (stderr, "%s: %s %i\n", __FUNCTION__, key, key_hash);
  CtxClient *client = ctx_client_by_id (ct->id);
  if (!client)
    return 0;
  switch (key_hash)
  {
    case CTX_title:
      sprintf (str, "setkey %s %s\n", key, client->title);
      break;
    case CTX_x:      
      sprintf (str, "setkey %s %i\n", key, client->x);
      break;
    case CTX_y:    
      sprintf (str, "setkey %s %i\n", key, client->y);
      break;
    case CTX_width:
      sprintf (str, "setkey %s %i\n", key, client->width);
      break;
    case CTX_height:
      sprintf (str, "setkey %s %i\n", key, client->width);
      break;
    default:
      sprintf (str, "setkey %s undefined\n", key);
      break;
  }
  if (str[0])
  {
    vtpty_write ((void*)ct, str, strlen (str));
    fprintf (stderr, "%s", str);
  }
#endif
  return 0;
}

static void vtcmd_set_mode (VT *vt, const char *sequence)
{
  int set = 1;
  if (sequence[strlen (sequence)-1]=='l')
    { set = 0; }
  if (sequence[1]=='?')
    {
      int qval;
      sequence++;
qagain:
      qval = parse_int (sequence, 1);
      switch (qval)
        {
          case 1: /*MODE;DECCKM;Cursor key mode;Application;Cursor;*/
            vt->cursor_key_application = set;
            break;
          case 2: /*MODE;DECANM;VT52 emulation;on;off; */
            if (set==0)
              { vt->state = vt_state_vt52; }
            break;
          case 3: /*MODE;DECCOLM;Column mode;132 columns;80 columns;*/
            vtcmd_set_132_col (vt, set);
            break; // set 132 col
          case 4: /*MODE;DECSCLM;Scrolling mode;smooth;jump;*/
            vt->smooth_scroll = set;
            break; // set 132 col
          case 5: /*MODE;DECSCNM;Screen mode;Reverse;Normal;*/
            vt->reverse_video = set;
            break;
          case 6: /*MODE;DECOM;Origin mode;Relative;Absolute;*/
            vt->origin = set;
            if (set)
              {
                _vt_move_to (vt, vt->margin_top, 1);
                vt_carriage_return (vt);
              }
            else
              { _vt_move_to (vt, 1, 1); }
            break;
          case 7: /*MODE;DECAWM;Autowrap;on;off;*/
            vt->autowrap = set;
            break;
          case 8: /*MODE;DECARM;Auto repeat;on;off;*/
            vt->keyrepeat = set;
            break;
          // case 9: // send mouse x & y on button press 

          // 10 - Block DECEDM
          // 18 - Print form feed  DECPFF  default off
          // 19 - Print extent fullscreen DECPEX  default on
          case 12:
            vtcmd_ignore (vt, sequence);
            break; // blinking_cursor
          case 25:/*MODE;DECTCEM;Cursor visible;on;off; */
            vt->cursor_visible = set;
            break;
          case 30: // from rxvt - show/hide scrollbar
            break;
          case 34: // DECRLM - right to left mode
            break;
          case 38: // DECTEK - enter tektronix mode
            break;
          case 60: // horizontal cursor coupling
          case 61: // vertical cursor coupling
            break;
          case 69:/*MODE;DECVSSM;Left right margin mode;on;off; */
            vt->left_right_margin_mode = set;
            break;
          case 80:/* DECSDM Sixel scrolling */
            break;
          case 437:/*MODE;;Encoding/cp437mode;cp437;utf8; */
            vt->encoding = set ? 1 : 0;
            break;
          case 1000:/*MODE;;Mouse reporting;on;off;*/
            vt->mouse = set;
            break;
          case 1001:
          case 1002:/*MODE;;Mouse drag;on;off;*/
            vt->mouse_drag = set;
            break;
          case 1003:/*MODE;;Mouse all;on;off;*/
            vt->mouse_all = set;
            break;
          case 1006:/*MODE;;Mouse decimal;on;off;*/
            vt->mouse_decimal = set;
            break;
          case 47:
          case 1047:
          //case 1048:
          case 1049:/*MODE;;Alt screen;on;off;*/
            if (set)
              {
                if (vt->in_alt_screen)
                  {
                  }
                else
                  {
                    vtcmd_save_cursor (vt, "");
                    vt->saved_lines = vt->lines;
                    vt->saved_line_count = vt->line_count;
                    vt->line_count = 0;
                    vt->lines = NULL;
                    for (int i = 0; i <  vt->rows; i++)
                      {
                        vt->current_line = vt_line_new_with_size ("", vt->cols);
                        ctx_list_append (&vt->lines, vt->current_line);
                        vt->line_count++;
                      }
                    vt->in_alt_screen = 1;
                    vt_line_feed (vt);
                    _vt_move_to (vt, 1, 1);
                    vt_carriage_return (vt);
                  }
              }
            else
              {
                if (vt->in_alt_screen)
                  {
                    while (vt->lines)
                      {
                        vt_line_free (vt->lines->data, 1);
                        ctx_list_remove (&vt->lines, vt->lines->data);
                      }
                    vt->line_count = vt->saved_line_count;
                    vt->lines = vt->saved_lines;
                    vtcmd_restore_cursor (vt, "");
                    vt->saved_lines = NULL;
                    vt->in_alt_screen = 0;
                  }
                else
                  {
                  }
              }
            break; // alt screen
          case 1010: /*MODE;;scroll on output;on;off; */ //rxvt
            vt->scroll_on_output = set;
            break;
          case 1011:/*MODE:;scroll on input;on;off; */ //rxvt)
            vt->scroll_on_input = set;
            break;
          case 2004:/*MODE;;bracketed paste;on;off; */
            vt->bracket_paste = set;
            break;
          case 201:/*MODE;;ctx-events;on;off;*/
            vt->ctx_events = set;
            break;
          
          case 200:/*MODE;;ctx vector graphics mode;on;;*/
            if (set)
              {
                if (!vt->current_line->ctx)
                  {
                    vt->current_line->ctx = ctx_new ();
                    vt->current_line->ctx_copy = ctx_new ();
                    ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx);
                    _ctx_set_transformation (vt->current_line->ctx, 0);
                    _ctx_set_transformation (vt->current_line->ctx_copy, 0);

                    //ctx_set_texture_cache (vt->current_line->ctx, vt->current_line->ctx_copy);
                    //ctx_set_texture_cache (vt->current_line->ctx_copy, vt->current_line->ctx);
#if CTX_VT_USE_FRAME_DIFF
                    vt->current_line->frame = ctx_string_new ("");
#endif
                  }
                if (vt->ctxp)
                  ctx_parser_free (vt->ctxp);

                vt->ctxp = ctx_parser_new (vt->current_line->ctx,
                                           vt->cols * vt->cw, vt->rows * vt->ch,
                                           vt->cw, vt->ch, vt->cursor_x, vt->cursor_y,
                                           (void*)vt_set_prop, (void*)vt_get_prop, vt, vt_ctx_exit, vt);
                vt->utf8_holding[vt->utf8_pos=0]=0; // XXX : needed?
                vt->state = vt_state_ctx;
              }
            break;
          default:
            VT_warning ("unhandled CSI ? %i%s", qval, set?"h":"l");
            return;
        }
      if (strchr (sequence + 1, ';') )
        {
          sequence = strchr (sequence + 1, ';');
          goto qagain;
        }
    }
  else
    {
      int val;
again:
      val = parse_int (sequence, 1);
      switch (val)
        {
          case 1:/*GATM - transfer enhanced data */
          case 2:/*KAM - keyboard action mode */
            break;
          case 3:/*CRM - control representation mode */
            /* show control chars? */
            break;
          case 4:/*MODE2;IRM;Insert Mode;Insert;Replace; */
            vt->insert_mode = set;
            break;
          case 9: /* interlace mode */
            break;
          case 12:/*MODE2;SRM;Local echo;on;off; */
            vt->echo = set;
            break;
          case 20:/*MODE2;LNM;Carriage Return on LF/Newline;on;off;*/
            ;
            vt->cr_on_lf = set;
            break;
          case 21: // GRCM - whether SGR accumulates or a reset on each command
            break;
          case 32: // WYCRTSAVM - screen saver
            break;
          case 33: // WYSTCURM  - steady cursor
            break;
          case 34: // WYULCURM  - underline cursor
            break;
          default:
            VT_warning ("unhandled CSI %ih", val);
            return;
        }
      if (strchr (sequence, ';') && sequence[0] != ';')
        {
          sequence = strchr (sequence, ';');
          goto again;
        }
    }
}

static void vtcmd_request_mode (VT *vt, const char *sequence)
{
  char buf[64]="";
  if (sequence[1]=='?')
    {
      int qval;
      sequence++;
      qval = parse_int (sequence, 1);
      int is_set = -1; // -1 undefiend   0 reset 1 set  1000 perm_reset  1001 perm_set
      switch (qval)
        {
          case 1:
            is_set = vt->cursor_key_application;
            break;
          case 2: /*VT52 emulation;;enable; */
          //if (set==0) vt->in_vt52 = 1;
            is_set = 1001;
            break;
          case 3:
            is_set = 0;
            break;
          case 4:
            is_set = vt->smooth_scroll;
            break;
          case 5:
            is_set = vt->reverse_video;
            break;
          case 6:
            is_set = vt->origin;
            break;
          case 7:
            is_set = vt->autowrap;
            break;
          case 8:
            is_set = vt->keyrepeat;
            break;
          case 9: // should be dynamic
            is_set = 1000;
            break;
          case 10:
          case 11:
          case 12:
          case 13:
          case 14:
          case 16:
          case 18:
          case 19:
            is_set = 1000;
            break;
          case 25:
            is_set = vt->cursor_visible;
            break;
          case 45:
            is_set = 1000;
            break;
          case 47:
            is_set = vt->in_alt_screen;
            break;
          case 69:
            is_set = vt->left_right_margin_mode;
            break;
          case 437:
            is_set = vt->encoding;
            break;
          case 1000:
            is_set = vt->mouse;
            break;
            // 1001 hilite tracking
          case 1002:
            is_set = vt->mouse_drag;
            break;
          case 1003:
            is_set = vt->mouse_all;
            break;
          case 1006:
            is_set = vt->mouse_decimal;
            break;
          case 201:
            is_set = vt->ctx_events;
            break;
          case 2004:
            is_set = vt->bracket_paste;
            break;
          case 1010: // scroll to bottom on tty output (rxvt)
            is_set = vt->scroll_on_output;
            break;
          case 1011: // scroll to bottom on key press (rxvt)
            is_set = vt->scroll_on_input;
            break;
          case 1049:
            is_set = vt->in_alt_screen;
            break;
            break;
          case 200:/*ctx protocol;On;;*/
            is_set = (vt->state == vt_state_ctx);
            break;
          case 80:/* DECSDM Sixel scrolling */
          case 30: // from rxvt - show/hide scrollbar
          case 34: // DECRLM - right to left mode
          case 60: // horizontal cursor coupling
          case 61: // vertical cursor coupling
          default:
            break;
        }
      switch (is_set)
      {
        case 0:
          sprintf (buf, "\033[?%i;%i$y", qval, 2);
          break;
        case 1:
          { sprintf (buf, "\033[?%i;%i$y", qval, 1); }
          break;
        case 1000:
          sprintf (buf, "\033[?%i;%i$y", qval, 4);
          break;
        case 1001:
          sprintf (buf, "\033[?%i;%i$y", qval, 3);
          break;
        case -1:
          { sprintf (buf, "\033[?%i;%i$y", qval, 0); }
      }
    }
  else
    {
      int val;
      val = parse_int (sequence, 1);
      switch (val)
        {
          case 1:
            sprintf (buf, "\033[%i;%i$y", val, 0);
            break;
          case 2:/* AM - keyboard action mode */
            sprintf (buf, "\033[%i;%i$y", val, 0);
            break;
          case 3:/*CRM - control representation mode */
            sprintf (buf, "\033[%i;%i$y", val, 0);
            break;
          case 4:/*Insert Mode;Insert;Replace; */
            sprintf (buf, "\033[%i;%i$y", val, vt->insert_mode?1:2);
            break;
          case 9: /* interlace mode */
            sprintf (buf, "\033[%i;%i$y", val, 1);
            break;
          case 12:
            sprintf (buf, "\033[%i;%i$y", val, vt->echo?1:2);
            break;
          case 20:/*Carriage Return on LF/Newline;on;off;*/
            ;
            sprintf (buf, "\033[%i;%i$y", val, vt->cr_on_lf?1:2);
            break;
          case 21: // GRCM - whether SGR accumulates or a reset on each command
          default:
            sprintf (buf, "\033[%i;%i$y", val, 0);
        }
    }
  if (buf[0])
    { vt_write (vt, buf, strlen (buf) ); }
}

static void vtcmd_set_t (VT *vt, const char *sequence)
{
  /* \e[21y is request title - allows inserting keychars */
  if      (!strcmp (sequence,  "[1t")) { ctx_client_unshade (vt->id); }
  else if (!strcmp (sequence,  "[2t")) { ctx_client_shade (vt->id); } 
  else if (!strncmp (sequence, "[3;", 3)) {
    int x=0,y=0;
    sscanf (sequence, "[3;%i;%ir", &y, &x);
    ctx_client_move (vt->id, x, y);
  }
  else if (!strncmp (sequence, "[4;", 3))
  {
    int width = 0, height = 0;
    sscanf (sequence, "[4;%i;%ir", &height , &width);
    if (width < 0) width = vt->cols * vt->cw;
    if (height < 0) height = vt->rows * vt->ch;
    if (width == 0) width = ctx_width (vt->root_ctx);
    if (height == 0) height = ctx_height (vt->root_ctx);
    ctx_client_resize (vt->id, width, height);
  }
  else if (!strcmp (sequence, "[5t") ) { ctx_client_raise_top (vt->id); } 
  else if (!strcmp (sequence, "[6t") ) { ctx_client_lower_bottom (vt->id); } 
  else if (!strcmp (sequence, "[7t") ) { vt->rev++; /* refresh */ }
  else if (!strncmp (sequence, "[8;", 3) )
  {
    int cols = 0, rows = 0;
    sscanf (sequence, "[8;%i;%ir", &rows, &cols);
    if (cols < 0) cols = vt->cols;
    if (rows < 0) rows = vt->rows;
    if (cols == 0) cols = ctx_width (vt->root_ctx) / vt->cw;
    if (rows == 0) rows = ctx_height (vt->root_ctx) / vt->ch;
    ctx_client_resize (vt->id, cols * vt->cw, rows * vt->ch);
  }
  else if (!strcmp (sequence, "[9;0t") ) { ctx_client_unmaximize (vt->id); } 
  else if (!strcmp (sequence, "[9;1t") ) { ctx_client_maximize (vt->id);} 

  /* should actually be full-screen */
  else if (!strcmp (sequence, "[10;0t") ) { ctx_client_unmaximize (vt->id); } 
  else if (!strcmp (sequence, "[10;1t") ) { ctx_client_maximize (vt->id);} 
  else if (!strcmp (sequence, "[10;2t") ) { ctx_client_toggle_maximized (vt->id);} 

  else if (!strcmp (sequence, "[11t") )  /* report window state  */
    {
      char buf[128];
      if (ctx_client_is_iconified (vt->id))
        sprintf (buf, "\033[2t");
      else
        sprintf (buf, "\033[1t");
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[13t") ) /* request terminal position */
    {
      char buf[128];
      sprintf (buf, "\033[3;%i;%it", ctx_client_y (vt->id), ctx_client_x (vt->id));
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[14t") ) /* request terminal dimensions in px */
    {
      char buf[128];
      sprintf (buf, "\033[4;%i;%it", vt->rows * vt->ch, vt->cols * vt->cw);
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[15t") ) /* request root dimensions in px */
    {
      char buf[128];
      sprintf (buf, "\033[5;%i;%it", ctx_height (vt->root_ctx), ctx_width(vt->root_ctx));
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[16t") ) /* request char dimensions in px */
    {
      char buf[128];
      sprintf (buf, "\033[6;%i;%it", vt->ch, vt->cw);
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[18t") ) /* request terminal dimensions */
    {
      char buf[128];
      sprintf (buf, "\033[8;%i;%it", vt->rows, vt->cols);
      vt_write (vt, buf, strlen (buf) );
    }
  else if (!strcmp (sequence, "[19t") ) /* request root window size in char */
    {
      char buf[128];
      sprintf (buf, "\033[9;%i;%it", ctx_height(vt->root_ctx)/vt->ch, ctx_width (vt->root_ctx)/vt->cw);
      vt_write (vt, buf, strlen (buf) );
    }

#if 0
  {"[", 's',  foo, VT100}, /*args:PnSP id:DECSWBV Set warning bell volume */
#endif
  else if (sequence[strlen (sequence)-2]==' ') /* DECSWBV */
    {
      int val = parse_int (sequence, 0);
      if (val <= 1) { vt->bell = 0; }
      if (val <= 8) { vt->bell = val; }
    }
  else
    {
      // XXX: X for ints >=24 resize to that number of lines
      VT_info ("unhandled subsequence %s", sequence);
    }
}

static void _vt_htab (VT *vt)
{
  do
    {
      vt->cursor_x ++;
    }
  while (vt->cursor_x < VT_MARGIN_RIGHT &&  ! vt->tabs[ (int) vt->cursor_x-1]);
  if (vt->cursor_x > VT_MARGIN_RIGHT)
    { vt->cursor_x = VT_MARGIN_RIGHT; }
}

static void _vt_rev_htab (VT *vt)
{
  do
    {
      vt->cursor_x--;
    }
  while ( ! vt->tabs[ (int) vt->cursor_x-1] && vt->cursor_x > 1);
  if (vt->cursor_x < VT_MARGIN_LEFT)
    { vt_carriage_return (vt); }
}

static void vtcmd_insert_n_tabs (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  while (n--)
    {
      _vt_htab (vt);
    }
}

static void vtcmd_rev_n_tabs (VT *vt, const char *sequence)
{
  int n = parse_int (sequence, 1);
  while (n--)
    {
      _vt_rev_htab (vt);
    }
}

static void vtcmd_set_double_width_double_height_top_line
(VT *vt, const char *sequence)
{
  vt->current_line->double_width         = 1;
  vt->current_line->double_height_top    = 1;
  vt->current_line->double_height_bottom = 0;
}
static void vtcmd_set_double_width_double_height_bottom_line
(VT *vt, const char *sequence)
{
  vt->current_line->double_width         = 1;
  vt->current_line->double_height_top    = 0;
  vt->current_line->double_height_bottom = 1;
}
static void vtcmd_set_single_width_single_height_line
(VT *vt, const char *sequence)
{
  vt->current_line->double_width         = 0;
  vt->current_line->double_height_top    = 0;
  vt->current_line->double_height_bottom = 0;
}
static void
vtcmd_set_double_width_single_height_line
(VT *vt, const char *sequence)
{
  vt->current_line->double_width         = 1;
  vt->current_line->double_height_top    = 0;
  vt->current_line->double_height_bottom = 0;
}

static void vtcmd_set_led (VT *vt, const char *sequence)
{
  int val = 0;
  //fprintf (stderr, "%s\n", sequence);
  for (const char *s = sequence; *s; s++)
    {
      switch (*s)
        {
          case '0': val = 0; break;
          case '1': val = 1; break;
          case '2': val = 2; break;
          case '3': val = 3; break;
          case '4': val = 4; break;
          case ';':
          case 'q':
            if (val == 0)
              { vt->leds[0] = vt->leds[1] = vt->leds[2] = vt->leds[3] = 0; }
            else
              { vt->leds[val-1] = 1; }
            val = 0;
            break;
        }
    }
}

static void vtcmd_char_at_cursor (VT *vt, const char *sequence)
{
  char *buf="";
  vt_write (vt, buf, strlen (buf) );
}

static void vtcmd_DECELR (VT *vt, const char *sequence)
{
  int ps1 = parse_int (sequence, 0);
  int ps2 = 0;
  const char *s = strchr (sequence, ';');
  if (ps1) {/* unused */};
  if (s)
    { ps2 = parse_int (s, 0); }
  if (ps2 == 1)
    { vt->unit_pixels = 1; }
  else
    { vt->unit_pixels = 0; }
}

static void vtcmd_graphics (VT *vt, const char *sequence)
{
  fprintf (stderr, "gfx intro [%s]\n",sequence); // maybe implement such as well?
}

static void vtcmd_report (VT *vt, const char *sequence)
{
  char buf[64]="";
  if (!strcmp (sequence, "[5n") ) // DSR device status report
    {
      sprintf (buf, "\033[0n"); // we're always OK :)
    }
  else if (!strcmp (sequence, "[?15n") ) // printer status
    {
      sprintf (buf, "\033[?13n"); // no printer
    }
  else if (!strcmp (sequence, "[?26n") ) // keyboard dialect
    {
      sprintf (buf, "\033[?27;1n"); // north american/ascii
    }
  else if (!strcmp (sequence, "[?25n") ) // User Defined Key status
    {
      sprintf (buf, "\033[?21n"); // locked
    }
#if 0
  {"[6n", 0, },  /* id:DSR  cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */
#endif
  else if (!strcmp (sequence, "[6n") ) // DSR cursor position report
    {
      sprintf (buf, "\033[%i;%iR", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) );
    }
  else if (!strcmp (sequence, "[?6n") ) // DECXPR extended cursor position report
    {
#if 0
  {"[?6n", 0, },  /* id:DEXCPR  extended cursor position report, yields a reply <tt>\e[Pl;PcR</tt> */
#endif
      sprintf (buf, "\033[?%i;%i;1R", vt->cursor_y - (vt->origin? (vt->margin_top - 1) :0), (int) vt->cursor_x - (vt->origin? (VT_MARGIN_LEFT-1) :0) );
    }
  else if (!strcmp (sequence, "[>c") )
    {
      sprintf (buf, "\033[>23;01;1c");
    }
  else if (sequence[strlen (sequence)-1]=='c') // device attributes
    {
      //buf = "\033[?1;2c"; // what rxvt reports
      //buf = "\033[?1;6c"; // VT100 with AVO ang GPO
      //buf = "\033[?2c";     // VT102
      sprintf (buf, "\033[?63;14;4;22c");
    }
  else if (sequence[strlen (sequence)-1]=='x') // terminal parameters
    {
      if (!strcmp (sequence, "[1x") )
        { sprintf (buf, "\033[3;1;1;120;120;1;0x"); }
      else
        { sprintf (buf, "\033[2;1;1;120;120;1;0x"); }
    }
  if (buf[0])
    { vt_write (vt, buf, strlen (buf) ); }
}

static char *charmap_cp437[]=
{
  " ","☺","☻","♥","♦","♣","♠","•","◘","○","◙","♂","♀","♪","♫","☼",
  "►","◄","↕","‼","¶","§","▬","↨","↑","↓","→","←","∟","↔","▲","▼",
  " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/",
  "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
  "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O",
  "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
  "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
  "p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","⌂",
  "Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å",
  "É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","¢","£","¥","₧","ƒ",
  "á","í","ó","ú","ñ","Ñ","ª","º","¿","⌐","¬","½","¼","¡","«","»",
  "░","▒","▓","│","┤","╡","╢","╖","╕","╣","║","╗","╝","╜","╛","┐",
  "└","┴","┬","├","─","┼","╞","╟","╚","╔","╩","╦","╠","═","╬","╧",
  "╨","╤","╥","╙","╘","╒","╓","╫","╪","┘","┌","█","▄","▌","▐","▀",
  "α","ß","Γ","π","Σ","σ","µ","τ","Φ","Θ","Ω","δ","∞","φ","ε","∩",
  "≡","±","≥","≤","⌠","⌡","÷","≈","°","∙","·","√","ⁿ","²","■"," "
};


static char *charmap_graphics[]=
{
  " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0",
  "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
  "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P",
  "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
  "◆","▒","␉","␌","␍","␊","°","±","␤","␋","┘","┐","┌","└","┼","⎺","⎻",
  "─","⎼","⎽","├","┤","┴","┬","│","≤","≥","π","≠","£","·"," "
};

static char *charmap_uk[]=
{
  " ","!","\"","£","$","%","&","'","(",")","*","+",",","-",".","/","0",
  "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
  "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P",
  "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
  "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p",
  "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," "
};

static char *charmap_ascii[]=
{
  " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0",
  "1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
  "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P",
  "Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
  "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p",
  "q","r","s","t","u","v","w","x","y","z","{","|","}","~"," "
};

static void vtcmd_justify (VT *vt, const char *sequence)
{
  int n = parse_int (vt->argument_buf, 0);
  switch (n)
    {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
      case 8:
        vt->justify = n;
        break;
      default:
        vt->justify = 0;
    }
}

static void vtcmd_sixel_related_req (VT *vt, const char *sequence)
{
  fprintf (stderr, "it happens!\n");
}

static void vtcmd_set_charmap (VT *vt, const char *sequence)
{
  int slot = 0;
  int set = sequence[1];
  if (sequence[0] == ')') { slot = 1; }
  if (set == 'G') { set = 'B'; }
  vt->charset[slot] = set;
}
#if 0

CSI Pm ' }    '
Insert Ps Column (s) (default = 1) (DECIC), VT420 and up.

CSI Pm ' ~    '
Delete Ps Column (s) (default = 1) (DECDC), VT420 and up.


in text.  When bracketed paste mode is set, the program will receive:
ESC [ 2 0 0 ~,
      followed by the pasted text, followed by
      ESC [ 2 0 1 ~ .


            CSI I
            when the terminal gains focus, and CSI O  when it loses focus.
#endif

#define COMPAT_FLAG_LEVEL_0   (1<<1)
#define COMPAT_FLAG_LEVEL_1   (1<<2)
#define COMPAT_FLAG_LEVEL_2   (1<<3)
#define COMPAT_FLAG_LEVEL_3   (1<<4)
#define COMPAT_FLAG_LEVEL_4   (1<<5)
#define COMPAT_FLAG_LEVEL_5   (1<<6)

#define COMPAT_FLAG_LEVEL_102 (1<<7)

#define COMPAT_FLAG_ANSI      (1<<8)
#define COMPAT_FLAG_OBSOLETE  (1<<9)

#define COMPAT_FLAG_ANSI_COLOR (1<<10)
#define COMPAT_FLAG_256_COLOR  (1<<11)
#define COMPAT_FLAG_24_COLOR   (1<<12)

#define COMPAT_FLAG_MOUSE_REPORT (1<<13)

#define COMPAT_FLAG_AUDIO      (1<<14)
#define COMPAT_FLAG_GRAPHICS   (1<<15)

#define ANSI    COMPAT_FLAG_ANSI
#define OBS     COMPAT_FLAG_OBSOLETE

#define VT100   (COMPAT_FLAG_LEVEL_0|COMPAT_FLAG_LEVEL_1)
#define VT102   (VT100|COMPAT_FLAG_LEVEL_102)
#define VT200   (VT102|COMPAT_FLAG_LEVEL_2)
#define VT300   (VT200|COMPAT_FLAG_LEVEL_3)
#define VT400   (VT300|COMPAT_FLAG_LEVEL_4)
#define VT220   VT200
#define VT320   VT300

#define XTERM   (VT400|COMPAT_FLAG_24_COLOR|COMPAT_FLAG_256_COLOR|COMPAT_FLAG_ANSI_COLOR)

#define VT2020  (XTERM|COMPAT_FLAG_GRAPHICS|COMPAT_FLAG_AUDIO)


            static Sequence sequences[]=
  {
    /*
      prefix suffix  command */
    //{"B",  0,  vtcmd_break_permitted},
    //{"C",  0,  vtcmd_nobreak_here},
    {"D", 0,    vtcmd_index, VT100}, /* args: id:IND Index  */
    {"E",  0,   vtcmd_next_line}, /* ref:none id:  Next line */
    {"_", 'G',  vtcmd_graphics},
    {"H",   0,  vtcmd_horizontal_tab_set, VT100}, /* id:HTS Horizontal Tab Set */

    //{"I",  0,  vtcmd_char_tabulation_with_justification},
    //{"K",  0,  PLD partial line down
    //{"L",  0,  PLU partial line up
    {"M",  0,   vtcmd_reverse_index, VT100}, /* ref:none id:RI Reverse Index */
    //{"N",  0,  vtcmd_ignore}, /* Set Single Shift 2 - SS2*/
    //{"O",  0,  vtcmd_ignore}, /* Set Single Shift 3 - SS3*/

#if 0
    {"[0F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY disable justification and wordwrap  */ // needs special link to ANSI standard
    {"[1F", 0, vtcmd_justify, ANSI}, /* ref:none id:JFY enable wordwrap  */
#endif

    /* these need to occur before vtcmd_preceding_line to have precedence */
    {"[0 F", 0, vtcmd_justify, ANSI},
    {"[1 F", 0, vtcmd_justify, ANSI},
    {"[2 F", 0, vtcmd_justify},
    {"[3 F", 0, vtcmd_justify},
    {"[4 F", 0, vtcmd_justify},
    {"[5 F", 0, vtcmd_justify},
    {"[6 F", 0, vtcmd_justify},
    {"[7 F", 0, vtcmd_justify},
    {"[8 F", 0, vtcmd_justify},
// XXX missing DECIC DECDC  insert and delete column
    {"[", 'A', vtcmd_cursor_up, VT100},   /* args:Pn    id:CUU Cursor Up */
    {"[",  'B', vtcmd_cursor_down, VT100}, /* args:Pn    id:CUD Cursor Down */
    {"[",  'C', vtcmd_cursor_forward, VT100}, /* args:Pn id:CUF Cursor Forward */
    {"[",  'D', vtcmd_cursor_backward, VT100}, /* args:Pn id:CUB Cursor Backward */
    {"[",  'j', vtcmd_cursor_backward, ANSI}, /* args:Pn ref:none id:HPB Horizontal Position Backward */
    {"[",  'k', vtcmd_cursor_up, ANSI}, /* args:Pn ref:none id:VPB Vertical Position Backward */
    {"[",  'E', vtcmd_next_line, VT100}, /* args:Pn id:CNL Cursor Next Line */
    {"[",  'F', vtcmd_cursor_preceding_line, VT100}, /* args:Pn id:CPL Cursor Preceding Line */
    {"[",  'G', vtcmd_horizontal_position_absolute}, /* args:Pn id:CHA Cursor Horizontal Absolute */
    {"[",  'H', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:CUP Cursor Position */
    {"[",  'I', vtcmd_insert_n_tabs}, /* args:Pn id:CHT Cursor Horizontal Forward Tabulation */
    {"[",  'J', vtcmd_erase_in_display, VT100}, /* args:Ps id:ED Erase in Display */
    {"[",  'K', vtcmd_erase_in_line, VT100}, /* args:Ps id:EL Erase in Line */
    {"[",  'L', vtcmd_insert_blank_lines, VT102}, /* args:Pn id:IL Insert Line */
    {"[",  'M', vtcmd_delete_n_lines, VT102}, /* args:Pn id:DL Delete Line   */
    // [ N is EA - Erase in field
    // [ O is EA - Erase in area
    {"[",  'P', vtcmd_delete_n_chars, VT102}, /* args:Pn id:DCH Delete Character */
    // [ Q is SEE - Set editing extent
    // [ R is CPR - active cursor position report
    {"[?", 'S', vtcmd_sixel_related_req},
    {"[",  'S', vtcmd_scroll_up, VT100},   /* args:Pn id:SU Scroll Up */
    {"[",  'T', vtcmd_scroll_down, VT100}, /* args:Pn id:SD Scroll Down */
    {"[",/*SP*/'U', vtcmd_set_line_home, ANSI}, /* args:PnSP id=SLH Set Line Home */
    {"[",/*SP*/'V', vtcmd_set_line_limit, ANSI},/* args:PnSP id=SLL Set Line Limit */
    // [ W is cursor tabulation control
    // [ Pn Y  - cursor line tabulation
    //
    {"[",  'X', vtcmd_erase_n_chars}, /* args:Pn id:ECH Erase Character */
    {"[",  'Z', vtcmd_rev_n_tabs},    /* args:Pn id:CBT Cursor Backward Tabulation */
    {"[",  '^', vtcmd_scroll_down}  , /* muphry alternate from ECMA */
    {"[",  '@', vtcmd_insert_character, VT102}, /* args:Pn id:ICH Insert Character */

    {"[",  'a', vtcmd_cursor_forward, ANSI}, /* args:Pn id:HPR Horizontal Position Relative */
    {"[",  'b', vtcmd_cursor_forward, ANSI}, /* REP previous char XXX incomplete */
    {"[",  'c', vtcmd_report}, /* ref:none id:DA args:... Device Attributes */
    {"[",  'd', vtcmd_goto_row},       /* args:Pn id:VPA Vertical Position Absolute  */
    {"[",  'e', vtcmd_cursor_down},    /* args:Pn id:VPR Vertical Position Relative */
    {"[",  'f', vtcmd_cursor_position, VT100}, /* args:Pl;Pc id:HVP Cursor Position */
    {"[g", 0,   vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */
    {"[0g", 0,  vtcmd_clear_current_tab, VT100}, /* id:TBC clear current tab */
    {"[3g", 0,  vtcmd_clear_all_tabs, VT100},    /* id:TBC clear all tabs */
    {"[",  'm', vtcmd_set_graphics_rendition, VT100}, /* args:Ps;Ps;.. id:SGR Select Graphics Rendition */
    {"[",  'n', vtcmd_report, VT200}, /* id:DSR args:... CPR Cursor Position Report  */
    {"[",  'r', vtcmd_set_top_and_bottom_margins, VT100}, /* args:Pt;Pb id:DECSTBM Set Top and Bottom Margins */
#if 0
    // handled by set_left_and_right_margins - in if 0 to be documented
    {"[s",  0,  vtcmd_save_cursor_position, VT100}, /*ref:none id:SCP Save Cursor Position */
#endif
    {"[u",  0,  vtcmd_restore_cursor_position, VT100}, /*ref:none id:RCP Restore Cursor Position */
    {"[",  's', vtcmd_set_left_and_right_margins, VT400}, /* args:Pl;Pr id:DECSLRM Set Left and Right Margins */
    {"[",  '`', vtcmd_horizontal_position_absolute, ANSI},  /* args:Pn id:HPA Horizontal Position Absolute */

    {"[",  'h', vtcmd_set_mode, VT100},   /* args:Pn[;...] id:SM Set Mode */
    {"[",  'l', vtcmd_set_mode, VT100}, /* args:Pn[;...]  id:RM Reset Mode */
    {"[",  't', vtcmd_set_t},
    {"[",  'q', vtcmd_set_led, VT100}, /* args:Ps id:DECLL Load LEDs */
    {"[",  'x', vtcmd_report}, /* ref:none id:DECREQTPARM */
    {"[",  'z', vtcmd_DECELR}, /* ref:none id:DECELR set locator res  */

    {"5",   0,  vtcmd_char_at_cursor, VT300}, /* ref:none id:DECXMIT */
    {"6",   0,  vtcmd_back_index, VT400}, /* id:DECBI Back index (hor. scroll) */
    {"7",   0,  vtcmd_save_cursor, VT100}, /* id:DECSC Save Cursor */
    {"8",   0,  vtcmd_restore_cursor, VT100}, /* id:DECRC Restore Cursor */
    {"9",   0,  vtcmd_forward_index, VT400}, /* id:DECFI Forward index (hor. scroll)*/

    //{"Z", 0,  vtcmd_device_attributes},
    //{"%G",0,  vtcmd_set_default_font}, // set_alternate_font


    {"(0",  0,   vtcmd_set_charmap},
    {"(1",  0,   vtcmd_set_charmap},
    {"(2",  0,   vtcmd_set_charmap},
    {"(A",  0,   vtcmd_set_charmap},
    {"(B",  0,   vtcmd_set_charmap},
    {")0",  0,   vtcmd_set_charmap},
    {")1",  0,   vtcmd_set_charmap},
    {")2",  0,   vtcmd_set_charmap},
    {")A",  0,   vtcmd_set_charmap},
    {")B",  0,   vtcmd_set_charmap},
    {"%G",  0,   vtcmd_set_charmap},

    {"#3",  0,   vtcmd_set_double_width_double_height_top_line, VT100}, /*id:DECDHL Top half of double-width, double-height line */
    {"#4",  0,   vtcmd_set_double_width_double_height_bottom_line, VT100}, /*id:DECDHL Bottom half of double-width, double-height line */
    {"#5",  0,   vtcmd_set_single_width_single_height_line, VT100}, /* id:DECSWL Single-width line */
    {"#6",  0,   vtcmd_set_double_width_single_height_line, VT100}, /* id:DECDWL Double-width line */

    {"#8",  0,   vtcmd_screen_alignment_display, VT100}, /* id:DECALN Screen Alignment Pattern */
    {"=",   0,   vtcmd_ignore},  // keypad mode change
    {">",   0,   vtcmd_ignore},  // keypad mode change
    {"c",   0,   vtcmd_reset_to_initial_state, VT100}, /* id:RIS Reset to Initial State */
    {"[!", 'p',  vtcmd_ignore},       // soft reset?
    {"[",  'p',  vtcmd_request_mode}, /* args:Pa$ id:DECRQM Request ANSI Mode */
#if 0
    {"[?",  'p',  vtcmd_request_mode}, /* args:Pd$ id:DECRQM Request DEC Mode */
#endif

    {NULL, 0, NULL}
  };

  static void handle_sequence (VT *vt, const char *sequence)
{
  int i0 = strlen (sequence)-1;
  int i;
  vt->rev ++;
  for (i = 0; sequences[i].prefix; i++)
    {
      if (!strncmp (sequence, sequences[i].prefix, strlen (sequences[i].prefix) ) )
        {
          if (! (sequences[i].suffix && (sequence[i0] != sequences[i].suffix) ) )
            {
              VT_command ("%s", sequence);
              sequences[i].vtcmd (vt, sequence);
              return;
            }
        }
    }
#ifndef ASANBUILD
  VT_warning ("unhandled: %c%c%c%c%c%c%c%c%c\n", sequence[0], sequence[1], sequence[2], sequence[3], sequence[4], sequence[5], sequence[6], sequence[7], sequence[8]);
#endif
}

static void vt_line_feed (VT *vt)
{
  int was_home = vt->at_line_home;
  if (vt->margin_top == 1 && vt->margin_bottom == vt->rows)
    {
      if (vt->lines == NULL ||
          (vt->lines->data == vt->current_line && vt->cursor_y != vt->rows) )
        {
          vt->current_line = vt_line_new_with_size ("", vt->cols);
          ctx_list_prepend (&vt->lines, vt->current_line);
          vt->line_count++;
        }
      if (vt->cursor_y >= vt->margin_bottom)
        {
          vt->cursor_y = vt->margin_bottom;
          vt_scroll (vt, -1);
        }
      else
        {
          vt->cursor_y++;
        }
    }
  else
    {
      if (vt->lines->data == vt->current_line &&
          (vt->cursor_y != vt->margin_bottom) && 0)
        {
          vt->current_line = vt_line_new_with_size ("", vt->cols);
          ctx_list_prepend (&vt->lines, vt->current_line);
          vt->line_count++;
        }
      vt->cursor_y++;
      if (vt->cursor_y > vt->margin_bottom)
        {
          vt->cursor_y = vt->margin_bottom;
          vt_scroll (vt, -1);
        }
    }
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  if (vt->cr_on_lf)
    { vt_carriage_return (vt); }
  vt_trimlines (vt, vt->rows);
  if (was_home)
    { vt_carriage_return (vt); }
}

//#include "vt-encodings.h"

#ifndef NO_SDL
static void vt_state_apc_audio (VT *vt, int byte)
{
  if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
      vt_audio (vt, vt->argument_buf);
      vt->state = ( (byte == 27) ?  vt_state_swallow : vt_state_neutral);
    }
  else
    {
      vt_argument_buf_add (vt, byte);
    }
}

#else

void vt_audio_task (VT *vt, int click)
{
}

void vt_bell (VT *vt)
{
}
static void vt_state_apc_audio (VT *vt, int byte)
{
  vt->state = vt_state_apc_generic;
}

#endif

static void
vt_carriage_return (VT *vt)
{
  _vt_move_to (vt, vt->cursor_y, vt->cursor_x);
  vt->cursor_x = VT_MARGIN_LEFT;
  vt->at_line_home = 1;
}

/* if the byte is a non-print control character, handle it and return 1
 * oterhwise return 0*/
static int _vt_handle_control (VT *vt, int byte)
{
  /* the big difference between ANSI-BBS mode and VT100+ mode is that
   * most C0 characters are printable
   */
  if (CTX_UNLIKELY(vt->encoding == 1)) // this codepage is for ansi-bbs use
    switch (byte)
      {
        case '\0':
          return 1;
        case 1:    /* SOH start of heading */
        case 2:    /* STX start of text */
        case 3:    /* ETX end of text */
        case 4:    /* EOT end of transmission */
        case 5:    /* ENQuiry */
        case 6:    /* ACKnolwedge */
        case '\v': /* VT vertical tab */
        case '\f': /* VF form feed */
        case 14: /* SO shift in - alternate charset */
        case 15: /* SI shift out - (back to normal) */
        case 16: /* DLE data link escape */
        case 17: /* DC1 device control 1 - XON */
        case 18: /* DC2 device control 2 */
        case 19: /* DC3 device control 3 - XOFF */
        case 20: /* DC4 device control 4 */
        case 21: /* NAK negative ack */
        case 22: /* SYNchronous idle */
        case 23: /* ETB end of trans. blk */
        case 24: /* CANcel (vt100 aborts sequence) */
        case 25: /* EM  end of medium */
        case 26: /* SUB stitute */
        case 28: /* FS file separator */
        case 29: /* GS group separator */
        case 30: /* RS record separator */
        case 31: /* US unit separator */
          _vt_add_str (vt, charmap_cp437[byte]);
          return 1;
      }
  switch (byte)
    {
      case '\0':
      case 1:    /* SOH start of heading */
      case 2:    /* STX start of text */
      case 3:    /* ETX end of text */
      case 4:    /* EOT end of transmission */
      case 6:    /* ACKnolwedge */
        return 1;
      case 5:    /* ENQuiry */
        {
          const char *reply = getenv ("TERM_ENQ_REPLY");
          if (reply)
            {
              char *copy = strdup (reply);
              for (uint8_t *c = (uint8_t *) copy; *c; c++)
                {
                  if (*c < ' ' || * c > 127) { *c = 0; }
                }
              vt_write (vt, reply, strlen (reply) );
              free (copy);
            }
        }
        return 1;
      case '\a': /* BELl */
        vt_bell (vt);
        return 1;
      case '\b': /* BS */
        _vt_backspace (vt);
        return 1;
      case '\t': /* HT tab */
        _vt_htab (vt);
        return 1;
      case '\v': /* VT vertical tab */
      case '\f': /* VF form feed */
      case '\n': /* LF line ffed */
        vt_line_feed (vt);
        return 1;
      case '\r': /* CR carriage return */
        vt_carriage_return (vt);
        return 1;
      case 14: /* SO shift in - alternate charset */
        vt->shifted_in = 1;  // XXX not in vt52
        return 1;
      case 15: /* SI shift out - (back to normal) */
        vt->shifted_in = 0;
        return 1;
      case 16: /* DLE data link escape */
      case 17: /* DC1 device control 1 - XON */
      case 18: /* DC2 device control 2 */
      case 19: /* DC3 device control 3 - XOFF */
      case 20: /* DC4 device control 4 */
      case 21: /* NAK negative ack */
      case 22: /* SYNchronous idle */
      case 23: /* ETB end of trans. blk */
      case 24: /* CANcel (vt100 aborts sequence) */
      case 25: /* EM  end of medium */
      case 26: /* SUB stitute */
        _vt_add_str (vt, "¿");  // in vt52? XXX
        return 1;
      case 27: /* ESCape */
        return 0;
        break;
      case 28: /* FS file separator */
      case 29: /* GS group separator */
      case 30: /* RS record separator */
      case 31: /* US unit separator */
      case 127: /* DEL */
        return 1;
      default:
        return 0;
    }
}

void vt_open_log (VT *vt, const char *path)
{
  unlink (path);
  vt->log = fopen (path, "w");
}

/* the function shared by sixels, kitty mode and iterm2 mode for
 * doing inline images. it attaches an image to the current line
 */
static void display_image (VT *vt, Image *image,
                           int col,
                           float xoffset,
                           float yoffset,
                           int rows,
                           int cols,
                           int subx,
                           int suby,
                           int subw,
                           int subh
                          )
{
  int i = 0;
  for (i = 0; vt->current_line->images[i] && i < 4; i++)
  {
     if (vt->current_line->image_col[i] == vt->cursor_x)
       break;
  }
  //for (i = 0; vt->current_line->images[i] && i < 4; i++);
  if (i >= 4) { i = 3; }
  /* this needs a struct and dynamic allocation */
  vt->current_line->images[i] = image;
  vt->current_line->image_col[i] = vt->cursor_x;
  vt->current_line->image_X[i] = xoffset;
  vt->current_line->image_Y[i] = yoffset;
  vt->current_line->image_subx[i] = subx;
  vt->current_line->image_suby[i] = suby;
  vt->current_line->image_subw[i] = subw;
  vt->current_line->image_subh[i] = subh;
  vt->current_line->image_rows[i] = rows;
  vt->current_line->image_cols[i] = cols;
}

static int vt_gfx_pending=0;

void vt_gfx (VT *vt, const char *command)
{
  const char *payload = NULL;
  char key = 0;
  int  value;
  int  pos = 1;
  if (vt->gfx.multichunk == 0)
    {
      memset (&vt->gfx, 0, sizeof (GfxState) );
      vt->gfx.action='t';
      vt->gfx.transmission='d';
    }
  while (command[pos] != ';')
    {
      pos ++; // G or ,
      if (command[pos] == ';') { break; }
      key = command[pos];
      pos++;
      if (command[pos] == ';') { break; }
      pos ++; // =
      if (command[pos] == ';') { break; }
      if (command[pos] >= '0' && command[pos] <= '9')
        { value = atoi (&command[pos]); }
      else
        { value = command[pos]; }
      while (command[pos] &&
             command[pos] != ',' &&
             command[pos] != ';') { pos++; }
      switch (key)
        {
          case 'a':
            vt->gfx.action = value;
            break;
          case 'd':
            vt->gfx.delete = value;
            break;
          case 'i':
            vt->gfx.id = value;
            break;
          case 'S':
            vt->gfx.buf_size = value;
            break;
          case 's':
            vt->gfx.buf_width = value;
            break;
          case 'v':
            vt->gfx.buf_height = value;
            break;
          case 'f':
            vt->gfx.format = value;
            break;
          case 'm':
            vt->gfx.multichunk = value;
            break;
          case 'o':
            vt->gfx.compression = value;
            break;
          case 't':
            vt->gfx.transmission = value;
            break;
          case 'x':
            vt->gfx.x = value;
            break;
          case 'y':
            vt->gfx.y = value;
            break;
          case 'w':
            vt->gfx.w = value;
            break;
          case 'h':
            vt->gfx.h = value;
            break;
          case 'X':
            vt->gfx.x_cell_offset = value;
            break;
          case 'Y':
            vt->gfx.y_cell_offset = value;
            break;
          case 'c':
            vt->gfx.columns = value;
            break;
          case 'r':
            vt->gfx.rows = value;
            break;
          case 'z':
            vt->gfx.z_index = value;
            break;
        }
    }
  payload = &command[pos+1];
  {
    int chunk_size = strlen (payload);
    int old_size = vt->gfx.data_size;
    // accumulate incoming data
    if (vt->gfx.data == NULL)
      {
        vt->gfx.data_size = chunk_size;
        vt->gfx.data = malloc (vt->gfx.data_size + 1);
      }
    else
      {
        vt->gfx.data_size += chunk_size;
        vt->gfx.data = realloc (vt->gfx.data, vt->gfx.data_size + 1);
      }
    memcpy (vt->gfx.data + old_size, payload, chunk_size);
    vt->gfx.data[vt->gfx.data_size]=0;
  }
  if (vt->gfx.multichunk == 0)
    {
      if (vt->gfx.transmission != 'd') /* */
        {
          char buf[256];
          sprintf (buf, "\033_Gi=%i;only direct transmission supported\033\\",
                   vt->gfx.id);
          vt_write (vt, buf, strlen (buf) );
          goto cleanup;
        }
      {
        int bin_length = vt->gfx.data_size;
        uint8_t *data2 = malloc (vt->gfx.data_size);
        bin_length = ctx_base642bin ( (char *) vt->gfx.data,
                                     &bin_length,
                                     data2);
        memcpy (vt->gfx.data, data2, bin_length + 1);
        vt->gfx.data_size = bin_length;
        free (data2);
      }
      if (vt->gfx.buf_width)
        {
          // implicit buf_size
          vt->gfx.buf_size = vt->gfx.buf_width * vt->gfx.buf_height *
                             (vt->gfx.format == 24 ? 3 : 4);
        }
      if (vt->gfx.compression == 'z')
        {
          //vt->gfx.buf_size)
          unsigned char *data2 = malloc (vt->gfx.buf_size + 1);
          /* if a buf size is set (rather compression, but
           * this works first..) then */
          unsigned long actual_uncompressed_size = vt->gfx.buf_size;
          int z_result = uncompress (data2, &actual_uncompressed_size,
                                     vt->gfx.data,
                                     vt->gfx.data_size);
          if (z_result != Z_OK)
            {
              char buf[256]= "\033_Go=z;zlib error\033\\";
              vt_write (vt, buf, strlen (buf) );
              goto cleanup;
            }
          free (vt->gfx.data);
          vt->gfx.data = data2;
          vt->gfx.data_size = actual_uncompressed_size;
          vt->gfx.compression = 0;
        }
      if (vt->gfx.format == 100)
        {
          int channels;
          uint8_t *new_data = stbi_load_from_memory (vt->gfx.data, vt->gfx.data_size, &vt->gfx.buf_width, &vt->gfx.buf_height, &channels, 4);
          if (!new_data)
            {
              char buf[256]= "\033_Gf=100;image decode error\033\\";
              vt_write (vt, buf, strlen (buf) );
              goto cleanup;
            }
          vt->gfx.format = 32;
          free (vt->gfx.data);
          vt->gfx.data = new_data;
          vt->gfx.data_size= vt->gfx.buf_width * vt->gfx.buf_height * 4;
        }
      Image *image = NULL;
      switch (vt->gfx.action)
        {
          case 't': // transfer
          case 'T': // transfer and present
            switch (vt->gfx.format)
              {
                case 24:
                case 32:
                  image = image_add (vt->gfx.buf_width, vt->gfx.buf_height, vt->gfx.id,
                                     vt->gfx.format, vt->gfx.data_size, vt->gfx.data);
                  vt->gfx.data = NULL;
                  vt->gfx.data_size=0;
                  break;
              }
            if (vt->gfx.action == 't')
              { break; }
          // fallthrough
          case 'p': // present
            if (!image && vt->gfx.id)
              { image = image_query (vt->gfx.id); }
            if (image)
              {
                display_image (vt, image, vt->cursor_x, vt->gfx.rows, vt->gfx.columns,
                               vt->gfx.x_cell_offset * 1.0 / vt->cw,
                               vt->gfx.y_cell_offset * 1.0 / vt->ch,
                               vt->gfx.x,
                               vt->gfx.y,
                               vt->gfx.w,
                               vt->gfx.h);
                int right = (image->width + (vt->cw-1) ) /vt->cw;
                int down = (image->height + (vt->ch-1) ) /vt->ch;
                for (int i = 0; i<down - 1; i++)
                  { vtcmd_index (vt, " "); }
                for (int i = 0; i<right; i++)
                  { vtcmd_cursor_forward (vt, " "); }
              }
            break;
          case 'q': // query
            if (image_query (vt->gfx.id) )
              {
                char buf[256];
                sprintf (buf, "\033_Gi=%i;OK\033\\", vt->gfx.id);
                vt_write (vt, buf, strlen (buf) );
              }
            break;
          case 'd': // delete
            {
              int row = vt->rows; // probably not right at start of session XXX
              for (CtxList *l = vt->lines; l; l = l->next, row --)
                {
                  VtLine *line = l->data;
                  for (int i = 0; i < 4; i ++)
                    {
                      int free_resource = 0;
                      int match = 0;
                      if (line->images[i])
                        switch (vt->gfx.delete)
                          {
                            case 'A':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'a': /* all images visible on screen */
                              match = 1;
                              break;
                            case 'I':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'i': /* all images with specified id */
                              if ( ( (Image *) (line->images[i]) )->id == vt->gfx.id)
                                { match = 1; }
                              break;
                            case 'P':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'p': /* all images intersecting cell
          specified with x and y */
                              if (line->image_col[i] == vt->gfx.x &&
                                  row == vt->gfx.y)
                                { match = 1; }
                              break;
                            case 'Q':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'q': /* all images with specified cell (x), row(y) and z */
                              if (line->image_col[i] == vt->gfx.x &&
                                  row == vt->gfx.y)
                                { match = 1; }
                              break;
                            case 'Y':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'y': /* all images with specified row (y) */
                              if (row == vt->gfx.y)
                                { match = 1; }
                              break;
                            case 'X':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'x': /* all images with specified column (x) */
                              if (line->image_col[i] == vt->gfx.x)
                                { match = 1; }
                              break;
                            case 'Z':
                              free_resource = 1;
                              /* FALLTHROUGH */
                            case 'z': /* all images with specified z-index (z) */
                              break;
                          }
                      if (match)
                        {
                          line->images[i] = NULL;
                          if (free_resource)
                            {
                              // XXX : NYI
                            }
                        }
                    }
                }
            }
            break;
        }
cleanup:
      if (vt->gfx.data)
        { free (vt->gfx.data); }
      vt->gfx.data = NULL;
      vt->gfx.data_size=0;
      vt->gfx.multichunk=0;
      vt_gfx_pending = 0;
    }
  else
     vt_gfx_pending = 1;
}

static void vt_state_vt52 (VT *vt, int byte)
{
  /* in vt52 mode, utf8_pos being non 0 means we got ESC prior */
  switch (vt->utf8_pos)
    {
      case 0:
        if (_vt_handle_control (vt, byte) == 0)
          switch (byte)
            {
              case 27: /* ESC */
                vt->utf8_pos = 1;
                break;
              default:
                {
                  char str[2] = {byte & 127, 0};
                  /* we're not validating utf8, and our utf8 manipulation
                   * functions are not robust against malformed utf8,
                   * hence we strip to ascii
                   */
                  _vt_add_str (vt, str);
                }
                break;
            }
        break;
      case 1:
        vt->utf8_pos = 0;
        switch (byte)
          {
            case 'A':
              vtcmd_cursor_up (vt, " ");
              break;
            case 'B':
              vtcmd_cursor_down (vt, " ");
              break;
            case 'C':
              vtcmd_cursor_forward (vt, " ");
              break;
            case 'D':
              vtcmd_cursor_backward (vt, " ");
              break;
            case 'F':
              vtcmd_set_alternate_font (vt, " ");
              break;
            case 'G':
              vtcmd_set_default_font (vt, " ");
              break;
            case 'H':
              _vt_move_to (vt, 1, 1);
              break;
            case 'I':
              vtcmd_reverse_index (vt, " ");
              break;
            case 'J':
              vtcmd_erase_in_display (vt, "[0J");
              break;
            case 'K':
              vtcmd_erase_in_line (vt, "[0K");
              break;
            case 'Y':
              vt->utf8_pos = 2;
              break;
            case 'Z':
              vt_write (vt, "\033/Z", 3);
              break;
            case '<':
              vt->state = vt_state_neutral;
              break;
            default:
              break;
          }
        break;
      case 2:
        _vt_move_to (vt, byte - 31, vt->cursor_x);
        vt->utf8_pos = 3;
        break;
      case 3:
        _vt_move_to (vt, vt->cursor_y, byte - 31);
        vt->utf8_pos = 0;
        break;
    }
}

static void vt_sixels (VT *vt, const char *sixels)
{
  uint8_t colors[256][3];
  int width = 0;
  int height = 0;
  int x = 0;
  int y = 0;
  Image *image;
  uint8_t *pixels = NULL;
  int repeat = 1;
  const char *p = sixels;
  int pal_no = 0;
#if 0
  for (; *p && *p != ';'; p++);
  if (*p == ';') { p ++; }
  printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) );
  // should be 0
  for (; *p && *p != ';'; p++);
  if (*p == ';') { p ++; }
  printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p) );
  // if 1 then transparency is enabled - otherwise use bg color
  for (; *p && *p != 'q'; p++);
#endif
  //for (; *p && *p != '"'; p++);
  while (*p && *p != 'q') { p++; }
  if (*p == 'q') { p++; }
  if (*p == '"') { p++; }
  //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p));
  for (; *p && *p != ';'; p++);
  if (*p == ';') { p ++; }
  //printf ("%i:[%c]%i\n", __LINE__, *p, atoi (p));
  for (; *p && *p != ';'; p++);
  if (*p == ';') { p ++; }
  width = atoi (p);
  for (; *p && *p != ';'; p++);
  if (*p == ';') { p ++; }
  height = atoi (p);
  if (width * height > 2048 * 2048)
    return;
  if (width <= 0 || height <=0)
    {
      width = 0;
      height = 0;
      // XXX  : a copy paste dry-run
      for (const char *t=p; *t; t++)
        {
          if (*t == '#')
            {
              t++;
              while (*t && *t >= '0' && *t <= '9') { t++; }
              if (*t == ';')
                {
                  for (; *t && *t != ';'; t++);
                  if (*t == ';') { t ++; }
                  for (; *t && *t != ';'; t++);
                  if (*t == ';') { t ++; }
                  for (; *t && *t != ';'; t++);
                  if (*t == ';') { t ++; }
                  for (; *t && *t != ';'; t++);
                  if (*t == ';') { t ++; }
                  while (*t && *t >= '0' && *t <= '9') { t++; }
                  t--;
                }
              else
                {
                  t--;
                }
            }
          else if (*t == '$') // carriage return
            {
              if (x > width) { width = x; }
              x = 0;
            }
          else if (*t == '-') // line feed
            {
              y += 6;
              x = 0;
            }
          else if (*t == '!') // repeat
            {
              t++;
              repeat = atoi (t);
              while (*t && *t >= '0' && *t <= '9') { t++; }
              t--;
            }
          else if (*t >= '?' && *t <= '~') /* sixel data */
            {
              x += repeat;
              repeat = 1;
            }
        }
      height = y;
    }
  x = 0;
  y = 0;
  pixels = calloc (width * (height + 6), 4);
  image = image_add (width, height, 0,
                     32, width*height*4, pixels);
  uint8_t *dst = pixels;
  for (; *p; p++)
    {
      if (*p == '#')
        {
          p++;
          pal_no = atoi (p);
          if (pal_no < 0 || pal_no > 255) { pal_no = 255; }
          while (*p && *p >= '0' && *p <= '9') { p++; }
          if (*p == ';')
            {
              /* define a palette */
              for (; *p && *p != ';'; p++);
              if (*p == ';') { p ++; }
              // color_model , 2 is rgb
              for (; *p && *p != ';'; p++);
              if (*p == ';') { p ++; }
              colors[pal_no][0] = atoi (p) * 255 / 100;
              for (; *p && *p != ';'; p++);
              if (*p == ';') { p ++; }
              colors[pal_no][1] = atoi (p) * 255 / 100;
              for (; *p && *p != ';'; p++);
              if (*p == ';') { p ++; }
              colors[pal_no][2] = atoi (p) * 255 / 100;
              while (*p && *p >= '0' && *p <= '9') { p++; }
              p--;
            }
          else
            {
              p--;
            }
        }
      else if (*p == '$') // carriage return
        {
          x = 0;
          dst = &pixels[ (4 * width * y)];
        }
      else if (*p == '-') // line feed
        {
          y += 6;
          x = 0;
          dst = &pixels[ (4 * width * y)];
        }
      else if (*p == '!') // repeat
        {
          p++;
          repeat = atoi (p);
          while (*p && *p >= '0' && *p <= '9') { p++; }
          p--;
        }
      else if (*p >= '?' && *p <= '~') /* sixel data */
        {
          int sixel = (*p) - '?';
          if (x + repeat <= width && y < height)
            {
              for (int bit = 0; bit < 6; bit ++)
                {
                  if (sixel & (1 << bit) )
                    {
                      for (int u = 0; u < repeat; u++)
                        {
                          for (int c = 0; c < 3; c++)
                            {
                              dst[ (bit * width * 4) + u * 4 + c] = colors[pal_no][c];
                              dst[ (bit * width * 4) + u * 4 + 3] = 255;
                            }
                        }
                    }
                }
            }
          x   += repeat;
          dst += (repeat * 4);
          repeat = 1;
        }
    }
  if (image)
    {
      display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0);
      int right = (image->width + (vt->cw-1) ) /vt->cw;
      int down = (image->height + (vt->ch-1) ) /vt->ch;
      for (int i = 0; i<down - 1; i++)
        { vtcmd_index (vt, " "); }
      for (int i = 0; i<right; i++)
        { vtcmd_cursor_forward (vt, " "); }
      vt_line_feed (vt);
      vt_carriage_return (vt);
    }
  vt->rev++;
}

static inline void vt_ctx_unrled (VT *vt, char byte)
{
#if CTX_VT_USE_FRAMEDIFF
  ctx_string_append_byte (vt->current_line->frame, byte);
#endif
  ctx_parser_feed_byte (vt->ctxp, byte);
}

static void vt_state_ctx (VT *vt, int byte)
{
#if 0
  //fprintf (stderr, "%c", byte);
  if (CTX_UNLIKELY(byte == CTX_CODEC_CHAR))
  {
    if (CTX_UNLIKELY(vt->in_prev_match))
    {
      char *prev = vt->current_line->prev;
      int prev_length = vt->current_line->prev_length;
      int start = atoi (vt->reference);
      int len = 0;
      if (strchr (vt->reference, ' '))
        len = atoi (strchr (vt->reference, ' ')+1);

      //fprintf (stderr, "%i-%i:", start, len);

      if (start < 0) start = 0;
      if (start >= (prev_length))start = prev_length-1;
      if (len + start > prev_length)
        len = prev_length - start;

      //fprintf (stderr, "%i-%i\n", start, len);

      if (CTX_UNLIKELY (start == 0 && len == 0))
      {
        vt_ctx_unrled (vt, CTX_CODEC_CHAR);
      }
      else
      {
        if (prev)
        for (int i = 0; i < len && start + i < prev_length; i++)
        {
          vt_ctx_unrled (vt, prev[start + i]);
        }
      }
      vt->ref_len = 0;
      vt->reference[0]=0;
      vt->in_prev_match = 0;
    }
    else
    {
      vt->reference[0]=0;
      vt->ref_len = 0;
      vt->in_prev_match = 1;
    }
  }
  else
  {
    if (CTX_UNLIKELY(vt->in_prev_match))
    {
      if (vt->ref_len < 15)
      {
        vt->reference[vt->ref_len++]=byte;
        vt->reference[vt->ref_len]=0;
      }
    }
    else
    {
      vt_ctx_unrled (vt, byte);
    }
  }
#else
      vt_ctx_unrled (vt, byte);
#endif
}

static int vt_decoder_feed (VT *vt, int byte)
{
  int encoding = vt->encoding;
  switch (encoding)
    {
      case 0: /* utf8 */
        if (!vt->utf8_expected_bytes)
          {
            vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1;
            vt->utf8_pos = 0;
          }
        if (vt->utf8_expected_bytes)
          {
            vt->utf8_holding[vt->utf8_pos++] = byte;
            vt->utf8_holding[vt->utf8_pos] = 0;
            if (vt->utf8_pos == vt->utf8_expected_bytes + 1)
              {
                vt->utf8_expected_bytes = 0;
                vt->utf8_pos = 0;
              }
            else
              {
                return 1;
              }
          }
        else
          {
            vt->utf8_holding[0] = byte;
            vt->utf8_holding[0] &= 127;
            vt->utf8_holding[1] = 0;
            if (vt->utf8_holding[0] == 0)
              { vt->utf8_holding[0] = 32; }
          }
        break;
      case 1:
        if ( ! (byte>=0 && byte < 256) )
          { byte = 255; }
        strcpy ( (char *) &vt->utf8_holding[0], &charmap_cp437[byte][0]);
        vt->utf8_expected_bytes = mrg_utf8_len (byte) - 1; // ?
        break;
      default:
        vt->utf8_holding[0] = byte & 127;
        vt->utf8_holding[1] = 0;
        break;
    }
  return 0;
}

static void vt_state_swallow (VT *vt, int byte)
{
  vt->state = vt_state_neutral;
}

static int vt_decode_hex_digit (char digit)
{
  if (digit >= '0' && digit <='9')
    return digit - '0';
  if (digit >= 'a' && digit <='f')
    return digit - 'a' + 10;
  if (digit >= 'A' && digit <='F')
    return digit - 'A' + 10;
  return 0;
}

static int vt_decode_hex (const char *two_digits)
{
  return vt_decode_hex_digit (two_digits[0]) * 16 +
         vt_decode_hex_digit (two_digits[1]);
}

static uint8_t palettes[][16][3]=
{
  {
{0, 0, 0},
{160, 41, 41},
{74, 160, 139},
{135, 132, 83},
{36, 36, 237},
{171, 74, 223},
{59, 107, 177},
{195, 195, 195},
{111, 111, 111},
{237, 172, 130},
{153, 237, 186},
{233, 216, 8},
{130, 180, 237},
{214, 111, 237},
{29, 225, 237},
{255, 255, 255},

  },

  {
    {0, 0, 0},
    {127, 0, 0},
    {90, 209, 88},
    {136, 109, 0},
    {3, 9, 235},
    {90, 4, 150},
    {43, 111, 150},
    {178, 178, 178},
    {87, 87, 87},
    {193, 122, 99},
    {110, 254, 174},
    {255, 200, 0},
    {10, 126, 254},
    {146, 155, 249},
    {184, 208, 254},
    {255, 255, 255},

  },{
    {0, 0, 0},
    {147, 53, 38},
    {30, 171, 82},
    {188, 153, 0},
    {32, 71, 193},
    {236, 49, 188},
    {42, 182, 253},
    {149, 149, 149},
    {73, 73, 73},
    {210, 36, 0},
    {96, 239, 97},
    {247, 240, 2},
    {93, 11, 249},
    {222, 42, 255},
    {11, 227, 255},
    {233, 235, 235},
  },


  { {0, 0, 0},{97, 27, 0},{129, 180, 0},{127, 100, 0},{44, 15, 255},{135, 10, 167},{20, 133, 164},{174, 174, 174},{71, 71, 71},{167, 114, 90},{162, 214, 127},{255, 251, 83},{118, 77, 253},{192, 121, 255},{14, 217, 255},{255, 255, 255},
  },{


#if 0
    {
      {0, 0, 0},
      {144, 0, 0},
      {9, 154, 9},
      {255, 137, 113},
      {3, 0, 255},
      {56, 0, 132},
      {0, 131, 131},
      {204, 204, 204},
      {127, 127, 127},
      {255, 33, 0},
      {113, 255, 88},
      {255, 236, 8},
      {1, 122, 255},
      {235, 0, 222},
      {0, 217, 255},
      {255, 255, 255},
    },{
#endif


    {0, 0, 0},
    {139, 0, 0},
    {9, 154, 9},
    {255, 137, 113},
    {3, 0, 255},
    {56, 0, 132},
    {0, 111, 111},
    {204, 204, 204},
    {127, 127, 127},
    {255, 33, 0},
    {118, 255, 92},
    {255, 230, 15},
    {1, 122, 255},
    {232, 0, 220},
    {1, 217, 255},
    {255, 255, 255},
  },
  {

    {0, 0, 0},
    {191, 0, 0},
    {3, 187, 0},
    {254, 212, 0},
    {0, 0, 255},
    {80, 0, 128},
    {0, 156, 255},
    {166, 166, 166},
    {84, 84, 84},
    {255, 62, 0},
    {85, 255, 143},
    {255, 255, 0},
    {67, 80, 255},
    {243, 70, 255},
    {30, 255, 222},
    {255, 255, 255},
  },
  {
    /* */
    { 32, 32, 32}, // 0 - background (black)
    {165, 15, 21}, // 1               red
    { 95,130, 10}, // 2               green
    {205,145, 60}, // 3               yellow
    { 49,130,189}, // 4               blue
    {120, 40,160}, // 5               magenta
    {120,230,230}, // 6               cyan
    {196,196,196},// 7                light-gray
    { 85, 85, 85},// 8                dark gray

    {251,106, 74},// 9                light red
    {130,215,140},// 10               light green
    {255,255,  0},// 11               light yellow
    {107,174,214},// 12               light blue
    {215,130,160},// 13               light magenta
    {225,255,245},// 14               light cyan
    {255,255,255},// 15 - foreground (white)
  },{
    /* */
    { 32, 32, 32}, // 0 - background (black)
    {160,  0,  0}, // 1               red
    {  9,233,  0}, // 2               green
    {220,110, 44}, // 3               yellow
    {  0,  0,200}, // 4               blue
    { 90,  0,130}, // 5               magenta
    {  0,156,180}, // 6               cyan
    {196,196,196}, // 7                light-gray
    { 85, 85, 85}, // 8                dark gray

    {240, 60, 40}, // 9                light red
    {170,240, 80}, // 10               light green
    {248,248,  0}, // 11               light yellow
    {  0, 40,255}, // 12               light blue
    {204, 62,214}, // 13               light magenta
    { 10,234,254}, // 14               light cyan
    {255,255,255}, // 15 - foreground (white)
  },
  /* inspired by DEC */
  { {  0,  0,  0}, // 0 - background  black
    {150, 10, 10}, // 1               red
    { 21,133,  0}, // 2               green

    {103,103, 24}, // 3               yellow
    { 44, 44,153}, // 4               blue
    {123, 94,183}, // 5               magenta

    { 20,183,193}, // 6               cyan

    {177,177,177},// 7                light-gray
    {100,100,100},// 8                dark gray

    {244, 39, 39},// 9                light red
    { 61,224, 81},// 10               light green
    {255,255,  0},// 11               light yellow
    { 61, 61,244},// 12               light blue
    {240, 11,240},// 13               light magenta
    { 61,234,234},// 14               light cyan

    {255,255,255},// 15 - foreground  white
  },
};

static void vt_state_osc (VT *vt, int byte)
{
  // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html
  // and in "\033\" rather than just "\033", this would cause
  // a stray char
  //if (byte == '\a' || byte == 27 || byte == 0 || byte < 32)
  if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
      int n = parse_int (vt->argument_buf, 0);
      switch (n)
        {
          case 0:
          case 1:
          case 2:
#if 0
    {"]0;New_title\e\",  0, , }, /* id: set window title */ "
#endif
            vt_set_title (vt, vt->argument_buf + 3);
            break;
          case 4: // set palette entry
            {
            int color_no = parse_int (vt->argument_buf + 2, 0);
            char *rest = vt->argument_buf + 3;
            rest = strchr (rest, ';');

            if (rest++)
            if (strlen(rest)>10 &&
                rest[0] == 'r' &&
                rest[1] == 'g' &&
                rest[2] == 'b' &&
                rest[3] == ':' &&
                rest[6] == '/' &&
                rest[9] == '/')
            {
              int red = vt_decode_hex (&rest[4]);
              int green = vt_decode_hex (&rest[7]);
              int blue = vt_decode_hex (&rest[10]);
          //  fprintf (stderr, "set color:%i  %i %i %i\n", color_no, red, green, blue);
              if (color_no >= 0 && color_no <= 15)
              {
                palettes[0][color_no][0]=red;
                palettes[0][color_no][1]=green;
                palettes[0][color_no][2]=blue;
              }
            }
            }
            break;
          case 12: // text cursor color
            break;
          case 17: // highlight color
            break;
          case 19: // ??
            break;

          case 10: // text fg
#if 0
#if 0
    {"]11;",  0, , }, /* id: set foreground color */
#endif
            {
              /* request current foreground color, xterm does this to
                 determine if it can use 256 colors, when this test fails,
                 it still mixes in color 130 together with stock colors
               */
              char buf[128];
              sprintf (buf, "\033]10;rgb:%2x/%2x/%2x\033\\",
                       vt->fg_color[0], vt->fg_color[1], vt->fg_color[2]);
              vt_write (vt, buf, strlen (buf) );
            }
#endif
            break;
          case 11: // text bg
#if 0
    {"]11;",  0, , }, /* id: get background color */
            {
              /* get background color */
              char buf[128];
              sprintf (buf, "\033]11;rgb:%2x/%2x/%2x\033\\",
                       vt->bg_color[0], vt->bg_color[1], vt->bg_color[2]);
              vt_write (vt, buf, strlen (buf) );
            }
#endif
            break;
#if 0
    {"]1337;key=value:base64data\b\",  0, vtcmd_erase_in_line, VT100}, /* args:keyvalue id: iterm2 graphics */ "
#endif
          case 1337:
            if (!strncmp (&vt->argument_buf[6], "File=", 5) )
              {
                {
                  /* iTerm2 image protocol */
                  int width = 0;
                  int height = 0;
                  int file_size = 0;
                  int show_inline = 0;
                  int preserve_aspect = 1;
                  char *name = NULL;
                  char *p = &vt->argument_buf[11];
                  char key[128]="";
                  char value[128]="";
                  int in_key=1;
                  if (preserve_aspect) {}; /* XXX : NYI */
                  for (; *p && *p!=':'; p++)
                    {
                      if (in_key)
                        {
                          if (*p == '=')
                            { in_key = 0; }
                          else
                            {
                              if (strlen (key) < 124)
                                {
                                  key[strlen (key)+1] = 0;
                                  key[strlen (key)] = *p;
                                }
                            }
                        }
                      else
                        {
                          if (*p == ';')
                            {
                              if (!strcmp (key, "name") )
                                {
                                  name = strdup (value);
                                }
                              else if (!strcmp (key, "width") )
                                {
                                  width = atoi (value);
                                  if (strchr (value, 'x') )
                                    { /* pixels */ }
                                  else if (strchr (value, '%') )
                                    {
                                      /* percent */
                                      width = width / 100.0 * (vt->cw * vt->cols);
                                    }
                                  else
                                    { /* chars */ width = width * vt->cw; }
                                }
                              else if (!strcmp (key, "height") )
                                {
                                  height = atoi (value);
                                  if (strchr (value, 'x') )
                                    { /* pixels */ }
                                  else if (strchr (value, '%') )
                                    {
                                      /* percent */
                                      height = height / 100.0 * (vt->ch * vt->rows);
                                    }
                                  else
                                    { /* chars */ height = height * vt->ch; }
                                }
                              else if (!strcmp (key, "preserveAspectRatio") )
                                {
                                  preserve_aspect = atoi (value);
                                }
                              else if (!strcmp (key, "inline") )
                                {
                                  show_inline = atoi (value);
                                }
                              key[0]=0;
                              value[0]=0;
                              in_key = 1;
                            }
                          else
                            {
                              if (strlen (value) < 124)
                                {
                                  value[strlen (value)+1] = 0;
                                  value[strlen (value)] = *p;
                                }
                            }
                        }
                    }
                  if (key[0])
                    {
                      // code-dup
                      if (!strcmp (key, "name") )
                        {
                          name = strdup (value);
                        }
                      else if (!strcmp (key, "width") )
                        {
                          width = atoi (value);
                          if (strchr (value, 'x') )
                            { /* pixels */ }
                          else if (strchr (value, '%') )
                            {
                              /* percent */
                              width = width / 100.0 * (vt->cw * vt->cols);
                            }
                          else
                            { /* chars */ width = width * vt->cw; }
                        }
                      else if (!strcmp (key, "height") )
                        {
                          height = atoi (value);
                          if (strchr (value, 'x') )
                            { /* pixels */ }
                          else if (strchr (value, '%') )
                            {
                              /* percent */
                              height = height / 100.0 * (vt->ch * vt->rows);
                            }
                          else
                            { /* chars */ height = height * vt->ch; }
                        }
                      else if (!strcmp (key, "preserveAspectRatio") )
                        {
                          preserve_aspect = atoi (value);
                        }
                      else if (!strcmp (key, "inline") )
                        {
                          show_inline = atoi (value);
                        }
                    }
                  if (*p == ':')
                    {
                      p++;
                    }
                  if (0)
                    fprintf (stderr, "%s %i %i %i %i{%s\n", name?name:"",
                             width, height, file_size, show_inline,
                             p);
                  Image *image = NULL;
                  {
                    int bin_length = vt->argument_buf_len;
                    uint8_t *data2 = malloc (bin_length);
                    bin_length = ctx_base642bin ( (char *) p,
                                                 &bin_length,
                                                 data2);
                    int channels = 4;
                    int buf_width = 0;
                    int buf_height = 0;
                    uint8_t *new_data = stbi_load_from_memory (data2, bin_length, &buf_width, &buf_height, &channels, 4);
                    free (data2);
                    if (new_data)
                      {
                        image = image_add (buf_width, buf_height, 0,
                                           32, buf_width*buf_height*4, new_data);
                      }
                    else
                      {
                        fprintf (stderr, "image decoding problem %s\n", stbi_failure_reason());
                        fprintf (stderr, "len: %i\n", bin_length);
                      }
                  }
                  if (image)
                    {
                      display_image (vt, image, vt->cursor_x, 0,0, 0.0, 0.0, 0,0,0,0);
                      int right = (image->width + (vt->cw-1) ) /vt->cw;
                      int down = (image->height + (vt->ch-1) ) /vt->ch;
                      for (int i = 0; i<down - 1; i++)
                        { vtcmd_index (vt, " "); }
                      for (int i = 0; i<right; i++)
                        { vtcmd_cursor_forward (vt, " "); }
                    }
                }
              }
            break;
          case 104:
            break;
          case 8:
            fprintf (stderr, "unhandled OSC 8, hyperlink\n");
            break;
          default:
            fprintf (stderr, "unhandled OSC %i\n", n);
            break;
        }
      if (byte == 27)
        {
          vt->state = vt_state_swallow;
        }
      else
        {
          vt->state = vt_state_neutral;
        }
    }
  else
    {
      vt_argument_buf_add (vt, byte);
    }
}


static void vt_state_sixel (VT *vt, int byte)
{
  // https://ttssh2.osdn.jp/manual/4/en/about/ctrlseq.html
  // and in "\033\" rather than just "\033", this would cause
  // a stray char
  if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
      vt_sixels (vt, vt->argument_buf);
      if (byte == 27)
        {
          vt->state = vt_state_swallow;
        }
      else
        {
          vt->state = vt_state_neutral;
        }
    }
  else
    {
      vt_argument_buf_add (vt, byte);
      //fprintf (stderr, "\r%i ", vt->argument_buf_len);
    }
}

//void add_tab (Ctx *ctx, const char *commandline, int can_launch);
//void vt_screenshot (const char *output_path);

static void vt_state_apc_generic (VT *vt, int byte)
{
  if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
      if (vt->argument_buf[1] == 'G') /* graphics - from kitty */
        {
          vt_gfx (vt, vt->argument_buf);
        }
      else if (vt->argument_buf[1] == 'C') /* launch command */
      {
        if (vt->can_launch)
        {
          int   can_launch = 0;
          int   no_title = 0;
          int   no_move = 0;
          int   no_resize = 0;
          int   layer = 0;
  // escape subsequent arguments so that we dont have to pass a string?
          float x = -1.0;
          float y = -1.0;
          int   z = 0;
          float width = -1.0;
          float height = -1.0;

          for (int i=2; vt->argument_buf[i]; i++)
          {
            if (!strncmp (&vt->argument_buf[i], "can_launch=1", strlen ("can_launch=1")))
              can_launch = 1;
            if (!strncmp (&vt->argument_buf[i], "no_title=1", strlen("no_title=1")))
              no_title = 1;
            if (!strncmp (&vt->argument_buf[i], "no_move=1", strlen("no_move=1")))
              no_move = 1;
            else if (!strncmp (&vt->argument_buf[i], "z=", 2))
              z=atoi(&vt->argument_buf[i]+strlen("z="));
            else if (!strncmp (&vt->argument_buf[i], "x=", 2))
              x=atof(&vt->argument_buf[i]+strlen("x="));
            else if (!strncmp (&vt->argument_buf[i], "y=", 2))
              y=atof(&vt->argument_buf[i]+strlen("y="));
            else if (!strncmp (&vt->argument_buf[i], "width=", 6))
              width=atof(&vt->argument_buf[i]+strlen("width="));
            else if (!strncmp (&vt->argument_buf[i], "height=", 7))
              height=atof(&vt->argument_buf[i]+strlen("height="));
          }

          if (width + no_resize + layer + height + x + y + no_title + no_move + z + can_launch) {};

          char *sep = strchr(vt->argument_buf, ';');
          if (sep)
          {
            //fprintf (stderr, "[%s]", sep +  1);
            if (!strncmp (sep + 1, "fbsave", 6))
            {
              // vt_screenshot (sep + 8);
            }
            else
            {
          //  add_tab (ctx, sep + 1, can_launch);
            }
          }
        }

      }
      vt->state = ( (byte == 27) ?  vt_state_swallow : vt_state_neutral);
    }
  else
    {
      vt_argument_buf_add (vt, byte);
    }
}

#if 0
    {"_G..\e\", 0, vtcmd_delete_n_chars, VT102}, /* ref:none id: <a href='https://sw.kovidgoyal.net/kitty/graphics-protocol.html'>kitty graphics</a> */ "
    {"_A..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id:  <a href='https://github.com/hodefoting/atty/'>atty</a> audio input/output */ "
    {"_C..\e\", 0, vtcmd_delete_n_chars, VT102}, /* id:  run command */ "
#endif
static void vt_state_apc (VT *vt, int byte)
{
  if (byte == 'A')
    {
      vt_argument_buf_add (vt, byte);
      vt->state = vt_state_apc_audio;
    }
  else if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
      vt->state = ( (byte == 27) ?  vt_state_swallow : vt_state_neutral);
    }
  else
    {
      vt_argument_buf_add (vt, byte);
      vt->state = vt_state_apc_generic;
    }
}

static void vt_state_esc_foo (VT *vt, int byte)
{
  vt_argument_buf_add (vt, byte);
  vt->state = vt_state_neutral;
  handle_sequence (vt, vt->argument_buf);
}

static void vt_state_esc_sequence (VT *vt, int byte)
{
  if (_vt_handle_control (vt, byte) == 0)
    {
      if (byte == 27)
        {
        }
      else if (byte >= '@' && byte <= '~')
        {
          vt_argument_buf_add (vt, byte);
          vt->state = vt_state_neutral;
          handle_sequence (vt, vt->argument_buf);
        }
      else
        {
          vt_argument_buf_add (vt, byte);
        }
    }
}

static void vt_state_esc (VT *vt, int byte)
{
  if (_vt_handle_control (vt, byte) == 0)
    switch (byte)
      {
        case 27: /* ESCape */
          break;
        case ')':
        case '#':
        case '(':
          {
            char tmp[]= {byte, '\0'};
            vt_argument_buf_reset (vt, tmp);
            vt->state = vt_state_esc_foo;
          }
          break;
        case '[':
        case '%':
        case '+':
        case '*':
          {
            char tmp[]= {byte, '\0'};
            vt_argument_buf_reset (vt, tmp);
            vt->state = vt_state_esc_sequence;
          }
          break;

#if 0
    {"Psixel_data\e\",  0, , }, /* id: sixels */ "
#endif

        case 'P':
          {
            char tmp[]= {byte, '\0'};
            vt_argument_buf_reset (vt, tmp);
            vt->state = vt_state_sixel;
          }
          break;
        case ']':
          {
            char tmp[]= {byte, '\0'};
            vt_argument_buf_reset (vt, tmp);
            vt->state = vt_state_osc;
          }
          break;
        case '^':  // privacy message
        case '_':  // APC
        case 'X':  // SOS
          {
            char tmp[]= {byte, '\0'};
            vt_argument_buf_reset (vt, tmp);
            vt->state = vt_state_apc;
          }
          break;
        default:
          {
            char tmp[]= {byte, '\0'};
            tmp[0]=byte;
            vt->state = vt_state_neutral;
            handle_sequence (vt, tmp);
          }
          break;
      }
}

static void vt_state_neutral (VT *vt, int byte)
{
  if (CTX_UNLIKELY(_vt_handle_control (vt, byte) != 0))
    return;
  if (CTX_LIKELY(byte != 27))
  {
    if (vt_decoder_feed (vt, byte) )
      return;
    if (vt->charset[vt->shifted_in] != 0 &&
        vt->charset[vt->shifted_in] != 'B')
      {
        char **charmap;
        switch (vt->charset[vt->shifted_in])
          {
            case 'A':
              charmap = charmap_uk;
              break;
            case 'B':
              charmap = charmap_ascii;
              break;
            case '0':
              charmap = charmap_graphics;
              break;
            case '1':
              charmap = charmap_cp437;
              break;
            case '2':
              charmap = charmap_graphics;
              break;
            default:
              charmap = charmap_ascii;
              break;
          }
        if ( (vt->utf8_holding[0] >= ' ') && (vt->utf8_holding[0] <= '~') )
          {
            _vt_add_str (vt, charmap[vt->utf8_holding[0]-' ']);
          }
      }
    else
      {
        // ensure vt->utf8_holding contains a valid utf8
        uint32_t codepoint;
        uint32_t state = 0;
        for (int i = 0; vt->utf8_holding[i]; i++)
          { utf8_decode (&state, &codepoint, vt->utf8_holding[i]); }
        if (state != UTF8_ACCEPT)
          {
            /* otherwise mangle it so that it does */
            vt->utf8_holding[0] &= 127;
            vt->utf8_holding[1] = 0;
            if (vt->utf8_holding[0] == 0)
              { vt->utf8_holding[0] = 32; }
          }
        _vt_add_str (vt, (char *) vt->utf8_holding);
      }
  }
  else // ESCape
  {
    vt->state = vt_state_esc;
  }
}

int vt_poll (VT *vt, int timeout)
{
  if (!vt) return 0;
  int read_size = sizeof (vt->buf);
  int got_data = 0;

  // read_size 1m1.142s
  // read_size*10  52s
  // read_size*5   53.8s
  // read_size*4   53.78s
  // read_size*3   .....s
  // read_size*2   56.99s
  int remaining_chars = read_size * 3;// * 100;
  int len = 0;
  vt_audio_task (vt, 0);
#if 1
  if (vt->cursor_visible && vt->smooth_scroll)
    {
      remaining_chars = vt->cols / 2;
    }
#endif
  read_size = MIN (read_size, remaining_chars);
  long start_ticks = ctx_ticks ();
  long ticks = start_ticks;
  while (remaining_chars > 0 &&
         vt_waitdata (vt, 0) &&
         ( ticks - start_ticks < timeout ||  vt->state == vt_state_ctx))
    {
  if (vt->in_smooth_scroll)
    {
      remaining_chars = 1;
      // XXX : need a bail condition -
      // /// so that we can stop accepting data until autowrap or similar
    }
      len = vt_read (vt, vt->buf, read_size);
      if (len >0)
      {
     // fwrite (vt->buf, len, 1, vt->log);
     // fwrite (vt->buf, len, 1, stdout);
      }
      for (int i = 0; i < len; i++)
        { vt->state (vt, vt->buf[i]); }
      // XXX allow state to break out in ctx mode on flush
      got_data+=len;
      remaining_chars -= len;
      if (vt->state == vt_state_ctx) {
         if (remaining_chars < read_size)
         {
           remaining_chars = read_size * 2;
         }
      }
      vt_audio_task (vt, 0);
      ticks = ctx_ticks ();
    }
  if (got_data < 0)
    {
      if (kill (vt->vtpty.pid, 0) != 0)
        {
          vt->vtpty.done = 1;
        }
    }
  return got_data;
}

/******/

static const char *keymap_vt52[][2]=
{
  {"up",    "\033A" },
  {"down",  "\033B" },
  {"right", "\033C" },
  {"left",  "\033D" },
};

static const char *keymap_application[][2]=
{
  {"up",    "\033OA" },
  {"down",  "\033OB" },
  {"right", "\033OC" },
  {"left",  "\033OD" },
};

static const char *keymap_general[][2]=
{
  {"up",             "\033[A"},
  {"down",           "\033[B"},
  {"right",          "\033[C"},
  {"left",           "\033[D"},
  {"end",            "\033[F"},
  {"home",           "\033[H"},
  {"shift-up",       "\033[1;2A"},
  {"shift-down",     "\033[1;2B"},
  {"shift-right",    "\033[1;2C"},
  {"shift-left",     "\033[1;2D"},
  {"alt-a",          "\033a"},
  {"alt-b",          "\033b"},
  {"alt-c",          "\033c"},
  {"alt-d",          "\033d"},
  {"alt-e",          "\033e"},
  {"alt-f",          "\033f"},
  {"alt-g",          "\033g"},
  {"alt-h",          "\033h"},
  {"alt-i",          "\033i"},
  {"alt-j",          "\033j"},
  {"alt-k",          "\033k"},
  {"alt-l",          "\033l"},
  {"alt-m",          "\033m"},
  {"alt-n",          "\033n"},
  {"alt-o",          "\033o"},
  {"alt-p",          "\033p"},
  {"alt-q",          "\033q"},
  {"alt-r",          "\033r"},
  {"alt-s",          "\033s"},
  {"alt-t",          "\033t"},
  {"alt-u",          "\033u"},
  {"alt-v",          "\033v"},
  {"alt-w",          "\033w"},
  {"alt-x",          "\033x"},
  {"alt-y",          "\033y"},
  {"alt-z",          "\033z"},
  {"alt- ",          "\033 "},
  {"alt-space",      "\033 "},
  {"alt-0",          "\0330"},
  {"alt-1",          "\0331"},
  {"alt-2",          "\0332"},
  {"alt-3",          "\0333"},
  {"alt-4",          "\0334"},
  {"alt-5",          "\0335"},
  {"alt-6",          "\0336"},
  {"alt-7",          "\0337"},
  {"alt-8",          "\0338"},
  {"alt-9",          "\0339"},
  {"alt-return",     "\033\r"},
  {"alt-backspace",  "\033\177"},
  {"alt-up",         "\033[1;3A"},
  {"alt-down",       "\033[1;3B"},
  {"alt-right",      "\033[1;3C"},
  {"alt-left",       "\033[1;3D"},
  {"shift-alt-up",   "\033[1;4A"},
  {"shift-alt-down", "\033[1;4B"},
  {"shift-alt-right","\033[1;4C"},
  {"shift-alt-left", "\033[1;4D"},
  {"control-space",  "\000"},
  {"control-up",     "\033[1;5A"},
  {"control-down",   "\033[1;5B"},
  {"control-right",  "\033[1;5C"},
  {"control-left",   "\033[1;5D"},
  {"shift-control-up",    "\033[1;6A"},
  {"shift-control-down",  "\033[1;6B"},
  {"shift-control-right", "\033[1;6C"},
  {"shift-control-left",  "\033[1;6D"},
  {"insert",         "\033[2~"},
  {"delete",         "\033[3~"},
  {"control-delete", "\033[3,5~"},
  {"shift-delete",   "\033[3,2~"},
  {"control-shift-delete",  "\033[3,6~"},
  {"page-up",        "\033[5~"},
  {"page-down",     "\033[6~"},
  {"return",         "\r"},
  {"shift-tab",      "\033Z"},
  {"shift-return",   "\r"},
  {"control-return", "\r"},
  {"space",          " "},
  {"shift-space",    " "},
  {"control-a",      "\001"},
  {"control-b",      "\002"},
  {"control-c",      "\003"},
  {"control-d",      "\004"},
  {"control-e",      "\005"},
  {"control-f",      "\006"},
  {"control-g",      "\007"},
  {"control-h",      "\010"},
  {"control-i",      "\011"},
  {"control-j",      "\012"},
  {"control-k",      "\013"},
  {"control-l",      "\014"},
  {"control-m",      "\015"},
  {"control-n",      "\016"},
  {"control-o",      "\017"},
  {"control-p",      "\020"},
  {"control-q",      "\021"},
  {"control-r",      "\022"},
  {"control-s",      "\023"},
  {"control-t",      "\024"},
  {"control-u",      "\025"},
  {"control-v",      "\026"},
  {"control-w",      "\027"},
  {"control-x",      "\030"},
  {"control-y",      "\031"},
  {"control-z",      "\032"},
  {"escape",         "\033"},
  {"tab",            "\t"},
  {"backspace",      "\177"},
  {"control-backspace", "\177"},
  {"shift-backspace","\177"},
  {"shift-tab",      "\033[Z"},

  {"control-F1",     "\033[>11~"},
  {"control-F2",     "\033[>12~"},
  {"control-F3",     "\033[>13~"},
  {"control-F4",     "\033[>14~"},
  {"control-F5",     "\033[>15~"},

  {"shift-F1",       "\033[?11~"},
  {"shift-F2",       "\033[?12~"},
  {"shift-F3",       "\033[?13~"},
  {"shift-F4",       "\033[?14~"},
  {"shift-F5",       "\033[?15~"},

  {"F1",             "\033[11~"},  // hold screen   // ESC O P
  {"F2",             "\033[12~"},  // print screen  //       Q
  {"F3",             "\033[13~"},  // set-up                 R
  {"F4",             "\033[14~"},  // data/talk              S
  {"F5",             "\033[15~"},  // break
  {"F6",             "\033[17~"},
  {"F7",             "\033[18~"},
  {"F8",             "\033[19~"},
  {"F9",             "\033[20~"},
  {"F10",            "\033[21~"},
  {"F11",            "\033[22~"},
  {"F12",            "\033[23~"},
  {"control-/",       "\037"},
  {"shift-control-/", "\037"},
  {"control-[",       "\033"},
  {"control-]",       "\035"},
  {"shift-control-[", "\033"},
  {"shift-control-]", "\031"},
  {"shift-control-`", "\036"},
  {"control-'",       "'"},
  {"shift-control-'", "'"},
  {"control-;",       ";"},
  {"shift-control-;", ";"},
  {"control-.",       "."},
  {"shift-control-.", "."},
  {"control-,",       ","},
  {"shift-control-,", ","},
  {"control-\\",      "\034"},
  {"control-1",       "1"},
  {"control-3",       "\033"},
  {"control-4",       "\034"},
  {"control-5",       "\035"},
  {"control-6",       "\036"},
  {"shift-control-6", "\036"},
  {"control-7",       "\037"},
  {"shift-control-7", "\036"},
  {"control-8",       "\177"},
  {"control-9",       "9"},


};

void ctx_client_lock (CtxClient *client);
void ctx_client_unlock (CtxClient *client);

void vt_feed_keystring (VT *vt, CtxEvent *event, const char *str)
{
  if (vt->ctx_events)
  {
    if (!strcmp (str, "control-l") )
    {
      vt->ctx_events = 0;
      return;
    }
    vt_write (vt, str, strlen (str) );
    vt_write (vt, "\n", 1);
    return;
  }
  if (!strncmp (str, "keyup",   5)) return;
  if (!strncmp (str, "keydown", 7)) return;

  if (!strcmp (str, "capslock")) return;

#if 0
  if (!strstr (str, "-page"))
    vt_set_scroll (vt, 0);
#endif

  if (!strcmp (str, "idle") )
     return;
  else if (!strcmp (str, "shift-control-home"))
    {
      vt_set_scroll (vt, vt->scrollback_count);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-control-end"))
    {
      int new_scroll = 0;
      vt_set_scroll (vt, new_scroll);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-control-down"))
    {
      int new_scroll = vt_get_scroll (vt) - 1;
      vt_set_scroll (vt, new_scroll);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-control-up"))
    {
      int new_scroll = vt_get_scroll (vt) + 1;
      vt_set_scroll (vt, new_scroll);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-page-up") ||
           !strcmp (str, "shift-control-page-up"))
    {
      int new_scroll = vt_get_scroll (vt) + vt_get_rows (vt) /2;
      vt_set_scroll (vt, new_scroll);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-page-down") ||
           !strcmp (str, "shift-control-page-down"))
    {
      int new_scroll = vt_get_scroll (vt) - vt_get_rows (vt) /2;
      if (new_scroll < 0) { new_scroll = 0; }
      vt_set_scroll (vt, new_scroll);
      vt_rev_inc (vt);
      return;
    }
  else if (!strcmp (str, "shift-control--") ||
           !strcmp (str, "control--") )
    {
      float font_size = vt_get_font_size (vt);
      //font_size /= 1.15;
      font_size -=2;//= roundf (font_size);
      if (font_size < 2) { font_size = 2; }
      vt_set_font_size (vt, font_size);
      vt_set_px_size (vt, vt->width, vt->height);
      return;
    }
  else if (!strcmp (str, "shift-control-=") ||
           !strcmp (str, "control-=") )
    {
      float font_size = vt_get_font_size (vt);
      float old = font_size;
      //font_size *= 1.15;
      //
      //font_size = roundf (font_size);
      font_size+=2;

      if (old == font_size) { font_size = old+1; }
      if (font_size > 200) { font_size = 200; }
      vt_set_font_size (vt, font_size);
      vt_set_px_size (vt, vt->width, vt->height);

      return;
    }
  else if (!strcmp (str, "shift-control-r") )
    {
      vt_open_log (vt, "/tmp/ctx-vt");
      return;
    }
  else if (!strcmp (str, "shift-control-l") )
    {
      vt_set_local (vt, !vt_get_local (vt) );
      return;
    }
  else if (!strncmp (str, "mouse-", 5) )
    {
      int cw = vt_cw (vt);
      int ch = vt_ch (vt);
      if (!strncmp (str + 6, "motion", 6) )
        {
          int x = 0, y = 0;
          char *s = strchr (str, ' ');
          if (s)
            {
              x = atoi (s);
              s = strchr (s + 1, ' ');
              if (s)
                {
                  y = atoi (s);
                  vt_mouse (vt, event, VT_MOUSE_MOTION, 1, x/cw + 1, y/ch + 1, x, y);
                }
            }
        }
      else if (!strncmp (str + 6, "press", 5) )
        {
          int x = 0, y = 0, b = 0;
          char *s = strchr (str, ' ');
          if (s)
            {
              x = atoi (s);
              s = strchr (s + 1, ' ');
              if (s)
                {
                  y = atoi (s);
                  s = strchr (s + 1, ' ');
                  if (s)
                  {
                    b = atoi (s);
                  }
                  vt_mouse (vt, event, VT_MOUSE_PRESS, b, x/cw + 1, y/ch + 1, x, y);
                }
            }
          //clients[active].drawn_rev = 0;
        }
      else if (!strncmp (str + 6, "drag", 4) )
        {
          int x = 0, y = 0, b = 0; // XXX initialize B
          char *s = strchr (str, ' ');
          if (s)
            {
              x = atoi (s);
              s = strchr (s + 1, ' ');
              if (s)
                {
                  y = atoi (s);
                  if (s)
                  {
                    b = atoi (s);
                  }
                  vt_mouse (vt, event, VT_MOUSE_DRAG, b, x/cw + 1, y/ch + 1, x, y);
                }
            }
          //clients[active].drawn_rev = 0;
        }
      else if (!strncmp (str + 6, "release", 7) )
        {
          int x = 0, y = 0, b = 0;
          char *s = strchr (str, ' ');
          if (s)
            {
              x = atoi (s);
              s = strchr (s + 1, ' ');
              if (s)
                {
                  y = atoi (s);
                  s = strchr (s + 1, ' ');
                  if (s)
                  {
                    b = atoi (s);
                  }
                  vt_mouse (vt, event, VT_MOUSE_RELEASE, b, x/cw + 1, y/ch + 1, x, y);
                }
            }
          //clients[active].drawn_rev = 0;
          // queue-draw
        }
      return;
    }

  if (vt->scroll_on_input)
  {
    vt->scroll = 0.0;
  }


  if (vt->state == vt_state_vt52)
    {
      for (unsigned int i = 0; i<sizeof (keymap_vt52) /sizeof (keymap_vt52[0]); i++)
        if (!strcmp (str, keymap_vt52[i][0]) )
          { str = keymap_vt52[i][1]; goto done; }
    }
  else
    {
      if (vt->cursor_key_application)
        {
          for (unsigned int i = 0; i<sizeof (keymap_application) /sizeof (keymap_application[0]); i++)
            if (!strcmp (str, keymap_application[i][0]) )
              { str = keymap_application[i][1]; goto done; }
        }
    }


  if (!strcmp (str, "return") )
    {
      if (vt->cr_on_lf)
        { str = "\r\n"; }
      else
        { str = "\r"; }
      goto done;
    }
  if (!strcmp (str, "control-space") ||
      !strcmp (str, "control-`") ||
      !strcmp (str, "control-2") ||
      !strcmp (str, "shift-control-2") ||
      !strcmp (str, "shift-control-space") )
    {
      str = "\0\0";
      vt_write (vt, str, 1);
      return;
    }
  for (unsigned int i = 0; i< sizeof (keymap_general) /
                              sizeof (keymap_general[0]); i++)
    if (!strcmp (str, keymap_general[i][0]) )
      {
        str = keymap_general[i][1];
        break;
      }
done:
  if (strlen (str) )
    {
      if (vt->local_editing)
        {
          for (int i = 0; str[i]; i++)
            {
              vt->state (vt, str[i]);
            }
        }
      else
        {
          vt_write (vt, str, strlen (str) );
        }
    }
}

void vt_paste (VT *vt, const char *str)
{
  if (vt->bracket_paste)
    {
      vt_write (vt, "\033[200~", 6);
    }
  vt_feed_keystring (vt, NULL, str);
  if (vt->bracket_paste)
    {
      vt_write (vt, "\033[201~", 6);
    }
}

const char *vt_find_shell_command (void)
{
  if (access ("/.flatpak-info", F_OK) != -1)
  {
    static char ret[512];
    char buf[256];
    FILE *fp = popen("flatpak-spawn --host getent passwd $USER|cut -f 7 -d :", "r");
    if (fp)
    {
      while (fgets (buf, sizeof(buf), fp) != NULL)
      {
        if (buf[strlen(buf)-1]=='\n')
          buf[strlen(buf)-1]=0;
        sprintf (ret, "flatpak-spawn --env=TERM=xterm --host %s", buf);
      }
      pclose (fp);
      return ret;
    }
  }

  if (getenv ("SHELL"))
  {
    return getenv ("SHELL");
  }
  int i;
  const char *command = NULL;
  struct stat stat_buf;
  static char *alts[][2] =
  {
    {"/bin/bash",     "/bin/bash"},
    {"/usr/bin/bash", "/usr/bin/bash"},
    {"/bin/sh",       "/bin/sh"},
    {"/usr/bin/sh",   "/usr/bin/sh"},
    {NULL, NULL}
  };
  for (i = 0; alts[i][0] && !command; i++)
    {
      lstat (alts[i][0], &stat_buf);
      if (S_ISREG (stat_buf.st_mode) || S_ISLNK (stat_buf.st_mode) )
        { command = alts[i][1]; }
    }
  return command;
}

static char *string_chop_head (char *orig) /* return pointer to reset after arg */
{
  int j=0;
  int eat=0; /* number of chars to eat at start */

  if(orig)
    {
      int got_more;
      char *o = orig;
      while(o[j] == ' ')
        {j++;eat++;}

      if (o[j]=='"')
        {
          eat++;j++;
          while(o[j] != '"' &&
                o[j] != 0)
            j++;
          o[j]='\0';
          j++;
        }
      else if (o[j]=='\'')
        {
          eat++;j++;
          while(o[j] != '\'' &&
                o[j] != 0)
            j++;
          o[j]='\0';
          j++;
        }
      else
        {
          while(o[j] != ' ' &&
                o[j] != 0 &&
                o[j] != ';')
            j++;
        }
      if (o[j] == 0 ||
          o[j] == ';')
        got_more = 0;
      else
        got_more = 1;
      o[j]=0; /* XXX: this is where foo;bar won't work but foo ;bar works*/

      if(eat)
       {
         int k;
         for (k=0; k<j-eat; k++)
           orig[k] = orig[k+eat];
       }
      if (got_more)
        return &orig[j+1];
    }
  return NULL;
}

void _ctx_add_listen_fd (int fd);
void _ctx_remove_listen_fd (int fd);

static pid_t
vt_forkpty (int  *amaster,
            char *aname,
            const struct termios *termp,
            const struct winsize *winsize)
{
  pid_t pid;
  int master = posix_openpt (O_RDWR|O_NOCTTY);
  int slave;

  if (master < 0)
    return -1;
  if (grantpt (master) != 0)
    return -1;
  if (unlockpt (master) != 0)
    return -1;
#if 0
  char name[1024];
  if (ptsname_r (master, name, sizeof(name)-1))
    return -1;
#else
  char *name = NULL;
  if ((name = ptsname (master)) == NULL)
    return -1;
#endif

  slave = open(name, O_RDWR|O_NOCTTY);

  if (termp)   tcsetattr(slave, TCSAFLUSH, termp);
  if (winsize) ioctl(slave, TIOCSWINSZ, winsize);

  pid = fork();
  if (pid < 0)
  {
    return pid;
  } else if (pid == 0)
  {
    close (master);
    setsid ();
    dup2 (slave, STDIN_FILENO);
    dup2 (slave, STDOUT_FILENO);
    dup2 (slave, STDERR_FILENO);

    close (slave);
    return 0;
  }
  ioctl (slave, TIOCSCTTY, NULL);
  close (slave);
  *amaster = master;
  return pid;
}

static void vt_run_command (VT *vt, const char *command, const char *term)
{
  struct winsize ws;
  //signal (SIGCHLD,signal_child);
#if 0
  int was_pidone = (getpid () == 1);
#else
  int was_pidone = 0; // do no special treatment, all child processes belong
                      // to root
#endif
  signal (SIGINT,SIG_DFL);
  ws.ws_row = vt->rows;
  ws.ws_col = vt->cols;
  ws.ws_xpixel = ws.ws_col * vt->cw;
  ws.ws_ypixel = ws.ws_row * vt->ch;
  vt->vtpty.pid = vt_forkpty (&vt->vtpty.pty, NULL, NULL, &ws);
  if (vt->vtpty.pid == 0)
    {
      int i;
      if (was_pidone)
      {
        if (setuid(1000)) fprintf (stderr, "setuid failed\n");
      }
      else
      {
        for (i = 3; i<768; i++) { close (i); } /*hack, trying to close xcb */
      }
      unsetenv ("TERM");
      unsetenv ("COLUMNS");
      unsetenv ("LINES");
      unsetenv ("TERMCAP");
      unsetenv ("COLOR_TERM");
      unsetenv ("COLORTERM");
      unsetenv ("VTE_VERSION");
      unsetenv ("CTX_BACKEND");
      //setenv ("TERM", "ansi", 1);
      //setenv ("TERM", "vt102", 1);
      //setenv ("TERM", "vt100", 1);
      // setenv ("TERM", term?term:"xterm", 1);
      setenv ("TERM", term?term:"xterm-256color", 1);
      setenv ("COLORTERM", "truecolor", 1);
      //setenv ("CTX_VERSION", "0", 1);
      setenv ("CTX_BACKEND", "ctx", 1); // speeds up launching of clients

      {
        char *cargv[32];
        int   cargc;
        char *rest, *copy;
        copy = calloc (strlen (command)+2, 1);
        strcpy (copy, command);
        rest = copy;
        cargc = 0;
        while (rest && cargc < 30 && rest[0] != ';')
        {
          cargv[cargc++] = rest;
          rest = string_chop_head (rest);
        }
        cargv[cargc] = NULL;
        execvp (cargv[0], cargv);
      }
      exit (0);
    }
  else if (vt->vtpty.pid < 0)
    {
      VT_error ("forkpty failed (%s)", command);
      return;
    }
  fcntl(vt->vtpty.pty, F_SETFL, O_NONBLOCK|O_NOCTTY);
  _ctx_add_listen_fd (vt->vtpty.pty);
}

void vt_destroy (VT *vt)
{
  while (vt->lines)
    {
      vt_line_free (vt->lines->data, 1);
      ctx_list_remove (&vt->lines, vt->lines->data);
      vt->line_count--;
    }
  while (vt->scrollback)
    {
      vt_line_free (vt->scrollback->data, 1);
      ctx_list_remove (&vt->scrollback, vt->scrollback->data);
    }
  if (vt->ctxp)
    ctx_parser_free (vt->ctxp);
  //if (vt->ctx)
  //  { ctx_free (vt->ctx); }
  free (vt->argument_buf);
  ctx_list_remove (&vts, vt);
  kill (vt->vtpty.pid, 9);
  _ctx_remove_listen_fd (vt->vtpty.pty);
  close (vt->vtpty.pty);
#if 1
  if (vt->title)
    free (vt->title);
#endif
  free (vt);
}

int vt_get_line_count (VT *vt)
{
  return vt->line_count;
}

const char *vt_get_line (VT *vt, int no)
{
  if (no >= vt->rows)
  {
    CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows);
    if (!l)
      { 
         return "";
      }
    CtxString *str = l->data;
    return str->str;
  }
  else
  {
    CtxList *l = ctx_list_nth (vt->lines, no);
    if (!l)
      { 
         return "-";
      }
    CtxString *str = l->data;
    return str->str;
  }
}

int vt_line_is_continuation (VT *vt, int no)
{
  if (no >= vt->rows)
  {
    CtxList *l = ctx_list_nth (vt->scrollback, no - vt->rows);
    if (!l)
      { 
         return 1;
      }
    VtLine *line = l->data;
    return line->wrapped;
  }
  else
  {
    CtxList *l = ctx_list_nth (vt->lines, no);
    if (!l)
      { 
         return 1;
      }
    VtLine *line = l->data;
    return line->wrapped;
  }
}

int vt_get_cols (VT *vt)
{
  return vt->cols;
}

int vt_get_rows (VT *vt)
{
  return vt->rows;
}

int vt_get_cursor_x (VT *vt)
{
  return vt->cursor_x;
}

int vt_get_cursor_y (VT *vt)
{
  return vt->cursor_y;
}

static void draw_braille_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v)
{
  ctx_rectangle (ctx, 0.167 * cw + x + u * cw * 0.5,
                 y - ch + 0.080 * ch + v * ch * 0.25,
                 0.33 *cw, 0.33 * cw);
}

static void draw_sextant_bit (Ctx *ctx, float x, float y, float cw, float ch, int u, int v)
{
  ctx_rectangle (ctx,  x + u * cw * 0.5,
                       y - ch + v * ch * 0.3333,
                       0.5 *cw, 0.34 * ch);
}

int vt_special_glyph (Ctx *ctx, VT *vt, float x, float y, int cw, int ch, int unichar)
{
  switch (unichar)
    {
      case 0x2594: // UPPER_ONE_EIGHT_BLOCK
        ctx_begin_path (ctx);
        {
          float factor = 1.0f/8.0f;
          ctx_rectangle (ctx, x, y - ch, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2581: // LOWER_ONE_EIGHT_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 1.0f/8.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2582: // LOWER_ONE_QUARTER_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 1.0f/4.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2583: // LOWER_THREE_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 3.0f/8.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2585: // LOWER_FIVE_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 5.0f/8.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2586: // LOWER_THREE_QUARTERS_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 3.0f/4.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2587: // LOWER_SEVEN_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        {
          float factor = 7.0f/8.0f;
          ctx_rectangle (ctx, x, y - ch * factor, cw, ch * factor);
          ctx_fill (ctx);
        }
        return 0;
      case 0x2589: // LEFT_SEVEN_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw*7/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258A: // LEFT_THREE_QUARTERS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw*3/4, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258B: // LEFT_FIVE_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw*5/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258D: // LEFT_THREE_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw*3/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258E: // LEFT_ONE_QUARTER_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw/4, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258F: // LEFT_ONE_EIGHT_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x258C: // HALF_LEFT_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw/2, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2590: // HALF_RIGHT_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch);
        ctx_fill (ctx);
        return 0;
      case 0x1fb8f: // VT_RIGHT_SEVEN_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw*1/8, y - ch, cw*7/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x1fb8d: // VT_RIGHT_FIVE_EIGHTS_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw*3/8, y - ch, cw*5/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x1fb8b: // VT_RIGHT_ONE_QUARTER_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw*3/4, y - ch, cw/4, ch);
        ctx_fill (ctx);
        return 0;
      case 0x1fb8e: // VT_RIGHT_THREE_QUARTER_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw*1/4, y - ch, cw*3/4, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2595: // VT_RIGHT_ONE_EIGHT_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw*7/8, y - ch, cw/8, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2580: // HALF_UP_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x2584: // _HALF_DOWN_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch/2, cw, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x2596: // _QUADRANT LOWER LEFT
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch/2, cw/2, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x2597: // _QUADRANT LOWER RIGHT
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x+cw/2, y - ch/2, cw/2, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x2598: // _QUADRANT UPPER LEFT
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw/2, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x259D: // _QUADRANT UPPER RIGHT
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2, y - ch, cw/2, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x2599: // _QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597);
        return 0;
      case 0x259A: // _QUADRANT UPPER LEFT AND LOWER RIGHT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597);
        return 0;
      case 0x259B: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596);
        return 0;
      case 0x259C: // _QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2598);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597);
        return 0;
      case 0x259E: // _QUADRANT UPPER RIGHT AND LOWER LEFT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596);
        return 0;
      case 0x259F: // _QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x259D);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2596);
        vt_special_glyph (ctx, vt, x, y, cw, ch, 0x2597);
        return 0;
      case 0x2588: // FULL_BLOCK:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2591: // LIGHT_SHADE:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw, ch);
        ctx_save (ctx);
        ctx_global_alpha (ctx, 0.25);
        ctx_fill (ctx);
        ctx_restore (ctx);
        return 0;
      case 0x2592: // MEDIUM_SHADE:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw, ch);
        ctx_save (ctx);
        ctx_global_alpha (ctx, 0.5);
        ctx_fill (ctx);
        ctx_restore (ctx);
        return 0;
      case 0x2593: // DARK SHADE:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch, cw, ch);
        ctx_save (ctx);
        ctx_global_alpha (ctx, 0.75);
        ctx_fill (ctx);
        ctx_restore (ctx);
        return 0;
      case 0x23BA: //HORIZONTAL_SCANLINE-1
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x,      y - ch + ch*0.1 - ch * 0.1,
                       cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x23BB: //HORIZONTAL_SCANLINE-3
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x,      y - ch + ch*0.3 - ch * 0.075,
                       cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x23BC: //HORIZONTAL_SCANLINE-7
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x,      y - ch + ch*0.7 - ch * 0.025,
                       cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x23BD: //HORIZONTAL_SCANLINE-9
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x,      y - ch + ch*0.9 + ch * 0.0,
                       cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2500: //VT_BOX_DRAWINGS_LIGHT_HORIZONTAL // and scanline 5
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2212: // minus -sign
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw * 0.1, y - ch/2 - ch * 0.1 / 2, cw * 0.8, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2502: // VT_BOX_DRAWINGS_LIGHT_VERTICAL:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch + 1);
        ctx_fill (ctx);
        return 0;
      case 0x250c: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_RIGHT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1, ch*0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2510: //VT_BOX_DRAWINGS_LIGHT_DOWN_AND_LEFT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, ch * 0.1, ch/2 + ch*0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x, y - ch/2 - ch*0.1/2, cw/2+ ch * 0.1/2, ch*0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2514: //VT_BOX_DRAWINGS_LIGHT_UP_AND_RIGHT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ch*0.1/2);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2 - ch*0.1/2, cw/2 + ch * 0.1, ch*0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2518: //VT_BOX_DRAWINGS_LIGHT_UP_AND_LEFT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch/2+ ch*0.1/2);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1);
        ctx_fill (ctx);
        return 0;
      case 0x251C: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_RIGHT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, cw/2+ch * 0.1, ch*0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2524: //VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_LEFT:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x, y - ch/2-ch*0.1/2, cw/2+ch * 0.1/2, ch*0.1);
        ctx_fill (ctx);
        return 0;
      case 0x252C: // VT_BOX_DRAWINGS_LIGHT_DOWN_AND_HORIZONTAL:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch/2-ch*0.1/2, ch * 0.1, ch/2+ch*0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1);
        ctx_fill (ctx);
        return 0;
      case 0x2534: // VT_BOX_DRAWINGS_LIGHT_UP_AND_HORIZONTAL:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch /2+ch*0.1/2);
        ctx_fill (ctx);
        return 0;
      case 0x253C: // VT_BOX_DRAWINGS_LIGHT_VERTICAL_AND_HORIZONTAL:
        ctx_begin_path (ctx);
        ctx_rectangle (ctx, x, y - ch/2 - ch * 0.1 / 2, cw, ch * 0.1);
        ctx_fill (ctx);
        ctx_rectangle (ctx, x + cw/2 - ch * 0.1 / 2, y - ch, ch * 0.1, ch);
        ctx_fill (ctx);
        return 0;
      case 0xe0a0: // PowerLine branch
        ctx_save (ctx);
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x+cw/2, y - 0.15 * ch);
        ctx_rel_line_to (ctx, -cw/3, -ch * 0.7);
        ctx_rel_line_to (ctx, cw/2, 0);
        ctx_rel_line_to (ctx, -cw/3, ch * 0.7);
        ctx_line_width (ctx, cw * 0.25);
        ctx_stroke (ctx);
        ctx_restore (ctx);
        break;
      // case 0xe0a1: // PowerLine LN
      // case 0xe0a2: // PowerLine Lock
      case 0xe0b0: // PowerLine left solid
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, 0, -ch);
        ctx_rel_line_to (ctx, cw, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0xe0b1: // PowerLine left line
        ctx_save (ctx);
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y - ch * 0.1);
        ctx_rel_line_to (ctx, cw * 0.9, -ch/2 * 0.8);
        ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8);
        ctx_line_width (ctx, cw * 0.2);
        ctx_stroke (ctx);
        ctx_restore (ctx);
        return 0;
      case 0xe0b2: // PowerLine Right solid
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_move_to (ctx, cw, 0);
        ctx_rel_line_to (ctx, -cw, -ch/2);
        ctx_rel_line_to (ctx, cw, -ch/2);
        ctx_fill (ctx);
        return 0;
      case 0xe0b3: // PowerLine right line
        ctx_save (ctx);
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y - ch * 0.1);
        ctx_rel_move_to (ctx, cw, 0);
        ctx_rel_line_to (ctx, -cw * 0.9, -ch/2 * 0.8);
        ctx_rel_line_to (ctx,  cw * 0.9, ch/2 * 0.8);
        ctx_line_width (ctx, cw * 0.2);
        ctx_stroke (ctx);
        ctx_restore (ctx);
        return 0;
        /*
      case 0x1fb70: // left triangular one quarter block
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, 0, -ch);
        ctx_rel_line_to (ctx, cw/2, -ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x1fb72: // right triangular one quarter block
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_move_to (ctx, cw/2, -ch/2);
        ctx_rel_line_to (ctx, cw/2, -ch/2);
        ctx_rel_line_to (ctx, 0, ch);
        ctx_fill (ctx);
        return 0;
      case 0x1fb73: // lower triangular one quarter block
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, cw/2, -ch/2);
        ctx_rel_line_to (ctx, cw/2, ch/2);
        ctx_fill (ctx);
        return 0;
      case 0x1fb71: // upper triangular one quarter block
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_move_to (ctx, cw/2, -ch/2);
        ctx_rel_line_to (ctx, -cw/2, -ch/2);
        ctx_rel_line_to (ctx, cw, 0);
        ctx_fill (ctx);
        return 0;
        */
      case 0x25E2: // VT_BLACK_LOWER_RIGHT_TRIANGLE:
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, cw, -ch);
        ctx_rel_line_to (ctx, 0, ch);
        ctx_fill (ctx);
        return 0;
      case 0x25E3: //  VT_BLACK_LOWER_LEFT_TRIANGLE:
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, 0, -ch);
        ctx_rel_line_to (ctx, cw, ch);
        ctx_fill (ctx);
        return 0;
      case 0x25E4: // tri
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y);
        ctx_rel_line_to (ctx, 0, -ch);
        ctx_rel_line_to (ctx, cw, 0);
        ctx_fill (ctx);
        return 0;
      case 0x25E5: // tri
        ctx_begin_path (ctx);
        ctx_move_to (ctx, x, y - ch);
        ctx_rel_line_to (ctx, cw, 0);
        ctx_rel_line_to (ctx, 0, ch);
        ctx_fill (ctx);
        return 0;
      case 0x2800:
      case 0x2801:
      case 0x2802:
      case 0x2803:
      case 0x2804:
      case 0x2805:
      case 0x2806:
      case 0x2807:
      case 0x2808:
      case 0x2809:
      case 0x280A:
      case 0x280B:
      case 0x280C:
      case 0x280D:
      case 0x280E:
      case 0x280F:
      case 0x2810:
      case 0x2811:
      case 0x2812:
      case 0x2813:
      case 0x2814:
      case 0x2815:
      case 0x2816:
      case 0x2817:
      case 0x2818:
      case 0x2819:
      case 0x281A:
      case 0x281B:
      case 0x281C:
      case 0x281D:
      case 0x281E:
      case 0x281F:
      case 0x2820:
      case 0x2821:
      case 0x2822:
      case 0x2823:
      case 0x2824:
      case 0x2825:
      case 0x2826:
      case 0x2827:
      case 0x2828:
      case 0x2829:
      case 0x282A:
      case 0x282B:
      case 0x282C:
      case 0x282D:
      case 0x282E:
      case 0x282F:
      case 0x2830:
      case 0x2831:
      case 0x2832:
      case 0x2833:
      case 0x2834:
      case 0x2835:
      case 0x2836:
      case 0x2837:
      case 0x2838:
      case 0x2839:
      case 0x283A:
      case 0x283B:
      case 0x283C:
      case 0x283D:
      case 0x283E:
      case 0x283F:
        ctx_begin_path (ctx);
        {
          int bit_pattern = unichar - 0x2800;
          int bit = 0;
          int u = 0;
          int v = 0;
          for (bit = 0; bit < 6; bit++)
            {
              if (bit_pattern & (1<<bit) )
                {
                  draw_braille_bit (ctx, x, y, cw, ch, u, v);
                }
              v++;
              if (v > 2)
                {
                  v = 0;
                  u++;
                }
            }
        }
        ctx_fill (ctx);
        return 0;
      case 0x2840:
      case 0x2841:
      case 0x2842:
      case 0x2843:
      case 0x2844:
      case 0x2845:
      case 0x2846:
      case 0x2847:
      case 0x2848:
      case 0x2849:
      case 0x284A:
      case 0x284B:
      case 0x284C:
      case 0x284D:
      case 0x284E:
      case 0x284F:
      case 0x2850:
      case 0x2851:
      case 0x2852:
      case 0x2853:
      case 0x2854:
      case 0x2855:
      case 0x2856:
      case 0x2857:
      case 0x2858:
      case 0x2859:
      case 0x285A:
      case 0x285B:
      case 0x285C:
      case 0x285D:
      case 0x285E:
      case 0x285F:
      case 0x2860:
      case 0x2861:
      case 0x2862:
      case 0x2863:
      case 0x2864:
      case 0x2865:
      case 0x2866:
      case 0x2867:
      case 0x2868:
      case 0x2869:
      case 0x286A:
      case 0x286B:
      case 0x286C:
      case 0x286D:
      case 0x286E:
      case 0x286F:
      case 0x2870:
      case 0x2871:
      case 0x2872:
      case 0x2873:
      case 0x2874:
      case 0x2875:
      case 0x2876:
      case 0x2877:
      case 0x2878:
      case 0x2879:
      case 0x287A:
      case 0x287B:
      case 0x287C:
      case 0x287D:
      case 0x287E:
      case 0x287F:
        ctx_begin_path (ctx);
        draw_braille_bit (ctx, x, y, cw, ch, 0, 3);
        {
          int bit_pattern = unichar - 0x2840;
          int bit = 0;
          int u = 0;
          int v = 0;
          for (bit = 0; bit < 6; bit++)
            {
              if (bit_pattern & (1<<bit) )
                {
                  draw_braille_bit (ctx, x, y, cw, ch, u, v);
                }
              v++;
              if (v > 2)
                {
                  v = 0;
                  u++;
                }
            }
        }
        ctx_fill (ctx);
        return 0;
      case 0x2880:
      case 0x2881:
      case 0x2882:
      case 0x2883:
      case 0x2884:
      case 0x2885:
      case 0x2886:
      case 0x2887:
      case 0x2888:
      case 0x2889:
      case 0x288A:
      case 0x288B:
      case 0x288C:
      case 0x288D:
      case 0x288E:
      case 0x288F:
      case 0x2890:
      case 0x2891:
      case 0x2892:
      case 0x2893:
      case 0x2894:
      case 0x2895:
      case 0x2896:
      case 0x2897:
      case 0x2898:
      case 0x2899:
      case 0x289A:
      case 0x289B:
      case 0x289C:
      case 0x289D:
      case 0x289E:
      case 0x289F:
      case 0x28A0:
      case 0x28A1:
      case 0x28A2:
      case 0x28A3:
      case 0x28A4:
      case 0x28A5:
      case 0x28A6:
      case 0x28A7:
      case 0x28A8:
      case 0x28A9:
      case 0x28AA:
      case 0x28AB:
      case 0x28AC:
      case 0x28AD:
      case 0x28AE:
      case 0x28AF:
      case 0x28B0:
      case 0x28B1:
      case 0x28B2:
      case 0x28B3:
      case 0x28B4:
      case 0x28B5:
      case 0x28B6:
      case 0x28B7:
      case 0x28B8:
      case 0x28B9:
      case 0x28BA:
      case 0x28BB:
      case 0x28BC:
      case 0x28BD:
      case 0x28BE:
      case 0x28BF:
        ctx_begin_path (ctx);
        draw_braille_bit (ctx, x, y, cw, ch, 1, 3);
        {
          int bit_pattern = unichar - 0x2880;
          int bit = 0;
          int u = 0;
          int v = 0;
          for (bit = 0; bit < 6; bit++)
            {
              if (bit_pattern & (1<<bit) )
                {
                  draw_braille_bit (ctx, x, y, cw, ch, u, v);
                }
              v++;
              if (v > 2)
                {
                  v = 0;
                  u++;
                }
            }
        }
        ctx_fill (ctx);
        return 0;
      case 0x28C0:
      case 0x28C1:
      case 0x28C2:
      case 0x28C3:
      case 0x28C4:
      case 0x28C5:
      case 0x28C6:
      case 0x28C7:
      case 0x28C8:
      case 0x28C9:
      case 0x28CA:
      case 0x28CB:
      case 0x28CC:
      case 0x28CD:
      case 0x28CE:
      case 0x28CF:
      case 0x28D0:
      case 0x28D1:
      case 0x28D2:
      case 0x28D3:
      case 0x28D4:
      case 0x28D5:
      case 0x28D6:
      case 0x28D7:
      case 0x28D8:
      case 0x28D9:
      case 0x28DA:
      case 0x28DB:
      case 0x28DC:
      case 0x28DD:
      case 0x28DE:
      case 0x28DF:
      case 0x28E0:
      case 0x28E1:
      case 0x28E2:
      case 0x28E3:
      case 0x28E4:
      case 0x28E5:
      case 0x28E6:
      case 0x28E7:
      case 0x28E8:
      case 0x28E9:
      case 0x28EA:
      case 0x28EB:
      case 0x28EC:
      case 0x28ED:
      case 0x28EE:
      case 0x28EF:
      case 0x28F0:
      case 0x28F1:
      case 0x28F2:
      case 0x28F3:
      case 0x28F4:
      case 0x28F5:
      case 0x28F6:
      case 0x28F7:
      case 0x28F8:
      case 0x28F9:
      case 0x28FA:
      case 0x28FB:
      case 0x28FC:
      case 0x28FD:
      case 0x28FE:
      case 0x28FF:
        ctx_begin_path (ctx);
        draw_braille_bit (ctx, x, y, cw, ch, 0, 3);
        draw_braille_bit (ctx, x, y, cw, ch, 1, 3);
        {
          int bit_pattern = unichar - 0x28C0;
          int bit = 0;
          int u = 0;
          int v = 0;
          for (bit = 0; bit < 6; bit++)
            {
              if (bit_pattern & (1<<bit) )
                {
                  draw_braille_bit (ctx, x, y, cw, ch, u, v);
                }
              v++;
              if (v > 2)
                {
                  v = 0;
                  u++;
                }
            }
        }
        ctx_fill (ctx);
        return 0;
      case 0x1fb00:
      case 0x1fb01:
      case 0x1fb02:
      case 0x1fb03:
      case 0x1fb04:
      case 0x1fb05:
      case 0x1fb06:
      case 0x1fb07:
      case 0x1fb08:
      case 0x1fb09:
      case 0x1fb0a:
      case 0x1fb0b:
      case 0x1fb0c:
      case 0x1fb0d:
      case 0x1fb0e:
      case 0x1fb0f:
      case 0x1fb10:
      case 0x1fb11:
      case 0x1fb12:
      case 0x1fb13:
      case 0x1fb14:
      case 0x1fb15:
      case 0x1fb16:
      case 0x1fb17:
      case 0x1fb18:
      case 0x1fb19:
      case 0x1fb1a:
      case 0x1fb1b:
      case 0x1fb1c:
      case 0x1fb1d:
      case 0x1fb1e:
      case 0x1fb1f:
      case 0x1fb20:
      case 0x1fb21:
      case 0x1fb22:
      case 0x1fb23:
      case 0x1fb24:
      case 0x1fb25:
      case 0x1fb26:
      case 0x1fb27:
      case 0x1fb28:
      case 0x1fb29:
      case 0x1fb2a:
      case 0x1fb2b:
      case 0x1fb2c:
      case 0x1fb2d:
      case 0x1fb2e:
      case 0x1fb2f:
      case 0x1fb30:
      case 0x1fb31:
      case 0x1fb32:
      case 0x1fb33:
      case 0x1fb34:
      case 0x1fb35:
      case 0x1fb36:
      case 0x1fb37:
      case 0x1fb38:
      case 0x1fb39:
      case 0x1fb3a:
      case 0x1fb3b:

        {
          ctx_begin_path (ctx);
          uint32_t bitmask = (unichar - 0x1fb00) + 1;
          if (bitmask > 20) bitmask ++;
          if (bitmask > 41) bitmask ++;
          int bit = 0;
          for (int v = 0; v < 3; v ++)
          for (int u = 0; u < 2; u ++, bit ++)
          {
            if (bitmask & (1<<bit))
            {
              draw_sextant_bit (ctx, x, y, cw, ch, u, v);
            }
          }
          ctx_fill (ctx);
          return 0;
        }
        break;
      case 0x1fb3c:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch / 3.0f);
          ctx_rel_line_to (ctx, cw/2, ch/3.0f);
          ctx_fill (ctx);
          return 0;
        }
        break;
      case 0x1fb3d:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0f);
          ctx_rel_line_to (ctx, cw, ch/3.0f);
          ctx_fill (ctx);
          return 0;
        }
        break;
      case 0x1fb3e:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0,   -ch/3.0f * 2);
          ctx_rel_line_to (ctx, cw/2, ch/3.0f * 2);
          ctx_fill (ctx);
          return 0;
        }
        break;
      case 0x1fb3f:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0,  -ch/3.0f * 2);
          ctx_rel_line_to (ctx, cw, ch/3.0f * 2);
          ctx_fill (ctx);
          return 0;
        }
        break;
      case 0x1fb40:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, ch);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb41:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw/2, -ch/3.0);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0.0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb42:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, -ch/3.0);
          ctx_rel_line_to (ctx, 0.0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb43:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0f);
          ctx_rel_line_to (ctx, cw/2, -ch/3.0f*2.0f);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0.0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb44:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, cw, -ch/3.0 * 2);
          ctx_rel_line_to (ctx, 0.0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb45:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw/2, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0.0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb46:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, cw, -ch/3.0);
          ctx_rel_line_to (ctx, 0.0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb47:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, cw/2, -ch/3.0);
          ctx_rel_line_to (ctx, 0.0, ch/3.0);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb48:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw, -ch/3.0);
          ctx_rel_line_to (ctx, 0.0, ch/3.0);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb49:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, cw/2, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0.0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4a:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0.0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4b:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, cw/2, ch/3);
          ctx_rel_line_to (ctx, 0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4c:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, ch/3);
          ctx_rel_line_to (ctx, 0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4d:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, ch/3);
          ctx_rel_line_to (ctx, 0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4e:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, cw/2, ch/3 *  2);
          ctx_rel_line_to (ctx, 0, ch/3.0);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb4f:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, ch/3 *  2);
          ctx_rel_line_to (ctx, 0, ch/3.0);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb50:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, cw/2, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb51:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, ch/3.0);
          ctx_rel_line_to (ctx, 0, ch/3.0);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb52:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb53:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw, -ch/3.0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb54:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb55:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw, -ch/3.0*2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb56:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_rel_line_to (ctx, -cw/2, -ch);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb57:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0, -ch/3);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, -cw/2, ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb58:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0, -ch/3);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw, ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb59:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, -cw/2, ch/3 * 2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5a:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0);
          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw, ch/3 * 2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5b:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, -cw/2, ch);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5c:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3);

          ctx_rel_line_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3);
          ctx_rel_line_to (ctx, -cw, ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5d:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3 * 2);
          ctx_rel_line_to (ctx, -cw/2, ch/3);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5e:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3 * 2);
          ctx_rel_line_to (ctx, -cw, ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb5f:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3);
          ctx_rel_line_to (ctx, -cw/2, ch/3*2);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb60:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3);
          ctx_rel_line_to (ctx, -cw, ch/3*2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb61:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw/2, ch);
          ctx_rel_line_to (ctx, -cw/2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb62:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/2, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0, ch/3);
          ctx_rel_line_to (ctx, -cw/2, -ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb63:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3);
          ctx_rel_line_to (ctx, -cw, -ch/3);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb64:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/2, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0, ch/3*2);
          ctx_rel_line_to (ctx, -cw/2, -ch/3*2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb65:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3*2);
          ctx_rel_line_to (ctx, -cw, -ch/3*2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb66:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/2, -ch);
          ctx_rel_line_to (ctx, cw/2, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, -ch);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb67:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/3.0*2);
          ctx_rel_line_to (ctx, 0, -ch/3);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/3.0*2);
          ctx_rel_line_to (ctx, -cw, -ch/3.0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb68:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb69:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6a:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6b:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6c:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6d:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6e:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw, -ch);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb6f:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb82:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/8 * 2);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_move_to (ctx, 0, ch/8 * 2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb83:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/8 * 3);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_move_to (ctx, 0, ch/8 * 3);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb84:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/8 * 5);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_move_to (ctx, 0, ch/8 * 5);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb85:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/8 * 6);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_move_to (ctx, 0, ch/8 * 6);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb86:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, 0, -ch/8 * 7);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_rel_move_to (ctx, 0, ch/8 * 7);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb87:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/8*6, 0);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_move_to (ctx, cw/8*2, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_move_to (ctx, -cw/8*2, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb88:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/8*5, 0);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_move_to (ctx, cw/8*3, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_move_to (ctx, -cw/8*3, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb89:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/8*3, 0);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_move_to (ctx, cw/8*5, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_move_to (ctx, -cw/8*5, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb8a:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_move_to (ctx, cw/8*2, 0);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_move_to (ctx, cw/8*6, 0);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_move_to (ctx, -cw/8*6, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb97:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch/4);
          ctx_rel_move_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/4);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_close_path (ctx);
          ctx_move_to (ctx, 0, -ch/2);
          ctx_rel_line_to (ctx, 0, -ch/4);
          ctx_rel_move_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, 0, ch/4);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb9a:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_rel_move_to (ctx, cw, 0);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, -cw, 0);
          ctx_fill (ctx);
          return 0;
        }
      case 0x1fb9b:
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x, y);
          ctx_rel_line_to (ctx, 0, -ch);
          ctx_rel_line_to (ctx, cw/2, ch/2);
          ctx_rel_line_to (ctx, cw/2, -ch/2);
          ctx_rel_line_to (ctx, 0, ch);
          ctx_rel_line_to (ctx, -cw/2, -ch/2);
          ctx_rel_line_to (ctx, -cw/2, ch/2);
          ctx_fill (ctx);
          return 0;
        }

    }
  return -1;
}

void vt_ctx_glyph (Ctx *ctx, VT *vt, float x, float y, int unichar, int bold, float scale_x, float scale_y, float offset_y)
{
  int did_save = 0;
  if (unichar <= ' ')
    return;
  scale_x *= vt->scale_x;
  scale_y *= vt->scale_y;

  if (!ctx_renderer_is_term (ctx))
  {
    // TODO : use our own special glyphs when glyphs are not passed through
    if (!vt_special_glyph (ctx, vt, x, y + offset_y * vt->ch, vt->cw * scale_x, vt->ch * scale_y, unichar) )
      return;
  }

  if (scale_x != 1.0 || scale_y != 1.0)
    {
      if (!did_save)
      {
        ctx_save (ctx);
        did_save = 1;
      }

      ctx_translate (ctx, x, y);
      ctx_scale (ctx, scale_x, scale_y);
      ctx_translate (ctx, -x, -y);
    }
  if (offset_y != 0.0f)
  {
    if (!did_save)
    {
      ctx_save (ctx);
      did_save = 1;
    }
    ctx_translate (ctx, 0, vt->font_size * offset_y);
  }
  y -= vt->font_size * 0.22;
  if (bold
      && !ctx_renderer_is_term (ctx)
     )
    {
      ctx_move_to (ctx, x - vt->font_size/30.0, y);
      //ctx_line_width (ctx, vt->font_size/30.0);
      ctx_glyph (ctx, unichar, 0);
    }
  ctx_move_to (ctx, x, y);
  ctx_glyph (ctx, unichar, 0);
  if (did_save)
    ctx_restore (ctx);
}

//static uint8_t palette[256][3];

/* optimized for ANSI ART - and avoidance of human metamers
 * among color deficient vision - by distributing and pertubating
 * until all 64 combinations - sans self application, have
 * likely to be discernable by humans.
 */


void vt_ctx_get_color (VT *vt, int no, int intensity, uint8_t *rgba)
{
  uint8_t r = 0, g = 0, b = 0;
  if (no < 16 && no >= 0)
    {
      switch (intensity)
        {
          case 0:
            no = 0;
            break;
          case 1:
            // 15 becomes 7
            if (no == 15) { no = 8; }
            else if (no > 8) { no -= 8; }
            break;
          case 2:
            /* give the normal color special treatment, and in really normal
             * cirumstances it is the dim variant of foreground that is used
             */
            if (no == 15) { no = 7; }
            break;
          case 3:
          case 4:
            if (no < 8)
              { no += 8; }
            break;
          default:
            break;
        }
      r = palettes[vt->palette_no][no][0];
      g = palettes[vt->palette_no][no][1];
      b = palettes[vt->palette_no][no][2];
    }
  else if (no < 16 + 6*6*6)
    {
      no = no-16;
      b = (no % 6) * 255 / 5;
      no /= 6;
      g = (no % 6) * 255 / 5;
      no /= 6;
      r = (no % 6) * 255 / 5;
    }
  else
    {
      int gray = no - (16 + 6*6*6);
      float val = gray * 255 / 24;
      r = g = b = val;
    }
  rgba[0]=r;
  rgba[1]=g;
  rgba[2]=b;
  rgba[3]=255;
}

int vt_keyrepeat (VT *vt)
{
  return vt->keyrepeat;
}

static void vt_flush_bg (VT *vt, Ctx *ctx)
{
  if (vt->bg_active)
  {
    ctx_rgba8 (ctx, vt->bg_rgba[0], vt->bg_rgba[1], vt->bg_rgba[2], vt->bg_rgba[3]);
    ctx_rectangle (ctx, vt->bg_x0, vt->bg_y0, vt->bg_width, vt->bg_height);
    ctx_fill (ctx);
    vt->bg_active = 0;
  }
}

static void vt_draw_bg (VT *vt, Ctx *ctx,
                        float x0, float y0,
                        float width, float height,
                        uint8_t *rgba)
{
   int same_color = !memcmp(rgba, vt->bg_rgba, 4);
   if (vt->bg_active && !same_color)
   {
     vt_flush_bg (vt, ctx);
   }

   if (vt->bg_active && same_color)
   {
     vt->bg_width += width;
   }
   else
   {
     memcpy (vt->bg_rgba, rgba, 4);
     vt->bg_active = 1;
     vt->bg_x0 = x0;
     vt->bg_y0 = y0;
     vt->bg_width = width;
     vt->bg_height = height;
   }
}

float vt_draw_cell (VT      *vt, Ctx *ctx,
                    int      row, int col, // pass 0 to force draw - like
                    float    x0, float y0, // for scrollback visible
                    uint64_t style,
                    uint32_t unichar,
                    int      dw, int dh,
                    int      in_smooth_scroll,
                    int      in_select,
                    int      is_fg)
// dw is 0 or 1
// dh is 0 1 or -1  1 is upper -1 is lower
{
  int on_white = vt->reverse_video;
  int color = 0;
  int bold = (style & STYLE_BOLD) != 0;
  int dim = (style & STYLE_DIM) != 0;
  int is_hidden = (style & STYLE_HIDDEN) != 0;
  int proportional = (style & STYLE_PROPORTIONAL) != 0;
  int fg_set = (style & STYLE_FG_COLOR_SET) != 0;
  int bg_intensity = 0;
  int fg_intensity = 2;
  int reverse = ( (style & STYLE_REVERSE) != 0) ^ in_select;
  int blink = ( (style & STYLE_BLINK) != 0);
  int blink_fast = ( (style & STYLE_BLINK_FAST) != 0);
  int cw = vt->cw;
  int ch = vt->ch;
  if (proportional)
    {
      if (vt->font_is_mono)
        {
          ctx_font (ctx, "regular");
          vt->font_is_mono = 0;
        }
      cw = ctx_glyph_width (ctx, unichar);
    }
  else
    {
      if (vt->font_is_mono == 0)
        {
          ctx_font (ctx, "mono");
          vt->font_is_mono = 1;
          if (col > 1)
            {
              int x = x0;
              int new_cw = cw - ( (x % cw) );
              if (new_cw < cw*3/2)
                { new_cw += cw; }
              cw = new_cw;
            }
        }
    }
  float scale_x  = 1.0f;
  float scale_y  = 1.0f;
  float offset_y = 0.0f;
  if (dw)
    {
      scale_x = 2.0f;
    }
  if (dh)
    {
      scale_y = 2.0f;
    }
  if (dh == 1)
    {
      offset_y = 0.5f;
    }
  else if (dh == -1)
    {
      offset_y =  0.0f;
    }
  if (in_smooth_scroll)
    {
      offset_y -= vt->scroll_offset / (dh?2:1);
    }
  cw *= scale_x;
  if (blink_fast)
    {
      if ( (vt->blink_state % 2) == 0)
        { blink = 1; }
      else
        { blink = 0; }
    }
  else if (blink)
    {
      if ( (vt->blink_state % 10) < 5)
        { blink = 1; }
      else
        { blink = 0; }
    }
  /*
     from the vt100 technical-manual:

     "Reverse characters [..] normally have dim backgrounds with
     black characters so that large white spaces have the same impact
     on the viewer's eye as the smaller brighter white areas of
     normal characters. Bold and reverse asserted together give a
     background of normal intensity. Blink applied to nonreverse
     characters causes them to alternate between their usual
     intensity and the next lower intensity. (Normal characters vary
     between normal and dim intensity. Bold characters vary between
     bright and normal intensity.) Blink applied to a reverse
     character causes that character to alternate between normal and
     reverse video representations of that character."

     This is in contrast with how the truth table appears to be
     meant used, since it uses a reverse computed as the xor of
     the global screen reverse and the reverse attribute of the
     cell.

     To fulfil the more asthethic resulting from implementing the
     text, and would be useful to show how the on_bright background
     mode of the vt100 actually displays the vttest.

     */
  if (on_white)
    {
          if (bold)
            {
              bg_intensity =           2;
              fg_intensity = blink?1:  0;
            }
          else if (dim)
            {
              bg_intensity =           2;
              fg_intensity = blink?3:  1;
            }
          else
            {
              bg_intensity =           2;
              fg_intensity = blink?1:  0;
            }
          if (fg_set)
            {
              fg_intensity = blink?2:3;
            }
    }
  else /* bright on dark */
    {
          if (bold)
            {
              bg_intensity =           0;
              fg_intensity = blink?2:  3;
            }
          else if (dim)
            {
              bg_intensity =           0;
              fg_intensity = blink?0:  1;
            }
          else
            {
              bg_intensity =           0;
              fg_intensity = blink?1:  2;
            }
    }
  uint8_t bg_rgb[4]= {0,0,0,255};
  uint8_t fg_rgb[4]= {255,255,255,255};
  {
      //ctx_begin_path (ctx);
      if (style &  STYLE_BG24_COLOR_SET)
        {
          uint64_t temp = style >> 40;
          bg_rgb[0] = temp & 0xff;
          temp >>= 8;
          bg_rgb[1] = temp & 0xff;
          temp >>= 8;
          bg_rgb[2] = temp & 0xff;
#if 0
          if (dh)
          {
             bg_rgb[0] = 
             bg_rgb[1] =
             bg_rgb[2] = 30;
          }
#endif
        }
      else
        {
          if (style & STYLE_BG_COLOR_SET)
            {
              color = (style >> 40) & 255;
              bg_intensity = -1;
              vt_ctx_get_color (vt, color, bg_intensity, bg_rgb);
            }
          else
            {
              switch (bg_intensity)
                {
                  case 0:
                    for (int i = 0; i <3 ; i++)
                      { bg_rgb[i] = vt->bg_color[i]; }
                    break;
                  case 1:
                    for (int i = 0; i <3 ; i++)
                      { bg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; }
                    break;
                  case 2:
                    for (int i = 0; i <3 ; i++)
                      { bg_rgb[i] = vt->bg_color[i] * 0.05 + vt->fg_color[i] * 0.95; }
                    break;
                  case 3:
                    for (int i = 0; i <3 ; i++)
                      { bg_rgb[i] = vt->fg_color[i]; }
                    break;
                }
            }
        }
  }
  if (style & STYLE_FG24_COLOR_SET)
    {
      uint64_t temp = style >> 16;
      fg_rgb[0] = temp & 0xff;
      temp >>= 8;
      fg_rgb[1] = temp & 0xff;
      temp >>= 8;
      fg_rgb[2] = temp & 0xff;
    }
  else
    {
      if ( (style & STYLE_FG_COLOR_SET) == 0)
        {
          switch (fg_intensity)
            {
              case 0:
                for (int i = 0; i <3 ; i++)
                  { fg_rgb[i] = vt->bg_color[i] * 0.7 + vt->fg_color[i] * 0.3; }
                break;
              case 1:
                for (int i = 0; i <3 ; i++)
                  { fg_rgb[i] = vt->bg_color[i] * 0.5 + vt->fg_color[i] * 0.5; }
                break;
              case 2:
                for (int i = 0; i <3 ; i++)
                  { fg_rgb[i] = vt->bg_color[i] * 0.20 + vt->fg_color[i] * 0.80; }
                break;
              case 3:
                for (int i = 0; i <3 ; i++)
                  { fg_rgb[i] = vt->fg_color[i]; }
            }
        }
      else
        {
          color = (style >> 16) & 255;
          vt_ctx_get_color (vt, color, fg_intensity, fg_rgb);
        }
  }

  if (reverse)
  {
    for (int c = 0; c < 3; c ++)
    {
      int t = bg_rgb[c];
      bg_rgb[c] = fg_rgb[c];
      fg_rgb[c] = t;
    }
  }

  if (is_fg ||
      ((!on_white) && bg_rgb[0]==0 && bg_rgb[1]==0 && bg_rgb[2]==0) ||
      ((on_white) && bg_rgb[0]==255 && bg_rgb[1]==255 && bg_rgb[2]==255))
          /* these comparisons are not entirely correct, when on dark background we assume black to
           * be default and non-set, even when theme might differ
           */
  {
    /* skipping draw of background */
  }
  else
  {
    if (dh)
    {
      vt_draw_bg (vt, ctx, ctx_floorf(x0),
         ctx_floorf(y0 - ch - ch * (vt->scroll_offset)), cw, ch, bg_rgb);
    }
    else
    {
      vt_draw_bg (vt, ctx, x0, y0 - ch + ch * offset_y, cw, ch, bg_rgb);
    }
  }

  if (!is_fg)
    return cw;

  int italic        = (style & STYLE_ITALIC) != 0;
  int strikethrough = (style & STYLE_STRIKETHROUGH) != 0;
  int overline      = (style & STYLE_OVERLINE) != 0;
  int underline     = (style & STYLE_UNDERLINE) != 0;
  int underline_var = (style & STYLE_UNDERLINE_VAR) != 0;
  if (dh == 1)
    {
      underline = underline_var = 0;
    }
  int double_underline = 0;
  int curved_underline = 0;
  if (underline_var)
    {
      if (underline)
        {
          double_underline = 1;
        }
      else
        {
          curved_underline = 1;
        }
    }

  int has_underline = (underline || double_underline || curved_underline);

  if (unichar == ' ' && !has_underline)
    is_hidden = 1;

  if (!is_hidden)
    {

      ctx_rgba8 (ctx, fg_rgb[0], fg_rgb[1], fg_rgb[2], 255);


      if (italic)
        {
          ctx_save (ctx);
          ctx_translate (ctx, (x0 + cw/3), (y0 + vt->ch/2) );
          ctx_scale (ctx, 0.9, 0.9);
          ctx_rotate (ctx, 0.15);
          ctx_translate (ctx, - (x0 + cw/3), - (y0 + vt->ch/2) );
        }
      vt_ctx_glyph (ctx, vt, x0, y0, unichar, bold, scale_x, scale_y, offset_y);
      if (italic)
        {
          ctx_restore (ctx);
        }
      if (curved_underline)
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05);
          ctx_rel_line_to (ctx, (cw+2) /3, vt->ch * 0.1);
          ctx_rel_line_to (ctx, (cw+2) /3, -vt->ch * 0.05);
          //ctx_rel_line_to (ctx, cw, 0);
          ctx_line_width (ctx, vt->font_size * (style &  STYLE_BOLD?0.050:0.04) );
          ctx_stroke (ctx);
        }
      else if (double_underline)
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.130 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.030 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_line_width (ctx, vt->font_size * (style &  STYLE_BOLD?0.050:0.04) );
          ctx_stroke (ctx);
        }
      else if (underline)
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.07 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_line_width (ctx, vt->font_size * (style &  STYLE_BOLD?0.075:0.05) );
          ctx_stroke (ctx);
        }
      if (overline)
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.94 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_line_width (ctx, vt->font_size * (style &  STYLE_BOLD?0.075:0.05) );
          ctx_stroke (ctx);
        }
      if (strikethrough)
        {
          ctx_begin_path (ctx);
          ctx_move_to (ctx, x0, y0 - vt->font_size * 0.43 - vt->ch * vt->scroll_offset);
          ctx_rel_line_to (ctx, cw, 0);
          ctx_line_width (ctx, vt->font_size * (style &  STYLE_BOLD?0.075:0.05) );
          ctx_stroke (ctx);
        }
    }
  return cw;
}

int vt_has_blink (VT *vt)
{
  if (!vt) return 0;
  return (vt->in_smooth_scroll ?  10 : 0);
  //return vt->has_blink + (vt->in_smooth_scroll ?  10 : 0);
}

//extern int enable_terminal_menu;
//

//void ctx_set_popup (Ctx *ctx, void (*popup)(Ctx *ctx, void *data), void *popup_data);

static char *primary = NULL;
static void scrollbar_drag (CtxEvent *event, void *data, void *data2);
static int scrollbar_down = 0;

void vt_mouse_event (CtxEvent *event, void *data, void *data2)
{
  VT   *vt = data;
  CtxClient *client = vt_get_client (vt);
  float  x = event->x;
  float  y = event->y;
  int device_no = event->device_no;
  char buf[128]="";
  if ((!vt->in_alt_screen) &&
      (event->x > vt->width - vt->cw * 1.5 || scrollbar_down) &&
      (event->type == CTX_DRAG_MOTION ||
      event->type == CTX_DRAG_PRESS ||
      event->type == CTX_DRAG_RELEASE))
    return scrollbar_drag (event, data, data2);
  switch (event->type)
  {
    case CTX_MOTION:
    case CTX_DRAG_MOTION:
      //if (event->device_no==1)
      {
        sprintf (buf, "mouse-motion %.0f %.0f %i", x, y, device_no);
//      ctx_set_dirty (event->ctx, 1);
        ctx_client_lock (client);
        vt_feed_keystring (vt, event, buf);
        ctx_client_unlock (client);
//      vt->rev++;
      }
      break;
    case CTX_DRAG_PRESS:
      if (event->device_no==2)
      {
        if (primary)
        {
          if (vt)
            vt_paste (vt, primary);
        }
      }
      else if (event->device_no==3 && !vt->in_alt_screen)
      {
        vt->popped = 1;
      }
      else
      {
        sprintf (buf, "mouse-press %.0f %.0f %i", x, y, device_no);
        ctx_client_lock (client);
        vt_feed_keystring (vt, event, buf);
        ctx_client_unlock (client);
//      ctx_set_dirty (event->ctx, 1);
//      vt->rev++;
      }
      break;
    case CTX_DRAG_RELEASE:
      if (event->device_no==3 && !vt->in_alt_screen)
      {
        vt->popped = 0;
      }
        ctx_set_dirty (event->ctx, 1);
        sprintf (buf, "mouse-release %.0f %.0f %i", x, y, device_no);
        ctx_client_lock (client);
        vt_feed_keystring (vt, event, buf);
        ctx_client_unlock (client);
      break;
    default:
      // we should not stop propagation
      return;
      break;
  }
  event->stop_propagate = 1;
//vt->rev++;
}
static int scrollbar_focused = 0;
#if 0
static void scrollbar_enter (CtxEvent *event, void *data, void *data2)
{
  VT *vt = data;
  vt->rev++;
  scrollbar_focused = 1;
}

static void scrollbar_leave (CtxEvent *event, void *data, void *data2)
{
  VT *vt = data;
  vt->rev++;
  scrollbar_focused = 0;
}
#endif

static void scrollbar_drag (CtxEvent *event, void *data, void *data2)
{
  VT *vt = data;
  float disp_lines = vt->rows;
  float tot_lines = vt->line_count + vt->scrollback_count;

  vt->scroll = tot_lines - disp_lines - (event->y*1.0/ ctx_client_height (vt->id)) * tot_lines + disp_lines/2;
  if (vt->scroll < 0) { vt->scroll = 0.0; }
  if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; }
  vt->rev++;
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;

  switch (event->type)
  {
    case CTX_DRAG_PRESS:
      scrollbar_down = 1;
      break;
    case CTX_DRAG_RELEASE:
      scrollbar_down = 0;
      break;
    default:
      break;
  }
}

#if 0
static void scroll_handle_drag (CtxEvent *event, void *data, void *data2)
{
  VT *vt = data;
  float tot_lines = vt->line_count + vt->scrollback_count;
  if (event->type == CTX_DRAG_MOTION)
  {
    vt->scroll -= (event->delta_y * tot_lines) / (vt->rows * vt->ch);
  }
  if (vt->scroll < 0) { vt->scroll = 0.0; }
  if (vt->scroll > vt->scrollback_count) { vt->scroll = vt->scrollback_count; }
  vt->rev++;
  event->stop_propagate = 1;
}
#endif

#if 0
static void test_popup (Ctx *ctx, void *data)
{
  VT *vt = data;

  float x = ctx_client_x (vt->id);
  float y = ctx_client_y (vt->id);
  ctx_rectangle (ctx, x, y, 100, 100);
  ctx_rgb (ctx, 1,0,0);
  ctx_fill (ctx);
}
#endif

void itk_style_color (Ctx *ctx, const char *name); // only itk fun used in vt

void vt_use_images (VT *vt, Ctx *ctx)
{
  /*  this is a call intended for minimized/shaded fully obscured
   *  clients to make sure their textures are kept alive
   *  in the server
   */
  //float x0=0;
  float y0=0;
  //vt->has_blink = 0;
  //vt->blink_state++;

  ctx_save (ctx);

  {
    /* draw graphics */
    for (int row = ((vt->scroll!=0.0f)?vt->scroll:0);
         row < (vt->scroll) + vt->rows * 4;
         row ++)
    {
       CtxList *l = ctx_list_nth (vt->lines, row);
       float y = y0 + vt->ch * (vt->rows - row);

       if (row >= vt->rows && !vt->in_alt_screen)
         {
           l = ctx_list_nth (vt->scrollback, row-vt->rows);
         }

       if (l && y <= (vt->rows - vt->scroll) *  vt->ch)
         {
           VtLine *line = l->data;
           if (line->ctx_copy)
             {
               ctx_render_ctx_textures (line->ctx_copy, ctx);
             }
         }
    }
  }
  ctx_restore (ctx);
}


void vt_register_events (VT *vt, Ctx *ctx, double x0, double y0)
{
  ctx_begin_path (ctx);
  ctx_save (ctx);
  ctx_translate (ctx, x0, y0);
  ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch);
  ctx_listen (ctx, CTX_DRAG,   vt_mouse_event, vt, NULL);
  ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL);
  ctx_begin_path (ctx);
  ctx_restore (ctx);
}

void vt_draw (VT *vt, Ctx *ctx, double x0, double y0)
{
  ctx_begin_path (ctx);
  ctx_save (ctx);
  ctx_translate (ctx, x0, y0);
  x0 = 0;
  y0 = 0;
  ctx_font (ctx, "mono");
  vt->font_is_mono = 0;
  ctx_font_size (ctx, vt->font_size * vt->font_to_cell_scale);
  vt->has_blink = 0;
  vt->blink_state++;
#if 0
  int cursor_x_px = 0;
  int cursor_y_px = 0;
  int cursor_w = vt->cw;
  int cursor_h = vt->ch;
  cursor_x_px = x0 + (vt->cursor_x - 1) * vt->cw;
  cursor_y_px = y0 + (vt->cursor_y - 1) * vt->ch;
  cursor_w = vt->cw;
  cursor_h = vt->ch;
#endif
  ctx_save (ctx);
  //if (vt->scroll || full)
    {
      ctx_begin_path (ctx);
#if 1
      ctx_rectangle (ctx, 0, 0, vt->width, //(vt->cols) * vt->cw,
                     (vt->rows) * vt->ch);
      if (vt->reverse_video)
        {
          itk_style_color (ctx, "terminal-bg-reverse");
          ctx_fill  (ctx);
        }
      else
        {
          itk_style_color (ctx, "terminal-bg");
          ctx_fill  (ctx);
        }
#endif
      if (vt->scroll != 0.0f)
        ctx_translate (ctx, 0.0, vt->ch * vt->scroll);
    }
  /* draw terminal lines */
   {
     for (int row = (vt->scroll!=0.0f)?vt->scroll:0; row < (vt->scroll) + vt->rows; row ++)
       {
         CtxList *l = ctx_list_nth (vt->lines, row);
         float y = y0 + vt->ch * (vt->rows - row);
         if (row >= vt->rows)
           {
             l = ctx_list_nth (vt->scrollback, row-vt->rows);
           }
         if (l && y <= (vt->rows - vt->scroll) *  vt->ch)
           {
             VtLine *line = l->data;
             int r = vt->rows - row;
             const char *data = line->string.str;

             vt->bg_active = 0;
             for (int is_fg = 0; is_fg < 2; is_fg++)
             {
               const char *d = data;
               float x = x0;
               uint64_t style = 0;
               uint32_t unichar = 0;
               int in_scrolling_region = vt->in_smooth_scroll &&
                   ((r >= vt->margin_top && r <= vt->margin_bottom) || r <= 0);
               if (is_fg)
                  vt_flush_bg (vt, ctx);
  
               for (int col = 1; col <= vt->cols * 1.33 && x < vt->cols * vt->cw; col++)
                 {
                   int c = col;
                   int real_cw;
                   int in_selected_region = 0;
                   //if (vt->in_alt_screen == 0)
                   {
                   if (r > vt->select_start_row && r < vt->select_end_row)
                     {
                       in_selected_region = 1;
                     }
                   else if (r == vt->select_start_row)
                     {
                       if (col >= vt->select_start_col) { in_selected_region = 1; }
                       if (r == vt->select_end_row)
                         {
                           if (col > vt->select_end_col) { in_selected_region = 0; }
                         }
                     }
                   else if (r == vt->select_end_row)
                     {
                       in_selected_region = 1;
                       if (col > vt->select_end_col) { in_selected_region = 0; }
                     }
                   }
                   if (vt->select_active == 0) in_selected_region = 0;
                   style = vt_line_get_style (line, col-1);
                   unichar = d?ctx_utf8_to_unichar (d) :' ';
  
                   int is_cursor = 0;
                   if (vt->cursor_x == col && vt->cursor_y == vt->rows - row && vt->cursor_visible)
                      is_cursor = 1;
  
                   real_cw=vt_draw_cell (vt, ctx, r, c, x, y, style, unichar,
                                         line->double_width,
                                         line->double_height_top?1:
                                         line->double_height_bottom?-1:0,
                                         in_scrolling_region,
                                         in_selected_region ^ is_cursor, is_fg);
                   if (r == vt->cursor_y && col == vt->cursor_x)
                     {
#if 0
                       cursor_x_px = x;
#endif
                     }
                   x+=real_cw;
                   if (style & STYLE_BLINK ||
                       style & STYLE_BLINK_FAST)
                     {
                       vt->has_blink = 1;
                     }
                   if (d)
                     {
                       d = mrg_utf8_skip (d, 1);
                       if (!*d) { d = NULL; }
                     }
                 }
             }
#if 0
             if (line->wrapped)
             {
               ctx_rectangle (ctx, x0, y, 10, 10);
               ctx_rgb (ctx, 1,0,0);
               ctx_fill (ctx);
             }
#endif
          }
      }
  }

#if 0
  /* draw cursor (done inline with fg/bg reversing, some cursor styles might need
   * additional drawing though
   */
  if (vt->cursor_visible)
    {
    //  ctx_rgba (ctx, 0.9, 0.8, 0.0, 0.5333);
      ctx_rgba (ctx, 1.0,1.0,1.0,1.0);
      ctx_begin_path (ctx);
      ctx_rectangle (ctx,
                     cursor_x_px, cursor_y_px,
                     cursor_w, cursor_h);
      ctx_fill (ctx);
    }
#endif


  {
    /* draw graphics */
     for (int row = ((vt->scroll!=0.0f)?vt->scroll:0); row < (vt->scroll) + vt->rows * 4; row ++)
      {
        CtxList *l = ctx_list_nth (vt->lines, row);
        float y = y0 + vt->ch * (vt->rows - row);

        if (row >= vt->rows && !vt->in_alt_screen)
          {
            l = ctx_list_nth (vt->scrollback, row-vt->rows);
          }

        if (l && y <= (vt->rows - vt->scroll) *  vt->ch)
          {
            VtLine *line = l->data;
            {
            for (int i = 0; i < 4; i++)
              {
                Image *image = line->images[i];
                if (image)
                  {
                    int u = (line->image_col[i]-1) * vt->cw + (line->image_X[i] * vt->cw);
                    int v = y - vt->ch + (line->image_Y[i] * vt->ch);
                //  int rows = (image->height + (vt->ch-1) ) /vt->ch;
                //
                //
                    if (v + image->height +vt->scroll * vt->ch > 0.0 &&
                        image->width && image->height /* some ghost images appear with these */
                        )
                    {
                    ctx_save (ctx);
                    ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols,
                                    vt->ch * vt->rows);
                    ctx_clip (ctx);
                    char texture_n[65]; 

                    sprintf (texture_n, "vtimg%i", image->eid_no);
                    ctx_rectangle (ctx, u, v, image->width, image->height);
                    ctx_translate (ctx, u, v);

                    //replace this texture_n with NULL to
                    // be content addressed - but bit slower
                    ctx_define_texture (ctx, texture_n, image->width,
                                        image->height,
                                        0,
                                        image->kitty_format == 32 ?
                                                 CTX_FORMAT_RGBA8 :
                                                 CTX_FORMAT_RGB8,
                                        image->data, texture_n);
                    ctx_fill (ctx);

                    ctx_restore (ctx);
                    }
                  }
              }

            if (line->ctx_copy)
              {
                //fprintf (stderr, " [%i]\n", _ctx_frame (ctx));
                //ctx_render_stream (line->ctx_copy, stderr, 1);

                ctx_begin_path (ctx);
                ctx_save (ctx);
                ctx_rectangle (ctx, x0, y0 - vt->scroll * vt->ch, vt->cw * vt->cols,
                                    vt->ch * vt->rows);
                ctx_clip (ctx);
                ctx_translate (ctx, 0.0, y - vt->ch);

                //(vt->rows-row-1) * (vt->ch) );
                //float factor = vt->cols * vt->cw / 1000.0;
                //ctx_scale (ctx, factor, factor);
                //
                ctx_render_ctx (line->ctx_copy, ctx);
                ctx_restore (ctx);
              }
            }
          }
    //  y -= vt->ch;
      }
  }


  for (int i = 0; i < 4; i++)
    {
      if (vt->leds[i])
        {
          ctx_rgba (ctx, .5,1,.5,0.8);
          ctx_rectangle (ctx, vt->cw * i + vt->cw * 0.25, vt->ch * 0.25, vt->cw/2, vt->ch/2);
          ctx_fill (ctx);
        }
    }
  ctx_restore (ctx);
//#define SCROLL_SPEED 0.25;
#define SCROLL_SPEED 0.001;
  if (vt->in_smooth_scroll)
   {
     if (vt->in_smooth_scroll<0)
       {
         vt->scroll_offset += SCROLL_SPEED;
         if (vt->scroll_offset >= 0.0)
           {
             vt->scroll_offset = 0;
             vt->in_smooth_scroll = 0;
             vt->rev++;
           }
       }
     else
       {
         vt->scroll_offset -= SCROLL_SPEED;
         if (vt->scroll_offset <= 0.0)
           {
             vt->scroll_offset = 0;
             vt->in_smooth_scroll = 0;
             vt->rev++;
           }
       }
   }

    /* scrollbar */
    if (!vt->in_alt_screen)
    {
      float disp_lines = vt->rows;
      float tot_lines = vt->line_count + vt->scrollback_count;
      float offset = (tot_lines - disp_lines - vt->scroll) / tot_lines;
      float win_len = disp_lines / tot_lines;

#if 0
      ctx_rectangle (ctx, (vt->cols *vt->cw), 0, 
                       (vt->width) - (vt->cols * vt->cw),
                       vt->rows *  vt->ch);
      ctx_rgb (ctx,1,0,0);
      ctx_fill (ctx);
#endif

      ctx_rectangle (ctx, (vt->width) - vt->cw * 1.5,
                     0, 1.5 * vt->cw,
                     vt->rows * vt->ch);
      //ctx_listen (ctx, CTX_DRAG,  scrollbar_drag, vt, NULL);
      //ctx_listen (ctx, CTX_ENTER, scrollbar_enter, vt, NULL);
      //ctx_listen (ctx, CTX_LEAVE, scrollbar_leave, vt, NULL);
      if (vt->scroll != 0 || scrollbar_focused)
        ctx_rgba (ctx, 0.5, 0.5, 0.5, .25);
      else
        ctx_rgba (ctx, 0.5, 0.5, 0.5, .10);
      ctx_fill (ctx);
      ctx_round_rectangle (ctx, (vt->width) - vt->cw * 1.5,
                           offset * vt->rows * vt->ch, (1.5-0.2) * vt->cw,
                           win_len * vt->rows * vt->ch,
                           vt->cw * 1.5 /2);
      //ctx_listen (ctx, CTX_DRAG, scroll_handle_drag, vt, NULL);
      if (vt->scroll != 0 || scrollbar_focused)
        ctx_rgba (ctx, 1, 1, 1, .25);
      else
        ctx_rgba (ctx, 1, 1, 1, .10);
      ctx_fill (ctx);
    }

    ctx_rectangle (ctx, 0, 0, vt->cols * vt->cw, vt->rows * vt->ch);
    ctx_listen (ctx, CTX_DRAG,   vt_mouse_event, vt, NULL);
    ctx_listen (ctx, CTX_MOTION, vt_mouse_event, vt, NULL);
    ctx_begin_path (ctx);

    ctx_restore (ctx);

    if (vt->popped)
    {
       //ctx_set_popup (ctx, test_popup, vt);
    }
}


int vt_is_done (VT *vt)
{
  return vt->vtpty.done;
}

int vt_get_result (VT *vt)
{
  /* we could block - at least for a while, here..? */
  return vt->result;
}

void vt_set_scrollback_lines (VT *vt, int scrollback_lines)
{
  vt->scrollback_limit = scrollback_lines;
}

int  vt_get_scrollback_lines (VT *vt)
{
  return vt->scrollback_limit;
}

void vt_set_scroll (VT *vt, int scroll)
{
  if (vt->scroll == scroll)
    return;
  vt->scroll = scroll;
  if (vt->scroll > ctx_list_length (vt->scrollback) )
    { vt->scroll = ctx_list_length (vt->scrollback); }
  if (vt->scroll < 0)
    { vt->scroll = 0; }
}

int vt_get_scroll (VT *vt)
{
  return vt->scroll;
}

char *
vt_get_selection (VT *vt)
{
  CtxString *str = ctx_string_new ("");
  char *ret;
  for (int row = vt->select_start_row; row <= vt->select_end_row; row++)
    {
      const char *line_str = vt_get_line (vt, vt->rows - row);
      int col = 1;
      for (const char *c = line_str; *c; c = mrg_utf8_skip (c, 1), col ++)
        {
          if (row == vt->select_end_row && col > vt->select_end_col)
            { continue; }
          if (row == vt->select_start_row && col < vt->select_start_col)
            { continue; }
          ctx_string_append_utf8char (str, c);
        }
      if (row < vt->select_end_row && !vt_line_is_continuation (vt, vt->rows-row-1))
      {
        _ctx_string_append_byte (str, '\n');
      }
    }
  ret = str->str;
  ctx_string_free (str, 0);
  return ret;
}

int vt_get_local (VT *vt)
{
  return vt->local_editing;
}

void vt_set_local (VT *vt, int local)
{
  vt->local_editing = local;
}

static unsigned long prev_press_time = 0;
static int short_count = 0;


void terminal_set_primary (const char *text)
{
  if (primary) free (primary);
  primary = NULL;
  if (text) primary = strdup (text);
}

void terminal_long_tap (Ctx *ctx, VT *vt);
static int long_tap_cb_id = 0;
static int single_tap (Ctx *ctx, void *data)
{
#if 0 // XXX
  VT *vt = data;
  if (short_count == 0 && !vt->select_active)
    terminal_long_tap (ctx, vt);
#endif
  return 0;
}

void vt_mouse (VT *vt, CtxEvent *event, VtMouseEvent type, int button, int x, int y, int px_x, int px_y)
{
 char buf[64]="";
 int button_state = 0;
 vt->rev++;
 ctx_ticks();
 if ((! (vt->mouse | vt->mouse_all | vt->mouse_drag)) ||
     (event && (event->state & CTX_MODIFIER_STATE_SHIFT)))
   {
     // regular mouse select, this is incomplete
     // fully ignorant of scrollback for now
     //
     if (type == VT_MOUSE_PRESS)
       {
         vt->cursor_down = 1;
         vt->select_begin_col = x;
         vt->select_begin_row = y - (int)vt->scroll;
         vt->select_start_col = x;
         vt->select_start_row = y - (int)vt->scroll;
         vt->select_end_col = x;
         vt->select_end_row = y - (int)vt->scroll;
         vt->select_active = 0;
         if (long_tap_cb_id)
           {
             ctx_remove_idle (vt->root_ctx, long_tap_cb_id);
             long_tap_cb_id = 0;
           }
         
         if ((ctx_ticks () - prev_press_time) < 1000*300 &&
             abs(px_x - vt->select_begin_x) + 
             abs(px_y - vt->select_begin_y) < 8)
         {
           short_count++;
           switch (short_count)
           {
             case 1:
             {
               /* extend selection until space, XXX  should handle utf8 instead of ascii here!  */

               int hit_space = 0;
           
               while (vt->select_start_col > 1 && !hit_space)
               {
                 vt->select_start_col --;
                 char *sel = vt_get_selection (vt);
                 if (sel[0] == ' ' || sel[0] == '\0')
                   hit_space = 1;
                 free (sel);
               }
               if (hit_space)
                 vt->select_start_col++;

               hit_space = 0;
               while ((hit_space == 0) &&
                      (vt->select_end_col < vt->cols))
               {
                 vt->select_end_col ++;
                 char *sel = vt_get_selection (vt);
                 int len = strlen(sel);
                 if (sel[len-1]==' ')
                   hit_space = 1;
                 free (sel);
               }
               if (hit_space)
                 vt->select_end_col--;

               vt->select_active = 1;

               { char *sel = vt_get_selection (vt);
                 if (sel)
                 {
                    terminal_set_primary (sel);
                    free (sel);
                 }
               }
               }
               break;
             case 2:
               vt->select_start_col = 1;
               vt->select_end_col = vt->cols;
               vt->select_active = 1;
               {
                 char *sel = vt_get_selection (vt);
                 if (sel){
                    terminal_set_primary (sel);
                    free (sel);
                 }
               }
               break;
             case 3:
               short_count = 0;
               vt->select_start_col = 
               vt->select_end_col = vt->select_begin_col;
               vt->select_active = 0;
               terminal_set_primary ("");
               break;
           }
         }
         else
         {
           if (vt->root_ctx && short_count == 0)
             long_tap_cb_id = ctx_add_timeout (vt->root_ctx, 1000, single_tap, vt);
           short_count = 0;
           //vt->select_start_col = 
           //vt->select_end_col = vt->select_begin_col;
         }
         vt->select_begin_x = px_x;
         vt->select_begin_y = px_y;
         prev_press_time = ctx_ticks ();
         vt->rev++;
       }
     else if (type == VT_MOUSE_RELEASE)
       {
         if (long_tap_cb_id)
           {
             ctx_remove_idle (vt->root_ctx, long_tap_cb_id);
             long_tap_cb_id = 0;
           }
         vt->cursor_down = 0;
       }
     else if (type == VT_MOUSE_MOTION && vt->cursor_down)
       {
         int row = y - (int)vt->scroll;
         int col = x;
         if ((row > vt->select_begin_row) ||
             ((row == vt->select_begin_row) && (col >= vt->select_begin_col)))
         {
           vt->select_start_col = vt->select_begin_col;
           vt->select_start_row = vt->select_begin_row;
           vt->select_end_col = col;
           vt->select_end_row = row;
         }
         else
         {
           vt->select_start_col = col;
           vt->select_start_row = row;
           vt->select_end_col = vt->select_begin_col;
           vt->select_end_row = vt->select_begin_row;
         }
         if (vt->select_end_row == vt->select_start_row &&
             abs (vt->select_begin_x - px_x) < vt->cw/2)
         {
           vt->select_active = 0;
         }
         else
         {
           vt->select_active = 1;
           char *selection = vt_get_selection (vt);
           if (selection)
           {
             terminal_set_primary (selection);
             free (selection);
           }
         }

         if (y < 1)
         {
           vt->scroll += 1.0f;
           if (vt->scroll > vt->scrollback_count)
             vt->scroll = vt->scrollback_count;
         }
         else if (y > vt->rows)
         {
           vt->scroll -= 1.0f;
           if (vt->scroll < 0)
             vt->scroll = 0.0f;
         }

         vt->rev++;
       }
     return;
   }
 if (type == VT_MOUSE_MOTION)
   { button_state = 3; }

 if (vt->unit_pixels && vt->mouse_decimal)
   {
     x = px_x;
     y = px_y;
   }
 switch (type)
   {
     case VT_MOUSE_MOTION:
       if (!vt->mouse_all)
         return;
       if (x==vt->lastx && y==vt->lasty)
         return;
       vt->lastx = x;
       vt->lasty = y;
   //  sprintf (buf, "\033[<35;%i;%iM", x, y);
       break;
     case VT_MOUSE_RELEASE:
       if (vt->mouse_decimal == 0)
         button_state = 3;
       break;
     case VT_MOUSE_PRESS:
       button_state = 0;
       break;
     case VT_MOUSE_DRAG: // XXX not really used - remove
       if (! (vt->mouse_all || vt->mouse_drag) )
         return;
       button_state = 32;
       break;
   }
 // todo : mix in ctrl/meta state
 if (vt->mouse_decimal)
   {
     sprintf (buf, "\033[<%i;%i;%i%c", button_state, x, y, type == VT_MOUSE_RELEASE?'m':'M');
   }
 else
   { 
     sprintf (buf, "\033[M%c%c%c", button_state + 32, x + 32, y + 32);
   }
 if (buf[0])
   {
     vt_write (vt, buf, strlen (buf) );
     fflush (NULL);
   }
}

pid_t vt_get_pid (VT *vt)
{
  return vt->vtpty.pid;
}

void vt_set_ctx (VT *vt, Ctx *ctx)
{
  vt->root_ctx = ctx;
}
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif

#if !__COSMOPOLITAN__
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <pty.h>
#include <math.h>
#include <malloc.h>
#include <sys/time.h>
#include <time.h>
#endif

#define VT_RECORD 0
extern Ctx *ctx;
#define flag_is_set(a, f) (((a) & (f))!=0)
#define flag_set(a, f)    ((a) |= (f));
#define flag_unset(a, f)  ((a) &= ~(f));

//#define CTX_x            CTX_STRH('x',0,0,0,0,0,0,0,0,0,0,0,0,0)
//#define CTX_y            CTX_STRH('y',0,0,0,0,0,0,0,0,0,0,0,0,0)
#define CTX_lower_bottom CTX_STRH('l','o','w','e','r','-','b','o','t','t','o','m',0,0)
#define CTX_lower        CTX_STRH('l','o','w','e','r',0,0,0,0,0,0,0,0,0)
#define CTX_raise        CTX_STRH('r','a','i','s','e',0,0,0,0,0,0,0,0,0)
#define CTX_raise_top    CTX_STRH('r','a','i','s','e','-','t','o','p',0,0,0,0,0)
#define CTX_terminate    CTX_STRH('t','e','r','m','i','n','a','t','e',0,0,0,0,0)
#define CTX_maximize     CTX_STRH('m','a','x','i','m','i','z','e',0,0,0,0,0,0)
#define CTX_unmaximize   CTX_STRH('u','n','m','a','x','i','m','i','z','e',0,0,0,0)
//#define CTX_width        CTX_STRH('w','i','d','t','h',0,0,0,0,0,0,0,0,0)
//#define CTX_title        CTX_STRH('t','i','t','l','e',0,0,0,0,0,0,0,0,0)
#define CTX_title        15643372
#define CTX_action       CTX_STRH('a','c','t','i','o','n',0,0,0,0,0,0,0,0)
//#define CTX_height       CTX_STRH('h','e','i','g','h','t',0,0,0,0,0,0,0,0)

void terminal_update_title  (const char *title);
int  ctx_renderer_is_sdl    (Ctx *ctx);
int  ctx_renderer_is_fb     (Ctx *ctx);
int  ctx_renderer_is_term   (Ctx *ctx);
void ctx_sdl_set_fullscreen (Ctx *ctx, int val);
int  ctx_sdl_get_fullscreen (Ctx *ctx);
float ctx_target_fps = 25.0;
static int ctx_fetched_bytes = 1;

CtxClient *vt_get_client (VT *vt);

CtxList *vts = NULL;

void ctx_clients_signal_child (int signum)
{
  pid_t pid;
  int   status;
  if ( (pid = waitpid (-1, &status, WNOHANG) ) != -1)
    {
      if (pid)
        {
          for (CtxList *l = vts; l; l=l->next)
            {
              VtPty *vt = l->data;
              if (vt->pid == pid)
                {
                  vt->done = 1;
                  //vt->result = status;
                }
            }
        }
    }
}



int vt_set_prop (VT *vt, uint32_t key_hash, const char *val)
{
#if 1
  switch (key_hash)
  {
    case CTX_title:  
     {
       CtxClient *client = vt_get_client (vt);
       if (client)
       {
         if (client->title) free (client->title);
         client->title = strdup (val);
       }
     }

     break;
  }
#else
  float fval = strtod (val, NULL);
  CtxClient *client = ctx_client_by_id (ct->id);
  uint32_t val_hash = ctx_strhash (val);
  if (!client)
    return 0;

  if (key_hash == ctx_strhash("start_move"))
  {
    start_moving (client);
    moving_client = 1;
    return 0;
  }

// set "pcm-hz"       "8000"
// set "pcm-bits"     "8"
// set "pcm-encoding" "ulaw"
// set "play-pcm"     "d41ata312313"
// set "play-pcm-ref" "foo.wav"

// get "free"
// storage of blobs for referencing when drawing or for playback
// set "foo.wav"      "\3\1\1\4\"
// set "fnord.png"    "PNG12.4a312"

  switch (key_hash)
  {
    case CTX_title:  ctx_client_set_title (ct->id, val); break;
    case CTX_x:      client->x = fval; break;
    case CTX_y:      client->y = fval; break;
    case CTX_width:  ctx_client_resize (ct->id, fval, client->height); break;
    case CTX_height: ctx_client_resize (ct->id, client->width, fval); break;
    case CTX_action:
      switch (val_hash)
      {
        case CTX_maximize:     ctx_client_maximize (client); break;
        case CTX_unmaximize:   ctx_client_unmaximize (client); break;
        case CTX_lower:        ctx_client_lower (client); break;
        case CTX_lower_bottom: ctx_client_lower_bottom (client);  break;
        case CTX_raise:        ctx_client_raise (client); break;
        case CTX_raise_top:    ctx_client_raise_top (client); break;
      }
      break;
  }
  ct->rev++;
#endif
  return 0;
}

static float _ctx_font_size = 10.0;

CtxList *clients = NULL;
CtxClient *active = NULL;
CtxClient *active_tab = NULL;

static CtxClient *ctx_client_by_id (int id);

int ctx_client_resize (int id, int width, int height);
void ctx_client_maximize (int id);

CtxClient *vt_get_client (VT *vt)
{
  for (CtxList *l = clients; l; l =l->next)
  {
    CtxClient *client = l->data;
    if (client->vt == vt)
            return client;
  }
  return NULL;
}

CtxClient *ctx_client_new (Ctx *ctx,
                           const char *commandline,
                           int x, int y, int width, int height,
                           CtxClientFlags flags)
{
  static int global_id = 0;
  float font_size = ctx_get_font_size (ctx);
  CtxClient *client = calloc (sizeof (CtxClient), 1);
  ctx_list_append (&clients, client);
  client->id = global_id++;
  client->x = x;
  client->y = y;
  client->flags = flags;
  client->ctx = ctx;
  client->width = width;
  client->height = height;

  if (ctx_renderer_is_term (ctx))
  {
    font_size = 3;
  }

      //fprintf (stderr, "client new:%f\n", font_size);
#if CTX_THREADS
  mtx_init (&client->mtx, mtx_plain);
#endif
  float line_spacing = 2.0f;
  client->vt = vt_new (commandline, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0);
  vt_set_ctx (client->vt, ctx);
  return client;
}

CtxClient *ctx_client_new_argv (Ctx *ctx, const char **argv, int x, int y, int width, int height, CtxClientFlags flags)
{
  CtxString *string = ctx_string_new ("");
  for (int i = 0; argv[i]; i++)
  {
    char space = ' ';
    if (i > 0)
      ctx_string_append_data (string, &space, 1);
    for (int c = 0; argv[i][c]; c++)
    {
       switch (argv[i][c])
       {
         case '"':ctx_string_append_str (string, "\\\"");break;
         case '\'':ctx_string_append_str (string, "\\\'");break;
         default:ctx_string_append_data (string, &argv[i][c], 1);break;
       }
    }
  }
  CtxClient *ret = ctx_client_new (ctx, string->str, x, y, width, height, flags);
  ctx_string_free (string, 1);
  return ret;
}

extern float ctx_shape_cache_rate;
extern int _ctx_max_threads;

static int focus_follows_mouse = 0;

static CtxClient *find_active (int x, int y)
{
  CtxClient *ret = NULL;
  float titlebar_height = _ctx_font_size;
  int resize_border = titlebar_height/2;

  for (CtxList *l = clients; l; l = l->next)
  {
     CtxClient *c = l->data;
     if ((c->flags & ITK_CLIENT_MAXIMIZED) && c == active_tab)
     if (x > c->x - resize_border && x < c->x+c->width + resize_border &&
         y > c->y - titlebar_height && y < c->y+c->height + resize_border)
     {
       ret = c;
     }
  }

  for (CtxList *l = clients; l; l = l->next)
  {
     CtxClient *c = l->data;
     if (!(c->flags &  ITK_CLIENT_MAXIMIZED))
     if (x > c->x - resize_border && x < c->x+c->width + resize_border &&
         y > c->y - titlebar_height && y < c->y+c->height + resize_border)
     {
       ret = c;
     }
  }
  return ret;
}

int id_to_no (int id)
{
  CtxList *l;
  int no = 0;

  for (l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if (client->id == id)
      return no;
    no++;
  }
  return -1;
}

void ctx_client_move (int id, int x, int y);
int ctx_client_resize (int id, int w, int h);
void ctx_client_shade_toggle (int id);
float ctx_client_min_y_pos (Ctx *ctx);
float ctx_client_max_y_pos (Ctx *ctx);

#if 0
void ensure_layout ()
{
  int n_clients = ctx_list_length (clients);
  if (n_clients == 1)
  {
    CtxClient *client = clients->data;
    if (client->flags & ITK_CLIENT_MAXIMIZED)
    {
      ctx_client_move (client->id, 0, 0);
      ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx));
      if (active_tab == NULL)
        active_tab = client;
    }
  }
  else
  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if (client->flags & ITK_CLIENT_MAXIMIZED)
    {
      ctx_client_move   (client->id, 0, client_min_y_pos (ctx));
      ctx_client_resize (client->id, ctx_width (ctx), ctx_height(ctx) -
                      ctx_client_min_y_pos (ctx) / 2);   // /2 to counter the double titlebar of non-maximized
      if (active_tab == NULL)
        active_tab = client;
    }
  }
}
#endif

static CtxClient *ctx_client_by_id (int id)
{
  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if (client->id == id)
      return client;
  }
  return NULL;
}

void ctx_client_remove (Ctx *ctx, CtxClient *client)
{
  ctx_client_lock (client);
  if (!client->internal)
  {

    if (client->vt)
      vt_destroy (client->vt);
  }

  if (client->title)
    free (client->title);

#if VT_RECORD
  if (client->recording)
    ctx_free (client->recording);
#endif

  ctx_list_remove (&clients, client);

  if (client == active_tab)
  {
    active_tab = NULL;
  }

  if (ctx)
  if (client == active)
  {
    active = find_active (ctx_pointer_x (ctx), ctx_pointer_y (ctx));
    if (!active) active = clients?clients->data:NULL;
  }

  ctx_client_unlock (client);
  free (client);
  //ensure_layout();
}

#if 0
void ctx_client_remove_by_id (int id)
{
  int no = id_to_no (id);
  if (no>=0)
    ctx_client_remove (no);
}
#endif

int ctx_client_height (int id)
{
  CtxClient *client = ctx_client_by_id (id);
  if (!client) return 0;
  return client->height;
}

int ctx_client_x (int id)
{
  CtxClient *client = ctx_client_by_id (id);
  if (!client) return 0;
  return client->x;
}

int ctx_client_y (int id)
{
  CtxClient *client = ctx_client_by_id (id);
  if (!client) return 0;
  return client->y;
}

void ctx_client_raise_top (int id)
{
  CtxClient *client = ctx_client_by_id (id);
  if (!client) return;
  ctx_list_remove (&clients, client);
  ctx_list_append (&clients, client);
}

void ctx_client_lower_bottom (int id)
{
  CtxClient *client = ctx_client_by_id (id);
  if (!client) return;
  ctx_list_remove (&clients, client);
  ctx_list_prepend (&clients, client);
}


void ctx_client_iconify (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   client->flags |= ITK_CLIENT_ICONIFIED;
}

int ctx_client_is_iconified (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_ICONIFIED) != 0;
}

void ctx_client_uniconify (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   client->flags &= ~ITK_CLIENT_ICONIFIED;
}

void ctx_client_maximize (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   if (client->flags &  ITK_CLIENT_MAXIMIZED)
     return;
   client->flags |= ITK_CLIENT_MAXIMIZED;
   client->unmaximized_x = client->x;
   client->unmaximized_y = client->y;
   client->unmaximized_width  = client->width;
   client->unmaximized_height = client->height;

   // enforce_layout does the size
   //client_resize (id, ctx_width (ctx), ctx_height(ctx) - ctx_client_min_y_pos (ctx));
   
   ctx_client_move (id, 0, ctx_client_min_y_pos (client->ctx));
   active_tab = client;
}

int ctx_client_is_maximized (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_MAXIMIZED) != 0;
}

void ctx_client_unmaximize (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   if ((client->flags & ITK_CLIENT_MAXIMIZED) == 0)
     return;
   client->flags &= ~ITK_CLIENT_MAXIMIZED;
   ctx_client_resize (id, client->unmaximized_width, client->unmaximized_height);
   ctx_client_move (id, client->unmaximized_x, client->unmaximized_y);
   active_tab = NULL;
}

void ctx_client_maximized_toggle (int id)
{
  if (ctx_client_is_maximized (id))
    ctx_client_unmaximize (id);
  else
    ctx_client_maximize (id);
}


void ctx_client_shade (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   client->flags |= ITK_CLIENT_SHADED;
}

int ctx_client_is_shaded (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_SHADED) != 0;
}

void ctx_client_unshade (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   client->flags &= ~ITK_CLIENT_SHADED;
}

void ctx_client_toggle_maximized (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   if (ctx_client_is_maximized (id))
     ctx_client_unmaximize (id);
   else
     ctx_client_maximize (id);
}

void ctx_client_shade_toggle (int id)
{
   CtxClient *client = ctx_client_by_id (id);
   if (!client) return;
   if (ctx_client_is_shaded (id))
    ctx_client_shade (id);
   else
    ctx_client_unshade (id);
}

void ctx_client_move (int id, int x, int y)
{
   CtxClient *client = ctx_client_by_id (id);
   if (client && (client->x != x || client->y != y))
   {
     client->x = x;
     client->y = y;
     vt_rev_inc (client->vt);
   }
}

int ctx_client_resize (int id, int width, int height)
{
   CtxClient *client = ctx_client_by_id (id);

   if (client && ((height != client->height) || (width != client->width) ))
   {
     client->width  = width;
     client->height = height;
     if (client->vt)
       vt_set_px_size (client->vt, width, height);
     return 1;
   }
   return 0;
}

static void ctx_client_titlebar_drag (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;

  if (event->type == CTX_DRAG_RELEASE)
  {
    static int prev_drag_end_time = 0;
    if (event->time - prev_drag_end_time < 500)
    {
      //client_shade_toggle (client->id);
      ctx_client_maximized_toggle (client->id);
    }
    prev_drag_end_time = event->time;
  }

  float new_x = client->x +  event->delta_x;
  float new_y = client->y +  event->delta_y;

  float snap_threshold = 8;

  if (ctx_renderer_is_term (event->ctx))
     snap_threshold = 1;

  if (new_y < ctx_client_min_y_pos (event->ctx)) new_y = ctx_client_min_y_pos (event->ctx);
  if (new_y > ctx_client_max_y_pos (event->ctx)) new_y = ctx_client_max_y_pos (event->ctx);

  if (fabs (new_x - 0) < snap_threshold) new_x = 0.0;
  if (fabs (ctx_width (event->ctx) - (new_x + client->width)) < snap_threshold) new_x = ctx_width (event->ctx) - client->width;

  ctx_client_move (client->id, new_x, new_y);

  //vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);

  event->stop_propagate = 1;
}

static float min_win_dim = 32;

static void ctx_client_resize_se (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  int new_w = client->width + event->delta_x;
  int new_h = client->height + event->delta_y;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  if (new_h <= min_win_dim) new_h = min_win_dim;
  ctx_client_resize (client->id, new_w, new_h);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_e (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  int new_w = client->width + event->delta_x;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  ctx_client_resize (client->id, new_w, client->height);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_s (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  int new_h = client->height + event->delta_y;
  if (new_h <= min_win_dim) new_h = min_win_dim;
  ctx_client_resize (client->id, client->width, new_h);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_n (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  float new_y = client->y +  event->delta_y;
  int new_h = client->height - event->delta_y;
  if (new_h <= min_win_dim) new_h = min_win_dim;
  ctx_client_resize (client->id, client->width, new_h);
  ctx_client_move (client->id, client->x, new_y);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_ne (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  float new_y = client->y +  event->delta_y;
  int new_h = client->height - event->delta_y;
  int new_w = client->width + event->delta_x;
  if (new_h <= min_win_dim) new_h = min_win_dim;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  ctx_client_resize (client->id, new_w, new_h);
  ctx_client_move (client->id, client->x, new_y);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_sw (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;

  float new_x = client->x +  event->delta_x;
  int new_w = client->width - event->delta_x;
  int new_h = client->height + event->delta_y;

  if (new_h <= min_win_dim) new_h = min_win_dim;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  ctx_client_resize (client->id, new_w, new_h);
  ctx_client_move (client->id, new_x, client->y);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_nw (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  float new_x = client->x +  event->delta_x;
  float new_y = client->y +  event->delta_y;
  int new_w = client->width - event->delta_x;
  int new_h = client->height - event->delta_y;
  if (new_h <= min_win_dim) new_h = min_win_dim;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  ctx_client_resize (client->id, new_w, new_h);
  ctx_client_move (client->id, new_x, new_y);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

static void ctx_client_resize_w (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;

  float new_x = client->x +  event->delta_x;
  int new_w = client->width - event->delta_x;
  if (new_w <= min_win_dim) new_w = min_win_dim;
  ctx_client_resize (client->id, new_w, client->height);
  ctx_client_move (client->id, new_x, client->y);
  if (client->vt) // force redraw
    vt_rev_inc (client->vt);
  ctx_set_dirty (event->ctx, 1);

  event->stop_propagate = 1;
}

static void ctx_client_close (CtxEvent *event, void *data, void *data2)
{
  //Ctx *ctx = event->ctx;
  CtxClient *client = data;

 // client->do_quit = 1;
  
  ctx_client_remove (event->ctx, client);

  ctx_set_dirty (event->ctx, 1);
  event->stop_propagate = 1;
}

/********************/
void vt_use_images (VT *vt, Ctx *ctx);
float _ctx_green = 0.5;

static void ctx_client_draw (Ctx *ctx, CtxClient *client, float x, float y)
{
    if (client->internal)
    {
#if 0
      ctx_save (ctx);

      ctx_translate (ctx, x, y);
      int width = client->width;
      int height = client->height;

      itk_panel_start (itk, "", 0, 0, width, height);
      //itk_seperator (itk);
#if 0
      if (itk_button (itk, "add tab"))
      {
        add_tab (vt_find_shell_command(), 1);
      }
#endif
      //itk_sameline (itk);
      //itk_toggle (itk, "on screen keyboard", &on_screen_keyboard);
      //itk_toggle (itk, "focus follows mouse", &focus_follows_mouse);
      itk_slider_float (itk, "CTX_GREEN", &_ctx_green, 0.0, 1.0, 0.5);
      itk_ctx_settings (itk);
      itk_itk_settings (itk);

      itk_panel_end (itk);
      itk_done (itk);
      //itk_key_bindings (itk);

      ctx_restore (ctx);
#endif
    }
    else
    {
       ctx_client_lock (client);

          int found = 0;
          for (CtxList *l2 = clients; l2; l2 = l2->next)
            if (l2->data == client) found = 1;
          if (found)
          {

      int rev = vt_rev (client->vt);
#if VT_RECORD
      if (client->drawn_rev != rev)
      {
        if (!client->recording)
          client->recording = ctx_new ();
        else
          ctx_reset (client->recording);
        vt_draw (client->vt, client->recording, 0.0, 0.0);
      }

      if (client->recording)
      {
        ctx_save (ctx);
        ctx_translate (ctx, x, y);
        ctx_render_ctx (client->recording, ctx);
        vt_register_events (client->vt, ctx, 0.0, 0.0);
        ctx_restore (ctx);
      }
#else

      vt_draw (client->vt, ctx, x, y);
      vt_register_events (client->vt, ctx, x, y);
#endif
      client->drawn_rev = rev;
      ctx_client_unlock (client);
          }
    }
}

static void ctx_client_use_images (Ctx *ctx, CtxClient *client)
{
  if (!client->internal)
  {
      uint32_t rev = vt_rev (client->vt);
#if VT_RECORD
      if (client->drawn_rev != rev)
      {
        if (!client->recording)
          client->recording = ctx_new ();
        else
          ctx_reset (client->recording);
        vt_draw (client->vt, client->recording, 0.0, 0.0);
      }

      if (client->recording)
      {
        ctx_save (ctx);
        ctx_render_ctx_textures (client->recording, ctx);
        ctx_restore (ctx);
      }
#else
    vt_use_images (client->vt, ctx);
#endif
    client->drawn_rev = rev;
  }
}

void ctx_client_lock (CtxClient *client)
{
#if CTX_THREADS
    mtx_lock (&client->mtx);
#endif
}

void ctx_client_unlock (CtxClient *client)
{
#if CTX_THREADS
    mtx_unlock (&client->mtx);
#endif
}

#if 0
void ctx_client_handle_event (Ctx *ctx, CtxEvent *ctx_event, const char *event)
{
  if (!active)
    return;
  if (active->internal)
    return;
  VT *vt = active->vt;
  CtxClient *client = vt_get_client (vt);

  ctx_client_lock (client);

  if (!strcmp (event, "F11"))
  {
#ifndef NO_SDL
    if (ctx_renderer_is_sdl (ctx))
    {
      ctx_sdl_set_fullscreen (ctx, !ctx_sdl_get_fullscreen (ctx));
    }
#endif
  }
  else if (!strcmp (event, "shift-return"))
  {
    vt_feed_keystring (vt, ctx_event, "return");
  }
  else if (!strcmp (event, "shift-control-v") )
    {
      char *text = ctx_get_clipboard (ctx);
      if (text)
        {
          if (vt)
            vt_paste (vt, text);
          free (text);
        }
    }
  else if (!strcmp (event, "shift-control-c") && vt)
    {
      char *text = vt_get_selection (vt);
      if (text)
        {
          ctx_set_clipboard (ctx, text);
          free (text);
        }
    }
  else if (!strcmp (event, "shift-control-t") ||
           ((ctx_renderer_is_fb (ctx) || ctx_renderer_is_term (ctx))
           &&   !strcmp (event, "control-t") ))
  {
    //XXX add_tab (vt_find_shell_command(), 1);
  }
  else if (!strcmp (event, "shift-control-n") )
    {
      pid_t pid;
      if ( (pid=fork() ) ==0)
        {
          unsetenv ("CTX_VERSION");
         // execlp (execute_self, execute_self, NULL);
          exit (0);
        }
    }

#if 0
  else if (!strcmp (event, "alt-1"))   switch_to_tab(0);
  else if (!strcmp (event, "alt-2"))   switch_to_tab(1);
  else if (!strcmp (event, "alt-3"))   switch_to_tab(2);
  else if (!strcmp (event, "alt-4"))   switch_to_tab(3);
  else if (!strcmp (event, "alt-5"))   switch_to_tab(4);
  else if (!strcmp (event, "alt-6"))   switch_to_tab(5);
  else if (!strcmp (event, "alt-7"))   switch_to_tab(6);
  else if (!strcmp (event, "alt-8"))   switch_to_tab(7);
  else if (!strcmp (event, "alt-9"))   switch_to_tab(8);
  else if (!strcmp (event, "alt-0"))   switch_to_tab(9);
#endif
  else if (!strcmp (event, "shift-control-q") )
    {
      ctx_quit (ctx);
    }
  else if (!strcmp (event, "shift-control-w") )
    {
      active->do_quit = 1;
    }
  else if (!strcmp (event, "shift-control-s") )
    {
      if (vt)
      {
        char *sel = vt_get_selection (vt);
        if (sel)
        {
          vt_feed_keystring (vt, ctx_event, sel);
          free (sel);
        }
      }
    }
  else
    {
      if (vt)
        vt_feed_keystring (vt, ctx_event, event);
    }
  ctx_client_unlock (client);
}
#endif

static int ctx_clients_dirty_count (void)
{
  int changes = 0;
  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if ((client->drawn_rev != vt_rev (client->vt) ) ||
        vt_has_blink (client->vt))
      changes++;
  }
  return changes;
}

static void ctx_client_titlebar_drag_maximized (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;

  active = active_tab = client;
  if (event->type == CTX_DRAG_RELEASE)
  {
    static int prev_drag_end_time = 0;
    if (event->time - prev_drag_end_time < 500)
    {
      //client_shade_toggle (client->id);
      ctx_client_unmaximize (client->id);
      ctx_client_raise_top (client->id);
      active_tab = NULL;
    }
    prev_drag_end_time = event->time;
  }
  ctx_set_dirty (event->ctx, 1);
  vt_rev_inc (client->vt);
  event->stop_propagate = 1;
}

float ctx_client_min_y_pos (Ctx *ctx)
{
  return _ctx_font_size * 2; // a titlebar and a panel
}

float ctx_client_max_y_pos (Ctx *ctx)
{
  return ctx_height (ctx);
}

void ctx_client_titlebar_draw (Ctx *ctx, CtxClient *client,
                               float x, float y, float width, float titlebar_height)
{
#if 0
  ctx_move_to (ctx, x, y + height * 0.8);
  if (client == active)
    ctx_rgba (ctx, 1, 1,0.4, 1.0);
  else
    ctx_rgba (ctx, 1, 1,1, 0.8);
  ctx_text (ctx, client->title);
#else
  ctx_rectangle (ctx, x, y - titlebar_height,
                 width, titlebar_height);
  if (client == active)
     itk_style_color (ctx, "titlebar-focused-bg");
  else
     itk_style_color (ctx, "titlebar-bg");

  if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y == titlebar_height)
  {
    ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag_maximized, client, NULL);
    ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL);
  }
  else
  {
    ctx_listen (ctx, CTX_DRAG, ctx_client_titlebar_drag, client, NULL);
    ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_ALL);
  }
  ctx_fill (ctx);
  //ctx_font_size (ctx, itk->font_size);//titlebar_height);// * 0.85);

  if (client == active &&
      (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) || y != titlebar_height))
#if 1
  ctx_rectangle (ctx, x + width - titlebar_height,
                  y - titlebar_height, titlebar_height,
                  titlebar_height);
#endif
  ctx_rgb (ctx, 1, 0,0);
  ctx_listen (ctx, CTX_PRESS, ctx_client_close, client, NULL);
  ctx_listen_set_cursor (ctx, CTX_CURSOR_ARROW);
  //ctx_fill (ctx);
  ctx_begin_path (ctx);
  ctx_move_to (ctx, x + width - titlebar_height * 0.8, y - titlebar_height * 0.22);
  if (client == active)
    itk_style_color (ctx, "titlebar-focused-close");
  else
    itk_style_color (ctx, "titlebar-close");
  ctx_text (ctx, "X");

  ctx_move_to (ctx, x +  width/2, y - titlebar_height * 0.22);
  if (client == active)
    itk_style_color (ctx, "titlebar-focused-fg");
  else
    itk_style_color (ctx, "titlebar-fg");

  ctx_save (ctx);
  ctx_text_align (ctx, CTX_TEXT_ALIGN_CENTER);
  if (client->title)
    ctx_text (ctx, client->title);
  else
    ctx_text (ctx, "untitled");
  ctx_restore (ctx);
#endif
}

#if 0
static void key_down (CtxEvent *event, void *data1, void *data2)
{
  fprintf (stderr, "down %i %s\n", event->unicode, event->string);
}
static void key_up (CtxEvent *event, void *data1, void *data2)
{
  fprintf (stderr, "up %i %s\n", event->unicode, event->string);
}
static void key_press (CtxEvent *event, void *data1, void *data2)
{
  fprintf (stderr, "press %i %s\n", event->unicode, event->string);
}
#endif

int ctx_clients_draw (Ctx *ctx)
{
  _ctx_font_size = ctx_get_font_size (ctx);
  float titlebar_height = _ctx_font_size;
  int n_clients         = ctx_list_length (clients);

  if (active && flag_is_set(active->flags, ITK_CLIENT_MAXIMIZED) && n_clients == 1)
  {
    ctx_client_draw (ctx, active, 0, 0);
    return 0;
  }

  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED))
    {
      if (client == active_tab)
      {
        ctx_client_draw (ctx, client, 0, titlebar_height);
      }
      else
      {
        ctx_client_use_images (ctx, client);
      }
    }
  }

  {
  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    VT *vt = client->vt;
    if (vt && !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED))
    {
      if (flag_is_set(client->flags, ITK_CLIENT_SHADED))
      {
        ctx_client_use_images (ctx, client);
      }
      else
      {
        ctx_client_draw (ctx, client, client->x, client->y);
      }

      // resize regions
      if (client == active &&
         !flag_is_set(client->flags, ITK_CLIENT_SHADED) &&
         !flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED) &&
         flag_is_set(client->flags, ITK_CLIENT_UI_RESIZABLE))
      {
        itk_style_color (ctx, "titlebar-focused-bg");

        ctx_rectangle (ctx,
                       client->x,
                       client->y - titlebar_height * 2,
                       client->width, titlebar_height);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_n, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_N);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x,
                       client->y + client->height - titlebar_height,
                       client->width, titlebar_height * 2);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_s, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_S);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x + client->width,
                       client->y - titlebar_height,
                       titlebar_height, client->height + titlebar_height);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_e, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_E);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x - titlebar_height,
                       client->y - titlebar_height,
                       titlebar_height, client->height + titlebar_height);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_w, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_W);
        ctx_begin_path (ctx); 

        ctx_rectangle (ctx,
                       client->x + client->width - titlebar_height,
                       client->y - titlebar_height * 2,
                       titlebar_height * 2, titlebar_height * 2);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_ne, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NE);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x - titlebar_height,
                       client->y - titlebar_height * 2,
                       titlebar_height * 2, titlebar_height * 2);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_nw, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_NW);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x - titlebar_height,
                       client->y + client->height - titlebar_height,
                       titlebar_height * 2, titlebar_height * 2);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_sw, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SW);
        ctx_begin_path (ctx);

        ctx_rectangle (ctx,
                       client->x + client->width - titlebar_height,
                       client->y + client->height - titlebar_height,
                       titlebar_height * 2, titlebar_height * 2);
        ctx_listen (ctx, CTX_DRAG, ctx_client_resize_se, client, NULL);
        ctx_listen_set_cursor (ctx, CTX_CURSOR_RESIZE_SE);
        ctx_begin_path (ctx);

      }

      if (client->flags & ITK_CLIENT_TITLEBAR)
        ctx_client_titlebar_draw (ctx, client, client->x, client->y, client->width, titlebar_height);
    }
  }
  }
  return 0;
}

extern int _ctx_enable_hash_cache;

void vt_audio_task (VT *vt, int click);

int ctx_input_pending (Ctx *ctx, int timeout);

int ctx_clients_need_redraw (Ctx *ctx)
{
  int changes = 0;
  int follow_mouse = focus_follows_mouse;
      CtxList *to_remove = NULL;
  //ensure_layout ();

//  if (print_shape_cache_rate)
//    fprintf (stderr, "\r%f ", ctx_shape_cache_rate);

   CtxClient *client = find_active (ctx_pointer_x (ctx),
                                    ctx_pointer_y (ctx));

   if (follow_mouse || ctx_pointer_is_down (ctx, 0) ||
       ctx_pointer_is_down (ctx, 1) || (active==NULL))
   {
        if (client)
        {
          if (active != client)
          {
            active = client;
            if (follow_mouse == 0 ||
                (ctx_pointer_is_down (ctx, 0) ||
                 ctx_pointer_is_down (ctx, 1)))
            {
              //if (client != clients->data)
       #if 1
              if ((client->flags & ITK_CLIENT_MAXIMIZED)==0)
              {
                ctx_list_remove (&clients, client);
                ctx_list_append (&clients, client);
              }
#endif
            }
            changes ++;
          }
        }
   }

   for (CtxList *l = clients; l; l = l->next)
   {
     CtxClient *client = l->data;
     if (client->vt)
       {
         if (vt_is_done (client->vt))
           ctx_list_prepend (&to_remove, client);
       }
   }
   for (CtxList *l = to_remove; l; l = l->next)
   {
     ctx_client_remove (ctx, l->data);
     changes++;
   }
   while (to_remove)
   {
     ctx_list_remove (&to_remove, to_remove->data);
   }

   changes += ctx_clients_dirty_count ();
   return changes != 0;
}

float ctx_avg_bytespeed = 0.0;

static void ctx_client_handle_events_iteration (Ctx *ctx)
{
  static int fail_safe = 0;
  //int n_clients = ctx_list_length (clients);
      int pending_data = 0;
      long time_start = ctx_ticks ();
      int sleep_time = 1000000/ctx_target_fps;

      pending_data = ctx_input_pending (ctx, sleep_time);

      ctx_fetched_bytes = 0;
      if (pending_data || fail_safe>100)
      {
        if (!pending_data)pending_data = 1;
        /* record amount of time spent - and adjust time of reading for
         * vts?
         */
        long int fractional_sleep = sleep_time / pending_data;
        for (CtxList *l = clients; l; l = l->next)
        {
          CtxClient *client = l->data;
          ctx_client_lock (client);
          int found = 0;
          for (CtxList *l2 = clients; l2; l2 = l2->next)
            if (l2->data == client) found = 1;
          if (!found)
            goto done;
          
          ctx_fetched_bytes += vt_poll (client->vt, fractional_sleep);
          //ctx_fetched_bytes += vt_poll (client->vt, sleep_time); //fractional_sleep);
          ctx_client_unlock (client);
        }
done:
        fail_safe = 0;
      }
      else
      {
        fail_safe ++;
        for (CtxList *l = clients; l; l = l->next)
        {
          CtxClient *client = l->data;
          vt_audio_task (client->vt, 0);
        }
      }

      //int got_events = 0;

      //while (ctx_get_event (ctx)) { }
#if 0
      if (changes /*|| pending_data */)
      {
        ctx_target_fps *= 1.6;
        if (ctx_target_fps > 60) ctx_target_fps = 60;
      }
      else
      {
        ctx_target_fps = ctx_target_fps * 0.95 + 30.0 * 0.05;

        // 20fps is the lowest where sun 8bit ulaw 8khz works reliably
      }

      if (ctx_avg_bytespeed > 1024 * 1024) ctx_target_fps = 10.0;

      if (_ctx_green < 0.4)
        ctx_target_fps = 120.0;
      else if (_ctx_green > 0.6)
        ctx_target_fps = 25.0;

      //ctx_target_fps = 30.0;
#else
      ctx_target_fps = 30.0;
#endif

      long time_end = ctx_ticks ();

      int timed = (time_end-time_start);
      float bytespeed = ctx_fetched_bytes / ((timed)/ (1000.0f * 1000.0f));

      ctx_avg_bytespeed = bytespeed * 0.2 + ctx_avg_bytespeed * 0.8;
#if 0
      fprintf (stderr, "%.2fmb/s %i/%i  %.2f                    \r", ctx_avg_bytespeed/1024/1024, ctx_fetched_bytes, timed, ctx_target_fps);
#endif
}


static int ctx_clients_handle_events_fun (void *data)
{
  Ctx *ctx = data;
  while (!ctx->quit)
  {
    int n_clients = ctx_list_length (clients);
    ctx_client_handle_events_iteration (data);
    switch (n_clients)
    {
      case 0:
        usleep (1000 * 10);
        break;
      case 1:
        usleep (1); // letting quit work - and also makes framerate for dump
        break;
      default:
        usleep (0); // the switching between clients should be enough
        break;
    }
  }
  return 0;
}

void ctx_clients_handle_events (Ctx *ctx)
{
#if CTX_THREADS==0
    ctx_client_handle_events_iteration (ctx);
#else
    static thrd_t tid = 0;
    if (tid == 0)
    {
      thrd_create (&tid, (void*)ctx_clients_handle_events_fun, ctx);
    }
#endif
}

#endif /* CTX_VT */
#endif //  __CTX_H__
