/* ctx git commit: 2cda4241 */
/* 
 * 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, 2021, 2022 Øyvind Kolås <pippin@gimp.org>
 *
 * ctx is a 2D vector graphics processing 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 backends, 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

#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>

typedef struct _Ctx            Ctx;

/**
 * ctx_new:
 * @width: with in device units
 * @height: height in device units
 * @backend: backend to use
 *
 *   valid values are:
 *     NULL/"auto", "drawlist", "sdl", "term", "ctx" the strings are
 *     the same as are valid for the CTX_BACKEND environment variable.
 *
 * Create a new drawing context, this context has no pixels but
 * accumulates commands and can be played back on other ctx
 * render contexts, this is a ctx context using the drawlist backend.
 */
Ctx *ctx_new (int width, int height, const char *backend);


/**
 * ctx_new_drawlist:
 *
 * Create a new drawing context that can record drawing commands,
 * this is also the basis for creating more complex contexts with
 * the backend swapped out.
 */
Ctx * ctx_new_drawlist (int width, int height);

typedef struct _CtxEntry CtxEntry;


/**
 * ctx_get_drawlist:
 * @ctx: a ctx context.
 * @count: return location for length of drawlist
 *
 * The returned pointer is only valid as long as no further drawing has been
 * done.
 *
 * Returns a read only pointer to the first entry of the contexts drawlist.
 */
const CtxEntry *ctx_get_drawlist (Ctx *ctx, int *count);

/**
 * ctx_drawlist_force_count:
 * @ctx: a ctx context
 * @count: new count to set, must be lower than the current count.
 *
 * Shortens the length of the internal drawlist, dropping the last
 * items.
 */
void ctx_drawlist_force_count (Ctx *ctx, int count);

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

/**
 * ctx_set_drawlist:
 *
 * Replaces the drawlist of a ctx context with a new one.  the length of the
 * data is expected to be length * 9;
 */
int  ctx_set_drawlist       (Ctx *ctx, void *data, int length);

/**
 * ctx_append_drawlist:
 *
 * Appends the commands in a binary drawlist, the length of the data is expected to
 * be length * 9;
 */
int  ctx_append_drawlist    (Ctx *ctx, void *data, int length);

/**
 * ctx_drawlist_clear:
 *
 * Clears the drawlist associated with the context.
 */
void  ctx_drawlist_clear (Ctx *ctx);


const char *ctx_get_font_name (Ctx *ctx, int no);

/* by default both are 0.0 which makes wrapping disabled
 */
void ctx_wrap_left (Ctx *ctx, float x);
void ctx_wrap_right (Ctx *ctx, float x);
void ctx_line_height (Ctx *ctx, float x);


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

/**
 * ctx_start_frame:
 *
 * Prepare for rendering a new frame, clears internal drawlist and initializes
 * the state.
 *
 */
void ctx_start_frame    (Ctx *ctx);

/**
 * ctx_end_frame:
 *
 * We're done rendering a frame, this does nothing on a context created for a framebuffer, where drawing commands are immediate.
 */
void ctx_end_frame      (Ctx *ctx);


/**
 * ctx_begin_path:
 *
 * Clears the current path if any.
 */
void ctx_begin_path     (Ctx *ctx);

/**
 * ctx_save:
 *
 * Stores the transform, clipping state, fill and stroke sources, font size,
 * stroking and dashing options.
 */
void ctx_save           (Ctx *ctx);

/**
 * ctx_restore:
 *
 * Restores the state previously saved with ctx_save, calls to
 * ctx_save/ctx_restore should be balanced.
 */
void ctx_restore        (Ctx *ctx);

/**
 * ctx_start_group:
 *
 * Start a compositing group.
 *
 */
void ctx_start_group    (Ctx *ctx);

/**
 * ctx_end_group:
 *
 * End a compositing group, the global alpha, compositing mode and blend mode
 * set before this call is used to apply the group.
 */
void ctx_end_group      (Ctx *ctx);

/**
 * ctx_clip:
 *
 * Use the current path as a clipping mask, subsequent draw calls are limited
 * by the path. The only way to increase the visible area is to first call
 * ctx_save and then later ctx_restore to undo the clip.
 */
void ctx_clip           (Ctx *ctx);


/**
 * ctx_image_smoothing:
 *
 * Set or unset bilinear / box filtering for textures, turning it off uses the
 * faster nearest neighbor for all cases.
 */
void ctx_image_smoothing  (Ctx *ctx, int enabled);

#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 */



/**
 * ctx_line_to:
 */
void  ctx_line_to         (Ctx *ctx, float x, float y);
/**
 * ctx_move_to:
 */
void  ctx_move_to         (Ctx *ctx, float x, float y);
/**
 * ctx_curve_to:
 */
void  ctx_curve_to        (Ctx *ctx, float cx0, float cy0,
                           float cx1, float cy1,
                           float x, float y);
/**
 * ctx_quad_to:
 */
void  ctx_quad_to         (Ctx *ctx, float cx, float cy,
                           float x, float y);
/**
 * ctx_arc:
 */
void  ctx_arc             (Ctx  *ctx,
                           float x, float y,
                           float radius,
                           float angle1, float angle2,
                           int   direction);
/**
 * ctx_arc_to:
 */
void  ctx_arc_to          (Ctx *ctx, float x1, float y1,
                           float x2, float y2, float radius);
/**
 * ctx_rel_arc_to:
 */
void  ctx_rel_arc_to      (Ctx *ctx, float x1, float y1,
                           float x2, float y2, float radius);

enum {
  CTX_TVG_FLAG_NONE       = 0,
  CTX_TVG_FLAG_LOAD_PAL   = 1<<0,
  CTX_TVG_FLAG_BBOX_CHECK = 1<<1,
  CTX_TVG_FLAG_DEFAULTS   = CTX_TVG_FLAG_LOAD_PAL
};


int ctx_tinyvg_get_size (uint8_t *data, int length, int *width, int *height);
int ctx_tinyvg_draw (Ctx *ctx, uint8_t *data, int length, int flags);

int ctx_tinyvg_fd_get_size (int fd, int *width, int *height);
int ctx_tinyvg_fd_draw (Ctx *ctx, int fd, int flags);

/**
 * ctx_rectangle:
 */
void  ctx_rectangle       (Ctx *ctx,
                           float x0, float y0,
                           float w, float h);
/**
 * ctx_round_rectangle:
 */
void  ctx_round_rectangle (Ctx *ctx,
                           float x0, float y0,
                           float w, float h,
                           float radius);
/**
 * ctx_rel_line_to:
 */
void  ctx_rel_line_to     (Ctx *ctx,
                           float x, float y);
/**
 * ctx_rel_move_to:
 */
void  ctx_rel_move_to     (Ctx *ctx,
                           float x, float y);
/**
 * ctx_rel_curve_to:
 */
void  ctx_rel_curve_to    (Ctx *ctx,
                           float x0, float y0,
                           float x1, float y1,
                           float x2, float y2);
/**
 * ctx_rel_quad_to:
 */
void  ctx_rel_quad_to     (Ctx *ctx,
                           float cx, float cy,
                           float x, float y);
/**
 * ctx_close_path:
 */
void  ctx_close_path      (Ctx *ctx);


/**
 * ctx_fill:
 */
void ctx_fill             (Ctx *ctx);

/**
 * ctx_stroke:
 */
void ctx_stroke           (Ctx *ctx);

/**
 * ctx_paint:
 */
void ctx_paint            (Ctx *ctx);

/**
 * ctx_preserve:
 */
void ctx_preserve         (Ctx *ctx);

/**
 * ctx_identity:
 *
 * Restore context to identity transform, NOTE: a bug makes this call currently
 * breaks mult-threaded rendering when used; since the rendering threads are
 * expecting an initial transform on top of the base identity.
 */
void ctx_identity       (Ctx *ctx);


/**
 * ctx_scale:
 *
 * Scales the user to device transform.
 */
void  ctx_scale           (Ctx *ctx, float x, float y);

/**
 * ctx_translate:
 *
 * Adds translation to the user to device transform.
 */
void  ctx_translate       (Ctx *ctx, float x, float y);

/**
 * ctx_rotate:
 *
 * Add rotatation to the user to device space transform.
 */
void ctx_rotate         (Ctx *ctx, float x);

/**
 * ctx_apply_transform:
 *
 * Adds a 3x3 matrix on top of the existing user to device space transform.
 */
void ctx_apply_transform (Ctx *ctx,
                     float a, float b, float c,
                     float d, float e, float f,
                     float g, float h, float i);

/**
 * ctx_set_transform:
 *
 * Redundant with identity+apply?
 */
void ctx_set_transform    (Ctx *ctx, float a, float b, float c,
                                     float d, float e, float f,
                                     float g, float h, float i);

/**
 * ctx_miter_limit:
 *
 * Specify the miter limit used when stroking.
 */
void ctx_miter_limit      (Ctx *ctx, float limit);

/**
 * ctx_line_width:
 *
 * Set the line width used when stroking.
 */
void ctx_line_width       (Ctx *ctx, float x);

/**
 * ctx_line_dash_offset:
 *
 * Specify phase offset for line dash pattern.
 */
void ctx_line_dash_offset (Ctx *ctx, float line_dash);

/**
 * ctx_line_dash:
 *
 * Specify the line dash pattern.
 */
void  ctx_line_dash       (Ctx *ctx, float *dashes, int count);

/**
 * ctx_font_size:
 */
void  ctx_font_size       (Ctx *ctx, float x);

/**
 * ctx_font:
 */
void  ctx_font            (Ctx *ctx, const char *font);

/**
 * ctx_font_family:
 */
void  ctx_font_family     (Ctx *ctx, const char *font_family);

int
ctx_font_extents (Ctx   *ctx,
                  float *ascent,
                  float *descent,
                  float *line_gap);

/**
 * ctx_parse:
 *
 * Parses a string containg text ctx protocol data.
 */
void ctx_parse            (Ctx *ctx, const char *string);

/**
 * low level glyph drawing calls, unless you are integrating harfbuzz
 * you probably want to use ctx_text instead.
 */
typedef struct _CtxGlyph CtxGlyph;

/**
 */
CtxGlyph *ctx_glyph_allocate     (int n_glyphs);
/**
 */
void      ctx_glyph_free         (CtxGlyph   *glyphs);
/**
 */
int       ctx_glyph              (Ctx        *ctx, uint32_t unichar, int stroke);
/**
 */
void      ctx_glyphs             (Ctx        *ctx,
                                  CtxGlyph   *glyphs,
                                  int         n_glyphs);
/**
 */
void  ctx_glyphs_stroke          (Ctx        *ctx,
                                  CtxGlyph   *glyphs,
                                  int         n_glyphs);

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);

/**
 * ctx_view_box:
 *
 * Specify the view box for the current page.
 */
void ctx_view_box         (Ctx *ctx,
                           float x0, float y0,
                           float w, float h);

void ctx_new_page         (Ctx *ctx);

/**
 * ctx_set_pixel_u8:
 *
 * Set a single pixel to the nearest possible the specified r,g,b,a value. Fast
 * for individual few pixels, slow for doing textures.
 */
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);

/**
 * ctx_global_alpha:
 *
 * Set a global alpha value that the colors, textures and gradients are modulated by.
 */
void  ctx_global_alpha     (Ctx *ctx, float global_alpha);


/**
 * ctx_stroke_source:
 *
 * The next source definition applies to stroking rather than filling, when a stroke source is
 * not explicitly set the value of filling is inherited.
 */
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);

/**
 * ctx_linear_gradient:
 * Change the source to a linear gradient from x0,y0 to x1 y1, by default an empty gradient
 * from black to white exist, add stops with ctx_gradient_add_stop to specify a custom gradient.
 */
void ctx_linear_gradient (Ctx *ctx, float x0, float y0, float x1, float y1);

/**
 * ctx_radial_gradient:
 * Change the source to a radial gradient from a circle x0,y0 with radius r0 to an outher circle x1, y1 with radius r1. (NOTE: currently ctx is only using the second circles origin, both radiuses are in use.)
 */
void ctx_radial_gradient (Ctx *ctx, float x0, float y0, float r0,
                          float x1, float y1, float r1);

/* ctx_graident_add_stop:
 *
 * Add an RGBA gradient stop to the current gradient at position pos.
 *
 * 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);

/* ctx_graident_add_stop:
 *
 * Add an RGBA gradient stop to the current gradient at position pos.
 *
 * XXX should be ctx_gradient_add_stop_u8 */
void ctx_gradient_add_stop_u8 (Ctx *ctx, float pos, uint8_t r, uint8_t g, uint8_t b, uint8_t a);

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

void ctx_drop_eid (Ctx *ctx, const char *eid);

/* ctx_source_transform:
 */
void
ctx_source_transform (Ctx *ctx, float a, float b,  float c,
                      float d, float e, float f, 
                      float g, float h, float i); 
typedef struct _CtxMatrix     CtxMatrix;

/* ctx_source_transform_matrix:
 */
void
ctx_source_transform_matrix (Ctx *ctx, CtxMatrix *matrix);



int   ctx_width                (Ctx *ctx);
int   ctx_height               (Ctx *ctx);
float ctx_x                    (Ctx *ctx);
float ctx_y                    (Ctx *ctx);
float ctx_get_global_alpha     (Ctx *ctx);
float ctx_get_font_size        (Ctx *ctx);
float ctx_get_miter_limit      (Ctx *ctx);
int   ctx_get_image_smoothing   (Ctx *ctx);
float ctx_get_line_dash_offset (Ctx *ctx);

float ctx_get_wrap_left        (Ctx *ctx);
float ctx_get_wrap_right       (Ctx *ctx);
float ctx_get_line_height      (Ctx *ctx);

const char *ctx_get_font       (Ctx *ctx);
float ctx_get_line_width       (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,
                                float *g, float *h,
                                float *i);

void
ctx_clip_extents (Ctx *ctx, float *x0, float *y0,
                            float *x1, float *y1);

/* 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 // matching flags
  CTX_FORMAT_RGBAF,  // 9
  CTX_FORMAT_GRAYF,  // 10
  CTX_FORMAT_GRAYAF, // 11
  CTX_FORMAT_GRAY1,  // 12
  CTX_FORMAT_CMYK8,  // 13
  CTX_FORMAT_CMYKAF, // 14
  CTX_FORMAT_CMYKA8, // 15 
  CTX_FORMAT_GRAY2,  // 16 // matching flags
  CTX_FORMAT_YUV420, // 17
  CTX_FORMAT_GRAY4=32, // to match flags
  CTX_FORMAT_CBRLE_1,   // CBRLE constant bitrate runlength encoded 1 bpp
  CTX_FORMAT_CBRLE_2,   // these formats are used internally with the CBRLE
  CTX_FORMAT_CBRLE_3,   // flag passed to the cb backend. (should be exteneded
  CTX_FORMAT_CBRLE_4,   // to also apply to the tiled backends).
  CTX_FORMAT_CBRLE_5,   //
  CTX_FORMAT_CBRLE_6,   // When used in other backends they determine automatically
  CTX_FORMAT_CBRLE_7,   // which specific CBRLE format to use, based on available
  CTX_FORMAT_CBRLE_8,   // memory budget.
  CTX_FORMAT_CBRLE_9,   // 
  CTX_FORMAT_CBRLE_10,  // The specific formats are also used for testing and
  CTX_FORMAT_CBRLE_11,  // improving the visual quality vs performance loss
  CTX_FORMAT_CBRLE_12,  // when lossy.
  CTX_FORMAT_CBRLE_13,  //
  CTX_FORMAT_CBRLE_14,  // 
  CTX_FORMAT_CBRLE_15,  //
  CTX_FORMAT_CBRLE_16,  // 
  CTX_FORMAT_CBRLE_17,  //  
  CTX_FORMAT_CBRLE_18,  //  
  CTX_FORMAT_CBRLE_19,  //  
  CTX_FORMAT_CBRLE_20,  //  
  CTX_FORMAT_CBRLE_21,  //  
  CTX_FORMAT_CBRLE_22,  //  
  CTX_FORMAT_CBRLE_23,  //  
  CTX_FORMAT_CBRLE_24,  //  
  CTX_FORMAT_CBRLE_32,  //  
  CTX_FORMAT_CBRLE,     // 
  CTX_FORMAT_BGRA8Z,    // 
  CTX_FORMAT_RGBA8_SEPARATE_ALPHA, //
};
typedef enum   _CtxPixelFormat CtxPixelFormat;

/**
 * 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);

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, CtxDrawlist *drawlist);
uint32_t ctx_hasher_get_hash (Ctx *ctx, int col, int row);

int ctx_utf8_strlen (const char *s);
int ctx_utf8_len (const unsigned char first_byte);

void ctx_deferred_scale       (Ctx *ctx, const char *name, float x, float y);
void ctx_deferred_translate   (Ctx *ctx, const char *name, float x, float y);
void ctx_deferred_move_to     (Ctx *ctx, const char *name, float x, float y);
void ctx_deferred_rel_line_to (Ctx *ctx, const char *name, float x, float y);
void ctx_deferred_rel_move_to (Ctx *ctx, const char *name, float x, float y);
void ctx_deferred_rectangle   (Ctx *ctx, const char *name, float x, float y,
                                                           float width, float height);
void ctx_resolve              (Ctx *ctx, const char *name,
                               void (*set_dim) (Ctx *ctx,
                                                void *userdata,
                                                const char *name,
                                                int         count,
                                                float *x,
                                                float *y,
                                                float *width,
                                                float *height),
                               void *userdata);


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

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

#ifndef CTX_TFT_ESPI
#ifdef _TFT_eSPIH_
#define CTX_TFT_ESPI 1
#else
#define CTX_TFT_ESPI 0
#endif
#endif

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

#ifndef CTX_FB
#define CTX_FB 0
#endif

#ifndef CTX_KMS
#define CTX_KMS 0
#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


/* these are configuration flags for a ctx renderer, not all
 * flags are applicable for all rendereres, the cb backend
 * has the widest support currently.
 */
typedef enum CtxFlags {
  //CTX_FLAG_DEFAULTS   = 0,
  CTX_FLAG_GRAY8        = 1 << 0, // use GRAY8, implies LOWFI
  CTX_FLAG_HASH_CACHE   = 1 << 1, // use a hashcache to determine which parts to redraw, implied by LOWFI
  CTX_FLAG_LOWFI        = 1 << 2, // use lower res and color fidelity preview renders
  CTX_FLAG_RGB332       = 1 << 3, // 8bit indexed with fixed palette, implies lowfi
  CTX_FLAG_GRAY2        = 1 << 4, // 4 level grayscale, implies lowfi
  CTX_FLAG_GRAY4        = 1 << 5, // 16 level grayscale, implies lowfi
  //CTX_FLAG_DAMAGE_CONTROL = 1 << 6,
  CTX_FLAG_SHOW_FPS     = 1 << 7, // possibly show fps in titlebar or printed to a log
  CTX_FLAG_KEEP_DATA    = 1 << 8, // keep existing fb-data instead of doing an initial clear
  CTX_FLAG_INTRA_UPDATE = 1 << 9,
  CTX_FLAG_STAY_LOW     = 1 << 10,  // stay with the color fidelity drop in lowfi
  CTX_FLAG_CBRLE        = 1 << 11   // use possibly lossy RLE encoded scanlines with constant bitrate
} CtxFlags;


Ctx *ctx_new_cb (int width, int height, CtxPixelFormat format,
                 void (*set_pixels) (Ctx *ctx, void *user_data, 
                                     int x, int y, int w, int h, void *buf,
                                     int buf_size),
                 void *set_pixels_user_data,
                 int (*update_fb) (Ctx *ctx, void *user_data),
                 void *update_fb_user_data,
                 int   memory_budget,
                 void *scratch_fb,
                 int flags);
void ctx_cb_set_flags (Ctx *ctx, int flags);
int ctx_cb_get_flags  (Ctx *ctx);
void ctx_cb_set_memory_budget (Ctx *ctx, int memory_budget);
void
ctx_cb_extent (Ctx *ctx, float *x0, float *y0, float *x1, float *y1);

#if CTX_TFT_ESPI
Ctx *ctx_new_tft (TFT_eSPI *tft, int memory_budget, void *scratch_fb, int flags);

#endif


#if CTX_CAIRO
#ifndef CAIRO_H
typedef struct _cairo_t cairo_t;
#endif

/* 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

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 = 0,
  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_EXTEND_NONE    = 0,
  CTX_EXTEND_REPEAT  = 1,
  CTX_EXTEND_REFLECT = 2,
  CTX_EXTEND_PAD     = 3
} CtxExtend;

void ctx_extend (Ctx *ctx, CtxExtend extend);

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,  // in mrg these didnt exist
  CTX_TEXT_ALIGN_END,        // but left/right did
  CTX_TEXT_ALIGN_JUSTIFY, // not handled in ctx
  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);
CtxExtend          ctx_get_extend     (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);
/* 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)


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

// XXX do not use?
void  ctx_fill_text     (Ctx        *ctx,
                         const char *string,
                         float       x,
                         float       y);

// XXX do not use?
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);


/**
 * 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);


#ifdef CTX_X86_64
int ctx_x86_64_level (void);
#endif


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_backend (Ctx *ctx, void *backend);
void *ctx_get_backend (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_need_redraw (Ctx *ctx);
void ctx_queue_draw (Ctx *ctx);
float ctx_get_float (Ctx *ctx, uint32_t hash);
void ctx_set_float (Ctx *ctx, uint32_t hash, float value);

unsigned long ctx_ticks (void);
void ctx_end_frame (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;
};
typedef struct _CtxFloatRectangle CtxFloatRectangle;
struct _CtxFloatRectangle {
  float x;
  float y;
  float width;
  float 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
typedef struct _CtxClient CtxClient;



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
                         *
                         */
                         /* XXX lifetime of this string should be longer
                         * than the events, preferably interned. XXX
                         * maybe add a flag for this?
                         */
  int owns_string; /* if 1 call free.. */
  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_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);
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);

/* 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);



int   ctx_client_resize        (Ctx *ctx, int id, int width, int height);
void  ctx_client_set_font_size (Ctx *ctx, int id, float font_size);
float ctx_client_get_font_size (Ctx *ctx, int id);
void  ctx_client_maximize      (Ctx *ctx, int id);


#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_vt_had_alt_screen (VT *vt);

int ctx_clients_handle_events (Ctx *ctx);

typedef struct _CtxList CtxList;
CtxList *ctx_clients (Ctx *ctx);

void ctx_set_fullscreen (Ctx *ctx, int val);
int ctx_get_fullscreen (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);

typedef enum CtxBackendType {
  CTX_BACKEND_NONE,
  CTX_BACKEND_CTX,
  CTX_BACKEND_RASTERIZER,
  CTX_BACKEND_HASHER,
  CTX_BACKEND_HEADLESS,
  CTX_BACKEND_TERM,
  CTX_BACKEND_FB,
  CTX_BACKEND_KMS,
  CTX_BACKEND_TERMIMG,
  CTX_BACKEND_CAIRO,
  CTX_BACKEND_SDL,
  CTX_BACKEND_DRAWLIST,
  CTX_BACKEND_PDF,
} CtxBackendType;

CtxBackendType ctx_backend_type (Ctx *ctx);

static inline int ctx_backend_is_tiled (Ctx *ctx)
{
  switch (ctx_backend_type (ctx))
  {
    case CTX_BACKEND_FB:
    case CTX_BACKEND_SDL:
    case CTX_BACKEND_KMS:
    case CTX_BACKEND_HEADLESS:
      return 1;
    default:
      return 0;
  }
}

#endif

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,
  ITK_CLIENT_LAYER2       = 1<<6,  // used for having a second set
                                   // to draw - useful for splitting
                                   // scrolled and HUD items
                                   // with HUD being LAYER2
                                  
  ITK_CLIENT_KEEP_ALIVE   = 1<<7,  // do not automatically
  ITK_CLIENT_FINISHED     = 1<<8,  // do not automatically
                                   // remove after process quits
  ITK_CLIENT_PRELOAD      = 1<<9,
  ITK_CLIENT_LIVE         = 1<<10
} CtxClientFlags;
typedef void (*CtxClientFinalize)(CtxClient *client, void *user_data);

int ctx_client_id (CtxClient *client);
int ctx_client_flags (CtxClient *client);
VT *ctx_client_vt (CtxClient *client);
void ctx_client_add_event (CtxClient *client, CtxEvent *event);
const char *ctx_client_title (CtxClient *client);
CtxClient *ctx_client_find (Ctx *ctx, const char *label); // XXX really unstable api?


void *ctx_client_userdata (CtxClient *client);

void ctx_client_quit (CtxClient *client);
CtxClient *vt_get_client (VT *vt);
CtxClient *ctx_client_new (Ctx *ctx,
                           const char *commandline,
                           int x, int y, int width, int height,
                           float font_size,
                           CtxClientFlags flags,
                           void *user_data,
                           CtxClientFinalize client_finalize);

CtxClient *ctx_client_new_argv (Ctx *ctx, char **argv, int x, int y, int width, int height, float font_size, CtxClientFlags flags, void *user_data,
                CtxClientFinalize client_finalize);
int ctx_clients_need_redraw (Ctx *ctx);

CtxClient *ctx_client_new_thread (Ctx *ctx, void (*start_routine)(Ctx *ctx, void *user_data),
                                  int x, int y, int width, int height, float font_size, CtxClientFlags flags, void *user_data, CtxClientFinalize finalize);

extern float ctx_shape_cache_rate;
extern int _ctx_max_threads;

CtxEvent *ctx_event_copy (CtxEvent *event);

void  ctx_client_move         (Ctx *ctx, int id, int x, int y);
void  ctx_client_shade_toggle (Ctx *ctx, int id);
float ctx_client_min_y_pos    (Ctx *ctx);
float ctx_client_max_y_pos    (Ctx *ctx);
void ctx_client_paste (Ctx *ctx, int id, const char *str);
char  *ctx_client_get_selection        (Ctx *ctx, int id);

void  ctx_client_rev_inc      (CtxClient *client);
long  ctx_client_rev          (CtxClient *client);

int   ctx_clients_active      (Ctx *ctx);

CtxClient *ctx_client_by_id (Ctx *ctx, int id);

int ctx_clients_draw (Ctx *ctx, int layer2);

void ctx_client_feed_keystring (CtxClient *client, CtxEvent *event, const char *str);
// need not be public?
void ctx_client_register_events (CtxClient *client, Ctx *ctx, double x0, double y0);

void ctx_client_remove (Ctx *ctx, CtxClient *client);

int  ctx_client_height           (Ctx *ctx, int id);
int  ctx_client_x                (Ctx *ctx, int id);
int  ctx_client_y                (Ctx *ctx, int id);
void ctx_client_raise_top        (Ctx *ctx, int id);
void ctx_client_lower_bottom     (Ctx *ctx, int id);
void ctx_client_iconify          (Ctx *ctx, int id);
int  ctx_client_is_iconified     (Ctx *ctx, int id);
void ctx_client_uniconify        (Ctx *ctx, int id);
void ctx_client_maximize         (Ctx *ctx, int id);
int  ctx_client_is_maximized     (Ctx *ctx, int id);
void ctx_client_unmaximize       (Ctx *ctx, int id);
void ctx_client_maximized_toggle (Ctx *ctx, int id);
void ctx_client_shade            (Ctx *ctx, int id);
int  ctx_client_is_shaded        (Ctx *ctx, int id);
void ctx_client_unshade          (Ctx *ctx, int id);
void ctx_client_toggle_maximized (Ctx *ctx, int id);
void ctx_client_shade_toggle     (Ctx *ctx, int id);
void ctx_client_move             (Ctx *ctx, int id, int x, int y);
int  ctx_client_resize           (Ctx *ctx, int id, int width, int height);
void ctx_client_set_opacity      (Ctx *ctx, int id, float opacity);
float ctx_client_get_opacity     (Ctx *ctx, int id);
void ctx_client_set_title        (Ctx *ctx, int id, const char *title);
const char *ctx_client_get_title (Ctx *ctx, int id);

typedef enum
{
  CTX_CONT             = '\0', // - contains args from preceding entry
  CTX_NOP              = ' ', //
                   //     !    UNUSED
                   //     "    start/end string
                   //     #    comment in parser
                   //     $    UNUSED
                   //     %    percent of viewport width or height
  CTX_EDGE             = '&', // not occuring in commandstream
                   //     '    start/end string
  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
                   //     ,    UNUSED/RESERVED
  CTX_SET_PIXEL        = '-', // 8bit "fast-path" r g b a x y - u8 for rgba, and u16 for x,y
  // set pixel might want a shorter ascii form with hex-color? or keep it an embedded
  // only option?
                   //     .    decimal seperator
                   //     /    UNUSED
 
  /* 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
                   //     :    UNUSED
  CTX_END_FRAME        = ';',
                   //     <    UNUSED
                   //     =    UNUSED/RESERVED
                   //     >    UNUSED
                   //     ?    UNUSED

  CTX_DEFINE_FONT      = 15,

  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_PAINT            = 'D', // 
  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 - variable arg count
  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_START_FRAME      = 'U', // XXX does this belong here?
  CTX_VER_LINE_TO      = 'V', // y
  CTX_APPLY_TRANSFORM  = 'W', // a b c d e f g h i j - for set_transform combine with identity
  CTX_EXIT             = 'X', //
  CTX_ROUND_RECTANGLE  = 'Y', // x y width height radius

  CTX_CLOSE_PATH2      = 'Z', //
  CTX_KERNING_PAIR     = '[', // glA glB kerning, glA and glB in u16 kerning in s32
                       // \   UNUSED
                       // ^   PARSER - vh unit
  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_EDGE_FLIPPED     = '^', // x0 y0 x1 y1 - s16  | also unit
  CTX_STROKE_SOURCE    = '_', // next source definition applies to strokes
  CTX_SOURCE_TRANSFORM = '`',
  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', // XXX - fix!
  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 XXX: move to keyvals?
  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', // XXX remove?
  CTX_CLOSE_PATH       = 'z', //
  CTX_START_GROUP      = '{',
                       // |    UNUSED
  CTX_END_GROUP        = '}',
                       // ~    UNUSED/textenc


  /* 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
   * they also should not be emitted when outputting, even compact mode ctx.
   *
   * rasterizer:    &^+
   * font:          @[
   *
   * unused:        !&<=>?: =/\`,
   * reserved:      '"&   #. %^@
   */

  CTX_FILL_RULE        = 128, // kr rule - u8, default = CTX_FILLE_RULE_EVEN_ODD
  CTX_BLEND_MODE       = 129, // kB mode - u32 , 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 - u32 , 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


  CTX_EXTEND           = 146, // ke u32 extend mode, default=0
  CTX_WRAP_LEFT        = 147, // kL
  CTX_WRAP_RIGHT       = 148, // kR
  CTX_LINE_HEIGHT      = 149, // kH
                              //
  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;
#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


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;
      uint32_t next_active_mask; // the tilehasher active flags for next
                                 // drawing command
      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 _CtxBackend CtxBackend;
void ctx_windowtitle (Ctx *ctx, const char *text);
struct _CtxBackend
{
  Ctx                      *ctx;

  void  (*process)         (Ctx *ctx, CtxCommand *entry);

  /* for interactive/event-handling backends */
  void  (*start_frame)     (Ctx *ctx);
  void  (*end_frame)       (Ctx *ctx);

  void  (*set_windowtitle) (Ctx *ctx, const char *text);

  char *(*get_event)       (Ctx *ctx, int timout_ms);

  void  (*consume_events)  (Ctx *ctx);
  void  (*get_event_fds)   (Ctx *ctx, int *fd, int *count);
  char *(*get_clipboard)   (Ctx *ctx);
  void  (*set_clipboard)   (Ctx *ctx, const char *text);
  void  (*destroy)         (void *backend); /* the free pointers are abused as the differentiatior
                                               between different backends   */
  CtxFlags                  flags;
  CtxBackendType            type;
  void                     *user_data; // not used by ctx core
};

typedef struct _CtxIterator CtxIterator;

void
ctx_logo (Ctx *ctx, float x, float y, float dim);

/* to be freed with ctx_free */
CtxDrawlist *
ctx_current_path (Ctx *ctx);

void
ctx_path_extents (Ctx *ctx, float *ex1, float *ey1, float *ex2, float *ey2);
CtxCommand *ctx_iterator_next (CtxIterator *iterator);
void
ctx_iterator_init (CtxIterator  *iterator,
                   CtxDrawlist  *drawlist,  // replace with Ctx*  ?
                   int           start_pos,
                   int           flags);    // need exposing for font bits
int ctx_iterator_pos (CtxIterator *iterator);

void ctx_handle_events (Ctx *ctx);
#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 _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_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, uint32_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);
int
ctx_get_contents2 (const char     *path,
                   unsigned char **contents,
                   long           *length,
                   long            max_len);

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

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


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

void ctx_apply_matrix (Ctx *ctx, CtxMatrix *matrix);
void ctx_matrix_apply_transform (const CtxMatrix *m, float *x, float *y);
void ctx_matrix_apply_transform_distance (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_translate (CtxMatrix *matrix, float x, float y);
void ctx_matrix_multiply (CtxMatrix       *result,
                          const CtxMatrix *t,
                          const CtxMatrix *s);


/* we already have the start of the file available which disambiguates some
 * of our important supported formats, give preference to magic, then extension
 * then text plain vs binary.
 */
const char *ctx_guess_media_type (const char *path, const char *content, int len);

/* get media-type, with preference towards using extension of path and
 * not reading the data at all.
 */
const char *ctx_path_get_media_type (const char *path);

typedef enum {
  CTX_MEDIA_TYPE_NONE=0,
  CTX_MEDIA_TYPE_TEXT,
  CTX_MEDIA_TYPE_HTML,
  CTX_MEDIA_TYPE_IMAGE,
  CTX_MEDIA_TYPE_VIDEO,
  CTX_MEDIA_TYPE_AUDIO,
  CTX_MEDIA_TYPE_INODE,
  CTX_MEDIA_TYPE_APPLICATION,
} CtxMediaTypeClass;

int ctx_media_type_is_text (const char *media_type);
CtxMediaTypeClass ctx_media_type_class (const char *media_type);


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

Ctx * ctx_new_pdf (const char *path, float width, float height);
void ctx_render_pdf (Ctx *ctx, const char *path);
const char *ctx_str_decode (uint32_t number);
uint32_t    ctx_strhash (const char *str);

#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

void _ctx_write_png (const char *dst_path, int w, int h, int num_chans, void *data);


void ctx_vt_write (Ctx *ctx, uint8_t byte);
int ctx_vt_has_data (Ctx *ctx);
int ctx_vt_read (Ctx *ctx);

#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);
CtxString   *ctx_string_new_printf (const char *format, ...);
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_pre_alloc       (CtxString *string, int size);
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, ...);
void        ctx_string_append_int     (CtxString *string, int val);
void        ctx_string_append_float   (CtxString *string, float val);

#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
/* glyph index: 
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
  jklmnopqrstuvwxyz{|}~  */
static const struct __attribute__ ((packed)) {uint8_t code; uint32_t a; uint32_t b;}
ctx_font_ascii[]={
{15, 0x0000a008, 0x000007a7},/* length:1959 CTX_SUBDIV:8 CTX_BAKE_FONT_SIZE:160 */
{'(', 0x00000008, 0x00000001},/* Roboto*/
{32, 0x6f626f52, 0x00006f74},
{')', 0x00000008, 0x00000001},
{'(', 0x0000004b, 0x00000009},/* Apache Licence, Version 2.0
                                Copyright 2014 Christian Robertson - Apache 2*/
{32, 0x63617041, 0x4c206568},
{'i', 0x636e6563, 0x56202c65},
{'e', 0x6f697372, 0x2e32206e},
{'0', 0x706f430a, 0x67697279},
{'h', 0x30322074, 0x43203431},
{'h', 0x74736972, 0x206e6169},
{'R', 0x7265626f, 0x6e6f7374},
{32, 0x7041202d, 0x65686361},
{32, 0x00000032, 0x00000000},
{')', 0x0000004b, 0x00000009},
{'@', 0x00000020, 0x000021dd},/*                 x-advance: 33.863281 */
{'@', 0x00000021, 0x00002333},/*        !        x-advance: 35.199219 */
{'M', 0x41c08889, 0xc2c22223},
{'l', 0xbf5ddde0, 0x428b5556},
{'4', 0x0000ffa7, 0xfdd3fff9},
{'6', 0x00000067, 0x02d6ff96},
{'8', 0xd80ee800, 0xf02bf00e},
{'8', 0x102b001c, 0x280f100f},
{'8', 0x27f11600, 0x10d510f2},
{'8', 0xf0d500e4, 0xd9f2f0f2},
{'@', 0x00000022, 0x00002bbb},/*        "        x-advance: 43.730469 */
{'M', 0x41944445, 0xc2baaaab},
{'l', 0xc0000000, 0x41be6664},
{'l', 0xc0ecccce, 0x00000000},
{'4', 0xfefa0000, 0x0000004b},
{'6', 0x00480000, 0x00000090},
{'l', 0xc0000000, 0x41be6664},
{'l', 0xc0ecccd0, 0x00000000},
{'l', 0x00000000, 0xc2037778},
{'l', 0x41166668, 0x00000000},
{'l', 0x00000000, 0x41111118},
{'[', 0x00770022, 0x000000bb},/*kerning*/
{'@', 0x00000023, 0x00005411},/*        #        x-advance: 84.066406 */
{'M', 0x4236eef0, 0x00000000},
{'l', 0x40aaaaa8, 0xc1daaaab},
{'l', 0xc18cccce, 0x00000000},
{'l', 0xc0aaaaa8, 0x41daaaab},
{'l', 0xc118888a, 0x00000000},
{'l', 0x40aaaaac, 0xc1daaaab},
{'l', 0xc1800000, 0x00000000},
{'l', 0xb5000000, 0xc1133336},
{'l', 0x418e6667, 0x00000000},
{'l', 0x40911110, 0xc1bc4444},
{'l', 0xc18a2222, 0x00000000},
{'l', 0xb5800000, 0xc1144444},
{'l', 0x4198888a, 0x00000000},
{'l', 0x40acccc8, 0xc1dddde0},
{'l', 0x4119999c, 0x00000000},
{'l', 0xc0acccd0, 0x41dddde0},
{'l', 0x418cccce, 0x00000000},
{'l', 0x40acccd0, 0xc1dddde0},
{'l', 0x41188888, 0x00000000},
{'l', 0xc0acccd0, 0x41dddde0},
{'l', 0x41588888, 0x00000000},
{'l', 0x00000000, 0x41144444},
{'l', 0xc1755558, 0x00000000},
{'l', 0xc0933330, 0x41bc4444},
{'l', 0x416eeef0, 0x00000000},
{'l', 0x00000000, 0x41133336},
{'4', 0x0000ff7b, 0x00daffd6},
{'6', 0x0000ffb4, 0xfedcffad},
{'l', 0x418ccccc, 0x00000000},
{'l', 0x40933338, 0xc1bc4444},
{'l', 0xc18cccce, 0x00000000},
{'l', 0xc0933330, 0x41bc4444},
{'@', 0x00000024, 0x00004cbb},/*        $        x-advance: 76.730469 */
{'M', 0x428aeeef, 0xc1c91112},
{'q', 0x00000000, 0x4139999b},
{0, 0xc0dffff8, 0x4192aaac},
{'9', 0x0035ffc9, 0x003fff6b},
{'4', 0x00650000, 0x0000ffb1},
{'l', 0x00000000, 0xc14aaaac},
{'q', 0xc1244446, 0xbf888887},
{0, 0xc1933334, 0xc0f9999a},
{'9', 0xffcaffc0, 0xff50ffc0},
{'l', 0x41466666, 0x00000000},
{'q', 0x00000000, 0x41344446},
{0, 0x40c00004, 0x41744446},
{'8', 0x1f641f30, 0xdf6e0047},
{'8', 0xa527de27, 0xadddd000},
{'q', 0xc08aaaa8, 0xc08cccd0},
{0, 0xc1700000, 0xc0f99998},
{'q', 0xc149999a, 0xc0800000},
{0, 0xc19ccccd, 0xc129999c},
{'q', 0xc0e00000, 0xc0d33330},
{0, 0xc0e00000, 0xc1919998},
{'q', 0x00000000, 0xc1311118},
{0, 0x40cccccc, 0xc18f7778},
{'9', 0xffc90033, 0xffbe008b},
{'4', 0xff8c0000, 0x00000050},
{'l', 0x00000000, 0x416aaaa8},
{'q', 0x41333334, 0x3fbbbbc0},
{0, 0x418b3334, 0x41166668},
{'9', 0x003e0032, 0x00ac0032},
{'l', 0xc1444448, 0x00000000},
{'q', 0x00000000, 0xc10eeef0},
{0, 0xc0888888, 0xc16aaaa8},
{'8', 0xd29ed2de, 0x229d00bd},
{'8', 0x59e122e1, 0x531f3200},
{'q', 0x407bbbc0, 0x40822220},
{0, 0x41844444, 0x41055554},
{'q', 0x414aaaac, 0x40888890},
{0, 0x4198888a, 0x412aaaac},
{'q', 0x40ceeee8, 0x40caaab0},
{0, 0x40ceeee8, 0x418d5556},
{'@', 0x00000025, 0x00006400},/*        %        x-advance: 100.000000 */
{'M', 0x40e00001, 0xc29ccccd},
{'q', 0x00000000, 0xc1044448},
{0, 0x40aaaaab, 0xc1622228},
{'q', 0x40aaaaac, 0xc0bddde0},
{0, 0x4168888a, 0xc0bddde0},
{'q', 0x41155554, 0x00000000},
{0, 0x41699998, 0x40bddde0},
{'9', 0x002e002a, 0x0071002a},
{'l', 0x00000000, 0x40a44440},
{'q', 0x00000000, 0x41022220},
{0, 0xc0a88888, 0x41611110},
{'q', 0xc0a88888, 0x40bbbbc0},
{0, 0xc168888a, 0x40bbbbc0},
{'q', 0xc1144446, 0x00000000},
{0, 0xc16aaaac, 0xc0bbbbc0},
{'9', 0xffd1ffd6, 0xff90ffd6},
{'6', 0xffd70000, 0x0029004a},
{'8', 0x42142400, 0x1d411d15},
{'8', 0xe43f002a, 0xbe14e314},
{'l', 0x00000000, 0xc0a44440},
{'8', 0xbeebdb00, 0xe3c0e3ec},
{'8', 0x1dc000d6, 0x42ec1dec},
{'6', 0x00290000, 0xffb001e7},
{'l', 0xc23d999a, 0x4297bbbc},
{'4', 0xffddffc9, 0xfda2017b},
{'6', 0x00230037, 0x01dbff49},
{'q', 0x00000000, 0xc1033330},
{0, 0x40aaaaa8, 0xc1611110},
{'q', 0x40aaaaa8, 0xc0bddde0},
{0, 0x4168888c, 0xc0bddde0},
{'q', 0x41155558, 0x00000000},
{0, 0x41699998, 0x40bddde0},
{'9', 0x002e002a, 0x0070002a},
{'l', 0x00000000, 0x40a66668},
{'q', 0x00000000, 0x41033333},
{0, 0xc0a88890, 0x41622222},
{'q', 0xc0a88880, 0x40bbbbbd},
{0, 0xc1688888, 0x40bbbbbd},
{'q', 0xc1155558, 0x00000000},
{0, 0xc16aaaac, 0xc0bbbbbc},
{'9', 0xffd1ffd6, 0xff8fffd6},
{'6', 0xffd70000, 0x0029004a},
{'8', 0x42142500, 0x1d411d15},
{'8', 0xe440002b, 0xbd14e314},
{'l', 0x00000000, 0xc0a66668},
{'8', 0xbeebdb00, 0xe3c0e3ec},
{'8', 0x1dc000d6, 0x42ec1cec},
{'l', 0x00000000, 0x40a66668},
{'@', 0x00000026, 0x000054ee},/*        &        x-advance: 84.929688 */
{'M', 0x428b5556, 0x00000000},
{'l', 0xc0ccccd0, 0xc0f55556},
{'8', 0x35a323d8, 0x129512cb},
{'q', 0xc168888a, 0xb4000000},
{0, 0xc1b77778, 0xc0f77779},
{'q', 0xc1066667, 0xc0f77778},
{0, 0xc1066667, 0xc19dddde},
{'q', 0x00000000, 0xc10aaaac},
{0, 0x40a66668, 0xc1699998},
{'8', 0xa26cd02a, 0xa6c0d0d8},
{'q', 0xc03bbbbc, 0xc0a88890},
{0, 0xc03bbbbc, 0xc1300000},
{'q', 0x00000000, 0xc1355558},
{0, 0x40d55556, 0xc18b3334},
{'q', 0x40d55558, 0xc0c44440},
{0, 0x418d5557, 0xc0c44440},
{'q', 0x412bbbbc, 0x00000000},
{0, 0x4186eeee, 0x40c44440},
{'q', 0x40c66668, 0x40c22220},
{0, 0x40c66668, 0x41655558},
{'8', 0x5ee43800, 0x4ab426e4},
{'4', 0x002bffc6, 0x00ce00ac},
{'9', 0xffbb0024, 0xff660024},
{'l', 0x41311110, 0x00000000},
{'9', 0x00880000, 0x00e1ffbf},
{'4', 0x0084006e, 0x0000ff8a},
{'m', 0xc22a6668, 0xc2968889},
{'9', 0x00310000, 0x0080003e},
{'l', 0x40e44448, 0xc0a22220},
{'8', 0xd233e823, 0xc311ea11},
{'8', 0xc7e8e100, 0xe6bbe6e8},
{'8', 0x1fba00d2, 0x49e81ee8},
{'m', 0xc0fddddc, 0x42448889},
{'q', 0x00000000, 0x40e22224},
{0, 0x40955554, 0x41433334},
{'q', 0x40955558, 0x40a44446},
{0, 0x41655556, 0x40a44446},
{'9', 0x0000004e, 0xffc5008f},
{'4', 0xff1eff43, 0x0010ffea},
{'8', 0x4dba2ac7, 0x34f423f4},
{'@', 0x00000027, 0x000017dd},/*        '        x-advance: 23.863281 */
{'M', 0x41877778, 0xc2ccccce},
{'l', 0x00000000, 0x40eaaab0},
{'l', 0xbfb33338, 0x41c44444},
{'l', 0xc109999a, 0x00000000},
{'l', 0x3d8888c0, 0xc1feeef0},
{'l', 0x411eeef0, 0x00000000},
{'[', 0x00770027, 0x000000bb},/*kerning*/
{'@', 0x00000028, 0x00002ebb},/*        (        x-advance: 46.730469 */
{'M', 0x410eeeef, 0xc21dddde},
{'q', 0x00000000, 0xc19aaaac},
{0, 0x40b11112, 0xc2073334},
{'q', 0x40b11114, 0xc1677778},
{0, 0x41522224, 0xc1bccccc},
{'9', 0xffb7003c, 0xff9b006f},
{'l', 0x40266660, 0x41022228},
{'q', 0xc0fbbbb8, 0x40bdddd0},
{0, 0xc1766666, 0x41a99998},
{'q', 0xc0eeeef0, 0x41733338},
{0, 0xc0eeeef0, 0x42262223},
{'q', 0x00000000, 0x41caaaab},
{0, 0x40eeeef0, 0x4222eeef},
{'9', 0x007b003c, 0x00ae007b},
{'l', 0xc0266660, 0x40eeeef0},
{'q', 0xc0caaab0, 0xc05ddde0},
{0, 0xc15eeef0, 0xc149999c},
{'q', 0xc0f33334, 0xc1122222},
{0, 0xc1522224, 0xc1bc4444},
{'q', 0xc0b11112, 0xc167777a},
{0, 0xc0b11112, 0xc20aaaab},
{'[', 0x00560028, 0x00000155},/*kerning*/
{'[', 0x00570028, 0x00000133},/*kerning*/
{'[', 0x00590028, 0x00000177},/*kerning*/
{'@', 0x00000029, 0x00002f88},/*        )        x-advance: 47.531250 */
{'M', 0x42173334, 0xc21b3334},
{'q', 0x00000000, 0x419bbbbd},
{0, 0xc0b11110, 0x4207bbbc},
{'q', 0xc0b11114, 0x4167777a},
{0, 0xc1533336, 0x41bcccce},
{'9', 0x0049ffc4, 0x0064ff92},
{'l', 0xc0266669, 0xc0eeeef0},
{'q', 0x40f9999a, 0xc0bddde0},
{0, 0x41755556, 0xc1ac4445},
{'q', 0x40f11110, 0xc179999b},
{0, 0x40f11110, 0xc227bbbc},
{'q', 0x00000000, 0xc186eef2},
{0, 0xc06eeef0, 0xc1eb3334},
{'q', 0xc06eeef0, 0xc14999a0},
{0, 0xc1111111, 0xc1a6eef0},
{'9', 0xffbeffd6, 0xff9fffb0},
{'l', 0x40266666, 0xc0f11110},
{'q', 0x40c88889, 0x40622220},
{0, 0x415dddde, 0x414aaab0},
{'q', 0x40f55558, 0x41122220},
{0, 0x41533336, 0x41bccccc},
{'q', 0x40b11110, 0x41666668},
{0, 0x40b11110, 0x4209ddde},
{'@', 0x0000002a, 0x00003acc},/*        *        x-advance: 58.796875 */
{'M', 0x4109999a, 0xc23ccccd},
{'l', 0x41566668, 0xc1933336},
{'l', 0xc1a11112, 0xc0c00000},
{'l', 0x4048888a, 0xc1200000},
{'l', 0x41a11112, 0x40ecccd0},
{'l', 0xbf1999a0, 0xc1b77778},
{'l', 0x41222222, 0x00000000},
{'l', 0xbf2aaac0, 0x41baaaac},
{'l', 0x419eeef0, 0xc0ecccd0},
{'l', 0x40444450, 0x41233338},
{'l', 0xc1a3bbbe, 0x40c22220},
{'l', 0x41522224, 0x41908888},
{'l', 0xc1044444, 0x40c66668},
{'l', 0xc1455556, 0xc199999a},
{'l', 0xc1411112, 0x4195ddde},
{'l', 0xc1055556, 0xc0c22220},
{'@', 0x0000002b, 0x00004d77},/*        +        x-advance: 77.464844 */
{'M', 0x428f7778, 0xc221ddde},
{'l', 0xc1d8888a, 0x00000000},
{'l', 0x00000000, 0x41f5ddde},
{'l', 0xc1455554, 0x00000000},
{'l', 0x00000000, 0xc1f5ddde},
{'l', 0xc1d91112, 0x00000000},
{'l', 0xb5000000, 0xc139999c},
{'l', 0x41d91112, 0x00000000},
{'l', 0x00000000, 0xc1e2aaaa},
{'l', 0x41455554, 0x00000000},
{'l', 0x00000000, 0x41e2aaaa},
{'l', 0x41d8888a, 0x00000000},
{'l', 0x00000000, 0x4139999c},
{'@', 0x0000002c, 0x00001add},/*        ,        x-advance: 26.863281 */
{'M', 0x41a4cccd, 0xc16aaaab},
{'l', 0x00000000, 0x411eeeef},
{'8', 0x66e83000, 0x5abc35e8},
{'l', 0xc0e00000, 0xc09bbbbe},
{'9', 0xffb90033, 0xff6e0034},
{'l', 0x00000000, 0xc12eeef0},
{'l', 0x41411111, 0x00000000},
{'@', 0x0000002d, 0x000025bb},/*        -        x-advance: 37.730469 */
{'M', 0x420c4445, 0xc2395556},
{'l', 0x00000000, 0x41222224},
{'l', 0xc2022223, 0x00000000},
{'l', 0x35400000, 0xc1222224},
{'l', 0x42022223, 0x00000000},
{'@', 0x0000002e, 0x00002400},/*        .        x-advance: 36.000000 */
{'M', 0x4119999a, 0xc0d11112},
{'8', 0xd60fe700, 0xef2def10},
{'8', 0x112d001d, 0x2a101110},
{'8', 0x29f01800, 0x11d311f1},
{'8', 0xefd300e3, 0xd7f1eff1},
{'@', 0x0000002f, 0x00003855},/*        /        x-advance: 56.332031 */
{'M', 0x42515556, 0xc2c22223},
{'l', 0xc221ddde, 0x42d2ccce},
{'l', 0xc129999c, 0xb6000000},
{'l', 0x42222223, 0xc2d2ccce},
{'l', 0x41288888, 0x00000000},
{'@', 0x00000030, 0x00004cbb},/*        0        x-advance: 76.730469 */
{'M', 0x428a0000, 0xc225ddde},
{'q', 0x00000000, 0x41beeeef},
{0, 0xc1044440, 0x42055555},
{'q', 0xc1033334, 0x41177779},
{0, 0xc1b2aaac, 0x41177779},
{'q', 0xc15bbbbc, 0x34c00000},
{0, 0xc1b11111, 0xc1133334},
{'9', 0xffb7ffbe, 0xff00ffbc},
{'l', 0x00000000, 0xc182aaac},
{'q', 0x00000000, 0xc1be6668},
{0, 0x41055555, 0xc203bbbc},
{'q', 0x41055556, 0xc1133330},
{0, 0x41b22224, 0xc1133330},
{'q', 0x415eeef0, 0x00000000},
{0, 0x41b1999a, 0x410eeee8},
{'9', 0x00460042, 0x00fd0044},
{'6', 0x00820000, 0xff7aff9d},
{'q', 0x00000000, 0xc1833334},
{0, 0xc0955558, 0xc1b9999c},
{'8', 0xca93cadb, 0x349500bb},
{'9', 0x0034ffdb, 0x00b3ffda},
{'l', 0x00000000, 0x419e6668},
{'q', 0x00000000, 0x41822222},
{0, 0x40977778, 0x41bbbbbc},
{'8', 0x396c3926, 0xc86c0048},
{'q', 0x40933338, 0xc0e44446},
{0, 0x40955558, 0xc1b88888},
{'l', 0x00000000, 0xc19b3334},
{'@', 0x00000031, 0x00004cbb},/*        1        x-advance: 76.730469 */
{'M', 0x42426667, 0xc2c33334},
{'l', 0x00000000, 0x42c33334},
{'l', 0xc1455554, 0x00000000},
{'l', 0x00000000, 0xc2a46667},
{'l', 0xc1c6eef0, 0x41111110},
{'l', 0xb5800000, 0xc1322220},
{'l', 0x420d1111, 0xc1555558},
{'l', 0x3ff77780, 0x00000000},
{'@', 0x00000032, 0x00004cbb},/*        2        x-advance: 76.730469 */
{'M', 0x428f5556, 0xc1222223},
{'l', 0x00000000, 0x41222223},
{'4', 0x0000fe04, 0xffba0000},
{'l', 0x4203bbbc, 0xc212aaac},
{'q', 0x41022220, 0xc1133330},
{0, 0x412eeef0, 0xc1699994},
{'8', 0xaa16d516, 0x9fddc700},
{'q', 0xc08cccc8, 0xc0a22220},
{0, 0xc1477778, 0xc0a22220},
{'q', 0xc11bbbbc, 0x00000000},
{0, 0xc1688888, 0x40b11110},
{'9', 0x002cffda, 0x0071ffda},
{'l', 0xc1455556, 0x00000000},
{'q', 0x35000000, 0xc1444440},
{0, 0x41011112, 0xc1a88888},
{'q', 0x41011112, 0xc10cccc8},
{0, 0x41bccccd, 0xc10cccc8},
{'q', 0x415bbbbc, 0x00000000},
{0, 0x41abbbbc, 0x40e44440},
{'q', 0x40f999a0, 0x40e44440},
{0, 0x40f999a0, 0x41966668},
{'q', 0x00000000, 0x41088884},
{0, 0xc0a88888, 0x41899998},
{'9', 0x0044ffd6, 0x0087ff99},
{'l', 0xc1d00001, 0x41e1999a},
{'l', 0x4242aaac, 0x35800000},
{'@', 0x00000033, 0x00004cbb},/*        3        x-advance: 76.730469 */
{'M', 0x41d08889, 0xc231ddde},
{'4', 0xffaf0000, 0x00000048},
{'q', 0x41188888, 0xbd888800},
{0, 0x41633334, 0xc0999998},
{'q', 0x40955558, 0xc0977780},
{0, 0x40955558, 0xc13cccd0},
{'q', 0x00000000, 0xc1888888},
{0, 0xc1877778, 0xc1888888},
{'8', 0x249b00c2, 0x60da23da},
{'l', 0xc1455556, 0x00000000},
{'q', 0x35000000, 0xc1322220},
{0, 0x41033334, 0xc1977778},
{'q', 0x41044444, 0xc0f99990},
{0, 0x41addddf, 0xc0f99990},
{'q', 0x41522220, 0x00000000},
{0, 0x41a9999a, 0x40e00000},
{'q', 0x41022220, 0x40ddddd0},
{0, 0x41022220, 0x41a3bbbc},
{'8', 0x5ce32b00, 0x4ca431e4},
{'8', 0x4d69194d, 0x691c341c},
{'q', 0x00000000, 0x4159999a},
{0, 0xc10ccccc, 0x41a80000},
{'q', 0xc10ccccc, 0x40eccccf},
{0, 0xc1af7778, 0x40eccccf},
{'q', 0xc14aaaac, 0xb4000000},
{0, 0xc1adddde, 0xc0e00001},
{'9', 0xffc8ffb8, 0xff60ffb8},
{'l', 0x41455556, 0x00000000},
{'8', 0x62273d00, 0x246c2428},
{'8', 0xdd6b0043, 0x9427dd27},
{'q', 0x00000000, 0xc1111112},
{0, 0xc0b33338, 0xc1555556},
{'q', 0xc0b33330, 0xc08aaaa8},
{0, 0xc1700000, 0xc08aaaa8},
{'l', 0xc10cccce, 0x00000000},
{'@', 0x00000034, 0x00004cbb},/*        4        x-advance: 76.730469 */
{'M', 0x40622223, 0xc1ee6667},
{'l', 0x422ddddf, 0xc2868889},
{'l', 0x41522220, 0x00000000},
{'l', 0x00000000, 0x4280ccce},
{'l', 0x4158888c, 0xb6800000},
{'l', 0x00000000, 0x41222222},
{'l', 0xc158888c, 0x00000000},
{'l', 0x00000000, 0x41b44445},
{'l', 0xc1455554, 0x00000000},
{'4', 0xff4c0000, 0x0000fe9e},
{'6', 0xffc60000, 0xffea0070},
{'l', 0x41f22223, 0x00000000},
{'l', 0x00000000, 0xc23eaaab},
{'l', 0xbfc44440, 0x402eeee0},
{'l', 0xc1e5dddf, 0x4233bbbd},
{'@', 0x00000035, 0x00004cbb},/*        5        x-advance: 76.730469 */
{'M', 0x41bd5556, 0xc238cccd},
{'l', 0xc11dddde, 0xc0222230},
{'l', 0x409bbbbc, 0xc2415556},
{'l', 0x42473333, 0x00000000},
{'4', 0x005b0000, 0x0000fec6},
{'l', 0xc03bbbc0, 0x41d33334},
{'q', 0x40eaaab0, 0xc0866668},
{0, 0x4181999a, 0xc0866668},
{'q', 0x41566668, 0x00000000},
{0, 0x41a91112, 0x410ddde0},
{'q', 0x40f99998, 0x410ccccc},
{0, 0x40f99998, 0x41bd5556},
{'q', 0x00000000, 0x415eeef0},
{0, 0xc0f33330, 0x41b91112},
{'q', 0xc0f33338, 0x41122221},
{0, 0xc1b91112, 0x41122221},
{'q', 0xc13cccce, 0x34c00000},
{0, 0xc1a33334, 0xc0d33333},
{'9', 0xffcbffbc, 0xff5effb1},
{'l', 0x413bbbbd, 0x00000000},
{'q', 0x40155550, 0x4185ddde},
{0, 0x4194cccd, 0x4185ddde},
{'q', 0x410bbbbc, 0x35800000},
{0, 0x41588888, 0xc0bdddde},
{'q', 0x409999a0, 0xc0bdddde},
{0, 0x409999a0, 0xc1808888},
{'q', 0x00000000, 0xc1122224},
{0, 0xc0a22228, 0xc1755558},
{'q', 0xc0a00000, 0xc0c88888},
{0, 0xc1655554, 0xc0c88888},
{'8', 0x0db500cf, 0x24cd0de7},
{'@', 0x00000036, 0x00004cbb},/*        6        x-advance: 76.730469 */
{'M', 0x428c6667, 0xc1fd5556},
{'q', 0x00000000, 0x415cccce},
{0, 0xc0f77778, 0x41bb3334},
{'q', 0xc0f55558, 0x41199999},
{0, 0xc1b33334, 0x41199999},
{'q', 0xc1277778, 0x34c00000},
{0, 0xc18b3334, 0xc0b11111},
{'q', 0xc0ddddde, 0xc0b33333},
{0, 0xc1266667, 0xc1622222},
{'9', 0xffbcffe5, 0xff74ffe5},
{'l', 0x00000000, 0xc0b999a0},
{'q', 0x00000000, 0xc15aaaa8},
{0, 0x40733334, 0xc1d33334},
{'q', 0x4077777c, 0xc14bbbb8},
{0, 0x415eeef1, 0xc1a6eef0},
{'9', 0xffbf0050, 0xffbf00e6},
{'4', 0x00000008, 0x00530000},
{'q', 0xc14eeef0, 0x00000000},
{0, 0xc1a11112, 0x40911110},
{'q', 0xc0e44448, 0x40911110},
{0, 0xc12aaaac, 0x413cccd0},
{'q', 0xc05ddde0, 0x40e66660},
{0, 0xc0866668, 0x41777778},
{'q', 0x40f77778, 0xc10bbbc0},
{0, 0x41a6eef0, 0xc10bbbc0},
{'q', 0x411aaaac, 0x00000000},
{0, 0x417ccccc, 0x40955558},
{'q', 0x40c66668, 0x40955558},
{0, 0x41122224, 0x41411114},
{'9', 0x003a0017, 0x007a0017},
{'m', 0xc243bbbc, 0xc0777780},
{'q', 0xb6000000, 0x414eeef2},
{0, 0x40b77774, 0x419e6668},
{'8', 0x3668362e, 0xcf690044},
{'q', 0x40977778, 0xc0c88888},
{0, 0x40977778, 0xc1800000},
{'q', 0x00000000, 0xc10aaaaa},
{0, 0xc08aaab0, 0xc178888a},
{'8', 0xca96cade, 0x1ea300cd},
{'q', 0xc0a8888c, 0x40777770},
{0, 0xc0eaaaac, 0x41166668},
{'l', 0x00000000, 0x40955550},
{'@', 0x00000037, 0x00004cbb},/*        7        x-advance: 76.730469 */
{'M', 0x428d999a, 0xc2c22223},
{'l', 0x00000000, 0x40dddde0},
{'l', 0xc220cccd, 0x42b44445},
{'l', 0xc1500002, 0x00000000},
{'l', 0x4220888a, 0xc2adddde},
{'l', 0xc2522223, 0x00000000},
{'l', 0xb5000000, 0xc1222228},
{'l', 0x42833334, 0x00000000},
{'@', 0x00000038, 0x00004cbb},/*        8        x-advance: 76.730469 */
{'M', 0x428a8889, 0xc1d22223},
{'q', 0x00000000, 0x41555556},
{0, 0xc10eeef0, 0x41a3bbbc},
{'q', 0xc10ddddc, 0x40e44447},
{0, 0xc1af7778, 0x40e44447},
{'q', 0xc1511112, 0xb4000000},
{0, 0xc1b00000, 0xc0e44445},
{'q', 0xc10ddddf, 0xc0e44446},
{0, 0xc10ddddf, 0xc1a3bbbc},
{'q', 0x00000000, 0xc1022224},
{0, 0x408aaaac, 0xc1655558},
{'8', 0xb55ece23, 0xbaaee7cd},
{'q', 0xc06eeef0, 0xc0b77778},
{0, 0xc06eeef0, 0xc14ddddc},
{'q', 0x00000000, 0xc14bbbc0},
{0, 0x41011112, 0xc19d5558},
{'q', 0x41022222, 0xc0ddddd0},
{0, 0x41a44445, 0xc0ddddd0},
{'q', 0x41477778, 0x00000000},
{0, 0x41a44444, 0x40ddddd0},
{'q', 0x41022220, 0x40dddde0},
{0, 0x41022220, 0x419d5558},
{'8', 0x67e23900, 0x46ae2de2},
{'q', 0x40f11110, 0x404cccd0},
{0, 0x41400000, 0x41177778},
{'9', 0x00320023, 0x00720023},
{'m', 0xc169999c, 0xc2355556},
{'8', 0xa1dcc600, 0xdba2dbdc},
{'8', 0x24a300c6, 0x61dd23dd},
{'8', 0x60233c00, 0x245e2423},
{'8', 0xdc5d003a, 0xa024dc24},
{'m', 0x400cccd0, 0x42344446},
{'8', 0x96d7bf00, 0xd795d7d7},
{'8', 0x299500bd, 0x6ad929d9},
{'8', 0x68274300, 0x256c2528},
{'8', 0xdb6c0044, 0x9827db27},
{'@', 0x00000039, 0x00004cbb},/*        9        x-advance: 76.730469 */
{'M', 0x42877778, 0xc25aaaab},
{'q', 0x00000000, 0x41177778},
{0, 0xbfd55540, 0x41991110},
{'q', 0xbfcccd00, 0x4119999c},
{0, 0xc0caaab0, 0x418ddddf},
{'q', 0xc0955558, 0x41011112},
{0, 0xc15ddde0, 0x41500001},
{'9', 0x0027ffb7, 0x0027ff34},
{'l', 0x00000000, 0xc1277778},
{'q', 0x416bbbbe, 0x00000000},
{0, 0x41aeeeef, 0xc0933334},
{'q', 0x40e66668, 0xc0933334},
{0, 0x41200004, 0xc1400000},
{'q', 0x40377770, 0xc0eeeef0},
{0, 0x404cccc0, 0xc17ddde0},
{'8', 0x3bb624e2, 0x16a316d5},
{'q', 0xc119999a, 0x00000000},
{0, 0xc17bbbbc, 0xc09999a0},
{'q', 0xc0c44446, 0xc0999998},
{0, 0xc1111112, 0xc1433334},
{'q', 0xc03bbbbc, 0xc0eeeef0},
{0, 0xc03bbbbc, 0xc1766664},
{'q', 0x00000000, 0xc15ddde0},
{0, 0x40f33334, 0xc1bd5558},
{'q', 0x40f55556, 0xc11dddd8},
{0, 0x41b44446, 0xc11dddd8},
{'q', 0x41311110, 0x00000000},
{0, 0x418eeeee, 0x40b77770},
{'q', 0x40dbbbc0, 0x40b77780},
{0, 0x411eeef4, 0x416bbbc0},
{'9', 0x00480019, 0x00960019},
{'6', 0x00230000, 0xffaafe79},
{'q', 0xb6000000, 0x410aaab0},
{0, 0x408aaaa8, 0x417bbbc0},
{'8', 0x386a3823, 0xe25b0032},
{'9', 0xffe20029, 0xffb5003c},
{'l', 0x00000000, 0xc09bbbc0},
{'q', 0x00000000, 0xc1544448},
{0, 0xc0b55558, 0xc1a2aaac},
{'8', 0xc898c8d4, 0x349600bc},
{'q', 0xc0955554, 0x40ceeef0},
{0, 0xc0955554, 0x41811110},
{'@', 0x0000003a, 0x00002111},/*        :        x-advance: 33.066406 */
{'M', 0x410dddde, 0xc0d11112},
{'8', 0xd60fe700, 0xef2def10},
{'8', 0x112d001d, 0x2a101110},
{'8', 0x29f01800, 0x11d311f1},
{'8', 0xefd300e3, 0xd7f1eff1},
{'m', 0x3d888880, 0xc26b7778},
{'8', 0xd60fe700, 0xef2def10},
{'8', 0x112d001d, 0x2a101110},
{'8', 0x29f01800, 0x11d311f1},
{'8', 0xefd300e3, 0xd7f1eff1},
{'@', 0x0000003b, 0x00001cdd},/*        ;        x-advance: 28.863281 */
{'M', 0x40eaaaab, 0xc282cccd},
{'8', 0xd60fe700, 0xef2def10},
{'8', 0x112d001d, 0x2a101110},
{'8', 0x29f01800, 0x11d311f1},
{'8', 0xefd300e3, 0xd7f1eff1},
{'m', 0x41611112, 0x424aeeef},
{'l', 0x00000000, 0x411eeef0},
{'8', 0x66e83000, 0x5abc35e8},
{'l', 0xc0e00000, 0xc09bbbbe},
{'9', 0xffb90033, 0xff6e0034},
{'l', 0x00000000, 0xc12eeef0},
{'l', 0x41411112, 0x00000000},
{'@', 0x0000003c, 0x00004566},/*        <        x-advance: 69.398438 */
{'M', 0x426d5556, 0xc1511112},
{'l', 0xc25a2223, 0xc1ca2223},
{'l', 0x35800000, 0xc11aaaac},
{'l', 0x425a2223, 0xc1c9999a},
{'l', 0x00000000, 0x41511114},
{'l', 0xc2266667, 0x41891110},
{'l', 0x42266667, 0x4186eef0},
{'l', 0x00000000, 0x41511112},
{'@', 0x0000003d, 0x00004aee},/*        =        x-advance: 74.929688 */
{'M', 0x42837778, 0xc2820000},
{'l', 0x00000000, 0x412bbbb8},
{'4', 0x0000fe44, 0xffab0000},
{'6', 0x000001bc, 0x00dd0000},
{'l', 0x00000000, 0x412bbbbc},
{'l', 0xc25e6667, 0x00000000},
{'l', 0xb5800000, 0xc12bbbbc},
{'l', 0x425e6667, 0x00000000},
{'@', 0x0000003e, 0x00004766},/*        >        x-advance: 71.398438 */
{'M', 0x41100000, 0xc292aaab},
{'l', 0x4263bbbc, 0x41c9999a},
{'l', 0x00000000, 0x411bbbbc},
{'l', 0xc263bbbc, 0x41ca2222},
{'l', 0x00000000, 0xc14bbbbc},
{'l', 0x42308889, 0xc18c4444},
{'l', 0xc2308889, 0xc189999a},
{'l', 0x00000000, 0xc14bbbbc},
{'@', 0x0000003f, 0x00004088},/*        ?        x-advance: 64.531250 */
{'M', 0x4210cccd, 0xc1daaaab},
{'l', 0xc1466666, 0x00000000},
{'q', 0x3d888900, 0xc11aaaae},
{0, 0x402aaaa8, 0xc167777a},
{'8', 0xa646da14, 0xbb3fdc23},
{'8', 0xa91bdf1b, 0xaae3ca00},
{'8', 0xe0ace0e3, 0x19ad00d2},
{'9', 0x0019ffdc, 0x0050ffdb},
{'l', 0xc1455556, 0x00000000},
{'q', 0x3e0888a0, 0xc1344448},
{0, 0x41000001, 0xc18d5558},
{'q', 0x40fdddde, 0xc0ccccc0},
{0, 0x419bbbbc, 0xc0ccccc0},
{'q', 0x414bbbbc, 0x00000000},
{0, 0x419d5556, 0x40d99990},
{'q', 0x40e00000, 0x40d999a0},
{0, 0x40e00000, 0x41944444},
{'q', 0x00000000, 0x410eeef0},
{0, 0xc0a88888, 0x4180888a},
{'q', 0xc0a88888, 0x40e22228},
{0, 0xc1377778, 0x414cccd0},
{'9', 0x002dffcf, 0x0086ffcf},
{'m', 0xc14eeeee, 0x41a91111},
{'8', 0xd80ee800, 0xf02bf00e},
{'8', 0x102b001c, 0x280e100e},
{'8', 0x27f21600, 0x10d510f2},
{'8', 0xf0d500e4, 0xd9f2f0f2},
{'@', 0x00000040, 0x00007a99},/*        @        x-advance: 122.597656 */
{'M', 0x42a8aaab, 0x41c22223},
{'8', 0x23a318db, 0x0b970bc9},
{'q', 0xc1caaaac, 0x00000000},
{0, 0xc21cccce, 0xc1855556},
{'q', 0xc15ccccb, 0xc185ddde},
{0, 0xc1499998, 0xc235999a},
{'q', 0x3f4cccd0, 0xc1922222},
{0, 0x40fddde0, 0xc2026666},
{'q', 0x40e66668, 0xc1666668},
{0, 0x419d5556, 0xc1b55558},
{'q', 0x41488888, 0xc1044440},
{0, 0x41ea2224, 0xc1044440},
{'q', 0x41cd5554, 0x00000000},
{0, 0x421bbbbc, 0x4185dddc},
{'q', 0x41555558, 0x4185ddde},
{0, 0x41433330, 0x42348889},
{'q', 0xbeaaaa00, 0x41033336},
{0, 0xc0488880, 0x41822223},
{'q', 0xc02eef00, 0x41011112},
{0, 0xc10aaaa8, 0x41566668},
{'q', 0xc0bbbbc0, 0x40a88889},
{0, 0xc1777778, 0x40a88889},
{'q', 0xc1488890, 0x00000000},
{0, 0xc1800004, 0xc1355556},
{'q', 0xc0e66660, 0x41355556},
{0, 0xc18eeeee, 0x41355556},
{'q', 0xc11ccccc, 0x35000000},
{0, 0xc1677778, 0xc1011111},
{'q', 0xc0955558, 0xc1011112},
{0, 0xc05ddde0, 0xc1a9999a},
{'q', 0x3fc44440, 0xc18c4444},
{0, 0x41200000, 0xc1de6666},
{'q', 0x4108888c, 0xc1255554},
{0, 0x4196eef2, 0xc1255554},
{'8', 0x115a0039, 0x273e1021},
{'l', 0xc05999a0, 0x4213bbbc},
{'8', 0x64114dfa, 0x16321618},
{'q', 0x41011118, 0x00000000},
{0, 0x41466668, 0xc0fbbbbc},
{'q', 0x408cccd0, 0xc0fbbbbe},
{0, 0x409bbbc0, 0xc1991112},
{'q', 0x3f911100, 0xc1c6eeee},
{0, 0xc1144448, 0xc21c0001},
{'q', 0xc1255550, 0xc1622220},
{0, 0xc2062222, 0xc1622220},
{'q', 0xc1a80000, 0x00000000},
{0, 0xc205ddde, 0x41733338},
{'q', 0xc1477778, 0x41733330},
{0, 0xc1577778, 0x421e6666},
{'q', 0xbf9999a0, 0x41c77778},
{0, 0x411eeeee, 0x421d5556},
{'q', 0x41333336, 0x41666668},
{0, 0x4201ddde, 0x41666668},
{'8', 0xf55e002e, 0xe251f530},
{'6', 0x003c0014, 0xfe5ffed8},
{'q', 0xbf5dde00, 0x4116666a},
{0, 0x3fe66660, 0x4168888c},
{'8', 0x29442915, 0xe83a001b},
{'9', 0xffe8001e, 0xffaf0034},
{'4', 0xfffc0000, 0xfef50018},
{'8', 0xf1c0f1e3, 0x3b9b00c6},
{'q', 0xc0aaaaa8, 0x40eeeef0},
{0, 0xc0d55550, 0x41b08888},
{'@', 0x00000041, 0x00005911},/*        A        x-advance: 89.066406 */
{'M', 0x3ff77778, 0x00000000},
{'l', 0x42140000, 0xc2c22223},
{'l', 0x41344444, 0x00000000},
{'l', 0x42148889, 0x42c22223},
{'l', 0xc1533330, 0x00000000},
{'l', 0xc1144448, 0xc1cb3334},
{'4', 0x0000febc, 0x00cbffb7},
{'6', 0x0000ff97, 0xfee100d1},
{'l', 0x4203bbbc, 0x00000000},
{'l', 0xc183bbbc, 0xc2351112},
{'l', 0xc183bbbc, 0x42351112},
{'[', 0x007a0041, 0x000000cc},/*kerning*/
{'@', 0x00000042, 0x00005511},/*        B        x-advance: 85.066406 */
{'M', 0x429aaaab, 0xc1e00001},
{'q', 0x00000000, 0x4159999b},
{0, 0xc10cccc8, 0x41a66667},
{'9', 0x0039ffbb, 0x0039ff46},
{'4', 0x0000fef0, 0xfcf80000},
{'l', 0x41fe6668, 0x00000000},
{'q', 0x416eeef0, 0x00000000},
{0, 0x41ba2222, 0x40c66670},
{'q', 0x41066668, 0x40c44440},
{0, 0x41066668, 0x419d5554},
{'8', 0x60e23500, 0x40ad29e2},
{'q', 0x41033338, 0x40111110},
{0, 0x41488888, 0x41099998},
{'9', 0x00320023, 0x00740023},
{'m', 0xc254cccd, 0xc26a2224},
{'4', 0x00f60000, 0x0000009a},
{'8', 0xdf6a0042, 0xa528df28},
{'9', 0xff8a0000, 0xff87ff70},
{'6', 0x0000ff63, 0x01d50143},
{'q', 0x00000000, 0xc1022220},
{0, 0xc08eeef8, 0xc14ccccc},
{'9', 0xffdbffdd, 0xffdbff8f},
{'4', 0x0000ff53, 0x01170000},
{'l', 0x41a91112, 0x00000000},
{'q', 0x41122220, 0x00000000},
{0, 0x41633334, 0xc0955556},
{'q', 0x40a22228, 0xc0977776},
{0, 0x40a22228, 0xc14bbbbd},
{'@', 0x00000043, 0x000058dd},/*        C        x-advance: 88.863281 */
{'M', 0x428bbbbc, 0xc1f6eef0},
{'l', 0x414cccd0, 0x00000000},
{'q', 0xbfbbbbc0, 0x415cccce},
{0, 0xc1266668, 0x41b80001},
{'q', 0xc10eeef0, 0x41133333},
{0, 0xc1d33334, 0x41133333},
{'q', 0xc1877778, 0x34c00000},
{0, 0xc1daaaab, 0xc1411111},
{'9', 0xff9fffae, 0xfeffffac},
{'l', 0x00000000, 0xc1266668},
{'q', 0x00000000, 0xc1a33334},
{0, 0x41277778, 0xc202eef0},
{'q', 0x4128888a, 0xc1455550},
{0, 0x41e44446, 0xc1455550},
{'q', 0x4183bbbc, 0x00000000},
{0, 0x41caaaaa, 0x41111110},
{'9', 0x00480046, 0x00bb0052},
{'l', 0xc14cccd0, 0x00000000},
{'q', 0xbfbbbbc0, 0xc1222228},
{0, 0xc0d11110, 0xc1800000},
{'q', 0xc0a22220, 0xc0bddde0},
{0, 0xc182aaaa, 0xc0bddde0},
{'q', 0xc14ddde0, 0x00000000},
{0, 0xc19c4446, 0x41177778},
{'9', 0x004bffcb, 0x00c7ffcb},
{'l', 0x00000000, 0x411cccd0},
{'q', 0x00000000, 0x41655556},
{0, 0x40c22224, 0x41c3bbbc},
{'q', 0x40c22220, 0x41211111},
{0, 0x41980000, 0x41211111},
{'q', 0x41444444, 0x00000000},
{0, 0x418a2222, 0xc0b77778},
{'q', 0x40a22228, 0xc0b77776},
{0, 0x40d77778, 0xc1800000},
{'@', 0x00000044, 0x00005999},/*        D        x-advance: 89.597656 */
{'M', 0x41344445, 0x00000000},
{'4', 0xfcf80000, 0x000000db},
{'q', 0x41991112, 0x00000000},
{0, 0x41f7777a, 0x41455558},
{'9', 0x0062005e, 0x010a005e},
{'l', 0x00000000, 0x40b99998},
{'q', 0x00000000, 0x41a88889},
{0, 0xc13eeef0, 0x42055556},
{'9', 0x0062ffa2, 0x0062ff00},
{'6', 0x0000ff2e, 0xfd4c0066},
{'4', 0x02600000, 0x0000006b},
{'q', 0x41788888, 0x00000000},
{0, 0x41bb3334, 0xc119999a},
{'9', 0xffb4003e, 0xff34003e},
{'l', 0x00000000, 0xc0bddde0},
{'q', 0x00000000, 0xc185ddde},
{0, 0xc0fbbbb8, 0xc1ceeeee},
{'q', 0xc0fbbbc0, 0xc1122228},
{0, 0xc1b1999c, 0xc1122228},
{'l', 0xc1699998, 0x00000000},
{'@', 0x00000045, 0x00004d99},/*        E        x-advance: 77.597656 */
{'M', 0x41344445, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x42740001, 0x00000000},
{'l', 0x00000000, 0x41288888},
{'l', 0xc2408889, 0x00000000},
{'l', 0x00000000, 0x41f9999c},
{'l', 0x42280001, 0x00000000},
{'l', 0x00000000, 0x41277778},
{'l', 0xc2280001, 0x00000000},
{'l', 0x00000000, 0x4209999a},
{'l', 0x42433333, 0x00000000},
{'l', 0x00000000, 0x41277778},
{'l', 0xc276aaab, 0x00000000},
{'[', 0x00540045, 0x00000155},/*kerning*/
{'@', 0x00000046, 0x00004b77},/*        F        x-advance: 75.464844 */
{'M', 0x41344445, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x42708889, 0x00000000},
{'l', 0x00000000, 0x41288888},
{'l', 0xc23d1111, 0x00000000},
{'l', 0x00000000, 0x4204888a},
{'l', 0x4222aaab, 0x00000000},
{'l', 0x00000000, 0x41288888},
{'l', 0xc222aaab, 0x00000000},
{'l', 0x00000000, 0x422b7778},
{'l', 0xc14ddddf, 0x00000000},
{'[', 0x00540046, 0x00000155},/*kerning*/
{'@', 0x00000047, 0x00005d00},/*        G        x-advance: 93.000000 */
{'M', 0x42a60001, 0xc2415556},
{'l', 0x00000000, 0x420e2222},
{'q', 0xc02eef00, 0x40800006},
{0, 0xc1266668, 0x41111114},
{'q', 0xc0f55560, 0x40a22223},
{0, 0xc1bf777a, 0x40a22223},
{'q', 0xc18dddde, 0xb4000000},
{0, 0xc1e91111, 0xc1422222},
{'9', 0xff9fffa5, 0xfef0ffa5},
{'l', 0x00000000, 0xc0f11110},
{'q', 0x00000000, 0xc1ad5558},
{0, 0x41222223, 0xc2077778},
{'q', 0x41222222, 0xc1433330},
{0, 0x41e77777, 0xc1433330},
{'q', 0x41855556, 0x00000000},
{0, 0x41ca2222, 0x41066660},
{'9', 0x00430045, 0x00aa0054},
{'l', 0xc14ddde0, 0x00000000},
{'q', 0xbfaaaac0, 0xc0fbbbc0},
{0, 0xc0d33338, 0xc1588888},
{'q', 0xc0a66668, 0xc0b55550},
{0, 0xc182aaac, 0xc0b55550},
{'q', 0xc1566664, 0x00000000},
{0, 0xc19dddde, 0x41177778},
{'9', 0x004bffce, 0x00ccffcd},
{'l', 0x00000000, 0x41000000},
{'q', 0x00000000, 0x41866667},
{0, 0x40f33334, 0x41d22223},
{'q', 0x40f33338, 0x41166667},
{0, 0x41a0888a, 0x41166667},
{'q', 0x41255554, 0x00000000},
{0, 0x416eeef0, 0xc0199998},
{'9', 0xffed0024, 0xffdb0034},
{'l', 0x00000000, 0xc1adddde},
{'l', 0xc1b3bbbc, 0x00000000},
{'l', 0x00000000, 0xc1266668},
{'l', 0x420d1112, 0x00000000},
{'@', 0x00000048, 0x00006166},/*        H        x-advance: 97.398438 */
{'M', 0x42922223, 0x00000000},
{'l', 0x00000000, 0xc2337778},
{'l', 0xc243bbbd, 0x00000000},
{'l', 0x00000000, 0x42337778},
{'l', 0xc14ddddf, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x414ddddf, 0x00000000},
{'l', 0x00000000, 0x4226eef0},
{'l', 0x4243bbbd, 0x00000000},
{'l', 0x00000000, 0xc226eef0},
{'l', 0x414cccc8, 0x00000000},
{'l', 0x00000000, 0x42c22223},
{'l', 0xc14cccc8, 0x00000000},
{'@', 0x00000049, 0x00002522},/*        I        x-advance: 37.132812 */
{'M', 0x41c88889, 0xc2c22223},
{'l', 0x00000000, 0x42c22223},
{'l', 0xc14dddde, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x414dddde, 0x00000000},
{'@', 0x0000004a, 0x00004b55},/*        J        x-advance: 75.332031 */
{'M', 0x42500001, 0xc2c22223},
{'4', 0x00000066, 0x02250000},
{'q', 0x00000000, 0x41666668},
{0, 0xc10aaaac, 0x41b0888a},
{'q', 0xc1099998, 0x40f33333},
{0, 0xc1af7778, 0x40f33333},
{'q', 0xc1566666, 0xb4000000},
{0, 0xc1b08888, 0xc0dddddf},
{'9', 0xffc9ffbc, 0xff56ffbc},
{'l', 0x414ddddf, 0x00000000},
{'8', 0x6b274900, 0x22662228},
{'q', 0x40f33338, 0x00000000},
{0, 0x414aaaac, 0xc09bbbbc},
{'q', 0x40a44448, 0xc09bbbba},
{0, 0x40a44448, 0xc1655555},
{'l', 0x00000000, 0xc2897778},
{'@', 0x0000004b, 0x000055aa},/*        K        x-advance: 85.664062 */
{'M', 0x428caaab, 0x00000000},
{'l', 0xc2095556, 0xc234cccd},
{'l', 0xc13ddddc, 0x41455554},
{'l', 0x00000000, 0x42037778},
{'l', 0xc14ddddf, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x414ddddf, 0x00000000},
{'l', 0x00000000, 0x423f7779},
{'l', 0x422c8889, 0xc23f7779},
{'l', 0x41777778, 0x00000000},
{'l', 0xc2188889, 0x422b7778},
{'l', 0x42244445, 0x4258ccce},
{'l', 0xc1755558, 0x00000000},
{'@', 0x0000004c, 0x00004988},/*        L        x-advance: 73.531250 */
{'M', 0x428c4445, 0xc1277778},
{'l', 0x00000000, 0x41277778},
{'l', 0xc26b7779, 0x00000000},
{'l', 0x35800000, 0xc2c22223},
{'l', 0x414ddddf, 0x00000000},
{'l', 0x00000000, 0x42ad3334},
{'l', 0x42380001, 0x00000000},
{'[', 0x0041004c, 0x00000144},/*kerning*/
{'@', 0x0000004d, 0x00007733},/*        M        x-advance: 119.199219 */
{'M', 0x426e6667, 0xc18f7778},
{'l', 0x41fdddde, 0xc29e4445},
{'l', 0x41844444, 0x00000000},
{'l', 0x00000000, 0x42c22223},
{'l', 0xc14cccc8, 0x00000000},
{'l', 0x00000000, 0xc2177778},
{'l', 0x3fa22200, 0xc2226666},
{'l', 0xc1ff7778, 0x429ceeef},
{'l', 0xc11bbbbc, 0x00000000},
{'l', 0xc1feeeef, 0xc29d3334},
{'l', 0x3fa22220, 0x4222eef0},
{'l', 0x00000000, 0x42177778},
{'l', 0xc14ccccd, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x41844444, 0x00000000},
{'l', 0x41fe6668, 0x429e4445},
{'@', 0x0000004e, 0x00006166},/*        N        x-advance: 97.398438 */
{'M', 0x42abddde, 0xc2c22223},
{'l', 0x00000000, 0x42c22223},
{'l', 0xc14eeef0, 0x00000000},
{'l', 0xc2437777, 0xc295bbbc},
{'l', 0x00000000, 0x4295bbbc},
{'l', 0xc14ddddf, 0x00000000},
{'l', 0x00000000, 0xc2c22223},
{'l', 0x414ddddf, 0x00000000},
{'l', 0x42444445, 0x42962223},
{'l', 0x00000000, 0xc2962223},
{'l', 0x414bbbb8, 0x00000000},
{'@', 0x0000004f, 0x00005dee},/*        O        x-advance: 93.929688 */
{'M', 0x42ac0001, 0xc235ddde},
{'q', 0x00000000, 0x41aeeeef},
{0, 0xc12999a0, 0x42095555},
{'q', 0xc1299998, 0x41477779},
{0, 0xc1e2aaaa, 0x41477779},
{'q', 0xc18a2223, 0x34c00000},
{0, 0xc1e1999b, 0xc1477778},
{'9', 0xff9dffa9, 0xfeeeffa9},
{'l', 0xb5000000, 0xc0c44448},
{'q', 0x00000000, 0xc1ae6666},
{0, 0x412dddde, 0xc2091111},
{'q', 0x412dddde, 0xc1488888},
{0, 0x41e11111, 0xc1488888},
{'q', 0x418cccd0, 0x00000000},
{0, 0x41e1999c, 0x41455550},
{'9', 0x00620055, 0x010d0056},
{'6', 0x00370000, 0xffceff9b},
{'q', 0x00000000, 0xc18aaaac},
{0, 0xc0dddde0, 0xc1d44444},
{'q', 0xc0dddde0, 0xc1133338},
{0, 0xc19b3334, 0xc1133338},
{'q', 0xc13eeef0, 0x00000000},
{0, 0xc1991111, 0x41133338},
{'9', 0x0049ffc7, 0x00d4ffc7},
{'l', 0x00000000, 0x40c88890},
{'q', 0x00000000, 0x418bbbbb},
{0, 0x40e66668, 0x41d5ddde},
{'q', 0x40e88888, 0x41133333},
{0, 0x4199999a, 0x41133333},
{'q', 0x41488888, 0x00000000},
{0, 0x419aaaaa, 0xc1133333},
{'q', 0x40dbbbc0, 0xc1144446},
{0, 0x40dbbbc0, 0xc1d5ddde},
{'l', 0x00000000, 0xc0c88890},
{'@', 0x00000050, 0x00005622},/*        P        x-advance: 86.132812 */
{'M', 0x41c11112, 0xc2184445},
{'l', 0x00000000, 0x42184445},
{'4', 0x0000ff9a, 0xfcf80000},
{'l', 0x420f3334, 0x00000000},
{'q', 0x41844444, 0x00000000},
{0, 0x41ca2222, 0x41055558},
{'q', 0x410cccd0, 0x41055558},
{0, 0x410cccd0, 0x41aa2224},
{'q', 0x00000000, 0x41611110},
{0, 0xc10cccd0, 0x41addddc},
{'9', 0x003dffbb, 0x003dff36},
{'6', 0x0000ff49, 0xfe7d0000},
{'4', 0x01300000, 0x000000b7},
{'q', 0x41355554, 0x00000000},
{0, 0x41822222, 0xc0a88888},
{'8', 0x9427d627, 0x96d9c500},
{'q', 0xc09ddde0, 0xc0bbbbc0},
{0, 0xc1822222, 0xc0bbbbc0},
{'l', 0xc1b77778, 0x00000000},
{'[', 0x00740050, 0x000000ee},/*kerning*/
{'[', 0x00760050, 0x00000100},/*kerning*/
{'[', 0x00790050, 0x00000100},/*kerning*/
{'@', 0x00000051, 0x00005dee},/*        Q        x-advance: 93.929688 */
{'M', 0x42ab7778, 0x41066667},
{'4', 0x0040ffbb, 0xff7eff5d},
{'q', 0xc0999998, 0x3f99999b},
{0, 0xc1222220, 0x3f99999b},
{'q', 0xc18a2224, 0x00000000},
{0, 0xc1e1999b, 0xc1477778},
{'9', 0xff9dffa9, 0xfeeeffa9},
{'l', 0xb5000000, 0xc0c44448},
{'q', 0x00000000, 0xc1ae6666},
{0, 0x412dddde, 0xc2091111},
{'q', 0x412dddde, 0xc1488888},
{0, 0x41e11112, 0xc1488888},
{'q', 0x418cccce, 0x00000000},
{0, 0x41e1999c, 0x41455550},
{'9', 0x00620055, 0x010d0056},
{'l', 0x00000000, 0x40dddde0},
{'q', 0x00000000, 0x415ffffe},
{0, 0xc08eeef0, 0x41c22222},
{'9', 0x0051ffdd, 0x007fff9d},
{'6', 0x006d008a, 0xfe1fff98},
{'q', 0x00000000, 0xc18aaaac},
{0, 0xc0dddde0, 0xc1d44444},
{'q', 0xc0dddde0, 0xc1133338},
{0, 0xc19b3334, 0xc1133338},
{'q', 0xc13eeef0, 0x00000000},
{0, 0xc1991112, 0x41133338},
{'9', 0x0049ffc7, 0x00d4ffc7},
{'l', 0x00000000, 0x40c88890},
{'q', 0x00000000, 0x418bbbbb},
{0, 0x40e66668, 0x41d5ddde},
{'q', 0x40e8888c, 0x41133333},
{0, 0x4199999b, 0x41133333},
{'q', 0x41488888, 0x00000000},
{0, 0x419aaaaa, 0xc1133333},
{'q', 0x40dbbbc0, 0xc1144446},
{0, 0x40dbbbc0, 0xc1d5ddde},
{'l', 0x00000000, 0xc0c88890},
{'@', 0x00000052, 0x00005422},/*        R        x-advance: 84.132812 */
{'M', 0x42880000, 0x00000000},
{'l', 0xc1a88888, 0xc21d5556},
{'l', 0xc1b66666, 0x00000000},
{'l', 0x00000000, 0x421d5556},
{'4', 0x0000ff9a, 0xfcf80000},
{'l', 0x42008889, 0x00000000},
{'q', 0x4182aaac, 0x00000000},
{0, 0x41c91114, 0x40eeeef0},
{'q', 0x410dddd8, 0x40eeeef0},
{0, 0x410dddd8, 0x41ad5558},
{'q', 0x00000000, 0x41111110},
{0, 0xc09ddde0, 0x417ddddc},
{'9', 0x0035ffda, 0x0050ff94},
{'4', 0x014900b6, 0x00060000},
{'6', 0x0000ff93, 0xfd4cfea2},
{'4', 0x01250000, 0x0000009d},
{'q', 0x41255554, 0x00000000},
{0, 0x41788888, 0xc0a88888},
{'8', 0x9a2ad62a, 0x95d8bd00},
{'q', 0xc0a00000, 0xc0a44450},
{0, 0xc1811112, 0xc0a44450},
{'l', 0xc19a2222, 0x00000000},
{'@', 0x00000053, 0x00005111},/*        S        x-advance: 81.066406 */
{'M', 0x427c0001, 0xc1c44445},
{'q', 0x00000000, 0xc0d55554},
{0, 0xc0911110, 0xc12aaaaa},
{'q', 0xc08eeef0, 0xc0800000},
{0, 0xc196eef0, 0xc1033334},
{'q', 0xc1666668, 0xc0888888},
{0, 0xc1b66667, 0xc12cccd0},
{'q', 0xc1055556, 0xc0d33330},
{0, 0xc1055556, 0xc18e6664},
{'q', 0x00000000, 0xc1355558},
{0, 0x41100000, 0xc196eef0},
{'q', 0x41111112, 0xc0f11110},
{0, 0x41c00000, 0xc0f11110},
{'q', 0x41833334, 0x00000000},
{0, 0x41ca2224, 0x41100000},
{'9', 0x00480047, 0x00a20047},
{'l', 0xc14cccd0, 0x00000000},
{'q', 0x00000000, 0xc1022220},
{0, 0xc0a88888, 0xc1577778},
{'q', 0xc0a88888, 0xc0aaaaa0},
{0, 0xc1811112, 0xc0aaaaa0},
{'q', 0xc1244444, 0x00000000},
{0, 0xc1733332, 0x40911110},
{'8', 0x59d923d9, 0x51293000},
{'q', 0x40a88888, 0x40800000},
{0, 0x41888889, 0x40eaaab0},
{'q', 0x4186eef0, 0x40977778},
{0, 0x41c4ccce, 0x413bbbbc},
{'q', 0x40f77770, 0x40dddde0},
{0, 0x40f77770, 0x41922222},
{'q', 0x00000000, 0x413dddde},
{0, 0xc1144440, 0x41977778},
{'q', 0xc1144448, 0x40e22223},
{0, 0xc1c4ccce, 0x40e22223},
{'q', 0xc10eeef0, 0xb4000000},
{0, 0xc18b3334, 0xc0555556},
{'q', 0xc1066666, 0xc0555556},
{0, 0xc15ddddf, 0xc11ddddf},
{'9', 0xffccffd5, 0xff7effd5},
{'l', 0x414cccce, 0x00000000},
{'q', 0x00000000, 0x411eeef0},
{0, 0x40e88888, 0x41677779},
{'q', 0x40e88888, 0x40911110},
{0, 0x4184ccce, 0x40911110},
{'q', 0x41211110, 0x00000000},
{0, 0x41777778, 0xc0844444},
{'q', 0x40aeeef0, 0xc0866666},
{0, 0x40aeeef0, 0xc1344445},
{'@', 0x00000054, 0x00005177},/*        T        x-advance: 81.464844 */
{'M', 0x40555556, 0xc2ad1112},
{'l', 0x00000000, 0xc1288888},
{'l', 0x42960000, 0x00000000},
{'l', 0x00000000, 0x41288888},
{'l', 0xc1f9999a, 0x00000000},
{'l', 0x00000000, 0x42ad1112},
{'l', 0xc14aaaac, 0x00000000},
{'l', 0x00000000, 0xc2ad1112},
{'l', 0xc1f91111, 0x00000000},
{'[', 0x00540054, 0x00000111},/*kerning*/
{'[', 0x00560054, 0x00000111},/*kerning*/
{'[', 0x00570054, 0x00000100},/*kerning*/
{'[', 0x00590054, 0x00000111},/*kerning*/
{'@', 0x00000055, 0x00005888},/*        U        x-advance: 88.531250 */
{'M', 0x4285999a, 0xc2c22223},
{'4', 0x00000066, 0x020d0000},
{'q', 0x00000000, 0x41833334},
{0, 0xc1288888, 0x41c4ccce},
{'q', 0xc128888c, 0x41022221},
{0, 0xc1c55558, 0x41022221},
{'q', 0xc16dddde, 0x34c00000},
{0, 0xc1c80000, 0xc1022222},
{'9', 0xffbfffb0, 0xff3cffb0},
{'4', 0xfdf30000, 0x00000065},
{'l', 0x00000000, 0x42835556},
{'q', 0x00000000, 0x41366666},
{0, 0x40c44444, 0x4186eef0},
{'q', 0x40c66668, 0x40acccca},
{0, 0x4181999a, 0x40acccca},
{'q', 0x41222224, 0x00000000},
{0, 0x41822222, 0xc0accccc},
{'q', 0x40c44448, 0xc0aeeef2},
{0, 0x40c44448, 0xc186eef0},
{'l', 0x00000000, 0xc2835556},
{'@', 0x00000056, 0x000056ee},/*        V        x-advance: 86.929688 */
{'M', 0x42aa4445, 0xc2c22223},
{'l', 0xc20fbbbd, 0x42c22223},
{'l', 0xc1366664, 0x00000000},
{'l', 0xc20f7778, 0xc2c22223},
{'l', 0x415eeeef, 0x00000000},
{'l', 0x41dc4444, 0x42a00001},
{'l', 0x41de6668, 0xc2a00001},
{'l', 0x415eeef0, 0x00000000},
{'[', 0x00290056, 0x00000155},/*kerning*/
{'[', 0x005d0056, 0x00000122},/*kerning*/
{'[', 0x007d0056, 0x00000144},/*kerning*/
{'@', 0x00000057, 0x00007922},/*        W        x-advance: 121.132812 */
{'M', 0x42ec6667, 0xc2c22223},
{'l', 0xc1bbbbbc, 0x42c22223},
{'l', 0xc13aaaa8, 0x00000000},
{'l', 0xc1a00002, 0xc28d7778},
{'l', 0xbfc44440, 0xc0ecccd0},
{'l', 0xbfc44440, 0x40ecccd0},
{'l', 0xc1a5ddde, 0x428d7778},
{'l', 0xc13aaaac, 0x00000000},
{'l', 0xc1bc4445, 0xc2c22223},
{'l', 0x414ccccc, 0x00000000},
{'l', 0x41755556, 0x4284ccce},
{'l', 0x3ff77780, 0x414dddda},
{'l', 0x402aaab0, 0xc1388888},
{'l', 0x419a2222, 0xc2877778},
{'l', 0x412bbbbc, 0x00000000},
{'l', 0x4195dde0, 0x42877778},
{'l', 0x402eeee0, 0x413cccce},
{'l', 0x40044440, 0xc1533334},
{'l', 0x41700000, 0xc284aaab},
{'l', 0x414ddde0, 0x00000000},
{'[', 0x00290057, 0x00000100},/*kerning*/
{'[', 0x00540057, 0x000000ee},/*kerning*/
{'[', 0x005d0057, 0x000000cc},/*kerning*/
{'[', 0x007d0057, 0x000000ee},/*kerning*/
{'@', 0x00000058, 0x00005599},/*        X        x-advance: 85.597656 */
{'M', 0x419ccccd, 0xc2c22223},
{'l', 0x41baaaab, 0x4214ccce},
{'l', 0x41baaaac, 0xc214ccce},
{'l', 0x41700000, 0x00000000},
{'l', 0xc1f55556, 0x42404445},
{'l', 0x41fb3336, 0x42440001},
{'l', 0xc1722228, 0x00000000},
{'l', 0xc1bf7778, 0xc217bbbc},
{'l', 0xc1bf7777, 0x4217bbbc},
{'l', 0xc1722224, 0x00000000},
{'l', 0x41fb3335, 0xc2440001},
{'l', 0xc1f55557, 0xc2404445},
{'l', 0x41700000, 0x00000000},
{'[', 0x00560058, 0x000000ee},/*kerning*/
{'@', 0x00000059, 0x00005200},/*        Y        x-advance: 82.000000 */
{'M', 0x417bbbbd, 0xc2c22223},
{'l', 0x41c9999a, 0x4242eef0},
{'l', 0x41ca2224, 0xc242eef0},
{'l', 0x41699998, 0x00000000},
{'l', 0xc205ddde, 0x42733334},
{'l', 0x00000000, 0x42111112},
{'l', 0xc14ddde0, 0x00000000},
{'l', 0x00000000, 0xc2111112},
{'l', 0xc205ddde, 0xc2733334},
{'l', 0x416bbbbd, 0x00000000},
{'[', 0x00290059, 0x00000155},/*kerning*/
{'[', 0x00540059, 0x00000122},/*kerning*/
{'[', 0x00560059, 0x00000133},/*kerning*/
{'[', 0x00570059, 0x00000122},/*kerning*/
{'[', 0x00580059, 0x000000dd},/*kerning*/
{'[', 0x00590059, 0x00000133},/*kerning*/
{'[', 0x005d0059, 0x00000133},/*kerning*/
{'[', 0x007d0059, 0x00000144},/*kerning*/
{'@', 0x0000005a, 0x000051cc},/*        Z        x-advance: 81.796875 */
{'M', 0x40d55556, 0xc2ad1112},
{'l', 0x00000000, 0xc1288888},
{'l', 0x42873334, 0x00000000},
{'l', 0x00000000, 0x41155558},
{'l', 0xc2555556, 0x429a8889},
{'l', 0x425dddde, 0x00000000},
{'l', 0x00000000, 0x41277778},
{'l', 0xc28d3333, 0x00000000},
{'l', 0xb6400000, 0xc119999a},
{'l', 0x4254ccce, 0xc299dddf},
{'l', 0xc2515556, 0x00000000},
{'[', 0x0041005a, 0x000000dd},/*kerning*/
{'@', 0x0000005b, 0x00002433},/*        [        x-advance: 36.199219 */
{'M', 0x420b7778, 0xc2dddddf},
{'l', 0x00000000, 0x41222228},
{'l', 0xc14bbbbc, 0x00000000},
{'l', 0x00000000, 0x42deeeef},
{'l', 0x414bbbbc, 0x36400000},
{'l', 0x00000000, 0x41222223},
{'l', 0xc1c8888a, 0x00000000},
{'l', 0x35800000, 0xc303bbbc},
{'l', 0x41c8888a, 0xb7000000},
{'@', 0x0000005c, 0x00003811},/*       \         x-advance: 56.066406 */
{'M', 0x422d1112, 0x41055556},
{'l', 0xc2222223, 0xc2d2ccce},
{'l', 0x413bbbbc, 0x00000000},
{'l', 0x42222223, 0x42d2ccce},
{'l', 0xc13bbbbc, 0xb6000000},
{'@', 0x0000005d, 0x00002433},/*        ]        x-advance: 36.199219 */
{'M', 0x3f2aaaab, 0xc2c9999a},
{'l', 0x00000000, 0xc1222228},
{'l', 0x41c9999b, 0x00000000},
{'l', 0x00000000, 0x4303bbbc},
{'l', 0xc1c9999b, 0x36c00000},
{'l', 0x35300000, 0xc1222223},
{'l', 0x414ccccd, 0x00000000},
{'l', 0x00000000, 0xc2deeeef},
{'l', 0xc14ccccd, 0x00000000},
{'@', 0x0000005e, 0x00003911},/*        ^        x-advance: 57.066406 */
{'M', 0x40888889, 0xc2426667},
{'l', 0x419f7778, 0xc241dddf},
{'l', 0x41088888, 0x00000000},
{'l', 0x419eeef0, 0x4241dddf},
{'l', 0xc1377778, 0x00000000},
{'l', 0xc14aaaaa, 0xc200cccd},
{'l', 0xc14bbbbd, 0x4200cccd},
{'l', 0xc1377778, 0x00000000},
{'@', 0x0000005f, 0x00003d99},/*        _        x-advance: 61.597656 */
{'M', 0x4275999a, 0x00000000},
{'l', 0x00000000, 0x41222223},
{'l', 0xc2748889, 0x00000000},
{'l', 0x34900000, 0xc1222223},
{'l', 0x42748889, 0x00000000},
{'@', 0x00000060, 0x00002a33},/*        `        x-advance: 42.199219 */
{'M', 0x4195ddde, 0xc2ccccce},
{'l', 0x414ddde0, 0x419cccd0},
{'l', 0xc129999a, 0x00000000},
{'l', 0xc189999a, 0xc19cccd0},
{'l', 0x416eeeee, 0x00000000},
{'@', 0x00000061, 0x00004a44},/*        a        x-advance: 74.265625 */
{'M', 0x4257bbbc, 0x00000000},
{'8', 0xc4f3ecf7, 0x32bb1de5},
{'q', 0xc0a66668, 0x40266668},
{0, 0xc13ddde0, 0x40266668},
{'q', 0xc1311112, 0xb4000000},
{0, 0xc18dddde, 0xc0c66667},
{'q', 0xc0d55557, 0xc0c66668},
{0, 0xc0d55557, 0xc1733334},
{'q', 0x00000000, 0xc139999a},
{0, 0x410cccce, 0xc18ccccd},
{'9', 0xffd00046, 0xffd000bd},
{'4', 0x00000061, 0xffd30000},
{'8', 0xafe2cd00, 0xe2a6e2e2},
{'8', 0x1ba600c9, 0x3fde1bde},
{'l', 0xc1455557, 0x00000000},
{'q', 0x00000000, 0xc0f77778},
{0, 0x40f9999a, 0xc168888c},
{'q', 0x40f9999c, 0xc0d99990},
{0, 0x41a6eeef, 0xc0d99990},
{'q', 0x413bbbbc, 0x00000000},
{0, 0x419a2224, 0x40c00000},
{'9', 0x002f003c, 0x0091003c},
{'l', 0x00000000, 0x4201999a},
{'9', 0x00500000, 0x007e0014},
{'4', 0x00080000, 0x0000ff9a},
{'m', 0xc1a3bbbc, 0xc1177778},
{'8', 0xe65c0035, 0xc537e627},
{'4', 0xff8a0000, 0x0000ffa5},
{'q', 0xc1a66666, 0x3ecccd00},
{0, 0xc1a66666, 0x41555558},
{'8', 0x451b2900, 0x1c521c1b},
{'@', 0x00000062, 0x00004caa},/*        b        x-advance: 76.664062 */
{'M', 0x428ceeef, 0xc20d1112},
{'q', 0x00000000, 0x417cccd0},
{0, 0xc0eaaaa8, 0x41d1999b},
{'q', 0xc0eaaaa8, 0x41266667},
{0, 0xc1a5ddde, 0x41266667},
{'9', 0x0000ff93, 0xffb3ff58},
{'l', 0xbf2aaaa0, 0x41055556},
{'l', 0xc1355556, 0x00000000},
{'4', 0xfccd0000, 0x00000063},
{'l', 0x00000000, 0x42184446},
{'q', 0x40eaaaac, 0xc1122220},
{0, 0x41a44446, 0xc1122220},
{'q', 0x41599998, 0x00000000},
{0, 0x41a6eeee, 0x41222220},
{'9', 0x0051003a, 0x00d5003a},
{'6', 0x000b0000, 0xff22ff06},
{'8', 0x1aa900cb, 0x3fcc19df},
{'l', 0x00000000, 0x41fb3334},
{'8', 0x40352613, 0x1a571a22},
{'q', 0x41200000, 0x00000000},
{0, 0x41655554, 0xc0f55556},
{'9', 0xffc30023, 0xff6d0023},
{'l', 0x00000000, 0xbfb33320},
{'q', 0x00000000, 0xc12bbbbc},
{0, 0xc0866668, 0xc1944446},
{'q', 0xc0844440, 0xc0fbbbb8},
{0, 0xc16aaaac, 0xc0fbbbb8},
{'@', 0x00000063, 0x00004777},/*        c        x-advance: 71.464844 */
{'M', 0x42191112, 0xc10ccccd},
{'8', 0xe15c0034, 0xb02be128},
{'l', 0x413bbbb8, 0x00000000},
{'q', 0xbeeeee00, 0x411aaaab},
{0, 0xc10ddddc, 0x41877778},
{'q', 0xc1066664, 0x40e66667},
{0, 0xc19eeeee, 0x40e66667},
{'q', 0xc1822223, 0xb4000000},
{0, 0xc1c1999b, 0xc12bbbbc},
{'9', 0xffabffc2, 0xff36ffc2},
{'l', 0x00000000, 0xc0333330},
{'q', 0x00000000, 0xc168888c},
{0, 0x40fbbbbd, 0xc1ca2224},
{'q', 0x40fddde0, 0xc12bbbb8},
{0, 0x41c1999b, 0xc12bbbb8},
{'q', 0x414aaaa8, 0x00000000},
{0, 0x41a3bbbc, 0x40f11110},
{'9', 0x003b003e, 0x00940042},
{'l', 0xc13bbbb8, 0x00000000},
{'8', 0xa6d8cbfd, 0xdba1dbdc},
{'8', 0x1ea100c4, 0x4ed01ede},
{'9', 0x002ffff3, 0x0061fff3},
{'l', 0x00000000, 0x40333330},
{'8', 0x620d3200, 0x4e302f0d},
{'q', 0x408aaaac, 0x40733334},
{0, 0x41400002, 0x40733334},
{'@', 0x00000064, 0x00004d00},/*        d        x-advance: 77.000000 */
{'M', 0x425fbbbc, 0x00000000},
{'l', 0xbf199980, 0xc0f77778},
{'q', 0xc0ecccd0, 0x41111111},
{0, 0xc1a4ccce, 0x41111111},
{'q', 0xc14aaaaa, 0x34c00000},
{0, 0xc1a3bbbc, 0xc1244444},
{'9', 0xffaeffc2, 0xff32ffc1},
{'l', 0x00000000, 0xbff77780},
{'q', 0x00000000, 0xc1844446},
{0, 0x40f9999b, 0xc1d55556},
{'q', 0x40fbbbbe, 0xc1222220},
{0, 0x41a5ddde, 0xc1222220},
{'9', 0x00000065, 0x004400a0},
{'l', 0x00000000, 0xc215dde0},
{'4', 0x00000063, 0x03330000},
{'6', 0x0000ffa6, 0xfee6fed7},
{'q', 0x00000000, 0x412bbbbe},
{0, 0x40911114, 0x4193bbbd},
{'q', 0x40911110, 0x40f55556},
{0, 0x4168888a, 0x40f55556},
{'9', 0x0000005b, 0xffad0088},
{'l', 0x00000000, 0xc204ccce},
{'q', 0xc0b11110, 0xc1244444},
{0, 0xc1877778, 0xc1244444},
{'q', 0xc1211110, 0x00000000},
{0, 0xc16aaaaa, 0x40fbbbb8},
{'q', 0xc0911114, 0x40f999a0},
{0, 0xc0911114, 0x41944446},
{'l', 0x00000000, 0x3fb33320},
{'@', 0x00000065, 0x00004866},/*        e        x-advance: 72.398438 */
{'M', 0x4284eeef, 0xc149999a},
{'q', 0xc0622210, 0x40aaaaab},
{0, 0xc11ffffc, 0x411aaaab},
{'q', 0xc0ceeef0, 0x40888889},
{0, 0xc1891112, 0x40888889},
{'q', 0xc1711112, 0xb4000000},
{0, 0xc1c11112, 0xc11cccce},
{'9', 0xffb2ffb8, 0xff38ffb8},
{'l', 0xb5000000, 0xc0333330},
{'q', 0x00000000, 0xc13ccccc},
{0, 0x408eeeef, 0xc1a08888},
{'q', 0x40911112, 0xc1055558},
{0, 0x413bbbbd, 0xc14aaab0},
{'q', 0x40e66668, 0xc08cccc0},
{0, 0x41755554, 0xc08cccc0},
{'q', 0x4177777c, 0x00000000},
{0, 0x41b44446, 0x41222220},
{'9', 0x00500039, 0x00c90039},
{'4', 0x002c0000, 0x0000fe7a},
{'q', 0x3e8888c0, 0x411eeef0},
{0, 0x40bbbbc0, 0x41877778},
{'q', 0x40b55558, 0x40dddde0},
{0, 0x4178888c, 0x40dddde0},
{'8', 0xeb580034, 0xc73feb24},
{'6', 0x002f003b, 0xfe6bff1b},
{'q', 0xc0eaaaa8, 0x00000000},
{0, 0xc1466666, 0x40aaaaa8},
{'9', 0x002affd8, 0x007affce},
{'4', 0x00000120, 0xfff90000},
{'8', 0x95dfc7fd, 0xce97cee3},
{'@', 0x00000066, 0x00002f77},/*        f        x-advance: 47.464844 */
{'M', 0x422c8889, 0xc27aaaac},
{'l', 0xc1755556, 0x00000000},
{'l', 0x00000000, 0x427aaaac},
{'l', 0xc1455556, 0x00000000},
{'l', 0x00000000, 0xc27aaaac},
{'0', 0xb50000a5, 0xc000005b},
{'q', 0x3e088880, 0xc1377778},
{0, 0x40ccccd0, 0xc18c4444},
{'q', 0x40caaaa8, 0xc0c22220},
{0, 0x418a2222, 0xc0c22220},
{'9', 0x00000020, 0x00080044},
{'l', 0xbf2aaa80, 0x41211110},
{'8', 0xfccbfcea, 0x699c009e},
{'l', 0x00000000, 0x41000000},
{'l', 0x41755556, 0x00000000},
{'l', 0x00000000, 0x41177778},
{'[', 0x00220066, 0x00000111},/*kerning*/
{'[', 0x00270066, 0x00000111},/*kerning*/
{'[', 0x00290066, 0x00000155},/*kerning*/
{'[', 0x005d0066, 0x00000133},/*kerning*/
{'[', 0x007d0066, 0x00000144},/*kerning*/
{'@', 0x00000067, 0x00004caa},/*        g        x-advance: 76.664062 */
{'M', 0x42133334, 0x41e3bbbd},
{'8', 0xeb9300d4, 0xb298ebbf},
{'l', 0x40ceeef0, 0xc0eaaaac},
{'q', 0x41000000, 0x411bbbbc},
{0, 0x419aaaab, 0x411bbbbc},
{'q', 0x410bbbbc, 0x00000000},
{0, 0x415eeef0, 0xc09bbbbc},
{'9', 0xffd90029, 0xff8d0029},
{'l', 0x00000000, 0xc0caaaab},
{'q', 0xc0eaaab0, 0x4109999a},
{0, 0xc1a1999a, 0x4109999a},
{'q', 0xc1500002, 0xb4000000},
{0, 0xc1a55556, 0xc1266668},
{'9', 0xffadffc3, 0xff2fffc3},
{'l', 0x00000000, 0xbfb33320},
{'q', 0x00000000, 0xc1844446},
{0, 0x40f33334, 0xc1d55556},
{'q', 0x40f55554, 0xc1222220},
{0, 0x41a6eeef, 0xc1222220},
{'9', 0x0000006a, 0x004a00a4},
{'4', 0xffc00004, 0x00000059},
{'l', 0x00000000, 0x428d3334},
{'q', 0x00000000, 0x41655556},
{0, 0xc1088888, 0x41b1999a},
{'9', 0x003effbc, 0x003eff50},
{'m', 0xc1900001, 0xc27eeef0},
{'q', 0x00000000, 0x412bbbbe},
{0, 0x408eeef0, 0x4193bbbd},
{'q', 0x40911110, 0x40f55556},
{0, 0x4168888a, 0x40f55556},
{'9', 0x0000005d, 0xffab0089},
{'l', 0x00000000, 0xc2037778},
{'8', 0xc5cdddee, 0xe8ace8df},
{'q', 0xc1211110, 0x00000000},
{0, 0xc169999a, 0x40fbbbb8},
{'q', 0xc0911110, 0x40f999a0},
{0, 0xc0911110, 0x41944446},
{'l', 0x00000000, 0x3fb33320},
{'@', 0x00000068, 0x00004b33},/*        h        x-advance: 75.199219 */
{'M', 0x421d5556, 0xc27c4445},
{'8', 0x19ad00d1, 0x42c719dc},
{'l', 0x00000000, 0x424e2223},
{'l', 0xc1455555, 0x00000000},
{'4', 0xfccd0000, 0x00000062},
{'l', 0x00000000, 0x421b7779},
{'q', 0x41033334, 0xc11eeeec},
{0, 0x41aa2224, 0xc11eeeec},
{'q', 0x41299998, 0x00000000},
{0, 0x41866666, 0x40bdddd0},
{'9', 0x002f0032, 0x009f0032},
{'4', 0x017c0000, 0x0000ff9d},
{'l', 0x00000000, 0xc23d999a},
{'8', 0xa0e3bd00, 0xe4abe4e3},
{'@', 0x00000069, 0x00002133},/*        i        x-advance: 33.199219 */
{'M', 0x41177778, 0xc2b68889},
{'8', 0xd80ee800, 0xf02bf00e},
{'8', 0x102b001c, 0x280f100f},
{'8', 0x27f11600, 0x10d510f2},
{'8', 0xf0d500e4, 0xd9f2f0f2},
{'m', 0x41555556, 0x41991110},
{'l', 0x00000000, 0x42904445},
{'l', 0xc1466667, 0x00000000},
{'l', 0x00000000, 0xc2904445},
{'l', 0x41466667, 0x00000000},
{'@', 0x0000006a, 0x000020aa},/*        j        x-advance: 32.664062 */
{'M', 0x41077778, 0xc2b68889},
{'8', 0xd80ee800, 0xf02bf00e},
{'8', 0x102b001c, 0x280e100e},
{'8', 0x27f21600, 0x10d510f2},
{'8', 0xf0d500e4, 0xd9f2f0f2},
{'m', 0x3fa22220, 0x41991110},
{'4', 0x00000063, 0x02850000},
{'q', 0x00000000, 0x41a44446},
{0, 0xc196eef0, 0x41a44446},
{'9', 0x0000ffdf, 0xfff7ffc3},
{'l', 0x3d888880, 0xc11eeef0},
{'8', 0x042d0413, 0xb6430041},
{'l', 0x00000000, 0xc2a31112},
{'@', 0x0000006b, 0x00004533},/*        k        x-advance: 69.199219 */
{'M', 0x425a6667, 0x00000000},
{'l', 0xc1c80000, 0xc205ddde},
{'l', 0xc0f9999c, 0x41011110},
{'l', 0x00000000, 0x41cb3334},
{'l', 0xc1466667, 0x00000000},
{'l', 0x00000000, 0xc2ccccce},
{'l', 0x41466667, 0x00000000},
{'l', 0x00000000, 0x42777779},
{'l', 0x40d33334, 0xc0fbbbb8},
{'l', 0x41b33334, 0xc1bddde0},
{'l', 0x41711110, 0x00000000},
{'l', 0xc1e11112, 0x41f0888a},
{'l', 0x41fb3336, 0x42284445},
{'l', 0xc168888c, 0x00000000},
{'@', 0x0000006c, 0x00002133},/*        l        x-advance: 33.199219 */
{'M', 0x41b66667, 0xc2ccccce},
{'l', 0x00000000, 0x42ccccce},
{'l', 0xc1466667, 0x00000000},
{'l', 0x00000000, 0xc2ccccce},
{'l', 0x41466667, 0x00000000},
{'@', 0x0000006d, 0x000077bb},/*        m        x-advance: 119.730469 */
{'M', 0x42191112, 0xc27c4445},
{'9', 0x0000ff9f, 0x0051ff7c},
{'l', 0x00000000, 0x42537778},
{'l', 0xc1466667, 0x00000000},
{'4', 0xfdbf0000, 0x0000005d},
{'l', 0x3eaaaa80, 0x40fbbbc0},
{'q', 0x40f9999c, 0xc1133330},
{0, 0x41aaaaab, 0xc1133330},
{'8', 0x16620037, 0x4642152b},
{'8', 0xbd4ad71c, 0xe76ce72e},
{'q', 0x41399998, 0x00000000},
{0, 0x418eeef0, 0x40c66660},
{'9', 0x00310032, 0x009e0032},
{'4', 0x017b0000, 0x0000ff9d},
{'l', 0x00000000, 0xc23e2223},
{'8', 0x9edfb800, 0xe6a7e6df},
{'8', 0x23a300c5, 0x55d923de},
{'4', 0x017e0000, 0x0000ff9e},
{'l', 0x00000000, 0xc23ddddf},
{'8', 0xa0dfbd00, 0xe4a7e4df},
{'@', 0x0000006e, 0x00004b66},/*        n        x-advance: 75.398438 */
{'M', 0x421d5556, 0xc27c4445},
{'8', 0x19ad00d1, 0x42c719dc},
{'l', 0x00000000, 0x424e2223},
{'l', 0xc1455555, 0x00000000},
{'4', 0xfdbf0000, 0x0000005d},
{'l', 0x3eccccc0, 0x41100004},
{'q', 0x41033334, 0xc1255554},
{0, 0x41ac4446, 0xc1255554},
{'q', 0x41299998, 0x00000000},
{0, 0x41866666, 0x40bdddd0},
{'9', 0x002f0032, 0x009f0032},
{'4', 0x017c0000, 0x0000ff9d},
{'l', 0x00000000, 0xc23d999a},
{'8', 0xa0e3bd00, 0xe4abe4e3},
{'@', 0x0000006f, 0x00004ddd},/*        o        x-advance: 77.863281 */
{'M', 0x40c44445, 0xc2133334},
{'q', 0x00000000, 0xc17aaaac},
{0, 0x410cccce, 0xc1d11112},
{'q', 0x410cccce, 0xc1288884},
{0, 0x41bf7778, 0xc1288884},
{'q', 0x41722224, 0x00000000},
{0, 0x41bf7778, 0x41255554},
{'9', 0x00520046, 0x00cd0048},
{'l', 0x00000000, 0x400cccc0},
{'q', 0x00000000, 0x417aaaae},
{0, 0xc10ddddc, 0x41d11112},
{'q', 0xc10cccd0, 0x41277779},
{0, 0xc1bf7778, 0x41277779},
{'q', 0xc1733336, 0x34c00000},
{0, 0xc1c0888a, 0xc1277778},
{'9', 0xffadffba, 0xff2fffba},
{'6', 0xfff40000, 0x000c0062},
{'q', 0x00000000, 0x412bbbbe},
{0, 0x40a22224, 0x4194ccce},
{'q', 0x40a44444, 0x40fbbbbe},
{0, 0x4177777a, 0x40fbbbbe},
{'q', 0x41211110, 0x00000000},
{0, 0x41733334, 0xc0f7777a},
{'9', 0xffc20029, 0xff6c0029},
{'l', 0x00000000, 0xbfdddde0},
{'q', 0x00000000, 0xc1299998},
{0, 0xc0a44440, 0xc1944444},
{'q', 0xc0a44448, 0xc1000000},
{0, 0xc1766668, 0xc1000000},
{'q', 0xc1233334, 0x00000000},
{0, 0xc1755556, 0x41000000},
{'q', 0xc0a22224, 0x40fddde0},
{0, 0xc0a22224, 0x41944444},
{'l', 0x00000000, 0x3fc44440},
{'@', 0x00000070, 0x00004caa},/*        p        x-advance: 76.664062 */
{'M', 0x42295556, 0x3faaaaab},
{'9', 0x0000ff98, 0xffbeff5c},
{'l', 0x00000000, 0x420aeef0},
{'l', 0xc1466666, 0xb6000000},
{'4', 0xfce20000, 0x0000005a},
{'l', 0x3f1999a0, 0x40fddde0},
{'q', 0x40f33334, 0xc1144440},
{0, 0x41a6eeef, 0xc1144440},
{'q', 0x415aaaac, 0x00000000},
{0, 0x41a77778, 0x41222220},
{'9', 0x0051003a, 0x00d5003a},
{'l', 0x00000000, 0x3fb33320},
{'q', 0x00000000, 0x417cccd0},
{0, 0xc0eaaaa8, 0x41d1999b},
{'9', 0x0053ffc6, 0x0053ff5b},
{'m', 0xc0733330, 0xc280cccd},
{'9', 0x0000ffa7, 0x004fff7a},
{'l', 0x00000000, 0x420a2222},
{'q', 0x40b55558, 0x411ccccf},
{0, 0x41877778, 0x411ccccf},
{'q', 0x41200000, 0x00000000},
{0, 0x4169999c, 0xc0fbbbbe},
{'9', 0xffc20025, 0xff6c0025},
{'l', 0x00000000, 0xbfb33320},
{'q', 0x00000000, 0xc12bbbbc},
{0, 0xc0955558, 0xc1944446},
{'q', 0xc0933338, 0xc0fbbbb8},
{0, 0xc16bbbbc, 0xc0fbbbb8},
{'@', 0x00000071, 0x00004d99},/*        q        x-advance: 77.597656 */
{'M', 0x42866667, 0xc2904445},
{'4', 0x031e0000, 0x0000ff9d},
{'l', 0x00000000, 0xc209999a},
{'q', 0xc0ecccd0, 0x40ffffff},
{0, 0xc19eeef0, 0x40ffffff},
{'q', 0xc1555556, 0xb4000000},
{0, 0xc1a80000, 0xc1266668},
{'9', 0xffadffc4, 0xff2fffc4},
{'l', 0x00000000, 0xbfb33320},
{'q', 0x00000000, 0xc1844446},
{0, 0x40f33335, 0xc1d55556},
{'q', 0x40f33334, 0xc1222220},
{0, 0x41a91112, 0xc1222220},
{'9', 0x00000066, 0x004400a2},
{'4', 0xffc60004, 0x0000005a},
{'m', 0xc241dddf, 0x42137778},
{'q', 0x00000000, 0x412bbbbe},
{0, 0x40933334, 0x4194ccce},
{'q', 0x40955558, 0x40fbbbbe},
{0, 0x416aaaae, 0x40fbbbbe},
{'9', 0x00000058, 0xffb30086},
{'l', 0x00000000, 0xc20d999a},
{'q', 0xc0b99998, 0xc1177778},
{0, 0xc1855556, 0xc1177778},
{'q', 0xc1222222, 0x00000000},
{0, 0xc16cccce, 0x41000000},
{'q', 0xc0933334, 0x40fddde0},
{0, 0xc0933334, 0x4195ddde},
{'l', 0x00000000, 0x3faaaaa0},
{'@', 0x00000072, 0x00002e44},/*        r        x-advance: 46.265625 */
{'M', 0x4218cccd, 0xc2766667},
{'9', 0x0000ffa0, 0x0053ff7d},
{'l', 0x00000000, 0x424cccce},
{'l', 0xc1455555, 0x00000000},
{'4', 0xfdbf0000, 0x00000060},
{'l', 0x3e888880, 0x41044448},
{'q', 0x40bddde0, 0xc1199998},
{0, 0x41891112, 0xc1199998},
{'9', 0x0000001b, 0x0007002b},
{'l', 0xbd888a00, 0x4137777c},
{'q', 0xc02eeef0, 0xbf088880},
{0, 0xc0c00000, 0xbf088880},
{'[', 0x00220072, 0x00000111},/*kerning*/
{'[', 0x00270072, 0x00000111},/*kerning*/
{'[', 0x00660072, 0x00000100},/*kerning*/
{'[', 0x00740072, 0x00000355},/*kerning*/
{'[', 0x00760072, 0x00000133},/*kerning*/
{'[', 0x00770072, 0x00000122},/*kerning*/
{'[', 0x00790072, 0x00000133},/*kerning*/
{'@', 0x00000073, 0x00004677},/*        s        x-advance: 70.464844 */
{'M', 0x424d999a, 0xc1991112},
{'8', 0xc8e9e100, 0xd696e7ea},
{'q', 0xc1411112, 0xc01dddd8},
{0, 0xc199999a, 0xc0e44444},
{'q', 0xc0e44446, 0xc0977778},
{0, 0xc0e44446, 0xc15bbbbc},
{'q', 0x00000000, 0xc108888c},
{0, 0x40e8888a, 0xc16cccd0},
{'q', 0x40eaaaac, 0xc0c88880},
{0, 0x419bbbbd, 0xc0c88880},
{'q', 0x414eeef0, 0x00000000},
{0, 0x41a1999a, 0x40d33330},
{'9', 0x0034003a, 0x007f003a},
{'l', 0xc1455558, 0x00000000},
{'8', 0xbde1dd00, 0xe0a7e0e1},
{'8', 0x1aa800c3, 0x3ae61ae6},
{'8', 0x33181f00, 0x25691319},
{'q', 0x41544448, 0x40400000},
{0, 0x419ddde0, 0x40fbbbb8},
{'q', 0x40d11110, 0x409bbbc0},
{0, 0x40d11110, 0x415bbbbe},
{'q', 0x00000000, 0x41188889},
{0, 0xc0f55558, 0x41777778},
{'q', 0xc0f33330, 0x40bddddf},
{0, 0xc1a1999a, 0x40bddddf},
{'q', 0xc1655556, 0xb4000000},
{0, 0xc1af7778, 0xc0eaaaac},
{'9', 0xffc6ffc4, 0xff7effc4},
{'l', 0x41466666, 0x00000000},
{'8', 0x542e3d03, 0x165a162c},
{'8', 0xe95c003c, 0xc520e920},
{'@', 0x00000074, 0x00002caa},/*        t        x-advance: 44.664062 */
{'M', 0x421fbbbc, 0x00000000},
{'8', 0x0ab40adf, 0xdfa300ca},
{'9', 0xffdfffda, 0xff88ffda},
{'l', 0x00000000, 0xc232eef0},
{'l', 0xc1533334, 0x00000000},
{'l', 0xb4c00000, 0xc1177778},
{'l', 0x41533334, 0x00000000},
{'l', 0x00000000, 0xc18c4444},
{'l', 0x41455556, 0x00000000},
{'l', 0x00000000, 0x418c4444},
{'l', 0x41577778, 0x00000000},
{'4', 0x004b0000, 0x0000ff95},
{'l', 0x00000000, 0x42333334},
{'8', 0x38132c00, 0x0c2c0c13},
{'q', 0x40155550, 0x00000000},
{0, 0x40b99998, 0xbf4cccd0},
{'l', 0x3d888800, 0x41211112},
{'@', 0x00000075, 0x00004b44},/*        u        x-advance: 75.265625 */
{'M', 0x42588889, 0x00000000},
{'l', 0xbe888880, 0xc0e44445},
{'q', 0xc0e22220, 0x41077778},
{0, 0xc1a88888, 0x41077778},
{'q', 0xc129999c, 0xb4000000},
{0, 0xc1891112, 0xc0c88889},
{'9', 0xffceffcc, 0xff5bffcc},
{'4', 0xfe8c0000, 0x00000062},
{'l', 0x00000000, 0x423aaaac},
{'8', 0x68204d00, 0x1a491a21},
{'9', 0x0000006f, 0xffad0096},
{'l', 0x00000000, 0xc2522224},
{'l', 0x41466664, 0x00000000},
{'l', 0x00000000, 0x42904445},
{'l', 0xc13ccccc, 0x00000000},
{'@', 0x00000076, 0x00004222},/*        v        x-advance: 66.132812 */
{'M', 0x427eaaac, 0xc2904445},
{'l', 0xc1cf777a, 0x42904445},
{'l', 0xc1166666, 0x00000000},
{'l', 0xc1d11111, 0xc2904445},
{'l', 0x414aaaab, 0x00000000},
{'l', 0x4192aaaa, 0x425d1112},
{'l', 0x418eeef0, 0xc25d1112},
{'l', 0x4149999c, 0x00000000},
{'[', 0x00220076, 0x00000100},/*kerning*/
{'[', 0x00270076, 0x00000100},/*kerning*/
{'[', 0x00660076, 0x000000dd},/*kerning*/
{'@', 0x00000077, 0x00006699},/*        w        x-advance: 102.597656 */
{'M', 0x42c6cccd, 0xc2904445},
{'l', 0xc1a77778, 0x42904445},
{'l', 0xc1200000, 0x00000000},
{'l', 0xc18c4444, 0xc25a6667},
{'l', 0xc1888888, 0x425a6667},
{'l', 0xc1211112, 0x00000000},
{'l', 0xc1a77778, 0xc2904445},
{'l', 0x41455556, 0x00000000},
{'l', 0x41633334, 0x42577778},
{'l', 0x41866666, 0xc2577778},
{'l', 0x411eeef0, 0x00000000},
{'l', 0x4188888a, 0x425c0001},
{'l', 0x415eeef0, 0xc25c0001},
{'l', 0x41444440, 0x00000000},
{'@', 0x00000078, 0x000043bb},/*        x        x-advance: 67.730469 */
{'M', 0x418dddde, 0xc2904445},
{'l', 0x417cccd0, 0x41d22224},
{'l', 0x41800000, 0xc1d22224},
{'l', 0x41677774, 0x00000000},
{'l', 0xc1bccccc, 0x420e6667},
{'l', 0x41c2aaac, 0x42122223},
{'l', 0xc1644444, 0x00000000},
{'l', 0xc1855556, 0xc1d88889},
{'l', 0xc1855556, 0x41d88889},
{'l', 0xc1655557, 0x00000000},
{'l', 0x41c22222, 0xc2122223},
{'l', 0xc1bc4444, 0xc20e6667},
{'l', 0x41633334, 0x00000000},
{'@', 0x00000079, 0x00004099},/*        y        x-advance: 64.597656 */
{'M', 0x42080000, 0x41322223},
{'9', 0x0087ffcd, 0x008fff66},
{'l', 0xc0044448, 0x3d888800},
{'9', 0x0000ffe1, 0xfff8ffc9},
{'4', 0xffb00000, 0x0002001a},
{'8', 0xec4e0032, 0xb62eec1c},
{'l', 0x402aaaa8, 0xc0eaaaac},
{'l', 0xc1cdddde, 0xc28eaaab},
{'l', 0x41577778, 0x00000000},
{'l', 0x41908888, 0x42580001},
{'l', 0x4185dde0, 0xc2580001},
{'l', 0x41533334, 0x00000000},
{'l', 0xc1e7777a, 0x42a68889},
{'[', 0x00220079, 0x00000100},/*kerning*/
{'[', 0x00270079, 0x00000100},/*kerning*/
{'[', 0x00660079, 0x000000dd},/*kerning*/
{'@', 0x0000007a, 0x000043bb},/*        z        x-advance: 67.730469 */
{'M', 0x40ceeef0, 0xc277bbbd},
{'l', 0x00000000, 0xc1233334},
{'l', 0x425aeef0, 0x00000000},
{'l', 0x00000000, 0x410bbbc0},
{'l', 0xc220888a, 0x42551111},
{'l', 0x42284445, 0x35800000},
{'l', 0x00000000, 0x41222223},
{'l', 0xc264cccd, 0x00000000},
{'l', 0xb5000000, 0xc1111112},
{'l', 0x421eeeef, 0xc2537778},
{'l', 0xc21ccccd, 0xb6800000},
{'@', 0x0000007b, 0x00002e33},/*        {        x-advance: 46.199219 */
{'M', 0x40888889, 0xc210cccd},
{'l', 0x00000000, 0xc11aaaac},
{'9', 0x00000071, 0xff7f0071},
{'l', 0x00000000, 0xc1577774},
{'q', 0x00000000, 0xc1288890},
{0, 0x40a22220, 0xc1966668},
{'9', 0xffbe0028, 0xff9f0095},
{'l', 0x40266670, 0x40f33340},
{'q', 0xc0fddde0, 0x401ddde0},
{0, 0xc12eeef0, 0x410dddd8},
{'9', 0x0032ffe8, 0x0074ffe8},
{'l', 0x00000000, 0x41533330},
{'q', 0x00000000, 0x41744444},
{0, 0xc1322222, 0x41aa2222},
{'9', 0x002e0059, 0x00aa0059},
{'l', 0x00000000, 0x41511112},
{'q', 0x00000000, 0x41033334},
{0, 0x40400008, 0x4168888a},
{'9', 0x00320018, 0x00460057},
{'l', 0xc0266670, 0x40f55558},
{'q', 0xc159999a, 0xc0777778},
{0, 0xc1955556, 0xc1433334},
{'9', 0xffbeffd8, 0xff6affd8},
{'l', 0x00000000, 0xc1555556},
{'q', 0x00000000, 0xc1822222},
{0, 0xc1622224, 0xc1822222},
{'@', 0x0000007c, 0x00002155},/*        |        x-advance: 33.332031 */
{'M', 0x41ad5556, 0xc2c22223},
{'l', 0x00000000, 0x42e62223},
{'l', 0xc11eeef0, 0x00000000},
{'l', 0x00000000, 0xc2e62223},
{'l', 0x411eeef0, 0x00000000},
{'@', 0x0000007d, 0x00002e33},/*        }        x-advance: 46.199219 */
{'M', 0x42273334, 0xc2377778},
{'l', 0x00000000, 0x411aaaac},
{'9', 0x0000ff8f, 0x0081ff8f},
{'l', 0x00000000, 0x41577778},
{'q', 0x00000000, 0x41277778},
{0, 0xc0a22224, 0x41966667},
{'9', 0x0042ffd8, 0x0061ff6b},
{'l', 0xc026666a, 0xc0f55558},
{'q', 0x40fbbbbd, 0xc01ddddc},
{0, 0x412dddde, 0xc10ccccc},
{'9', 0xffce0018, 0xff8c0018},
{'l', 0x00000000, 0xc1544444},
{'q', 0x00000000, 0xc17aaaae},
{0, 0x41422223, 0xc1a91113},
{'9', 0xffd5ff9f, 0xff57ff9f},
{'l', 0x00000000, 0xc1544440},
{'q', 0x00000000, 0xc1033338},
{0, 0xc0400000, 0xc1688890},
{'9', 0xffcdffe8, 0xffbaffa9},
{'l', 0x40266669, 0xc0f33340},
{'q', 0x415aaaab, 0x40777780},
{0, 0x41955555, 0x41433338},
{'9', 0x00420028, 0x00960028},
{'l', 0x00000000, 0x41599998},
{'q', 0x00000000, 0x41800000},
{0, 0x41622224, 0x41800000},
{'@', 0x0000007e, 0x00005cdd},/*        ~        x-advance: 92.863281 */
{'M', 0x42942223, 0xc24f3334},
{'l', 0x41222220, 0xbd888800},
{'q', 0x00000000, 0x41244444},
{0, 0xc0c22220, 0x418d5556},
{'q', 0xc0c00000, 0x40eaaaa8},
{0, 0xc178888c, 0x40eaaaa8},
{'8', 0xeeae00d2, 0xcab4eedd},
{'8', 0xdacee7e5, 0xf3cff3ea},
{'8', 0x1cc100d8, 0x4eea1cea},
{'l', 0xc12bbbbc, 0x3e088880},
{'q', 0x00000000, 0xc127777a},
{0, 0x40c00002, 0xc18bbbbd},
{'q', 0x40c22222, 0xc0e00000},
{0, 0x41788889, 0xc0e00000},
{'8', 0x1351002d, 0x354b1223},
{'8', 0x2c3b2328, 0x09290913},
{'8', 0xe1420029, 0xaf19e119},
};
#define ctx_font_ascii_name "Roboto"
#endif
#endif //_CTX_INTERNAL_FONT_
#ifndef __CTX_LIST__
#define __CTX_LIST__

#include <stdlib.h>

#ifndef CTX_EXTERNAL_MALLOC
static inline void *ctx_realloc (void *mem, size_t old_size, size_t new_size)
{
  return (void*)realloc (mem, new_size);
}

static inline void *ctx_malloc (size_t size)
{
  return (void*)malloc (size);
}

static inline void ctx_free (void *mem)
{
  free (mem);
}

static inline void *ctx_calloc (size_t size, size_t count)
{
  return calloc (size, count);
}

#endif

/* The whole ctx_list implementation is in the header and will be inlined
 * wherever it is used.
 */
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;
      ctx_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;
        ctx_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).
 *
 * improving this feature and making it runtime selectable could also
 * be part of encoding all text as beziers upon pdf export
 */
#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


#ifndef CTX_SHAPE_CACHE_DEFAULT // the default set runtime value
                                // when enabled
#define CTX_SHAPE_CACHE_DEFAULT   0
#endif

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

#ifndef CTX_SHAPE_CACHE_MAX_DIM
#define CTX_SHAPE_CACHE_MAX_DIM  256
#endif

/* maximum number of entries in shape cache
 */
#ifndef CTX_SHAPE_CACHE_ENTRIES
#define CTX_SHAPE_CACHE_ENTRIES  1024
#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_FAST_FILL_RECT
#define CTX_FAST_FILL_RECT 1    /*  matters most for tiny rectangles where it shaves overhead, for larger rectangles
                                    a ~15-20% performance win can be seen. */
#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    0
#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_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*8
#endif

#ifndef CTX_DRAWLIST_STATIC
#define CTX_DRAWLIST_STATIC  0
#endif

#ifndef CTX_MIN_EDGE_LIST_SIZE
#define CTX_MIN_EDGE_LIST_SIZE   1024*4
#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_MAX_KEYDB
#define CTX_MAX_KEYDB 64 // number of entries in keydb
                         // entries are "copy-on-change" between states
#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     2000 //
#endif

#ifndef CTX_32BIT_SEGMENTS
#define CTX_32BIT_SEGMENTS 1  // without this clipping problems might
                              // occur when drawing far outside the viewport
                              // or with large translate amounts
                              // on micro controllers you most often will
                              // want this set to 0
#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 backend 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,
 * and grayscale for RGBA8 manual loops overrrides. Disabling this should speed
 * up compiles at penalty for the given formats.
 */
#ifndef CTX_INLINED_NORMAL     
#define CTX_INLINED_NORMAL      0
#endif

/*
 *  do not use manual RGBA8 code but rely on ctx inline templating
 */
#ifndef CTX_INLINED_NORMAL     
#define CTX_INLINED_NORMAL_RGBA8  0
#endif

#ifndef CTX_RASTERIZER_SWITCH_DISPATCH
#define CTX_RASTERIZER_SWITCH_DISPATCH  1 // marginal improvement for some
                                          // modes, maybe get rid of this?
#endif

#ifndef CTX_U8_TO_FLOAT_LUT
#define CTX_U8_TO_FLOAT_LUT  0
#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_AVOID_CLIPPED_SUBDIVISION
#define CTX_AVOID_CLIPPED_SUBDIVISION 0
#endif

#ifndef CTX_MAX_DEVICES
#define CTX_MAX_DEVICES 16
#endif

#ifndef CTX_MAX_KEYBINDINGS
#define CTX_MAX_KEYBINDINGS 256
#endif


#ifndef CTX_PROTOCOL_U8_COLOR
#define CTX_PROTOCOL_U8_COLOR 0
#endif

#ifndef CTX_TERMINAL_EVENTS
#if CTX_EVENTS
#define CTX_TERMINAL_EVENTS 1
#else
#define CTX_TERMINAL_EVENTS 0
#endif
#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

#ifndef CTX_RESOLVED_FONTS
#define CTX_RESOLVED_FONTS 8   // how many font-strings to cache the resolution for in a static
			       // hash-table
#endif

/* by including ctx-font-regular.h, or ctx-font-mono.h the
 * built-in fonts using ctx drawlist encoding is enabled
 */
#ifndef CTX_NO_FONTS
#ifndef CTX_FONT_ENGINE_CTX
#define CTX_FONT_ENGINE_CTX        1
#endif
#endif

#ifndef CTX_ONE_FONT_ENGINE
#define CTX_ONE_FONT_ENGINE 0
#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

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

#ifndef _BABL_H
#undef CTX_BABL
#define CTX_BABL 0
#endif

#ifndef CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
#define CTX_ALWAYS_USE_NEAREST_FOR_SCALE1 0
#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  (128)
#endif

#ifndef CTX_MAX_FRAMEBUFFER_WIDTH
#define CTX_MAX_FRAMEBUFFER_WIDTH 2560
#endif

#ifndef CTX_MAX_FONTS
#define CTX_MAX_FONTS            32
#endif

#ifndef CTX_GLYPH_CACHE
#define CTX_GLYPH_CACHE 1
#endif

#ifndef CTX_GLYPH_CACHE_SIZE
#define CTX_GLYPH_CACHE_SIZE     256
#endif

#ifndef CTX_MAX_STATES
#define CTX_MAX_STATES           16
#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         32
#endif

#ifndef CTX_HASH_ROWS
#define CTX_HASH_ROWS            6
#endif
#ifndef CTX_HASH_COLS
#define CTX_HASH_COLS            5
#endif

#ifndef CTX_INLINE_FILL_RULE
#define CTX_INLINE_FILL_RULE 1
#endif

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

#ifndef CTX_FRAGMENT_SPECIALIZE
#define CTX_FRAGMENT_SPECIALIZE 1
#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

#ifndef CTX_MAX_SCANLINE_LENGTH
#define CTX_MAX_SCANLINE_LENGTH 4096
#endif


#ifndef CTX_MAX_CBS
#define CTX_MAX_CBS              1 //128
#endif

#ifndef static_OPAQUE // causes a CTX_MAX_SCANLINE_LENGTH
                          // buffer of 255 bytes to be part of
                          // rasterizer
#define static_OPAQUE 1
#endif

#ifndef CTX_SYNC_FRAMES
#define CTX_SYNC_FRAMES  1
#endif

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

#if CTX_SDL || CTX_FB || CTX_HEADLESS
#if CTX_EVENTS
#undef CTX_EVENTS
#endif
#define CTX_EVENTS 1
#endif




#ifndef CTX_HEADLESS

#if CTX_FB || CTX_SDL || CTX_KMS
#define CTX_HEADLESS 1
#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_MAX_DASHES
#define CTX_MAX_DASHES CTX_PARSER_MAX_ARGS
#endif

#ifndef CTX_SCREENSHOT
#define CTX_SCREENSHOT 0
#endif

#ifndef CTX_ALSA
#define CTX_ALSA 0
#endif

#ifndef CTX_AUDIO
#define CTX_AUDIO 0
#endif

#if CTX_AUDIO==0
#if CTX_ALSA
#undef CTX_ALSA
#define CTX_ALSA 0
#endif
#endif

#ifndef CTX_CURL
#define CTX_CURL 0
#endif

#ifndef CTX_TILED
#if CTX_SDL || CTX_FB || CTX_KMS || CTX_HEADLESS
#define CTX_TILED 1
#else
#define CTX_TILED 0
#endif
#if !CTX_RASTERIZER
#undef CTX_RASTERIZER
#define CTX_RASTERIZER 1
#endif
#endif


#ifndef CTX_TILED_MERGE_HORIZONTAL_NEIGHBORS
#define CTX_TILED_MERGE_HORIZONTAL_NEIGHBORS 1
#endif


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

#if CTX_THREADS
#include <pthread.h>
#define mtx_lock pthread_mutex_lock
#define mtx_unlock pthread_mutex_unlock
#define mtx_t pthread_mutex_t
#define cnd_t pthread_cond_t
#define mtx_plain NULL
#define mtx_init pthread_mutex_init
#define cnd_init(a) pthread_cond_init(a,NULL)
#define cnd_wait pthread_cond_wait
#define cnd_broadcast pthread_cond_broadcast
#define thrd_create(tid, tiled_render_fun, args) pthread_create(tid, NULL, tiled_render_fun, args)
#define thrd_t pthread_t
#endif

#ifndef CTX_SIMD_SUFFIX
#define CTX_SIMD_SUFFIX(symbol) symbol##_generic
#define CTX_SIMD_BUILD 0
#else


#define CTX_SIMD_BUILD 1
#ifdef CTX_COMPOSITE
#undef CTX_COMPOSITE
#define CTX_COMPOSITE 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_COMPOSITE
#define CTX_COMPOSITE 0
#endif

#ifndef CTX_MAX_GRADIENT_STOPS
#define CTX_MAX_GRADIENT_STOPS 16
#endif

#ifndef CTX_BRANCH_HINTS
#define CTX_BRANCH_HINTS  1
#endif

#ifdef EMSCRIPTEN
#define CTX_WASM 1
#else
#define CTX_WASM 0
#endif

#ifndef CTX_MAX_LISTEN_FDS
#define CTX_MAX_LISTEN_FDS 128 // becomes max clients..
#endif

#if CTX_WASM
#undef CTX_THREADS
#define CTX_THREADS 0
#undef CTX_HEADLESS
#define CTX_HEADLESS 0
#undef CTX_TILED
#define CTX_TILED 0
#undef CTX_EVENTS
#define CTX_EVENTS 1
#undef CTX_PARSER
#define CTX_PARSER 1
#undef CTX_RASTERIZER
#define CTX_RASTERIZER 1
#undef CTX_SHAPE_CACHE
#define CTX_SHAPE_CACHE 0
#endif

#ifndef CTX_TINYVG
#define CTX_TINYVG 0
#endif

#ifndef CTX_PDF
#define CTX_PDF 0
#endif

#if CTX_IMAGE_WRITE
#else
#define MINIZ_NO_ARCHIVE_APIS  1
#define MINIZ_NO_DEFLATE_APIS  1
#endif

//#define MINIZ_NO_ARCHIVE_WRITING_APIS 1
#define MINIZ_NO_STDIO                1


//#define uncompress tinf_uncompress
//#define Z_OK TINF_OK
//#define Z_BUF_ERROR TINF_BUF_ERROR
//#define Z_DATA_ERROR TINF_DATA_ERROR

#ifndef CTX_ENABLE_CBRLE
#define CTX_ENABLE_CBRLE 0
#endif


#ifndef CTX_RAW_KB_EVENTS
#define CTX_RAW_KB_EVENTS 1
#endif


#ifndef CTX_BAREMETAL
#define CTX_BAREMETAL 0
#endif

#if CTX_IMPLEMENTATION
#ifndef SQUOZE_IMPLEMENTATION
#define SQUOZE_IMPLEMENTATION         1
#define SQUOZE_LIMIT_IMPLEMENTATIONS  1
#define SQUOZE_IMPLEMENTATION_32_UTF8 1
#define SQUOZE_USE_INTERN             0
#endif
#endif

#ifndef CTX_PTY
#define CTX_PTY 1
#endif

#ifndef CTX_STROKE_1PX   
#define CTX_STROKE_1PX    1
#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

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


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


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_CTX_FILE,
  CTX_OUTPUT_MODE_CTX_COMPACT_FILE,
  CTX_OUTPUT_MODE_UI
} CtxOutputmode;






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

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

static CTX_INLINE float
ctx_invsqrtf (float x)
{
  union
  {
    float f;
    uint32_t i;
  } u = { x };
  u.i = 0x5f3759df - (u.i >> 1);
  u.f *= (1.5f - 0.5f * x * u.f * u.f);
  u.f *= (1.5f - 0.5f * x * u.f * u.f); //repeating Newton-Raphson step for higher precision
  return u.f;
}

static CTX_INLINE float
ctx_invsqrtf_fast (float x)
{
  union
  {
    float f;
    uint32_t i;
  } u = { x };
  u.i = 0x5f3759df - (u.i >> 1);
  return u.f;
}

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 = (long)(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 = (long)(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 CTX_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 CTX_INLINE float ctx_atanf (float a)
{
  return ctx_atan2f ( (a), 1.0f);
}

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

static CTX_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 CTX_INLINE float ctx_tanf (float a)
{
  return (ctx_cosf (a) /ctx_sinf (a) );
}
static CTX_INLINE float
ctx_floorf (float x)
{
  return (int)x; // XXX
}
static CTX_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_asinf (float x)            { return asinf (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); }

static inline float ctx_hypotf_fast (float a, float b)
{
  return ctx_sqrtf (ctx_pow2 (a)+ctx_pow2 (b) );
}
#endif

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

const char *ctx_get_string (Ctx *ctx, uint32_t hash);
void ctx_set_string (Ctx *ctx, uint32_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 (void);
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, uint32_t hash, CtxColor *color);
int  ctx_get_color (Ctx *ctx, uint32_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);


void ctx_device_to_user          (Ctx *ctx, float *x, float *y);
void ctx_device_to_user_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, uint32_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 SQZ_action 191466746u
#define SQZ_addStop 70177936u
#define SQZ_alphabetic 2838306314u
#define SQZ_aqua 1635086787u
#define SQZ_arc 6517443u
#define SQZ_arcTo 2233202270u
#define SQZ_beginPath 1761844968u
#define SQZ_bevel 426445178u
#define SQZ_black 1346400376u
#define SQZ_blend 262745544u
#define SQZ_blending 3480977226u
#define SQZ_blendMode 1036946672u
#define SQZ_blue 1702194373u
#define SQZ_bottom 297379412u
#define SQZ_cap 7365063u
#define SQZ_center 3333013908u
#define SQZ_clear 730795960u
#define SQZ_clip 1885957319u
#define SQZ_closePath 3976743578u
#define SQZ_cmyk 1803120071u
#define SQZ_cmyka 3062872642u
#define SQZ_cmykaS 1515489614u
#define SQZ_cmykS 2970776438u
#define SQZ_cmykSpace 2158213956u
#define SQZ_color 4113199138u
#define SQZ_colorSpace 2978079760u
#define SQZ_compositingMode 1747631346u
#define SQZ_copy 2037411783u
#define SQZ_currentColor 3045233808u
#define SQZ_curveTo 4214308788u
#define SQZ_cyan 1851881927u
#define SQZ_darken 3637594256u
#define SQZ_defineFont 3455431226u
#define SQZ_defineGlyph 517698760u
#define SQZ_defineTexture 2541406396u
#define SQZ_destinationAtop 1762261548u
#define SQZ_destinationIn 1974899656u
#define SQZ_destinationOut 3450048454u
#define SQZ_destinationOver 3410395560u
#define SQZ_deviceCMYK 475280306u
#define SQZ_deviceRGB 2503222352u
#define SQZ_difference 2667908970u
#define SQZ_done 1701736393u
#define SQZ_drgb 1650946761u
#define SQZ_drgba 2890318204u
#define SQZ_drgbaS 284498328u
#define SQZ_drgbS 2734523996u
#define SQZ_drgbSpace 1678108998u
#define SQZ_end 6581963u
#define SQZ_endFrame 2989111914u
#define SQZ_endGroup 3382848678u
#define SQZ_evenOdd 3958460824u
#define SQZ_exit 1953069259u
#define SQZ_extend 2410480036u
#define SQZ_fill 1819044301u
#define SQZ_fillRect 844863334u
#define SQZ_fillRule 3536928922u
#define SQZ_font 1953394637u
#define SQZ_fontSize 3677341438u
#define SQZ_fuchsia 1388713974u
#define SQZ_globalAlpha 2486696782u
#define SQZ_glyph 4143870566u
#define SQZ_gradientAddStop 4202125454u
#define SQZ_gray 2036429519u
#define SQZ_graya 1057020702u
#define SQZ_grayaS 3143946106u
#define SQZ_grayS 217848836u
#define SQZ_green 2210334888u
#define SQZ_hanging 315820168u
#define SQZ_height 2644040974u
#define SQZ_horLineTo 310288004u
#define SQZ_hue 6649297u
#define SQZ_identity 1295847768u
#define SQZ_ideographic 483722678u
#define SQZ_imageSmoothing 3051511248u
#define SQZ_join 1852403669u
#define SQZ_kerningPair 2107287266u
#define SQZ_lab 6447577u
#define SQZ_laba 1633837529u
#define SQZ_labaS 3945150942u
#define SQZ_labS 1398956505u
#define SQZ_lch 6841305u
#define SQZ_lcha 1634231257u
#define SQZ_lchaS 1426017758u
#define SQZ_lchS 1399350233u
#define SQZ_left 1952867801u
#define SQZ_lighten 3832222246u
#define SQZ_lime 1701669337u
#define SQZ_linearGradient 1237241186u
#define SQZ_lineCap 2995475454u
#define SQZ_lineDash 1187484528u
#define SQZ_lineDashOffset 3122102242u
#define SQZ_lineHeight 1792134122u
#define SQZ_lineJoin 3798665176u
#define SQZ_lineTo 2181648726u
#define SQZ_lineWidth 1866819118u
#define SQZ_lower 3494639400u
#define SQZ_lowerBottom 3353231678u
#define SQZ_magenta 41683490u
#define SQZ_maroon 3330270796u
#define SQZ_maximize 2843333630u
#define SQZ_middle 2338157914u
#define SQZ_miter 2474552592u
#define SQZ_miterLimit 484429810u
#define SQZ_moveTo 2673163660u
#define SQZ_multiply 1455254328u
#define SQZ_navy 2037801437u
#define SQZ_newPage 2996263020u
#define SQZ_newPath 2761203478u
#define SQZ_newState 2752539242u
#define SQZ_none 1701736413u
#define SQZ_nonzero 2128027674u
#define SQZ_normal 1043559938u
#define SQZ_olive 1261573064u
#define SQZ_paint 3703690370u
#define SQZ_purple 245805398u
#define SQZ_quadTo 1586848216u
#define SQZ_radialGradient 2234069350u
#define SQZ_raise 1568723874u
#define SQZ_raiseTop 3049736746u
#define SQZ_rect 1952671205u
#define SQZ_rectangle 979653324u
#define SQZ_red 6579685u
#define SQZ_relArcTo 1740342202u
#define SQZ_relCurveTo 1444075664u
#define SQZ_relHorLineTo 1006817000u
#define SQZ_relLineTo 567984930u
#define SQZ_relMoveTo 70490176u
#define SQZ_relQuadTo 3275675052u
#define SQZ_relSmoothqTo 3903392508u
#define SQZ_relSmoothTo 3177482922u
#define SQZ_relVerLineTo 861219474u
#define SQZ_restore 3389756388u
#define SQZ_rgb 6449125u
#define SQZ_rgba 1633839077u
#define SQZ_rgbaS 1598860234u
#define SQZ_rgbS 1398958053u
#define SQZ_rgbSpace 2863441362u
#define SQZ_right 2455820278u
#define SQZ_rotate 91843306u
#define SQZ_round 2051052438u
#define SQZ_roundRectangle 124772996u
#define SQZ_save 1702257127u
#define SQZ_scale 1608340156u
#define SQZ_screen 1686881180u
#define SQZ_setFontSize 3424593464u
#define SQZ_setLineCap 2943395354u
#define SQZ_setLineJoin 697170840u
#define SQZ_setLineWidth 1869312910u
#define SQZ_shadowBlur 1269325404u
#define SQZ_shadowColor 3671019726u
#define SQZ_shadowOffsetX 1199273834u
#define SQZ_shadowOffsetY 2589386586u
#define SQZ_silver 1756290066u
#define SQZ_smoothQuadTo 241605252u
#define SQZ_smoothTo 509706880u
#define SQZ_sourceAtop 251620176u
#define SQZ_sourceIn 3850864476u
#define SQZ_sourceOut 1431529336u
#define SQZ_sourceOver 2655053588u
#define SQZ_sourceTransform 4288534266u
#define SQZ_square 133625030u
#define SQZ_start 4265770694u
#define SQZ_startFrame 4116075414u
#define SQZ_startGroup 329986940u
#define SQZ_stroke 1713463584u
#define SQZ_strokeRect 98241244u
#define SQZ_strokeSource 711534692u
#define SQZ_strokeText 3684637194u
#define SQZ_teal 1818322409u
#define SQZ_text 1954047465u
#define SQZ_textAlign 3477973996u
#define SQZ_textBaseline 1424973332u
#define SQZ_textDirection 185607982u
#define SQZ_texture 971666452u
#define SQZ_title 946515406u
#define SQZ_top 7368681u
#define SQZ_transform 3862209768u
#define SQZ_translate 3005601842u
#define SQZ_transparent 4231278256u
#define SQZ_unmaximize 3186335298u
#define SQZ_userCMYK 4190017706u
#define SQZ_userRGB 363668428u
#define SQZ_verLineTo 625107550u
#define SQZ_viewBox 3037148798u
#define SQZ_white 3660660630u
#define SQZ_width 303173726u
#define SQZ_winding 2804912752u
#define SQZ_wrapLeft 2241102244u
#define SQZ_wrapRight 1285667914u
#define SQZ_x 241u
#define SQZ_xor 7499761u
#define SQZ_y 243u
#define SQZ_yellow 4030545924u
#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; }
  if (i >=n) 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)
    {
      if (!ctx_strncmp (h, n, needle_len) )
        { return (char *) h; }
      h++;
    }
  return NULL;
}

static inline char *ctx_strdup (const char *str)
{
  int len = ctx_strlen (str);
  char *ret = (char*)ctx_malloc (len + 1);
  memcpy (ret, str, len);
  ret[len]=0;
  return ret;
}

#endif

CtxColor   *ctx_color_new      (void);
int         ctx_get_int        (Ctx *ctx, uint32_t hash);
int         ctx_get_is_set     (Ctx *ctx, uint32_t hash);
Ctx        *ctx_new_for_buffer (CtxBuffer *buffer);
#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
/* Copyright (c) 2021-2022 Øyvind Kolås <pippin@gimp.org>

  Fast cache-miss eliminating unicode strings for C.

  All features are optional:
    optimized 32bit 52bit 62bit and 64bit squoze encodings in UTF5+ and/or UTF-8
    string interning and APIS (for only getting the core squoze reference
                               implementation)
      both utf8, unichar and printf for core APIs
      embedding of strings (only for debug/profiling)
      reference counting
      embedded length

  License to be determined, the core implementation the snippet for
  squoze64_utf8 on https://squoz.org/ is ISC licensed



*/
#if 0
Minimal usage example:

#define SQUOZE_IMPLEMENTATION
#include "squoze.h"

int main (int argc, char **argv)
{:q
  char temp[10];
  Sqz *string = NULL;
  
  sqz_set (&string, "hello");


}

#endif

#ifndef SQUOZE_H
#define SQUOZE_H

#include <stdint.h>
#include <stddef.h>

// configuration of internal squoze, these
// are values that must be set before both header
// and implementation uses of squoze.h the values only
// impact the string interning implementation and not
// the low-level APIs


#ifndef SQUOZE_INTERN_DIRECT_STRING     // when 1 the pointers returned are
#define SQUOZE_INTERN_DIRECT_STRING  1  // directly string pointers
                                        // when 0 the struct of the per entry
                                        // allocation is returned, for integration
                                        // with garbage collectors that scan
                                        // for pointers 0 is preferable.
#endif

#ifndef SQUOZE_ID_BITS         // number of bits to use for interning API
#define SQUOZE_ID_BITS 64      // 32 52 62 or 64
#endif

#ifndef SQUOZE_ID_UTF5         // use UTF5+ as the embed encoding
#define SQUOZE_ID_UTF5 0       // if not set then UTF8 is used
#endif

#ifndef SQUOZE_ID_MURMUR       // use murmurhash and no embedding
#define SQUOZE_ID_MURMUR 0     //
#endif

#ifndef SQUOZE_REF_COUNTING    // build the refcounting support, adds
#define SQUOZE_REF_COUNTING 0  // per-interned-string overhead
#endif

#ifndef SQUOZE_STORE_LENGTH    // store byte-lengths as part of
#define SQUOZE_STORE_LENGTH 1  // per-interned-string data
#endif

#ifndef SQUOZE_USE_INTERN      // enable interning hash-table
#define SQUOZE_USE_INTERN 1    // without this only a single
                               // core implementation can be built
			       //
/*  XXX - you should not need to tweak anything below here,
 *        though the tweaks are available for tinkering
 *        and debugging.
 */
#ifndef SQUOZE_REF_SANITY
#define SQUOZE_REF_SANITY      0 // report consistency errors and use more RAM
#endif

#ifndef SQUOZE_CLOBBER_ON_FREE
#define SQUOZE_CLOBBER_ON_FREE 0
  // clobber strings when freeing, not a full leak report
  // but better to always glitch than silently succeding or failing
#endif

#ifndef SQUOZE_INITIAL_POOL_SIZE
#define SQUOZE_INITIAL_POOL_SIZE   (1<<8)  // initial hash-table capacity
#endif

#ifndef SQUOZE_USE_BUILTIN_CLZ
#define SQUOZE_USE_BUILTIN_CLZ  1 // use builtin for determining highest bit in unicode char
#endif

#ifndef SQUOZE_UTF8_MANUAL_UNROLL
#define SQUOZE_UTF8_MANUAL_UNROLL 1 // use manually unrolled UTF8 code
#endif

#ifndef SQUOZE_LIMIT_IMPLEMENTATIONS
#define SQUOZE_LIMIT_IMPLEMENTATIONS 0
#endif

#ifndef SQUOZE_IMPLEMENTATION_32_UTF8
#define SQUOZE_IMPLEMENTATION_32_UTF8 (!SQUOZE_LIMIT_IMPLEMENTATIONS)
#endif
#ifndef SQUOZE_IMPLEMENTATION_32_UTF5
#define SQUOZE_IMPLEMENTATION_32_UTF5 (!SQUOZE_LIMIT_IMPLEMENTATIONS)
#endif
#ifndef SQUOZE_IMPLEMENTATION_52_UTF5
#define SQUOZE_IMPLEMENTATION_52_UTF5 (!SQUOZE_LIMIT_IMPLEMENTATIONS)
#endif
#ifndef SQUOZE_IMPLEMENTATION_62_UTF5
#define SQUOZE_IMPLEMENTATION_62_UTF5 (!SQUOZE_LIMIT_IMPLEMENTATIONS)
#endif
#ifndef SQUOZE_IMPLEMENTATION_64_UTF8
#define SQUOZE_IMPLEMENTATION_64_UTF8 (!SQUOZE_LIMIT_IMPLEMENTATIONS)
#endif
#endif

#if SQUOZE_USE_INTERN

#if SQUOZE_ID_BITS==32
typedef uint32_t sqz_id_t;
#else
typedef uint64_t sqz_id_t;
#endif


typedef struct _Sqz      Sqz;      /* handle representing a squozed string  */


/* create a new string that is the concatenation of a and b
 */
Sqz         *sqz_utf8             (const char *str);
const char  *sqz_decode           (Sqz *squozed, char *temp);


int          sqz_length           (Sqz *squozed);
sqz_id_t     sqz_id               (Sqz *squozed);
uint32_t     sqz_unichar_at       (Sqz *a, int pos);
int          sqz_strcmp           (Sqz *a, Sqz *b);
inline int   sqz_equal            (Sqz *a, Sqz *b) { return a == b; }
void         sqz_unset            (Sqz **a);


Sqz         *sqz_cat              (Sqz *a, Sqz *b);
Sqz         *sqz_substring        (Sqz *a, int pos, int length);

void         sqz_insert           (Sqz **a, int pos, Sqz *b);
void         sqz_set              (Sqz **a, Sqz *b);
void         sqz_erase            (Sqz **a, int pos, int length);

#include <stdarg.h>
Sqz         *sqz_printf           (const char *format, ...);
Sqz         *sqz_printf_va_list   (const char *format, va_list list);
Sqz         *sqz_unichar          (uint32_t unichar);
Sqz         *sqz_double           (double value);
Sqz         *sqz_int              (int value);

/* the following is APIs mostly implemented in terms of the above */

int          sqz_has_prefix       (Sqz *a, Sqz *prefix);
int          sqz_has_suffix       (Sqz *a, Sqz *suffix);

void         sqz_insert_double    (Sqz **a, int pos, double value);
void         sqz_insert_int       (Sqz **a, int pos, int value);


void         sqz_insert_unichar   (Sqz **a, int pos, uint32_t unichar);
void         sqz_replace_unichar  (Sqz **a, int pos, int length, uint32_t unichar);
void         sqz_append_unichar   (Sqz **a, uint32_t unichar);
void         sqz_append_utf8      (Sqz **a, const char *utf8);
int          sqz_has_prefix_utf8  (Sqz  *a, const char *utf8);
int          sqz_has_suffix_utf8  (Sqz  *a, const char *utf8);
void         sqz_insert_utf8      (Sqz **a, int pos, const char *utf8);
void         sqz_set_utf8         (Sqz **a, const char *utf8);
void         sqz_replace_utf8     (Sqz **a, int pos, int length, const char *utf8);
void         sqz_set_printf       (Sqz **a, const char *format, ...);
void         sqz_append_printf    (Sqz **a, const char *format, ...);
void         sqz_insert_printf    (Sqz **a, int pos, const char *format, ...);
void         sqz_replace_printf   (Sqz **a, int pos, int length, const char *format, ...);
/* increase reference count of string */
Sqz         *sqz_ref              (Sqz *squozed);
Sqz         *sqz_dup              (Sqz *squozed);
/* decrement reference count of string */
void         sqz_unref            (Sqz *squozed);
typedef struct _SqzPool  SqzPool;  /* a pool for grouping allocated strings */


/* create a new string pool, with fallback to another pool -
 * or NULL for fallback to default pool, takes a reference on fallback.
 */
SqzPool    *sqz_pool_new          (SqzPool *fallback);

/* increase reference count of pool
 */
void         sqz_pool_ref         (SqzPool *pool);

/* decrease reference point of pool, when matching _new() + _ref() calls
 * the pool is destoryed.
 */
void         sqz_pool_unref       (SqzPool *pool);

/* add a string to a squoze pool
 */
Sqz         *sqz_pool_add         (SqzPool *pool, const char *str);

Sqz *sqz_concat (Sqz *a, Sqz *b);

/* Report stats on interned strings 
 */
void sqz_pool_mem_stats (SqzPool *pool,
                         size_t     *size,
                         size_t     *slack,
                         size_t     *intern_alloc);

/* empty all pools
 */
void sqz_cleanup (void);

#endif


#if SQUOZE_IMPLEMENTATION_32_UTF5 || \
    SQUOZE_IMPLEMENTATION_52_UTF5 || \
    SQUOZE_IMPLEMENTATION_62_UTF5
#define SQUOZE_USE_UTF5 1
#else
#define SQUOZE_USE_UTF5 0
#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if SQUOZE_IMPLEMENTATION_32_UTF5
uint32_t     squoze32_utf5        (const char *utf8, size_t len);
const char  *squoze32_utf5_decode (uint32_t    id,   char *dest);
#endif

#if SQUOZE_IMPLEMENTATION_32_UTF8
uint32_t     squoze32_utf8        (const char *utf8, size_t len);
const char  *squoze32_utf8_decode (uint32_t    id,   char *dest);
#endif

#if SQUOZE_IMPLEMENTATION_52_UTF5
uint64_t     squoze52_utf5        (const char *utf8, size_t len);
const char  *squoze52_utf5_decode (uint64_t    id,   char *dest);
#endif

#if SQUOZE_IMPLEMENTATION_62_UTF5
uint64_t     squoze62_utf5        (const char *utf8, size_t len);
const char  *squoze62_utf5_decode (uint64_t    id,   char *dest);
#endif

#if SQUOZE_IMPLEMENTATION_64_UTF8
uint64_t     squoze64_utf8        (const char *utf8, size_t len);
const char  *squoze62_utf8_decode (uint64_t    id,   char *dest);
#endif

#endif

#ifdef SQUOZE_IMPLEMENTATION


static inline uint32_t MurmurOAAT32 (const char * key, int len)
{
  size_t h = 3323198485ul;
  for (int i = 0;i < len;i++) {
    h ^= key[i];
    h *= 0x5bd1e995;
    //h &= 0xffffffff;
    h ^= h >> 15;
  }
  return h;
}

static inline uint64_t MurmurOAAT64 ( const char * key, int len)
{
  uint64_t h = 525201411107845655ull;
  for (int i = 0;i < len;i++) {
    h ^= key[i];
    h *= 0x5bd1e9955bd1e995;
    h ^= h >> 47;
  }
  return h;
}

#if SQUOZE_USE_UTF5 // YYY

// TODO:  UTF5+ should operate directly on bits instead of
//        going via bytes
static inline void squoze5_encode (const char *input, int inlen,
                                   char *output, int *r_outlen,
                                   int   permit_squeezed,
                                   int   escape_endzero);
static void squoze_decode_utf5_bytes (int is_utf5, 
                                      const unsigned char *input, int inlen,
                                      char *output, int *r_outlen);
static inline size_t squoze5_encode_int (const char *input, int inlen,
                                         int maxlen, int *overflow,
                                         int escape_endzero);

#endif


/* this should have the same behavior as the bitwidth and encoding
 * specific implementations
 */
static inline uint64_t squoze_encode_id (int squoze_dim, int utf5, const char *stf8, size_t len)
{
  int length = len;
  uint64_t id = 0;
#if SQUOZE_USE_UTF5
  if (utf5)
  {
    int max_quintets = squoze_dim / 5;
    if (length <= max_quintets)
    {
      int overflow = 0;
      id = squoze5_encode_int (stf8, length, max_quintets, &overflow, 1);
      if (!overflow)
        return id;
    }
    id = 0;
    id = MurmurOAAT32(stf8, length);
    id &= ~1;
  }
  else
#endif
  {
    const uint8_t *utf8 = (const uint8_t*)stf8;
    if (squoze_dim > 32)
      squoze_dim = 64;
    int bytes_dim = squoze_dim / 8;
  
    uint8_t first_byte = ((uint8_t*)utf8)[0];
    if (first_byte<128
        && first_byte != 11
        && (length <= bytes_dim))
    {
      id = utf8[0] * 2 + 1;
      for (int i = 1; i < length; i++)
        id += ((uint64_t)utf8[i]<<(8*(i)));
    }
    else if (length <= bytes_dim-1)
    {
      id = 23;
      for (int i = 0; i < length; i++)
        id += ((uint64_t)utf8[i]<<(8*(i+1)));
    }
    else
    {
      id = MurmurOAAT32(stf8, len);
      id &= ~1;  // make even - intern marker
    }
  }
  return id;
}

#ifdef __CTX_H__ // override with ctx variants if included from ctx
#define strdup ctx_strdup
#define strstr ctx_strstr
#endif


#if SQUOZE_IMPLEMENTATION_32_UTF5
uint32_t squoze32_utf5 (const char *utf8, size_t len)
{
  return squoze_encode_id (32, 1, utf8, len);
}
#endif

#if SQUOZE_IMPLEMENTATION_52_UTF5
uint64_t squoze52_utf5 (const char *utf8, size_t len)
{
  return squoze_encode_id (52, 1, utf8, len);
}
#endif

#if SQUOZE_IMPLEMENTATION_62_UTF5
uint64_t squoze62_utf5 (const char *utf8, size_t len)
{
  return squoze_encode_id (62, 1, utf8, len);
}
#endif

static inline uint64_t squoze_utf8 (size_t bytes_dim, const char *stf8, size_t length)
{
  uint64_t id;
  const uint8_t *utf8 = (const uint8_t*)stf8;

  uint8_t first_byte = ((uint8_t*)utf8)[0];
  if (   first_byte < 128
      && first_byte != 11
      && (length <= bytes_dim))
  {
      switch (length)
      {
#if SQUOZE_UTF8_MANUAL_UNROLL
        case 0: id = 1;
                break;
        case 1: id = utf8[0] * 2 + 1;
                break;
        case 2: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1));
                break;
        case 3: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1))
                                     + (utf8[2] << (8*2));
                break;
        case 4: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1))
                                     + (utf8[2] << (8*2))
                                     + (utf8[3] << (8*3));
                break;
        case 5: id = utf8[0] * 2 + 1 + ((uint64_t)utf8[1] << (8*1))
                                     + ((uint64_t)utf8[2] << (8*2))
                                     + ((uint64_t)utf8[3] << (8*3))
                                     + ((uint64_t)utf8[4] << (8*4));
                break;
        case 6: id = utf8[0] * 2 + 1 + ((uint64_t)utf8[1] << (8*1))
                                     + ((uint64_t)utf8[2] << (8*2))
                                     + ((uint64_t)utf8[3] << (8*3))
                                     + ((uint64_t)utf8[4] << (8*4))
                                     + ((uint64_t)utf8[5] << (8*5));
                break;
        case 7: id = utf8[0] * 2 + 1 + ((uint64_t)utf8[1] << (8*1))
                                     + ((uint64_t)utf8[2] << (8*2))
                                     + ((uint64_t)utf8[3] << (8*3))
                                     + ((uint64_t)utf8[4] << (8*4))
                                     + ((uint64_t)utf8[5] << (8*5))
                                     + ((uint64_t)utf8[6] << (8*6));
                break;
        case 8: id = utf8[0] * 2 + 1 + ((uint64_t)utf8[1] << (8*1))
                                     + ((uint64_t)utf8[2] << (8*2))
                                     + ((uint64_t)utf8[3] << (8*3))
                                     + ((uint64_t)utf8[4] << (8*4))
                                     + ((uint64_t)utf8[5] << (8*5))
                                     + ((uint64_t)utf8[6] << (8*6))
                                     + ((uint64_t)utf8[7] << (8*7));
                break;
#endif
        default:
          id = utf8[0] * 2 + 1;
          for (unsigned int i = 1; i < length; i++)
            id += ((uint64_t)utf8[i]<<(8*(i)));
      }
    return id;
  }
  else if (length <= bytes_dim-1)
  {
      switch (length)
      {
#if SQUOZE_UTF8_MANUAL_UNROLL
        case 0: id = 23;
          break;
        case 1: id = 23 + (utf8[0] << (8*1));
          break;
        case 2: id = 23 + (utf8[0] << (8*1))
                        + (utf8[1] << (8*2));
          break;
        case 3: id = 23 + (utf8[0] << (8*1))
                        + (utf8[1] << (8*2))
                        + (utf8[2] << (8*3));
          break;
        case 4: id = 23 + ((uint64_t)utf8[0] << (8*1))
                        + ((uint64_t)utf8[1] << (8*2))
                        + ((uint64_t)utf8[2] << (8*3))
                        + ((uint64_t)utf8[3] << (8*4));
          break;
        case 5: id = 23 + ((uint64_t)utf8[0] << (8*1))
                        + ((uint64_t)utf8[1] << (8*2))
                        + ((uint64_t)utf8[2] << (8*3))
                        + ((uint64_t)utf8[3] << (8*4))
                        + ((uint64_t)utf8[4] << (8*5));
          break;
        case 6: id = 23 + ((uint64_t)utf8[0] << (8*1))
                        + ((uint64_t)utf8[1] << (8*2))
                        + ((uint64_t)utf8[2] << (8*3))
                        + ((uint64_t)utf8[3] << (8*4))
                        + ((uint64_t)utf8[4] << (8*5))
                        + ((uint64_t)utf8[5] << (8*6));
          break;
        case 7: id = 23 + ((uint64_t)utf8[0] << (8*1))
                        + ((uint64_t)utf8[1] << (8*2))
                        + ((uint64_t)utf8[2] << (8*3))
                        + ((uint64_t)utf8[3] << (8*4))
                        + ((uint64_t)utf8[4] << (8*5))
                        + ((uint64_t)utf8[5] << (8*6))
                        + ((uint64_t)utf8[6] << (8*7));
          break;
#endif
        default:
          id = 23;
          for (unsigned int i = 0; i < length; i++)
            id += ((uint64_t)utf8[i]<<(8*(i+1)));
      }
    return id;
  }

  id = MurmurOAAT32(stf8, length);
  id &= ~1;  // make even - intern marker
  return id;
}

#if SQUOZE_IMPLEMENTATION_64_UTF8
uint64_t squoze64_utf8 (const char *stf8, size_t length)
{
  return squoze_utf8 (8, stf8, length);
}
#endif

#if SQUOZE_IMPLEMENTATION_32_UTF8
uint32_t squoze32_utf8 (const char *stf8, size_t length)
{
  uint32_t id;
  const uint8_t *utf8 = (const uint8_t*)stf8;
  size_t bytes_dim = 4;

  uint8_t first_byte = ((uint8_t*)utf8)[0];
  if (first_byte    < 128
      && first_byte != 11
      && (length <= bytes_dim))
  {
      switch (length)
      {
#if SQUOZE_UTF8_MANUAL_UNROLL
        case 0: id = 1;
                break;
        case 1: id = utf8[0] * 2 + 1;
                break;
        case 2: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1));
                break;
        case 3: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1))
                                     + (utf8[2] << (8*2));
                break;
        case 4: id = utf8[0] * 2 + 1 + (utf8[1] << (8*1))
                                     + (utf8[2] << (8*2))
                                     + (utf8[3] << (8*3));
                break;
#endif
        default:
          id = utf8[0] * 2 + 1;
          for (unsigned int i = 1; i < length; i++)
            id += ((uint32_t)utf8[i]<<(8*(i)));
      }
    return id;
  }
  else if (length <= bytes_dim-1)
  {
      switch (length)
      {
#if SQUOZE_UTF8_MANUAL_UNROLL
        case 0: id = 23;
          break;
        case 1: id = 23 + (utf8[0] << (8*1));
          break;
        case 2: id = 23 + (utf8[0] << (8*1))
                        + (utf8[1] << (8*2));
          break;
        case 3: id = 23 + (utf8[0] << (8*1))
                        + (utf8[1] << (8*2))
                        + (utf8[2] << (8*3));
          break;
#endif
        default:
          id = 23;
          for (unsigned int i = 0; i < length; i++)
            id += (utf8[i]<<(8*(i+1)));
      }
    return id;
  }

  id = MurmurOAAT32(stf8, length);
  id &= ~1;  // make even - intern marker
  return id;
}
#endif


static const char *squoze_id_decode_r (int squoze_dim, uint64_t hash, char *ret, int retlen, int is_utf5)
{
#if SQUOZE_USE_UTF5
  if (is_utf5)
  {
    int is_utf5       = (hash & 2)!=0;
    uint8_t utf5[20]=""; // we newer go really high since there isnt room
                          // in the integers
    uint64_t tmp = hash;
    int len = 0;
    tmp /= 4;
    utf5[len]=0;
    while (tmp > 0)
    {
      utf5[len++] = tmp & 31;
      tmp /= 32;
    }
    utf5[len]=0;
    squoze_decode_utf5_bytes (is_utf5, utf5, len, ret, &retlen);
    return ret;
  }
  else
#endif
  {
    if (squoze_dim == 32)
    {
      if ((hash & 0xff) == 23)
      {
         memcpy (ret, ((char*)&hash)+1, 3);
         ret[3] = 0;
      }
      else
      {
        memcpy (ret, &hash, 4);
        ((unsigned char*)ret)[0]/=2;
        ret[4] = 0;
      }
    }
    else
    {
      if ((hash & 0xff) == 23)
      {
        memcpy (ret, ((char*)&hash)+1, 7);
        ret[7] = 0;
      }
      else
      {
        memcpy (ret, &hash, 8);
        ((unsigned char*)ret)[0]/=2;
        ret[8] = 0;
      }
    }
    return ret;
  }
}

const char *squoze_id_decode (int squoze_dim, uint64_t id, int is_utf5, char *dest);
const char *squoze_id_decode (int squoze_dim, uint64_t id, int is_utf5, char *dest)
{
  if (id == 0 || ((id & 1) == 0)) {dest[0]=0;return NULL; }
  else if (id == 3) { dest[0]=0;return NULL;}
  squoze_id_decode_r (squoze_dim, id, dest, 16, is_utf5);
  return dest;
}

#if SQUOZE_IMPLEMENTATION_32_UTF5
const char *squoze32_utf5_decode (uint32_t id, char *dest)
{
  return squoze_id_decode (32, id, 1, dest);
}
#endif

#if SQUOZE_IMPLEMENTATION_52_UTF5
const char *squoze52_utf5_decode (uint64_t id, char *dest)
{
  return squoze_id_decode (52, id, 1, dest);
}
#endif

#if SQUOZE_IMPLEMENTATION_62_UTF5
const char *squoze62_utf5_decode (uint64_t id, char *dest)
{
  return squoze_id_decode (62, id, 1, dest);
}
#endif

#if SQUOZE_IMPLEMENTATION_64_UTF8
const char *squoze64_utf8_decode (uint64_t id, char *dest)
{
  return squoze_id_decode (64, id, 0, dest);
}
#endif

#if SQUOZE_IMPLEMENTATION_32_UTF8
const char *squoze32_utf8_decode (uint32_t id, char *dest)
{
  return squoze_id_decode (32, id, 0, dest);
}
#endif

static inline uint32_t
squoze_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;
}
static inline int
squoze_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;
}

static inline int squoze_utf8_strlen (const char *s)
{
  int count;
  if (!s)
    { return 0; }
  for (count = 0; *s; s++)
    if ( (*s & 0xC0) != 0x80)
      { count++; }
  return count;
}

static inline int
squoze_utf8_len (const unsigned char first_byte)
{
  if      ( (first_byte & 0x80) == 0)
    { return 1; }
  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;
}



/*  data structures and implementation for string interning, potentially
 *  with both ref-counting and pools of strings.
 */
#if SQUOZE_USE_INTERN

struct _Sqz {
#if SQUOZE_REF_COUNTING
    int32_t       ref_count; // set to magic value for ROM strings?
                             // and store pointer in string data?
#endif
#if SQUOZE_STORE_LENGTH
    int32_t       length;
#endif
    sqz_id_t      hash;
    char          string[];
};


static inline uint64_t sqz_pool_encode     (SqzPool *pool, const char *utf8, size_t len, Sqz **interned_ref);

struct _SqzPool
{
  int32_t     ref_count;
  SqzPool    *fallback;
  Sqz       **hashtable;
  int         count;
  int         size;
  SqzPool    *next;
};

static SqzPool global_pool = {0, NULL, NULL, 0, 0, NULL};

static SqzPool *sqz_pools = NULL;

static int sqz_pool_find (SqzPool *pool, uint64_t hash, int length, const uint8_t *bytes)
{
  if (pool->size == 0)
    return -1;
  int pos = (hash/2) & (pool->size-1);
  if (!pool->hashtable[pos])
    return -1;
  while (pool->hashtable[pos]->hash != hash
#if SQUOZE_STORE_LENGTH
         || pool->hashtable[pos]->length != length
#endif
         || strcmp (pool->hashtable[pos]->string, (char*)bytes)
         )
  {
    pos++;
    pos &= (pool->size-1);
    if (!pool->hashtable[pos])
      return -1;
  }
  return pos;
}
static int sqz_pool_add_entry (SqzPool *pool, Sqz *str)
{
  if (pool->count + 1 >= pool->size / 2)
  {
     Sqz **old = pool->hashtable;
     int old_size = pool->size;
     if (old_size == 0)
       pool->size = SQUOZE_INITIAL_POOL_SIZE;
     else
       pool->size *= 2;
     pool->hashtable = (Sqz**)calloc (pool->size, sizeof (void*));
     if (old)
     {
       for (int i = 0; i < old_size; i++)
         if (old[i])
           sqz_pool_add_entry (pool, old[i]);
       free (old);
     }
  }
  pool->count++;

  int pos = (str->hash/2) & (pool->size-1);
  while (pool->hashtable[pos])
  {
    pos++;
    pos &= (pool->size-1);
  }
  pool->hashtable[pos]=str;
  return pos;
}

#if SQUOZE_REF_SANITY
static int sqz_pool_remove (SqzPool *pool, Sqz *squozed, int do_free)
{
  Sqz *str = squozed;
  int no = sqz_pool_find (pool, str->hash, strlen (str->string), (uint8_t*)str->string);
  if (no < 0)
    return 0;
  if (do_free)
    free (str);
#ifdef assert
  assert (pool->hashtable[no] == squozed);
#endif
  pool->hashtable[no]=0;
  
  // check if there is another one to promote now
  for (int i = no+1; pool->hashtable[i]; i = (i+1)&(pool->size-1))
  {
    if ((pool->hashtable[i]->hash & (pool->size-1)) == (unsigned)no)
    {
      Sqz *for_upgrade = pool->hashtable[i];
      sqz_pool_remove (pool, for_upgrade, 0);
      sqz_pool_add_entry (pool, for_upgrade);
      break;
    }
  }
  return 1;
}
#endif

static Sqz *sqz_lookup (SqzPool *pool, sqz_id_t id, int length, const uint8_t *bytes)
{
  int pos = sqz_pool_find (pool, id, length, bytes);
  if (pos >= 0)
    return pool->hashtable[pos];
  if (pool->fallback)
    return sqz_lookup (pool->fallback, id, length, bytes);
  return NULL;
}

void sqz_pool_mem_stats (SqzPool *pool,
                         size_t     *size,
                            size_t     *slack,
                            size_t     *intern_alloc)
{
  if (!pool) pool = &global_pool;
  if (size)
  {
    *size = sizeof (SqzPool) + pool->size * sizeof (void*);
  }
  if (slack)
  {
    *slack = (pool->size - pool->count) * sizeof (void*);
  }

  if (intern_alloc)
  {
    size_t sum = 0;
    for (int i = 0; i < pool->size; i++)
    {
      if (pool->hashtable[i])
      {
        Sqz *squoze = pool->hashtable[i];
        sum += strlen (squoze->string) + 1 + sizeof (Sqz);
      }
    }
    *intern_alloc = sum;
  }
}

 // we do 32bit also for 64bit - we want the same predetermined hashes to match
static inline Sqz *_sqz_pool_add (SqzPool *pool, const char *str)
{
  if (!pool) pool = &global_pool;
  Sqz *interned = NULL;
  uint64_t hash = sqz_pool_encode (pool, str, strlen (str), &interned);

  if (interned)
  {
#ifdef assert
    assert ((((size_t)interned)&0x1)==0);
#endif
#if SQUOZE_DIRECT_STRING
    return interned+1;
#else
    return interned;
#endif
  }
  else
    return (Sqz*)((size_t)hash);
}

Sqz *sqz_pool_add (SqzPool *pool, const char *str)
{
  return _sqz_pool_add (pool, str);
}

Sqz *sqz_utf8(const char *str)
{
  return _sqz_pool_add (NULL, str);
}

// encodes utf8 to a squoze id of squoze_dim bits - if interned_ret is provided overflowed ids
// are interned and a new interned squoze is returned.
static uint64_t sqz_pool_encode (SqzPool *pool, const char *utf8, size_t len, Sqz **interned_ref)
{
#if   SQUOZE_ID_BITS==32 && SQUOZE_ID_MURMUR
   uint64_t hash = MurmurOAAT32(utf8, len) & ~1;
#elif  SQUOZE_ID_BITS==32 && SQUOZE_ID_UTF5
   uint64_t hash = squoze32_utf5 (utf8, len);
#elif SQUOZE_ID_BITS==32 && SQUOZE_ID_UTF8
   uint64_t hash = squoze32_utf8 (utf8, len);
#elif SQUOZE_ID_BITS==62 && SQUOZE_ID_UTF5
   uint64_t hash = squoze62_utf5 (utf8, len);
#elif SQUOZE_ID_BITS==52 && SQUOZE_ID_UTF5
   uint64_t hash = squoze52_utf5 (utf8, len);
#elif SQUOZE_ID_BITS==64 && SQUOZE_ID_UTF8
   uint64_t hash = squoze64_utf8 (utf8, len);
#else
   uint64_t hash = squoze_encode_id (SQUOZE_ID_BITS, SQUOZE_ID_UTF5, utf8, len);
#endif

  if (!interned_ref)
    return hash;
  if (pool == NULL) pool = &global_pool;
  if ((hash & 1)==0)
  {
    Sqz *str = sqz_lookup (pool, hash, len, (const uint8_t*)utf8);
    if (str)
    {
#if SQUOZE_REF_COUNTING
      str->ref_count++;
#endif
      if (interned_ref) *interned_ref = str + SQUOZE_INTERN_DIRECT_STRING;
      return hash; 
    }

    {
      Sqz *entry = (Sqz*)calloc (len + 1 + sizeof(Sqz), 1);
      entry->hash = hash;
#if SQUOZE_STORE_LENGTH
      entry->length = len;
#endif
      strcpy (entry->string, utf8);
      if (interned_ref) *interned_ref = entry + SQUOZE_INTERN_DIRECT_STRING;
      sqz_pool_add_entry (pool, entry);
    }
  }
  return hash;
}

static inline int sqz_is_interned (Sqz *squozed)
{
  return ((((size_t)(squozed))&1) == 0);
}

static inline int sqz_is_embedded (Sqz *squozed)
{
  return !sqz_is_interned (squozed);
}

/* returns either the string or temp with the decode
 * embedded string decoded
 */
const char *sqz_decode (Sqz *squozed, char *temp)
{
  if (!squozed) return NULL;
  if (sqz_is_embedded (squozed))
  {
#if   SQUOZE_ID_BITS==32 && SQUOZE_ID_UTF5
    return squoze32_utf5_decode ((size_t)squozed, temp);
#elif SQUOZE_ID_BITS==32 && SQUOZE_ID_UTF8
    return squoze32_utf8_decode ((size_t)squozed, temp);
#elif SQUOZE_ID_BITS==52 && SQUOZE_ID_UTF5
    return squoze52_utf5_decode ((size_t)squozed, temp);
#elif SQUOZE_ID_BITS==62 && SQUOZE_ID_UTF5
    return squoze62_utf5_decode ((size_t)squozed, temp);
#elif SQUOZE_ID_BITS==64 && SQUOZE_ID_UTF8
    return squoze64_utf8_decode ((size_t)squozed, temp);
#else
    return squoze_id_decode (SQUOZE_ID_BITS,
                             ((size_t)squozed),
                             SQUOZE_ID_UTF5,
                             temp);
#endif
  }
  else
  {
#if SQUOZE_INTERN_DIRECT_STRING
    return (char*)squozed;
#else
    return squozed->string;
#endif
  }
}

Sqz *sqz_ref (Sqz *squozed)
{
#if SQUOZE_REF_COUNTING
  if (sqz_is_interned (squozed))
  {
     (squozed-SQUOZE_INTERN_DIRECT_STRING)->ref_count ++;
  }
#endif
  return squozed;
}
Sqz *sqz_dup (Sqz *squozed)
{
  return sqz_ref (squozed);
}

void sqz_unref (Sqz *squozed)
{
#if SQUOZE_REF_COUNTING
  if (sqz_is_interned (squozed))
  {
#if SQUOZE_INTERN_DIRECT_STRING
      squozed--;
#endif
      if (squozed->ref_count <= 0)
      {
#if SQUOZE_CLOBBER_ON_FREE
        squozed->string[-squozed->ref_count]='#';
#endif
#if SQUOZE_REF_SANITY
        if (squozed->ref_count < 0)
          fprintf (stderr, "double unref for \"%s\"\n", squozed->string);
        squozed->ref_count--;
#else
        SqzPool *pool = &global_pool;
        if (sqz_pool_remove (pool, squozed, 1))
        {
          return;
        }
        pool = sqz_pools;
        if (pool)
        do {
          if (sqz_pool_remove (pool, squozed, 1))
          {
            return;
          }
          pool = pool->next;
        } while (pool);
#endif
      }
      else
      {
        squozed->ref_count--;
      }
  }
#endif
}

int sqz_has_prefix (Sqz *a, Sqz *prefix)
{
  char tmp_a[16];
  char tmp_prefix[16];
  const char *a_str = sqz_decode (a, tmp_a);
  const char *prefix_str = sqz_decode (prefix, tmp_prefix);
  return !strncmp (a_str, prefix_str, strlen (prefix_str));
}

int sqz_has_prefix_utf8 (Sqz *a, const char *utf8)
{
  Sqz *b = sqz_utf8 (utf8);
  int ret = sqz_has_prefix (a, b);
  sqz_unref (b);
  return ret;
}

int sqz_has_suffix_utf8 (Sqz *a, const char *utf8)
{
  Sqz *b = sqz_utf8 (utf8);
  int ret = sqz_has_suffix (a, b);
  sqz_unref (b);
  return ret;
}

int sqz_has_suffix (Sqz *a, Sqz *suffix)
{
  char        tmp_a[16];
  const char *a_str = sqz_decode (a, tmp_a);
  int         a_len = strlen (a_str);
  char        tmp_suffix[16];
  const char *suffix_str = sqz_decode (suffix, tmp_suffix);
  int         suffix_len = strlen (suffix_str);
  
  if (a_len < suffix_len)
    return 0;
  return strcmp (a_str + a_len - suffix_len, suffix_str);
}


static void _sqz_prepend (Sqz **squoze, Sqz *head)
{
  if (!squoze) return;
  Sqz *combined = sqz_cat (head, *squoze);
  sqz_unref (*squoze);
  *squoze=combined;
}

static void _sqz_append (Sqz **squoze, Sqz *tail)
{
  if (!squoze) return;
  Sqz *combined = sqz_cat (*squoze, tail);
  sqz_unref (*squoze);
  *squoze=combined;
}

Sqz *sqz_substring (Sqz *a, int pos, int length)
{
  int src_length = sqz_length (a);
  if (pos > src_length)
    return sqz_utf8 ("");
  if (pos < 0)
    pos = src_length + pos + 1;
  char tmp[16];
  const char *src = sqz_decode (a, tmp);
  char *end;
  int allocated = 0;

  char *copy;
  if (src_length < 256)
  {
    copy = alloca (strlen (src) + 1);
    strcpy (copy, src);
  }
  else
  {
    copy  = strdup (src);
    allocated = 1;
  }
  char *p = copy;
  int i;
  for (i = 0; i < pos; i++)
    p += squoze_utf8_len (*p);
  end = p;
  for (i = 0; i < length && *end; i++)
    end += squoze_utf8_len (*end);
  *end = 0;

  Sqz *ret = sqz_utf8 (p);
  if (allocated)
    free (copy);
  return ret;
}

void sqz_erase (Sqz **a, int pos, int length)
{
  if (!a) return;
  if (!*a) return;

  if (length < 1)
    return;
  if (pos < 0)
  {
    pos = sqz_length (*a) + pos;
  }

  Sqz *pre  = sqz_substring (*a, 0, pos);
  Sqz *post = sqz_substring (*a, pos+length, 10000);
  sqz_unref (*a);
  *a = sqz_cat (pre, post);
  sqz_unref (pre);
  sqz_unref (post);
}

void sqz_insert (Sqz **a, int pos, Sqz *b)
{
  if (pos == 0)
  {
    _sqz_prepend (a, b);
    return;
  }
  if (pos == -1)
  {
    _sqz_append (a, b);
    return;
  }
  if (!a) return;
  if (!*a) return;
  if (pos < 0)
  {
    pos = sqz_length (*a) + pos + 1;
  }
  Sqz *pre  = sqz_substring (*a, 0, pos);
  Sqz *post = sqz_substring (*a, pos, 10000);
  sqz_unref (*a);

  *a = sqz_cat (pre, b);
  _sqz_append (a, post);
  sqz_unref (pre);
  sqz_unref (post);
}

void sqz_insert_utf8 (Sqz **a, int pos, const char *utf8)
{
  Sqz *b = sqz_utf8 (utf8);
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

Sqz *sqz_unichar (uint32_t unichar)
{
  char temp[5];
  temp[squoze_unichar_to_utf8 (unichar, (uint8_t*)temp)]=0;
  return sqz_utf8 (temp);
}

Sqz *sqz_int (int value)
{
  char temp[40];
  sprintf (temp, "%i", value);
  if (strchr (temp, ','))
    *strchr (temp, ',')='.';
  return sqz_utf8 (temp);
}

Sqz *sqz_double (double value)
{
  char temp[40];
  sprintf (temp, "%f", value);
  if (strchr (temp, ','))
    *strchr (temp, ',')='.';
  return sqz_utf8 (temp);
}

void sqz_insert_unichar (Sqz **a, int pos, uint32_t unichar)
{
  Sqz *b = sqz_unichar (unichar);
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

void sqz_insert_double (Sqz **a, int pos, double value)
{
  Sqz *b = sqz_double (value);
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

void sqz_insert_int (Sqz **a, int pos, int value)
{
  Sqz *b = sqz_int (value);
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

uint32_t sqz_unichar_at (Sqz *a, int pos)
{
  char tmp[16];
  const char *str = sqz_decode (a, tmp);
  const char *p = str;
  int i;
  if (pos < 0)
  {
    pos = sqz_length (a) + pos;
  }
  for (i = 0; i < pos; i++)
    p += squoze_utf8_len (*p);
  return squoze_utf8_to_unichar (p);
}

void sqz_replace (Sqz **a, int pos, int length, Sqz *b)
{
  sqz_erase (a, pos, length);
  sqz_insert (a, pos, b);
}

void sqz_replace_unichar  (Sqz **a, int pos, int length, uint32_t unichar)
{
  Sqz *b = sqz_unichar (unichar);
  sqz_erase (a, pos, length);
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

void sqz_replace_utf8  (Sqz **a, int pos, int length, const char *utf8)
{
  sqz_erase (a, pos, length);
  sqz_insert_utf8 (a, pos, utf8);
}

void sqz_append_utf8 (Sqz **a, const char *utf8)
{
  sqz_insert_utf8 (a, -1, utf8);
}

void sqz_append_unichar (Sqz **a, uint32_t unichar)
{
  sqz_insert_unichar (a, -1, unichar);
}

#define SQZ_EXPAND_PRINTF \
  va_list ap; \
  size_t needed; \
  char *buffer; \
  va_start (ap, format); \
  needed = vsnprintf (NULL, 0, format, ap) + 1; \
  if (needed < 256) \
    buffer = alloca (needed);\
  else\
    buffer = malloc (needed);\
  va_end (ap);\
  va_start (ap, format);\
  vsnprintf (buffer, needed, format, ap);\
  va_end (ap);\
  Sqz *b = sqz_utf8 (buffer);\
  if (needed >= 256)\
    free (buffer);

Sqz      *sqz_printf (const char *format, ...)
{
  SQZ_EXPAND_PRINTF;
  return b;
}

void sqz_insert_printf (Sqz **a, int pos, const char *format, ...)
{
  SQZ_EXPAND_PRINTF;
  sqz_insert (a, pos, b);
  sqz_unref (b);
}

void sqz_replace_printf (Sqz **a, int pos, int length, const char *format, ...)
{
  SQZ_EXPAND_PRINTF;
  sqz_replace (a, pos, length, b);
  sqz_unref (b);
}

void sqz_append_printf (Sqz **a, const char *format, ...)
{
  SQZ_EXPAND_PRINTF;
  sqz_insert (a, -1, b);
  sqz_unref (b);
}

int sqz_strcmp (Sqz *a, Sqz *b)
{
  if (a == b) return 0;
  char tmp_a[16];
  char tmp_b[16];
  return strcmp (sqz_decode (a, tmp_a), sqz_decode (b, tmp_b));
}

static void _sqz_steal (Sqz **a, Sqz *b)
{
  if (*a)
    sqz_unref (*a);
  *a = b;
}

void sqz_set (Sqz **a, Sqz *b)
{
  if (*a)
    sqz_unref (*a);
  *a = sqz_ref (b);
}

void sqz_set_utf8 (Sqz **a, const char *str)
{
  _sqz_steal (a, sqz_utf8 (str));
}

void sqz_set_printf (Sqz **a, const char *format, ...)
{
  SQZ_EXPAND_PRINTF;
  _sqz_steal (a, b);
}

void sqz_unset (Sqz **a)
{
  if (*a == NULL) return;
  sqz_unref (*a);
  *a = NULL;
}

sqz_id_t sqz_id (Sqz *squozed)
{
  if (!squozed) return 0;
  if (sqz_is_embedded (squozed))
    return ((size_t)(squozed));
  else
  {
#if SQUOZE_INTERN_DIRECT_STRING
    squozed--;
#endif
    return squozed->hash;
  }
}

int sqz_length (Sqz *squozed)
{
  char buf[15];
  if (!squozed) return 0;
  return squoze_utf8_strlen(sqz_decode (squozed, buf));
}

// XXX : not used - remove it, and be unicode native?
int sqz_byte_length (Sqz *squozed)
{
  char buf[15];
  if (!squozed) return 0;
#if 0
  return strlen(sqz_decode (squozed, buf));
#else
  if (sqz_is_embedded (squozed))
  {
    sqz_decode (squozed, buf);
    return strlen (buf);
  }
  else
  {
#if SQUOZE_INTERN_DIRECT_STRING
    squozed--;
#endif
#if SQUOZE_STORE_LENGTH
    return squozed->length;
#endif
    return strlen (squozed->string);
  }
#endif
  return 0;
}

Sqz *sqz_cat (Sqz *a, Sqz *b)
{
  char buf_a[16];
  char buf_b[16];
  const char *str_a = sqz_decode (a, buf_a);
  const char *str_b = sqz_decode (b, buf_b);
  int len_a = strlen (str_a);
  int len_b = strlen (str_b);
  if (len_a + len_b < 128)
  {
    char temp[128];
    temp[0]=0;
    strcpy (temp, str_a);
    if (str_b)
      strcpy (&temp[strlen(temp)], str_b);
    return sqz_utf8 (temp);
  }
  else
  {
    char *temp = malloc (len_a + len_b + 1);
    temp[0]=0;
    strcpy (temp, str_a);
    if (str_b)
      strcpy (&temp[strlen(temp)], str_b);
    Sqz *ret = sqz_utf8 (temp);
    free (temp);
    return ret;
  }
}


SqzPool *sqz_pool_new     (SqzPool *fallback)
{
  SqzPool *pool = (SqzPool*)calloc (sizeof (SqzPool), 1);
  pool->fallback = fallback;
  pool->next = sqz_pools;
  sqz_pools = pool;
  if (fallback)
    sqz_pool_ref (fallback);
  return pool;
}

void sqz_pool_ref (SqzPool *pool)
{
  if (!pool) return;
  pool->ref_count--;
}

static void sqz_pool_destroy (SqzPool *pool)
{
#if 0
    fprintf (stderr, "destorying pool: size:%i count:%i embedded:%i\n",
       pool->size, pool->count, pool->count_embedded);
#endif
    for (int i = 0; i < pool->size; i++)
    {
      if (pool->hashtable[i])
        free (pool->hashtable[i]);
      pool->hashtable[i] = 0;
    }
    if (pool->fallback)
      sqz_pool_unref (pool->fallback);

    if (pool == sqz_pools)
    {
       sqz_pools = pool->next;
    }
    else
    {
      SqzPool *prev = NULL;
      SqzPool *iter = sqz_pools;
      while (iter && iter != pool)
      {
         prev = iter;
         iter = iter->next;
      }
      if (prev) // XXX not needed
        prev->next = pool->next;
    }
    pool->size = 0;
    pool->count = 0;
    if (pool->hashtable)
      free (pool->hashtable);
    pool->hashtable = NULL;

    // XXX report non unreffed items based on config
}

void sqz_pool_unref (SqzPool *pool)
{
  if (!pool) return;
  if (pool->ref_count == 0)
  {
    sqz_pool_destroy (pool);
    free (pool);
  }
  else
  {
    pool->ref_count--;
  }
}

void
sqz_cleanup (void)
{
  sqz_pool_destroy (&global_pool);
  // also destory other known pools
  // XXX : when debugging report leaked pools
}
#endif

// UTF5 implementation

#if SQUOZE_USE_UTF5

// extra value meaning in UTF5 mode
#define SQUOZE_ENTER_SQUEEZE    16

// value meanings in squeeze mode
#define SQUOZE_SPACE            0
#define SQUOZE_DEC_OFFSET_A     27
#define SQUOZE_INC_OFFSET_A     28
#define SQUOZE_DEC_OFFSET_B     29
#define SQUOZE_INC_OFFSET_B     30
#define SQUOZE_ENTER_UTF5       31


static inline uint32_t squoze_utf8_to_unichar (const char *input);
static inline int      squoze_unichar_to_utf8 (uint32_t  ch, uint8_t  *dest);
static inline int      squoze_utf8_len        (const unsigned char first_byte);
static inline int      squoze_utf8_strlen     (const char *s);


/* returns the base-offset of the segment this unichar belongs to,
 *
 * segments are 26 items long and are offset so that 'a'-'z' is
 * one segment.
 */
#define SQUOZE_JUMP_STRIDE      26
#define SQUOZE_JUMP_OFFSET      19
static inline int squoze_new_offset (uint32_t unichar)
{
  uint32_t ret = unichar - (unichar % SQUOZE_JUMP_STRIDE) + SQUOZE_JUMP_OFFSET;
  if (ret > unichar) ret -= SQUOZE_JUMP_STRIDE;
  return ret;
}

static inline int squoze_needed_jump (uint32_t off, uint32_t unicha)
{
  int count = 0;
  int unichar = unicha;
  int offset = off;

  if (unichar == 32) // space is always in range
    return 0;

  /* TODO: replace this with direct computation of values instead of loop */
  while (unichar < offset)
  {
    offset -= SQUOZE_JUMP_STRIDE;
    count --;
  }
  if (count)
    return count;

  return (unichar - offset) / SQUOZE_JUMP_STRIDE;
}


static inline int
squoze_utf5_length (uint32_t unichar)
{
  if (unichar == 0)
    return 1;
#if SQUOZE_USE_BUILTIN_CLZ
  return __builtin_clz(unichar)/4+1;
#else
  int nibbles = 1;
  while (unichar)
  {
    nibbles ++;
    unichar /= 16;
  }
  return nibbles;
#endif
}

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

static inline int squoze_compute_cost_utf5 (int offset, int val, int utf5_length, int next_val, int next_utf5_length)
{
  int cost = 0; 
  cost += utf5_length;
  if (next_val)
  {
    cost += next_utf5_length;
  }
  return cost;
}

static inline int squoze_compute_cost_squeezed (int offset, int val, int needed_jump, int next_val, int next_utf5_length)
{
  int cost = 0;
  if (needed_jump == 0)
  {
    cost += 1;
  }
  else if (needed_jump >= -2 && needed_jump <= 2)
  {
    cost += 2;
    offset += SQUOZE_JUMP_STRIDE * needed_jump;
  }
  else if (needed_jump >= -10 && needed_jump <= 10)
  {
    cost += 3;
    offset += SQUOZE_JUMP_STRIDE * needed_jump;
  }
  else
  {
    cost += 100; // very expensive, makes the other choice win
  }

  if (next_val)
  {
    int change_cost = 1 + squoze_utf5_length (next_val);
    int no_change_cost = 0;
    needed_jump = squoze_needed_jump (offset, next_val);

    if (needed_jump == 0)
    {
      no_change_cost += 1;
    }
    else if (needed_jump >= -2 && needed_jump <= 2)
    {
      no_change_cost += 2;
    }
    else if (needed_jump >= -10 && needed_jump <= 10)
    {
      no_change_cost += 3;
      offset += SQUOZE_JUMP_STRIDE * needed_jump;
    }
    else
    {
      no_change_cost = change_cost;
    }
    if (change_cost < no_change_cost)
      cost += change_cost;
    else
      cost += no_change_cost;
  }

  return cost;
}

static inline void squoze5_encode (const char *input, int inlen,
                                   char *output, int *r_outlen,
                                   int   permit_squeezed,
                                   int   escape_endzero)
{
  int offset  = 97;//squoze_new_offset('a');
  int is_utf5 = 1;
  int len     = 0;

  int first_len;
  int next_val = squoze_utf8_to_unichar (&input[0]);
  int next_utf5_length = squoze_utf5_length (next_val);
  for (int i = 0; i < inlen; i+= first_len)
  {
    int val = next_val;
    int utf5_length = next_utf5_length;
    int needed_jump = squoze_needed_jump (offset, val);
    first_len = squoze_utf8_len (input[i]);
    if (i + first_len < inlen)
    {
      next_val = squoze_utf8_to_unichar (&input[i+first_len]);
      next_utf5_length = squoze_utf5_length (next_val);
    }

    if (is_utf5)
    {
      int change_cost    = squoze_compute_cost_squeezed (offset, val, needed_jump, next_val, next_utf5_length);
      int no_change_cost = squoze_compute_cost_utf5 (offset, val, utf5_length, next_val, next_utf5_length);
  
      if (i != 0)          /* ignore cost of initial 'G' */
        change_cost += 1;

      if (permit_squeezed && change_cost <= no_change_cost)
      {
        output[len++] = SQUOZE_ENTER_SQUEEZE;
        is_utf5 = 0;
      }
    }
    else
    {
      int change_cost    = 1 + squoze_compute_cost_utf5 (offset, val, utf5_length, next_val, next_utf5_length);
      int no_change_cost = squoze_compute_cost_squeezed (offset, val, needed_jump, next_val, next_utf5_length);

      if (change_cost < no_change_cost)
      {
        output[len++] = SQUOZE_ENTER_UTF5;
        is_utf5 = 1;
      }
    }

    if (!is_utf5)
    {
      if (needed_jump)
      {
        if (needed_jump >= -2 && needed_jump <= 2)
        {
          switch (needed_jump)
          {
            case -1: output[len++] = SQUOZE_DEC_OFFSET_B; break;
            case  1: output[len++] = SQUOZE_INC_OFFSET_B; break;
            case -2: output[len++] = SQUOZE_DEC_OFFSET_A; break;
            case  2: output[len++] = SQUOZE_INC_OFFSET_A; break;
          }
          offset += SQUOZE_JUMP_STRIDE * needed_jump;
        }
        else if (needed_jump >= -10 && needed_jump <= 10) {
              int encoded_val;
              if (needed_jump < -2)
                encoded_val = 5 - needed_jump;
              else
                encoded_val = needed_jump - 3;

              output[len++] = (encoded_val / 4) + SQUOZE_DEC_OFFSET_A;
              output[len++] = (encoded_val % 4) + SQUOZE_DEC_OFFSET_A;

              offset += SQUOZE_JUMP_STRIDE * needed_jump;
        }
        else
        {
#ifdef assert
          assert(0); // should not be reached
#endif
          output[len++] = SQUOZE_ENTER_UTF5;
          is_utf5 = 1;
        }
      }
    }

    if (is_utf5)
    {
      offset = squoze_new_offset (val);
      int quintet_no = 0;
      uint8_t temp[12]={0,};

      while (val)
      {
        int oval = val % 16;
        int hi = 16;
        if (val / 16)
          hi = 0;
        temp[quintet_no++] = oval + hi;
        val /= 16;
      }
      for (int i = 0; i < quintet_no; i++)
        output[len++] = temp[quintet_no-1-i];
    }
    else 
    {
      output[len++] = (val == ' ')?SQUOZE_SPACE:val-offset+1;
    }
  }

  if (escape_endzero && len && output[len-1]==0)
  {
    if (is_utf5)
      output[len++] = 16;
    else
      output[len++] = SQUOZE_ENTER_UTF5;
  }
  output[len]=0;
  if (r_outlen)
    *r_outlen = len;
}

/* squoze_encode_int:
 * @input utf8 input data
 * @inlen length of @input in bytes
 * @maxlen maximum number of quintets to encode
 * @overflow pointer to int that gets set to 1 if we overflow
 * @permit_squeezed 
 *
 */
static inline size_t squoze5_encode_int (const char *input, int inlen,
                                         int maxlen, int *overflow,
                                         int escape_endzero)
{
  size_t ret  = 0;
  int offset  = 97;//squoze_new_offset('a');
  int is_utf5 = 1;
  int len     = 0;

  int start_utf5 = 1;
  int gotzero = 0;

#define ADD_QUINTET(q) \
  do { \
    if (len + inlen-i > maxlen) {\
      *overflow = 1;\
      return 0;\
    }\
    ret |= ((size_t)(q))<<(5*len++); gotzero = (q==0);\
  } while (0)

  int first_len;
  int next_val = squoze_utf8_to_unichar (&input[0]);
  int next_utf5_length = squoze_utf5_length (next_val);
  int i = 0;
  for (int i = 0; i < inlen; i+= first_len)
  {
    int val         = next_val;
    int utf5_length = squoze_utf5_length (val);
    int needed_jump = squoze_needed_jump (offset, val);
    first_len = squoze_utf8_len (input[i]);
    if (i + first_len < inlen)
    {
      next_val         = squoze_utf8_to_unichar (&input[i+first_len]);
      next_utf5_length = squoze_utf5_length (next_val);
    }
    else
    {
      next_val = 0;
      next_utf5_length = 0;
    }

    if (is_utf5)
    {
      int change_cost    = squoze_compute_cost_squeezed (offset, val, needed_jump, next_val, next_utf5_length);
      int no_change_cost = squoze_compute_cost_utf5 (offset, val, utf5_length, next_val, next_utf5_length);
  
      if (i != 0)          /* ignore cost of initial 'G' */
        change_cost += 1;

      if (change_cost <= no_change_cost)
      {
        if (i != 0)
        { 
          ADD_QUINTET(SQUOZE_ENTER_SQUEEZE);
        }
        else
          start_utf5 = 0;

        is_utf5 = 0;
      }
    }
    else
    {
      int change_cost    = 1 + squoze_compute_cost_utf5 (offset, val, utf5_length, next_val, next_utf5_length);
      int no_change_cost = squoze_compute_cost_squeezed (offset, val, needed_jump, next_val, next_utf5_length);

      if (change_cost < no_change_cost)
      {
        ADD_QUINTET(SQUOZE_ENTER_UTF5);
        is_utf5 = 1;
      }
    }

    if (!is_utf5)
    {
      if (needed_jump)
      {
        if (needed_jump >= -2 && needed_jump <= 2)
        {
          switch (needed_jump)
          {
            case -1: ADD_QUINTET(SQUOZE_DEC_OFFSET_B); break;
            case  1: ADD_QUINTET(SQUOZE_INC_OFFSET_B); break;
            case -2: ADD_QUINTET(SQUOZE_DEC_OFFSET_A); break;
            case  2: ADD_QUINTET(SQUOZE_INC_OFFSET_A); break;
          }
          offset += SQUOZE_JUMP_STRIDE * needed_jump;
        }
        else if (needed_jump >= -10 && needed_jump <= 10) {
              int encoded_val;
              if (needed_jump < -2)
                encoded_val = 5 - needed_jump;
              else
                encoded_val = needed_jump - 3;

              ADD_QUINTET ((encoded_val/4) + SQUOZE_DEC_OFFSET_A);
              ADD_QUINTET ((encoded_val%4) + SQUOZE_DEC_OFFSET_A);

              offset += SQUOZE_JUMP_STRIDE * needed_jump;
        }
        else
        {
#ifdef assert
          assert(0); // should not be reached
#endif
          ADD_QUINTET (SQUOZE_ENTER_UTF5);
          is_utf5 = 1;
        }
      }
    }

    if (is_utf5)
    {
      offset = squoze_new_offset (val);
      int quintet_no = 0;
      uint8_t temp[12]={0,};

      while (val)
      {
        temp[quintet_no++] = (val&0xf) + (val/16)?0:16;
        val /= 16;
      }
      for (int j = 0; j < quintet_no; j++)
        ADD_QUINTET(temp[quintet_no-1-j]);
    }
    else 
    {
      ADD_QUINTET((val == ' ')?SQUOZE_SPACE:val-offset+1);
    }
  }

#if 1
  if (escape_endzero && len && gotzero)
  {
    // do a mode-change after 0 to avoid 0 being interpreted
    // as end of quintets
    ADD_QUINTET(is_utf5?16:SQUOZE_ENTER_UTF5);
  }
#endif

#undef ADD_QUINTET

  return (ret<<2) | ((start_utf5*2)|1);
}

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

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

static void squoze_decode_utf5_append_unichar_as_utf8 (uint32_t unichar, void *write_data)
{
  SquozeUtf5DecDefaultData *data = (SquozeUtf5DecDefaultData*)write_data;
  int length = squoze_unichar_to_utf8 (unichar, &data->buf[data->length]);
  data->buf[data->length += length] = 0;
}

static void squoze_decode_jump (SquozeUtf5Dec *dec, uint8_t in)
{
  dec->offset -= SQUOZE_JUMP_STRIDE * dec->jumped_amount;
  int jump_len = (dec->jump_mode - SQUOZE_DEC_OFFSET_A) * 4 +
                 (in - SQUOZE_DEC_OFFSET_A);
  if (jump_len > 7)
    jump_len = 5 - jump_len;
  else
    jump_len += 3;
  dec->offset += jump_len * SQUOZE_JUMP_STRIDE;
  dec->jumped_amount = 0;
}

static void squoze_decode_utf5 (SquozeUtf5Dec *dec, uint8_t in)
{
  if (dec->is_utf5)
  {
    if (in >= 16)
    {
      if (dec->current)
      {
        dec->offset = squoze_new_offset (dec->current);
        dec->append_unichar (dec->current, dec->write_data);
        dec->current = 0;
      }
    }
    if (in == SQUOZE_ENTER_SQUEEZE)
    {
      if (dec->current)
      {
        dec->offset = squoze_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
  {
    if (dec->jumped_amount)
    {
      switch (in)
      {
        case SQUOZE_DEC_OFFSET_A:
        case SQUOZE_DEC_OFFSET_B:
        case SQUOZE_INC_OFFSET_A:
        case SQUOZE_INC_OFFSET_B:
          squoze_decode_jump (dec, in);
          break;
        default:
          dec->append_unichar (dec->offset + (in - 1), dec->write_data);
          dec->jumped_amount = 0;
          dec->jump_mode = 0;
          break;
      }
    }
    else
    {
      switch (in)
      {
        case SQUOZE_ENTER_UTF5:
          dec->is_utf5 = 1;
          dec->jumped_amount = 0;
          dec->jump_mode = 0;
          break;
        case SQUOZE_SPACE: 
          dec->append_unichar (' ', dec->write_data);
          dec->jumped_amount = 0;
          dec->jump_mode = 0;
          break;
        case SQUOZE_DEC_OFFSET_A:
          dec->jumped_amount = -2;
          dec->jump_mode = in;
          dec->offset += dec->jumped_amount * SQUOZE_JUMP_STRIDE;
          break;
        case SQUOZE_INC_OFFSET_A:
          dec->jumped_amount = 2;
          dec->jump_mode = in;
          dec->offset += dec->jumped_amount * SQUOZE_JUMP_STRIDE;
          break;
        case SQUOZE_DEC_OFFSET_B:
          dec->jumped_amount = -1;
          dec->jump_mode = in;
          dec->offset += dec->jumped_amount * SQUOZE_JUMP_STRIDE;
          break;
        case SQUOZE_INC_OFFSET_B:
          dec->jumped_amount = 1;
          dec->jump_mode = in;
          dec->offset += dec->jumped_amount * SQUOZE_JUMP_STRIDE;
          break;
        default:
          dec->append_unichar (dec->offset + (in - 1), dec->write_data);
          dec->jumped_amount = 0;
          dec->jump_mode = 0;
      }
    }
  }
}

static void squoze_decode_utf5_bytes (int is_utf5, 
                                      const unsigned char *input, int inlen,
                                      char *output, int *r_outlen)
{
  SquozeUtf5DecDefaultData append_data = {(unsigned char*)output, 0};
  SquozeUtf5Dec dec = {is_utf5,
                     97,//squoze_new_offset('a'),
                     &append_data,
                     0,
                     squoze_decode_utf5_append_unichar_as_utf8,
                     0, 0
                    };
  for (int i = 0; i < inlen; i++)
    squoze_decode_utf5 (&dec, input[i]);
  if (dec.current)
    dec.append_unichar (dec.current, dec.write_data);
  if (r_outlen)
    *r_outlen = append_data.length;
}
#endif

#endif

#if CTX_IMPLEMENTATION || CTX_SIMD_BUILD


#ifndef MINIZ_EXPORT
#define MINIZ_EXPORT
#endif
/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
   See "unlicense" statement at the end of this file.
   Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
   Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt

   Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define
   MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).

   * Low-level Deflate/Inflate implementation notes:

     Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or
     greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses
     approximately as well as zlib.

     Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function
     coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory
     block large enough to hold the entire file.

     The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.

   * zlib-style API notes:

     miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in
     zlib replacement in many apps:
        The z_stream struct, optional memory allocation callbacks
        deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound
        inflateInit/inflateInit2/inflate/inflateReset/inflateEnd
        compress, compress2, compressBound, uncompress
        CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.
        Supports raw deflate streams or standard zlib streams with adler-32 checking.

     Limitations:
      The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.
      I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but
      there are no guarantees that miniz.c pulls this off perfectly.

   * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by
     Alex Evans. Supports 1-4 bytes/pixel images.

   * ZIP archive API notes:

     The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to
     get the job done with minimal fuss. There are simple API's to retrieve file information, read files from
     existing archives, create new archives, append new files to existing archives, or clone archive data from
     one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),
     or you can specify custom file read/write callbacks.

     - Archive reading: Just call this function to read a single file from a disk archive:

      void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,
        size_t *pSize, mz_uint zip_flags);

     For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central
     directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.

     - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:

     int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);

     The locate operation can optionally check file comments too, which (as one example) can be used to identify
     multiple versions of the same file in an archive. This function uses a simple linear search through the central
     directory, so it's not very fast.

     Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and
     retrieve detailed info on each file by calling mz_zip_reader_file_stat().

     - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data
     to disk and builds an exact image of the central directory in memory. The central directory image is written
     all at once at the end of the archive file when the archive is finalized.

     The archive writer can optionally align each file's local header and file data to any power of 2 alignment,
     which can be useful when the archive will be read from optical media. Also, the writer supports placing
     arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still
     readable by any ZIP tool.

     - Archive appending: The simple way to add a single file to an archive is to call this function:

      mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,
        const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

     The archive will be created if it doesn't already exist, otherwise it'll be appended to.
     Note the appending is done in-place and is not an atomic operation, so if something goes wrong
     during the operation it's possible the archive could be left without a central directory (although the local
     file headers and file data will be fine, so the archive will be recoverable).

     For more complex archive modification scenarios:
     1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to
     preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the
     compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and
     you're done. This is safe but requires a bunch of temporary disk space or heap memory.

     2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),
     append new files as needed, then finalize the archive which will write an updated central directory to the
     original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a
     possibility that the archive's central directory could be lost with this method if anything goes wrong, though.

     - ZIP archive support limitations:
     No spanning support. Extraction functions can only handle unencrypted, stored or deflated files.
     Requires streams capable of seeking.

   * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the
     below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.

   * Important: For best perf. be sure to customize the below macros for your target platform:
     #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
     #define MINIZ_LITTLE_ENDIAN 1
     #define MINIZ_HAS_64BIT_REGISTERS 1

   * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz
     uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files
     (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).
*/
#pragma once



/* Defines to completely disable specific portions of miniz.c: 
   If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */

/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */
/*#define MINIZ_NO_STDIO */

/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */
/* get/set file times, and the C run-time funcs that get/set times won't be called. */
/* The current downside is the times written to your archives will be from 1979. */
/*#define MINIZ_NO_TIME */

/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */
/*#define MINIZ_NO_DEFLATE_APIS */

/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */
/*#define MINIZ_NO_INFLATE_APIS */

/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */
/*#define MINIZ_NO_ARCHIVE_APIS */

/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */
/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */

/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */
/*#define MINIZ_NO_ZLIB_APIS */

/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */
/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */

/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. 
   Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc
   callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user
   functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */
/*#define MINIZ_NO_MALLOC */

#ifdef MINIZ_NO_INFLATE_APIS
#define MINIZ_NO_ARCHIVE_APIS
#endif

#ifdef MINIZ_NO_DEFLATE_APIS
#define MINIZ_NO_ARCHIVE_WRITING_APIS
#endif

#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */
#define MINIZ_NO_TIME
#endif

#include <stddef.h>

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)
#include <time.h>
#endif

#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */
#define MINIZ_X86_OR_X64_CPU 1
#else
#define MINIZ_X86_OR_X64_CPU 0
#endif

/* Set MINIZ_LITTLE_ENDIAN only if not set */
#if !defined(MINIZ_LITTLE_ENDIAN)
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)

#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
#define MINIZ_LITTLE_ENDIAN 1
#else
#define MINIZ_LITTLE_ENDIAN 0
#endif

#else

#if MINIZ_X86_OR_X64_CPU
#define MINIZ_LITTLE_ENDIAN 1
#else
#define MINIZ_LITTLE_ENDIAN 0
#endif

#endif
#endif

/* Using unaligned loads and stores causes errors when using UBSan */
#if defined(__has_feature)
#if __has_feature(undefined_behavior_sanitizer)
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
#endif
#endif

/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */
#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES)
#if MINIZ_X86_OR_X64_CPU
/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
#define MINIZ_UNALIGNED_USE_MEMCPY
#else
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
#endif
#endif

#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */
#define MINIZ_HAS_64BIT_REGISTERS 1
#else
#define MINIZ_HAS_64BIT_REGISTERS 0
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- zlib-style API Definitions. */

/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */
typedef unsigned long mz_ulong;

/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */
MINIZ_EXPORT void mz_free(void *p);

#define MZ_ADLER32_INIT (1)
/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */
MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);

#define MZ_CRC32_INIT (0)
/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */
MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);

/* Compression strategies. */
enum
{
    MZ_DEFAULT_STRATEGY = 0,
    MZ_FILTERED = 1,
    MZ_HUFFMAN_ONLY = 2,
    MZ_RLE = 3,
    MZ_FIXED = 4
};

/* Method */
#define MZ_DEFLATED 8

/* Heap allocation callbacks.
Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */
typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);
typedef void (*mz_free_func)(void *opaque, void *address);
typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);

/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */
enum
{
    MZ_NO_COMPRESSION = 0,
    MZ_BEST_SPEED = 1,
    MZ_BEST_COMPRESSION = 9,
    MZ_UBER_COMPRESSION = 10,
    MZ_DEFAULT_LEVEL = 6,
    MZ_DEFAULT_COMPRESSION = -1
};

#define MZ_VERSION "11.0.0"
#define MZ_VERNUM 0xB000
#define MZ_VER_MAJOR 11
#define MZ_VER_MINOR 0
#define MZ_VER_REVISION 0
#define MZ_VER_SUBREVISION 0

#ifndef MINIZ_NO_ZLIB_APIS

/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */
enum
{
    MZ_NO_FLUSH = 0,
    MZ_PARTIAL_FLUSH = 1,
    MZ_SYNC_FLUSH = 2,
    MZ_FULL_FLUSH = 3,
    MZ_FINISH = 4,
    MZ_BLOCK = 5
};

/* Return status codes. MZ_PARAM_ERROR is non-standard. */
enum
{
    MZ_OK = 0,
    MZ_STREAM_END = 1,
    MZ_NEED_DICT = 2,
    MZ_ERRNO = -1,
    MZ_STREAM_ERROR = -2,
    MZ_DATA_ERROR = -3,
    MZ_MEM_ERROR = -4,
    MZ_BUF_ERROR = -5,
    MZ_VERSION_ERROR = -6,
    MZ_PARAM_ERROR = -10000
};

/* Window bits */
#define MZ_DEFAULT_WINDOW_BITS 15

struct mz_internal_state;

/* Compression/decompression stream struct. */
typedef struct mz_stream_s
{
    const unsigned char *next_in; /* pointer to next byte to read */
    unsigned int avail_in;        /* number of bytes available at next_in */
    mz_ulong total_in;            /* total number of bytes consumed so far */

    unsigned char *next_out; /* pointer to next byte to write */
    unsigned int avail_out;  /* number of bytes that can be written to next_out */
    mz_ulong total_out;      /* total number of bytes produced so far */

    char *msg;                       /* error msg (unused) */
    struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */

    mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */
    mz_free_func zfree;   /* optional heap free function (defaults to free) */
    void *opaque;         /* heap alloc function user pointer */

    int data_type;     /* data_type (unused) */
    mz_ulong adler;    /* adler32 of the source or uncompressed data */
    mz_ulong reserved; /* not used */
} mz_stream;

typedef mz_stream *mz_streamp;

/* Returns the version string of miniz.c. */
MINIZ_EXPORT const char *mz_version(void);

#ifndef MINIZ_NO_DEFLATE_APIS

/* mz_deflateInit() initializes a compressor with default options: */
/* Parameters: */
/*  pStream must point to an initialized mz_stream struct. */
/*  level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */
/*  level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */
/*  (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */
/* Return values: */
/*  MZ_OK on success. */
/*  MZ_STREAM_ERROR if the stream is bogus. */
/*  MZ_PARAM_ERROR if the input parameters are bogus. */
/*  MZ_MEM_ERROR on out of memory. */
MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level);

/* mz_deflateInit2() is like mz_deflate(), except with more control: */
/* Additional parameters: */
/*   method must be MZ_DEFLATED */
/*   window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */
/*   mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */
MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);

/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */
MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream);

/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */
/* Parameters: */
/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
/*   flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */
/* Return values: */
/*   MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */
/*   MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */
/*   MZ_STREAM_ERROR if the stream is bogus. */
/*   MZ_PARAM_ERROR if one of the parameters is invalid. */
/*   MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */
MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush);

/* mz_deflateEnd() deinitializes a compressor: */
/* Return values: */
/*  MZ_OK on success. */
/*  MZ_STREAM_ERROR if the stream is bogus. */
MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream);

/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */
MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);

/* Single-call compression functions mz_compress() and mz_compress2(): */
/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */
MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);

/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */
MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len);

#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/

#ifndef MINIZ_NO_INFLATE_APIS

/* Initializes a decompressor. */
MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream);

/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */
/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */
MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits);

/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */
MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream);

/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */
/* Parameters: */
/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
/*   flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */
/*   On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */
/*   MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */
/* Return values: */
/*   MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */
/*   MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */
/*   MZ_STREAM_ERROR if the stream is bogus. */
/*   MZ_DATA_ERROR if the deflate stream is invalid. */
/*   MZ_PARAM_ERROR if one of the parameters is invalid. */
/*   MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */
/*   with more input data, or with more room in the output buffer (except when using single call decompression, described above). */
MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush);

/* Deinitializes a decompressor. */
MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream);

/* Single-call decompression. */
/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */
MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len);
#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/

/* Returns a string description of the specified error code, or NULL if the error code is invalid. */
MINIZ_EXPORT const char *mz_error(int err);

/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */
/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */
#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
typedef unsigned char Byte;
typedef unsigned int uInt;
typedef mz_ulong uLong;
typedef Byte Bytef;
typedef uInt uIntf;
typedef char charf;
typedef int intf;
typedef void *voidpf;
typedef uLong uLongf;
typedef void *voidp;
typedef void *const voidpc;
#define Z_NULL 0
#define Z_NO_FLUSH MZ_NO_FLUSH
#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH
#define Z_SYNC_FLUSH MZ_SYNC_FLUSH
#define Z_FULL_FLUSH MZ_FULL_FLUSH
#define Z_FINISH MZ_FINISH
#define Z_BLOCK MZ_BLOCK
#define Z_OK MZ_OK
#define Z_STREAM_END MZ_STREAM_END
#define Z_NEED_DICT MZ_NEED_DICT
#define Z_ERRNO MZ_ERRNO
#define Z_STREAM_ERROR MZ_STREAM_ERROR
#define Z_DATA_ERROR MZ_DATA_ERROR
#define Z_MEM_ERROR MZ_MEM_ERROR
#define Z_BUF_ERROR MZ_BUF_ERROR
#define Z_VERSION_ERROR MZ_VERSION_ERROR
#define Z_PARAM_ERROR MZ_PARAM_ERROR
#define Z_NO_COMPRESSION MZ_NO_COMPRESSION
#define Z_BEST_SPEED MZ_BEST_SPEED
#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION
#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION
#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY
#define Z_FILTERED MZ_FILTERED
#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY
#define Z_RLE MZ_RLE
#define Z_FIXED MZ_FIXED
#define Z_DEFLATED MZ_DEFLATED
#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS
#define alloc_func mz_alloc_func
#define free_func mz_free_func
#define internal_state mz_internal_state
#define z_stream mz_stream

#ifndef MINIZ_NO_DEFLATE_APIS
#define deflateInit mz_deflateInit
#define deflateInit2 mz_deflateInit2
#define deflateReset mz_deflateReset
#define deflate mz_deflate
#define deflateEnd mz_deflateEnd
#define deflateBound mz_deflateBound
#define compress mz_compress
#define compress2 mz_compress2
#define compressBound mz_compressBound
#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/

#ifndef MINIZ_NO_INFLATE_APIS
#define inflateInit mz_inflateInit
#define inflateInit2 mz_inflateInit2
#define inflateReset mz_inflateReset
#define inflate mz_inflate
#define inflateEnd mz_inflateEnd
#define uncompress mz_uncompress
#define uncompress2 mz_uncompress2
#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/

#define crc32 mz_crc32
#define adler32 mz_adler32
#define MAX_WBITS 15
#define MAX_MEM_LEVEL 9
#define zError mz_error
#define ZLIB_VERSION MZ_VERSION
#define ZLIB_VERNUM MZ_VERNUM
#define ZLIB_VER_MAJOR MZ_VER_MAJOR
#define ZLIB_VER_MINOR MZ_VER_MINOR
#define ZLIB_VER_REVISION MZ_VER_REVISION
#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION
#define zlibVersion mz_version
#define zlib_version mz_version()
#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */

#endif /* MINIZ_NO_ZLIB_APIS */

#ifdef __cplusplus
}
#endif





#pragma once
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>



/* ------------------- Types and macros */
typedef unsigned char mz_uint8;
typedef signed short mz_int16;
typedef unsigned short mz_uint16;
typedef unsigned int mz_uint32;
typedef unsigned int mz_uint;
typedef int64_t mz_int64;
typedef uint64_t mz_uint64;
typedef int mz_bool;

#define MZ_FALSE (0)
#define MZ_TRUE (1)

/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */
#ifdef _MSC_VER
#define MZ_MACRO_END while (0, 0)
#else
#define MZ_MACRO_END while (0)
#endif

#ifdef MINIZ_NO_STDIO
#define MZ_FILE void *
#else
#include <stdio.h>
#define MZ_FILE FILE
#endif /* #ifdef MINIZ_NO_STDIO */

#ifdef MINIZ_NO_TIME
typedef struct mz_dummy_time_t_tag
{
    mz_uint32 m_dummy1;
    mz_uint32 m_dummy2;
} mz_dummy_time_t;
#define MZ_TIME_T mz_dummy_time_t
#else
#define MZ_TIME_T time_t
#endif

#define MZ_ASSERT(x) assert(x)

#ifdef MINIZ_NO_MALLOC
#define MZ_MALLOC(x) NULL
#define MZ_FREE(x) (void)x, ((void)0)
#define MZ_REALLOC(p, x) NULL
#else
#define MZ_MALLOC(x) malloc(x)
#define MZ_FREE(x) free(x)
#define MZ_REALLOC(p, x) realloc(p, x)
#endif

#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj))
#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj))

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
#define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
#define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
#else
#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
#endif

#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U))

#ifdef _MSC_VER
#define MZ_FORCEINLINE __forceinline
#elif defined(__GNUC__)
#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__))
#else
#define MZ_FORCEINLINE inline
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size);
extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address);
extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size);

#define MZ_UINT16_MAX (0xFFFFU)
#define MZ_UINT32_MAX (0xFFFFFFFFU)

#ifdef __cplusplus
}
#endif
 #pragma once


#ifndef MINIZ_NO_DEFLATE_APIS

#ifdef __cplusplus
extern "C" {
#endif
/* ------------------- Low-level Compression API Definitions */

/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */
#define TDEFL_LESS_MEMORY 0

/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */
/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */
enum
{
    TDEFL_HUFFMAN_ONLY = 0,
    TDEFL_DEFAULT_MAX_PROBES = 128,
    TDEFL_MAX_PROBES_MASK = 0xFFF
};

/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */
/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */
/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */
/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */
/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */
/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */
/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */
/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */
/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */
enum
{
    TDEFL_WRITE_ZLIB_HEADER = 0x01000,
    TDEFL_COMPUTE_ADLER32 = 0x02000,
    TDEFL_GREEDY_PARSING_FLAG = 0x04000,
    TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,
    TDEFL_RLE_MATCHES = 0x10000,
    TDEFL_FILTER_MATCHES = 0x20000,
    TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000,
    TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000
};

/* High level compression functions: */
/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */
/* On entry: */
/*  pSrc_buf, src_buf_len: Pointer and size of source block to compress. */
/*  flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */
/* On return: */
/*  Function returns a pointer to the compressed data, or NULL on failure. */
/*  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */
/*  The caller must free() the returned block when it's no longer needed. */
MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */
/* Returns 0 on failure. */
MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

/* Compresses an image to a compressed PNG file in memory. */
/* On entry: */
/*  pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */
/*  The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */
/*  level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */
/*  If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */
/* On return: */
/*  Function returns a pointer to the compressed data, or NULL on failure. */
/*  *pLen_out will be set to the size of the PNG image file. */
/*  The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */
MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);
MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);

/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */
typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);

/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */
MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

enum
{
    TDEFL_MAX_HUFF_TABLES = 3,
    TDEFL_MAX_HUFF_SYMBOLS_0 = 288,
    TDEFL_MAX_HUFF_SYMBOLS_1 = 32,
    TDEFL_MAX_HUFF_SYMBOLS_2 = 19,
    TDEFL_LZ_DICT_SIZE = 32768,
    TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1,
    TDEFL_MIN_MATCH_LEN = 3,
    TDEFL_MAX_MATCH_LEN = 258
};

/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */
#if TDEFL_LESS_MEMORY
enum
{
    TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024,
    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
    TDEFL_MAX_HUFF_SYMBOLS = 288,
    TDEFL_LZ_HASH_BITS = 12,
    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
};
#else
enum
{
    TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024,
    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
    TDEFL_MAX_HUFF_SYMBOLS = 288,
    TDEFL_LZ_HASH_BITS = 15,
    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
};
#endif

/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */
typedef enum {
    TDEFL_STATUS_BAD_PARAM = -2,
    TDEFL_STATUS_PUT_BUF_FAILED = -1,
    TDEFL_STATUS_OKAY = 0,
    TDEFL_STATUS_DONE = 1
} tdefl_status;

/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */
typedef enum {
    TDEFL_NO_FLUSH = 0,
    TDEFL_SYNC_FLUSH = 2,
    TDEFL_FULL_FLUSH = 3,
    TDEFL_FINISH = 4
} tdefl_flush;

/* tdefl's compression state structure. */
typedef struct
{
    tdefl_put_buf_func_ptr m_pPut_buf_func;
    void *m_pPut_buf_user;
    mz_uint m_flags, m_max_probes[2];
    int m_greedy_parsing;
    mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;
    mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;
    mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;
    mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;
    tdefl_status m_prev_return_status;
    const void *m_pIn_buf;
    void *m_pOut_buf;
    size_t *m_pIn_buf_size, *m_pOut_buf_size;
    tdefl_flush m_flush;
    const mz_uint8 *m_pSrc;
    size_t m_src_buf_left, m_out_buf_ofs;
    mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];
    mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
    mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];
    mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];
    mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];
    mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];
} tdefl_compressor;

/* Initializes the compressor. */
/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */
/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */
/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */
/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */
MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */
MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);

/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */
/* tdefl_compress_buffer() always consumes the entire input buffer. */
MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);

MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);
MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d);

/* Create tdefl_compress() flags given zlib-style compression parameters. */
/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */
/* window_bits may be -15 (raw deflate) or 15 (zlib) */
/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */
MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);

#ifndef MINIZ_NO_MALLOC
/* Allocate the tdefl_compressor structure in C so that */
/* non-C language bindings to tdefl_ API don't need to worry about */
/* structure size and allocation mechanism. */
MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void);
MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp);
#endif

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/
 #pragma once

/* ------------------- Low-level Decompression API Definitions */

#ifndef MINIZ_NO_INFLATE_APIS

#ifdef __cplusplus
extern "C" {
#endif
/* Decompression flags used by tinfl_decompress(). */
/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */
/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */
/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */
/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */
enum
{
    TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
    TINFL_FLAG_HAS_MORE_INPUT = 2,
    TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
    TINFL_FLAG_COMPUTE_ADLER32 = 8
};

/* High level decompression functions: */
/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */
/* On entry: */
/*  pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */
/* On return: */
/*  Function returns a pointer to the decompressed data, or NULL on failure. */
/*  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */
/*  The caller must call mz_free() on the returned block when it's no longer needed. */
MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */
/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */
#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */
/* Returns 1 on success or 0 on failure. */
typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);
MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

struct tinfl_decompressor_tag;
typedef struct tinfl_decompressor_tag tinfl_decompressor;

#ifndef MINIZ_NO_MALLOC
/* Allocate the tinfl_decompressor structure in C so that */
/* non-C language bindings to tinfl_ API don't need to worry about */
/* structure size and allocation mechanism. */
MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void);
MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp);
#endif

/* Max size of LZ dictionary. */
#define TINFL_LZ_DICT_SIZE 32768

/* Return status. */
typedef enum {
    /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */
    /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */
    /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */
    TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4,

    /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */
    TINFL_STATUS_BAD_PARAM = -3,

    /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */
    TINFL_STATUS_ADLER32_MISMATCH = -2,

    /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */
    TINFL_STATUS_FAILED = -1,

    /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */

    /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */
    /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */
    TINFL_STATUS_DONE = 0,

    /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */
    /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */
    /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */
    TINFL_STATUS_NEEDS_MORE_INPUT = 1,

    /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */
    /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */
    /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */
    /* so I may need to add some code to address this. */
    TINFL_STATUS_HAS_MORE_OUTPUT = 2
} tinfl_status;

/* Initializes the decompressor to its initial state. */
#define tinfl_init(r)     \
    do                    \
    {                     \
        (r)->m_state = 0; \
    }                     \
    MZ_MACRO_END
#define tinfl_get_adler32(r) (r)->m_check_adler32

/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */
/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */
MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);

/* Internal/private bits follow. */
enum
{
    TINFL_MAX_HUFF_TABLES = 3,
    TINFL_MAX_HUFF_SYMBOLS_0 = 288,
    TINFL_MAX_HUFF_SYMBOLS_1 = 32,
    TINFL_MAX_HUFF_SYMBOLS_2 = 19,
    TINFL_FAST_LOOKUP_BITS = 10,
    TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS
};

#if MINIZ_HAS_64BIT_REGISTERS
#define TINFL_USE_64BIT_BITBUF 1
#else
#define TINFL_USE_64BIT_BITBUF 0
#endif

#if TINFL_USE_64BIT_BITBUF
typedef mz_uint64 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (64)
#else
typedef mz_uint32 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (32)
#endif

struct tinfl_decompressor_tag
{
    mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];
    tinfl_bit_buf_t m_bit_buf;
    size_t m_dist_from_out_buf_start;
    mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE];
    mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2];
    mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2];
    mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2];
    mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0];
    mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1];
    mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2];
    mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
};

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/
 
#pragma once


/* ------------------- ZIP archive reading/writing */

#ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef __cplusplus
extern "C" {
#endif

enum
{
    /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */
    MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024,
    MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512,
    MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512
};

typedef struct
{
    /* Central directory file index. */
    mz_uint32 m_file_index;

    /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */
    mz_uint64 m_central_dir_ofs;

    /* These fields are copied directly from the zip's central dir. */
    mz_uint16 m_version_made_by;
    mz_uint16 m_version_needed;
    mz_uint16 m_bit_flag;
    mz_uint16 m_method;

    /* CRC-32 of uncompressed data. */
    mz_uint32 m_crc32;

    /* File's compressed size. */
    mz_uint64 m_comp_size;

    /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */
    mz_uint64 m_uncomp_size;

    /* Zip internal and external file attributes. */
    mz_uint16 m_internal_attr;
    mz_uint32 m_external_attr;

    /* Entry's local header file offset in bytes. */
    mz_uint64 m_local_header_ofs;

    /* Size of comment in bytes. */
    mz_uint32 m_comment_size;

    /* MZ_TRUE if the entry appears to be a directory. */
    mz_bool m_is_directory;

    /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */
    mz_bool m_is_encrypted;

    /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */
    mz_bool m_is_supported;

    /* Filename. If string ends in '/' it's a subdirectory entry. */
    /* Guaranteed to be zero terminated, may be truncated to fit. */
    char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];

    /* Comment field. */
    /* Guaranteed to be zero terminated, may be truncated to fit. */
    char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];

#ifdef MINIZ_NO_TIME
    MZ_TIME_T m_padding;
#else
    MZ_TIME_T m_time;
#endif
} mz_zip_archive_file_stat;

typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);
typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);
typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque);

struct mz_zip_internal_state_tag;
typedef struct mz_zip_internal_state_tag mz_zip_internal_state;

typedef enum {
    MZ_ZIP_MODE_INVALID = 0,
    MZ_ZIP_MODE_READING = 1,
    MZ_ZIP_MODE_WRITING = 2,
    MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
} mz_zip_mode;

typedef enum {
    MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100,
    MZ_ZIP_FLAG_IGNORE_PATH = 0x0200,
    MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400,
    MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800,
    MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */
    MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000,     /* validate the local headers, but don't decompress the entire file and check the crc32 */
    MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000,               /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */
    MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000,
    MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000,
    /*After adding a compressed file, seek back
    to local file header and set the correct sizes*/
    MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000
} mz_zip_flags;

typedef enum {
    MZ_ZIP_TYPE_INVALID = 0,
    MZ_ZIP_TYPE_USER,
    MZ_ZIP_TYPE_MEMORY,
    MZ_ZIP_TYPE_HEAP,
    MZ_ZIP_TYPE_FILE,
    MZ_ZIP_TYPE_CFILE,
    MZ_ZIP_TOTAL_TYPES
} mz_zip_type;

/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */
typedef enum {
    MZ_ZIP_NO_ERROR = 0,
    MZ_ZIP_UNDEFINED_ERROR,
    MZ_ZIP_TOO_MANY_FILES,
    MZ_ZIP_FILE_TOO_LARGE,
    MZ_ZIP_UNSUPPORTED_METHOD,
    MZ_ZIP_UNSUPPORTED_ENCRYPTION,
    MZ_ZIP_UNSUPPORTED_FEATURE,
    MZ_ZIP_FAILED_FINDING_CENTRAL_DIR,
    MZ_ZIP_NOT_AN_ARCHIVE,
    MZ_ZIP_INVALID_HEADER_OR_CORRUPTED,
    MZ_ZIP_UNSUPPORTED_MULTIDISK,
    MZ_ZIP_DECOMPRESSION_FAILED,
    MZ_ZIP_COMPRESSION_FAILED,
    MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE,
    MZ_ZIP_CRC_CHECK_FAILED,
    MZ_ZIP_UNSUPPORTED_CDIR_SIZE,
    MZ_ZIP_ALLOC_FAILED,
    MZ_ZIP_FILE_OPEN_FAILED,
    MZ_ZIP_FILE_CREATE_FAILED,
    MZ_ZIP_FILE_WRITE_FAILED,
    MZ_ZIP_FILE_READ_FAILED,
    MZ_ZIP_FILE_CLOSE_FAILED,
    MZ_ZIP_FILE_SEEK_FAILED,
    MZ_ZIP_FILE_STAT_FAILED,
    MZ_ZIP_INVALID_PARAMETER,
    MZ_ZIP_INVALID_FILENAME,
    MZ_ZIP_BUF_TOO_SMALL,
    MZ_ZIP_INTERNAL_ERROR,
    MZ_ZIP_FILE_NOT_FOUND,
    MZ_ZIP_ARCHIVE_TOO_LARGE,
    MZ_ZIP_VALIDATION_FAILED,
    MZ_ZIP_WRITE_CALLBACK_FAILED,
    MZ_ZIP_TOTAL_ERRORS
} mz_zip_error;

typedef struct
{
    mz_uint64 m_archive_size;
    mz_uint64 m_central_directory_file_ofs;

    /* We only support up to UINT32_MAX files in zip64 mode. */
    mz_uint32 m_total_files;
    mz_zip_mode m_zip_mode;
    mz_zip_type m_zip_type;
    mz_zip_error m_last_error;

    mz_uint64 m_file_offset_alignment;

    mz_alloc_func m_pAlloc;
    mz_free_func m_pFree;
    mz_realloc_func m_pRealloc;
    void *m_pAlloc_opaque;

    mz_file_read_func m_pRead;
    mz_file_write_func m_pWrite;
    mz_file_needs_keepalive m_pNeeds_keepalive;
    void *m_pIO_opaque;

    mz_zip_internal_state *m_pState;

} mz_zip_archive;

typedef struct
{
    mz_zip_archive *pZip;
    mz_uint flags;

    int status;

    mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf;
    void *pWrite_buf;

    size_t out_blk_remain;

    tinfl_decompressor inflator;

#ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
    mz_uint padding;
#else
    mz_uint file_crc32;
#endif

} mz_zip_reader_extract_iter_state;

/* -------- ZIP reading */

/* Inits a ZIP archive reader. */
/* These functions read and validate the archive's central directory. */
MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags);

MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags);

#ifndef MINIZ_NO_STDIO
/* Read a archive from a disk file. */
/* file_start_ofs is the file offset where the archive actually begins, or 0. */
/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */
MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);
MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size);

/* Read an archive from an already opened FILE, beginning at the current file position. */
/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */
/* The FILE will NOT be closed when mz_zip_reader_end() is called. */
MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags);
#endif

/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */
MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip);

/* -------- ZIP reading or writing */

/* Clears a mz_zip_archive struct to all zeros. */
/* Important: This must be done before passing the struct to any mz_zip functions. */
MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip);

MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip);
MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip);

/* Returns the total number of files in the archive. */
MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);

MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip);
MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip);
MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip);

/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */
MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n);

/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */
/* Note that the m_last_error functionality is not thread safe. */
MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num);
MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip);
MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip);
MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip);
MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err);

/* MZ_TRUE if the archive file entry is a directory entry. */
MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);

/* MZ_TRUE if the file is encrypted/strong encrypted. */
MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);

/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */
MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index);

/* Retrieves the filename of an archive file entry. */
/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */
MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);

/* Attempts to locates a file in the archive's central directory. */
/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */
/* Returns -1 if the file cannot be found. */
MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);

/* Returns detailed information about an archive file entry. */
MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);

/* MZ_TRUE if the file is in zip64 format. */
/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */
MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip);

/* Returns the total central directory size in bytes. */
/* The current max supported size is <= MZ_UINT32_MAX. */
MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip);

/* Extracts a archive file to a memory buffer using no memory allocation. */
/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */
MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);

/* Extracts a archive file to a memory buffer. */
MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);

/* Extracts a archive file to a dynamically allocated heap buffer. */
/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */
/* Returns NULL and sets the last error on failure. */
MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);
MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);

/* Extracts a archive file using a callback function to output the file's data. */
MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);

/* Extract a file iteratively */
MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);
MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState);

#ifndef MINIZ_NO_STDIO
/* Extracts a archive file to a disk file and sets its last accessed and modified times. */
/* This function only extracts files, not archive directory records. */
MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);

/* Extracts a archive file starting at the current position in the destination FILE stream. */
MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags);
#endif

#if 0
/* TODO */
	typedef void *mz_zip_streaming_extract_state_ptr;
	mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
	mz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
	mz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
	mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs);
	size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size);
	mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
#endif

/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */
/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */
MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);

/* Validates an entire archive by calling mz_zip_validate_file() on each file. */
MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags);

/* Misc utils/helpers, valid for ZIP reading or writing */
MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr);
#ifndef MINIZ_NO_STDIO
MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr);
#endif

/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */
MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip);

/* -------- ZIP writing */

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

/* Inits a ZIP archive writer. */
/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/
/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/
MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);
MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags);

MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);
MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags);

#ifndef MINIZ_NO_STDIO
MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);
MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags);
MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags);
#endif

/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */
/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */
/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */
/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */
/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */
/* the archive is finalized the file's central directory will be hosed. */
MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);
MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);

/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */
/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);

/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */
/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */
MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                              mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);

MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
                                                 const char *user_extra_data_central, mz_uint user_extra_data_central_len);

/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */
/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/
MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size,
	const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
	const char *user_extra_data_central, mz_uint user_extra_data_central_len);


#ifndef MINIZ_NO_STDIO
/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */
MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size,
                                const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
                                const char *user_extra_data_central, mz_uint user_extra_data_central_len);
#endif

/* Adds a file to an archive by fully cloning the data from another archive. */
/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */
MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index);

/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */
/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */
/* An archive must be manually finalized by calling this function for it to be valid. */
MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);

/* Finalizes a heap archive, returning a pointer to the heap block and its size. */
/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */
MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize);

/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */
/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */
MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip);

/* -------- Misc. high-level helper functions: */

/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */
/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */
/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */
MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr);

#ifndef MINIZ_NO_STDIO
/* Reads a single file from an archive into a heap block. */
/* If pComment is not NULL, only the file with the specified comment will be extracted. */
/* Returns NULL on failure. */
MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags);
MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr);
#endif

#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */

#ifdef __cplusplus
}
#endif

#endif /* MINIZ_NO_ARCHIVE_APIS */
#ifndef __CTX_CLIENTS_H
#define __CTX_CLIENTS_H



struct _CtxClient {
  VT    *vt;        // or NULL when thread

  long       rev;

  CtxList *events;  // we could use this queue also for vt

  Ctx     *ctx;
  char    *title;
  int      x;
  int      y;
  int      width;
  int      height;
  float    opacity;
  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
  thrd_t tid;     // and only split code path in processing?
                    // -- why?
#endif
  void (*start_routine)(Ctx *ctx, void *user_data);
  void    *user_data;
  CtxClientFinalize finalize;
  Ctx     *sub_ctx;
  CtxList *ctx_events;


  /* we want to keep variation at the end */
#if CTX_THREADS
  mtx_t    mtx;
#endif
#if VT_RECORD
  Ctx     *recording;
#endif
};



#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


#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;


enum _CtxAntialias
{
  CTX_ANTIALIAS_DEFAULT, //
  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?
};
typedef enum _CtxAntialias CtxAntialias;
void         ctx_set_antialias (Ctx *ctx, CtxAntialias antialias);
CtxAntialias ctx_get_antialias (Ctx *ctx);

#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
  float   red;
  float   green;
  float   blue;
#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
#endif
};

typedef struct _CtxGradientStop CtxGradientStop;

struct _CtxGradientStop
{
  CtxColor color;
  float   pos;
};


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;
  int                 frame;      // last frame used in, everything > 3 can be removed,
                                  // as clients wont rely on it.
  char               *eid;        // might be NULL, when not - should be unique for pixel contents
  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[CTX_MAX_GRADIENT_STOPS];
  int n_stops;
};

struct _CtxSource
{
  int type;
  CtxMatrix  set_transform;
  CtxMatrix  transform;
  uint32_t   pad;
  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;
  };
};


typedef struct _Ctx16f16Matrix     Ctx16f16Matrix;
struct
  _Ctx16f16Matrix
{
#if CTX_32BIT_SEGMENTS
  int64_t m[3][3];  // forcing higher precision easily, the extra
                    // memory cost is minuscle
#else
  int32_t m[3][3];
#endif
};


struct _CtxGState
{
#if CTX_32BIT_SEGMENTS
  uint32_t      keydb_pos;
  uint32_t      stringpool_pos;
#else
  uint16_t      keydb_pos;      // this limits these
  uint16_t      stringpool_pos; // 
#endif

  CtxMatrix     transform;
  Ctx16f16Matrix  prepped_transform;
  CtxSource     source_stroke;
  CtxSource     source_fill;
  float         global_alpha_f;

  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
  unsigned int  transform_type:3;
  unsigned int        clipped:1;
  CtxColorModel    color_model:8;
  /* 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;

  uint8_t       global_alpha_u8;
  int16_t       clip_min_x;
  int16_t       clip_min_y;
  int16_t       clip_max_x;
  int16_t       clip_max_y;
  int           n_dashes;

#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
  CtxExtend                 extend;

  float dashes[CTX_MAX_DASHES]; // XXX moving dashes 
                                //  to state storage,. will
                                //  allow it to be larger,
                                //  free up memory, and
                                //  make save/restore faster
};

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;
  unsigned int count;
  int size;
  uint32_t  flags;
  int       bitpack_pos;  // stream is bitpacked up to this offset
};

// 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
{
  uint32_t key;
  float value;
  //union { float f[1]; uint8_t u8[4]; }value;
};

struct _CtxState
{
  unsigned int  has_moved:1;
  unsigned int  has_clipped:1;
  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
  int16_t       gstate_no;

  float         x;
  float         y;
  int           ink_min_x;
  int           ink_min_y;
  int           ink_max_x;
  int           ink_max_y;
  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];
};


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);
};


#pragma pack(push,1)
struct _CtxFont
{
#if CTX_ONE_FONT_ENGINE==0
  CtxFontEngine *engine;
#endif
  union
  {
    struct
    {
      CtxEntry *data;
    //uint16_t length;
      /* we've got ~110 bytes to fill to cover as
         much data as stbtt_fontinfo */
      //int16_t glyph_pos[26]; // for a..z
    } ctx;
#if CTX_FONT_ENGINE_CTX_FS
    struct
    {
      const char *name;
      char *path;
    } ctx_fs;
#endif
#if CTX_FONT_ENGINE_STB
    struct
    {
      const char *name;
      stbtt_fontinfo ttf_info;
    } stb;
#endif
#if 0
    struct { int start; int end; int gw; int gh; const uint8_t *data;} monobitmap;
#endif
  };
#if CTX_ONE_FONT_ENGINE==0
  uint8_t type:3; // 0 ctx    1 stb    2 monobitmap
  uint8_t monospaced:1;
#endif
};
#pragma pack(pop)

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.
};

#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;



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. */
  CtxEvent         drag_event[CTX_MAX_DEVICES];
  CtxList         *idles;
  CtxList         *idles_to_remove;
  CtxList         *idles_to_add;
  CtxList         *events; // for ctx_get_event
  CtxBinding       bindings[CTX_MAX_KEYBINDINGS]; /*< better as list, uses no mem if unused */
  int              n_bindings;
  CtxItem         *prev[CTX_MAX_DEVICES];
  float            pointer_x[CTX_MAX_DEVICES];
  float            pointer_y[CTX_MAX_DEVICES];
  unsigned char    pointer_down[CTX_MAX_DEVICES];
  unsigned int     in_idle_dispatch:1;
  unsigned int     ctx_get_event_enabled:1;
  CtxModifierState modifier_state;
  int              idle_id;
  CtxList         *items;
  CtxItem         *last_item;
  float            tap_hysteresis;
#if CTX_VT
  CtxList         *clients;
  CtxClient *active;
  CtxClient *active_tab;
#endif
  int              tap_delay_min;
  int              tap_delay_max;
  int              tap_delay_hold;
};
#endif

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


struct _CtxGlyphEntry
{
  uint32_t  unichar;
  uint16_t  offset;
  CtxFont  *font;
};
typedef struct _CtxGlyphEntry CtxGlyphEntry;

struct _Ctx
{
  CtxBackend       *backend;
  CtxDrawlist       drawlist;
  int               transformation;
  int               width;
  int               height;
  int               dirty;
  Ctx              *texture_cache;
  CtxList          *deferred;
  CtxList          *eid_db;
  CtxState          state;        /**/
  int               frame; /* used for texture lifetime */
  uint32_t          bail;
  CtxBackend       *backend_pushed;
  CtxBuffer         texture[CTX_MAX_TEXTURES];
#if CTX_EVENTS 
  CtxCursor         cursor;
  int               quit;
  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
#if CTX_GLYPH_CACHE
  CtxGlyphEntry     glyph_index_cache[CTX_GLYPH_CACHE_SIZE];
#endif
  CtxFont *fonts; // a copy to keep it alive with mp's
                  // garbage collector, the fonts themselves
                  // are static and shared beyond ctx contexts
 

};

static inline void
ctx_process (Ctx *ctx, CtxEntry *entry)
{
  ctx->backend->process (ctx, (CtxCommand *) entry);
}

CtxBuffer *ctx_buffer_new (int width, int height,
                           CtxPixelFormat pixel_format);
void ctx_buffer_destroy (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:8;
  uint8_t        components; /* 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:8;

  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,
                          unsigned 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);

//extern CtxPixelFormatInfo *(*ctx_pixel_format_info) (CtxPixelFormat format);
CtxPixelFormatInfo *ctx_pixel_format_info (CtxPixelFormat format);



extern void (*ctx_composite_stroke_rect) (CtxRasterizer *rasterizer,
                           float          x0,
                           float          y0,
                           float          x1,
                           float          y1,
                           float          line_width);

extern void (*ctx_composite_setup) (CtxRasterizer *rasterizer);


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;

extern void (*ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule
#if CTX_SHAPE_CACHE
                ,CtxShapeEntry *shape
#endif
                );



extern void (*ctx_composite_fill_rect) (CtxRasterizer *rasterizer,
                           float        x0,
                           float        y0,
                           float        x1,
                           float        y1,
                           uint8_t      cov);


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, float z, void *out, int count, float dx, float dy, float dz);

#define CTX_MAX_GAUSSIAN_KERNEL_DIM    512



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

typedef struct _CtxShapeCache CtxShapeCache;

typedef enum {
   CTX_COV_PATH_FALLBACK =0,
   CTX_COV_PATH_RGBA8_OVER,
   CTX_COV_PATH_RGBA8_COPY,
   CTX_COV_PATH_RGBA8_COPY_FRAGMENT,
   CTX_COV_PATH_RGBA8_OVER_FRAGMENT,
   CTX_COV_PATH_GRAYA8_COPY,
   CTX_COV_PATH_GRAY1_COPY,
   CTX_COV_PATH_GRAY2_COPY,
   CTX_COV_PATH_GRAY4_COPY,
   CTX_COV_PATH_RGB565_COPY,
   CTX_COV_PATH_RGB332_COPY,
   CTX_COV_PATH_GRAY8_COPY,
   CTX_COV_PATH_RGBAF_COPY,
   CTX_COV_PATH_RGB8_COPY,
   CTX_COV_PATH_CMYK8_COPY,
   CTX_COV_PATH_CMYKA8_COPY,
   CTX_COV_PATH_CMYKAF_COPY,
   CTX_COV_PATH_GRAYAF_COPY,
   CTX_COV_PATH_CBRLE_COPY 
} CtxCovPath;

struct _CtxRasterizer
{
  CtxBackend backend;
  /* 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, unsigned int count
  void (*comp_op)(CTX_COMPOSITE_ARGUMENTS);
  CtxFragment fragment;
  //Ctx       *ctx;
  CtxState  *state;
  void      *buf;
  int fast_aa;
  CtxCovPath  comp;
  void       (*apply_coverage) (CtxRasterizer *r, uint8_t * __restrict__ dst, uint8_t * __restrict__ src, int x, uint8_t *coverage, unsigned int count);

  unsigned int aa;          // level of vertical aa
  unsigned int prev_active_edges;
  unsigned int active_edges;
  unsigned int pending_edges;
  unsigned int ending_edges;
  unsigned 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
  unsigned int horizontal_edges;

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

  int        inner_x;
  int        inner_y;

  float      x;
  float      y;

  float      first_x;
  float      first_y;

  uint16_t    blit_x;
  uint16_t    blit_y;
  uint16_t    blit_width;
  uint16_t    blit_height;
  uint16_t    blit_stride;

  unsigned int  clip_rectangle:1;
  unsigned int  has_shape:2;
  int  has_prev:2;
  unsigned int  preserve:1;
#if CTX_ENABLE_SHADOW_BLUR
  unsigned int  in_shadow:1;
#endif
  unsigned int  in_text:1;
  unsigned int  swap_red_green:1;

#if CTX_BRAILLE_TEXT
  unsigned int  term_glyphs:1; // store appropriate glyphs for redisplay
#endif
  int        shadow_x;
#if CTX_BRAILLE_TEXT
  CtxList   *glyphs;
#endif
  CtxPixelFormatInfo *format;
  Ctx       *texture_source; /* normally same as ctx */
  int        shadow_y;

  uint8_t    color[4*5];   // in compositing format
  uint16_t   color_native;  //
  uint16_t   color_nativeB[5];

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

#if CTX_GRADIENTS
#if CTX_GRADIENT_CACHE
  int gradient_cache_valid;
  uint8_t gradient_cache_u8[CTX_GRADIENT_CACHE_ELEMENTS][4];
  int gradient_cache_elements;
#endif
#endif

#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 static_OPAQUE
  uint8_t opaque[CTX_MAX_SCANLINE_LENGTH];
#endif

#if CTX_SHAPE_CACHE
  CtxShapeCache shape_cache; /* needs to be at end of struct, it
                                is excluded from clearing */
#endif
};

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


#pragma pack(push,1)
typedef struct CtxCommandState
{
  uint16_t pos;
  uint32_t active;
} CtxCommandState;
#pragma pack(pop)

struct _CtxHasher
{
  CtxRasterizer rasterizer;
  int           cols;
  int           rows;
  uint32_t      hashes[CTX_HASH_COLS*CTX_HASH_ROWS];
  CtxMurmur     murmur_fill[CTX_MAX_STATES]; 
  CtxMurmur     murmur_stroke[CTX_MAX_STATES];
  int           source_level;
  int           pos; 

  int           prev_command;

  CtxDrawlist  *drawlist;

};

#if CTX_RASTERIZER
void ctx_rasterizer_deinit (CtxRasterizer *rasterizer);
void ctx_rasterizer_destroy (CtxRasterizer *rasterizer);
#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;

typedef struct _CtxCtx CtxCtx;
struct _CtxCtx
{
   CtxBackend backend;
   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, uint32_t key_hash, const char *string, int len);
const char *
ctx_get (Ctx *ctx, const char *key);

Ctx *ctx_new_ctx (int width, int height);
Ctx *ctx_new_fb (int width, int height);
Ctx *ctx_new_headless (int width, int height);
Ctx *ctx_new_kms (int width, int height);
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);

#if CTX_U8_TO_FLOAT_LUT
extern float ctx_u8_float[256];
#define ctx_u8_to_float(val_u8) ctx_u8_float[((uint8_t)(val_u8))]
#else
#define ctx_u8_to_float(val_u8) (val_u8/255.0f)
#endif

static inline uint8_t ctx_float_to_u8 (float val_f)
{
#if 1 
  union { float f; uint32_t i; } u;
  u.f = 32768.0f + val_f * (255.0f / 256.0f);
  return (uint8_t)u.i;
#else
  return val_f < 0.0f ? 0 : val_f > 1.0f ? 0xff : 0xff * val_f +  0.5f;
#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, uint32_t key, float value);

static void
ctx_matrix_set (CtxMatrix *matrix, float a, float b, float c, float d, float e, float f, float g, float h, float i);


static void ctx_font_setup (Ctx *ctx);
static float ctx_state_get (CtxState *state, uint32_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 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_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 >>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 >> 8) - (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;
}

CTX_INLINE static float
ctx_catmull_rom (float v0, float v1, float v2, float v3, float t)
{
   float ya = v0, yb = v1, yc = v2, yd = v3;
   float a3 = 0.5f * (-ya + 3 * yb - 3 * yc + yd);
   float a2 = 0.5f * (2 * ya - 5 * yb + 4 * yc - yd);
   float a1 = 0.5f * (-ya + yc);
   float a0 = yb;
   return a3 * t * t * t +
          a2 * t * t +
          a1 * t +
          a0;
}

CTX_INLINE static float
ctx_catmull_rom_left (float v0, float v1, float v2, float t)
{
   float ya = v0, yb = v1, yc = v2;
   float a2 = 0.5f * (ya - 2 * yb + yc);
   float a1 = 0.5f * (-3 * ya + 4 * yb - yc);
   float a0 = ya;
   return a2 * t * t +
          a1 * t +
          a0;
}

CTX_INLINE static float
ctx_catmull_rom_right (float v0, float v1, float v2, float t)
{
   float ya = v0, yb = v1, yc = v2;
   float a2 = 0.5f * (ya - 2 * yb + yc);
   float a1 = 0.5f * (-ya + yc);
   float a0 = yb;
   return a2 * t * t +
          a1 * t +
          a0;
}


#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;


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.. */
};

struct _CtxTiled
{
   CtxBackend backend;
   void (*show_frame) (void *backend, int block);
   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;
   uint32_t  hashes[CTX_HASH_ROWS * CTX_HASH_COLS];
   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;
   int          vt_active;
   EvSource    *evsource[4];
   int          evsource_count;
   uint8_t      *fb;
#if CTX_THREADS
#if CTX_TILED
   cnd_t  cond;
   mtx_t  mtx;
#endif
#endif
};

static inline Ctx *ctx_backend_get_ctx (void *backend)
{
  CtxBackend *r = (CtxBackend*)backend;
  if (r) return r->ctx;
  return NULL;
}

void
_ctx_texture_prepare_color_management (CtxState  *state,
                                       CtxBuffer *buffer);

int ctx_is_set (Ctx *ctx, uint32_t hash);

static Ctx *_ctx_new_drawlist (int width, int height);


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



static inline void
_ctx_matrix_multiply (CtxMatrix       *result,
                      const CtxMatrix *t,
                      const CtxMatrix *s)
{
  CtxMatrix r;

  for (unsigned int i = 0; i < 3; i++)
  {
    r.m[i][0] = t->m[i][0] * s->m[0][0]
              + t->m[i][1] * s->m[1][0]
              + t->m[i][2] * s->m[2][0];
    r.m[i][1] = t->m[i][0] * s->m[0][1]
              + t->m[i][1] * s->m[1][1]
              + t->m[i][2] * s->m[2][1];
    r.m[i][2] = t->m[i][0] * s->m[0][2]
              + t->m[i][1] * s->m[1][2]
              + t->m[i][2] * s->m[2][2];
  }
  *result = r;
}

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

static inline void
_ctx_user_to_device_prepped (CtxState *state, float x, float y, int *out_x, int *out_y);
static inline void
_ctx_user_to_device_prepped_fixed (CtxState *state, int x, int y, int *x_out, int *y_out);

static int ctx_float_to_string_index (float val);

void
ctx_render_ctx_masked (Ctx *ctx, Ctx *d_ctx, uint32_t mask);

static void ctx_state_set_blob (CtxState *state, uint32_t key, uint8_t *data, int len);


static inline void
_ctx_transform_prime (CtxState *state);

void ctx_push_backend (Ctx *ctx,
                       void *backend);
void ctx_pop_backend (Ctx *ctx);


static inline float ctx_fmod1f (float val)
{
  return ctx_fabsf (val - (int)(val));
}

static inline float ctx_fmodf (float val, float modulus)
{
  return ctx_fmod1f(val/modulus) * modulus;
}

#if EMSCRIPTEN
#define CTX_EXPORT EMSCRIPTEN_KEEPALIVE
#else
#define CTX_EXPORT
#endif



#endif

#if CTX_EVENTS
#include <sys/select.h>
#endif
/*
 * each scanline is encoded as a header + rle data + free space + reversed palette
 * 
 * header:  u16_length, u8_colors, u8_0
 *
 * The u8_0 padding byte needs to be 0 in the future it might hold higher values, but
 * always smaller than colors, this to make it use a value that never naturally occurs
 * for u8 associated alpha, making it possible to store either CBRLE or raw pixel
 * values in 32bpp scanlines.
 * 
 * The indexed palette is per-scanline, the first eight palette entries are
 * fixed as a grayscale. The palette is allocated on the fly and is stored from
 * the end of the scanline. As the palette fills up it gets harder and harder
 * for new colors to warrant a allocating new entries, gradients only allocate
 * colors for their start and end colors.
 *
 * When we run out of free space the scanline gets recompressed, reducing color
 * depth and simplifying pixel data, for 32bpp scanlines this can in the future
 * mean fall over to uncompressed raw data. Doing composiing on RLE encoded
 * data can on high resolution framebuffers lead to significantly lower memory
 * bandwidth and computation required on frames with significant overdraw; to
 * fully realise this potential further integration is needed.
 *
 */



#if CTX_IMPLEMENTATION || CTX_SIMD_BUILD
#if CTX_ENABLE_CBRLE


#define CBRLE_GRADIENT  1
#define ALLOW_MERGE     1

#define PAL_GRAY_PREDEF 0

#define GRADIENT_THRESHOLD     1
#define COLOR_THRESHOLD_START  6
#define COLOR_THRESHOLD_END    (color_budget<=128?32:21)

#define MAX_GRADIENT_LENGTH    (color_budget<=128?63:63)

// with high numbers, repeated recompressions steal a *lot* of time
// when data is overflowing
//
#define MAX_RECOMPRESS         2

// increase to have larger safety margin between pixeldata
// and palette
#define BORDER                 1


//  -- currently unused
#define MERGE_THRESHOLD        64   //(no<2?50:98)
#define MERGE_CHANCE           42   //(no<2?25:80)

#define PAL_GRAY_OFFSET      (PAL_GRAY_PREDEF * 8)

enum {
        CBRLE_MODE_SET=0,
        CBRLE_MODE_OVER=1,
        CBRLE_MODE_COPY=2,
        CBRLE_MODE_SET_COLOR=3
};


///  max 32 or 64 colors per scanline
//      
//     1  5 2
//     1  7    8
//     1         4  4  4 1 1 1  - the last 3bits are lengths 0 means 1 and 1 means 2 for 2 first
static inline int encode_pix (uint8_t *cbrle, int pos, 
                              uint8_t idx, int range, int gradient,
                              int allow_merge)
{
#if ALLOW_MERGE
   if (allow_merge && pos > 3)
   {
      if (cbrle[pos-1]        < 128 &&  //
          cbrle[pos-2]        < 128 &&  // previous must be single
          (cbrle[pos-1] >> 4) == 0 && idx <4 && range == 1 &&
          (cbrle[pos-1]&15) <4)
      {
         int previdx = cbrle[pos-1]&15;
         cbrle[pos-1] = (8-1) * 16 + (idx << 2) + previdx;
         return 0;
      }
   }
#endif

   if (range > 64)
     fprintf (stderr, "ERROR trying to encode too big range %i!\n", range);
   if (idx <= 15 && range < 8 && !gradient)
   {
     cbrle[pos] = (range-1) * 16 + idx;
     return 1;
   }
   else
   {
     cbrle[pos] = (range) + 128 + gradient * 64;
     cbrle[pos+1] = idx;
     return 2;
   }
}


// pix: pointer to data to decode
// ret_col: where to store 8bit color index
// length_px: number of pixels wide
// gradient: set to 1 if it is a gradient
//
// returns number of bytes in encoded form.
static inline int decode_pix (const uint8_t *pix,
                              uint8_t       *ret_col1,
                              uint8_t       *ret_col2,
                              int           *length_px,
                              int           *gradient)
{
  int encoded_length = 1;
  int len = 1;
  int col = 0;
  int col2 = 0;
  if (gradient)
    *gradient = 0;
  if ((*pix & 0x80) == 0)
  {
    len = pix[0] >> 4;
    len++;
    if (len == 8)
    {
      len = 2;
      col = pix[0] & 15;
      col2 = col >> 2;
      col = col & 3;
      if (ret_col2)
        *ret_col2 = col2;
    }
    else
    {
      col = ( pix[0] & 15);
      if (ret_col2)
        *ret_col2 = col;
    }
  }
  else
  {
    len = pix[0]-128;
    if (len > 64)
    {
      len -= 64;
      if(gradient)
        *gradient = 1;
    }
    col = pix[1];
    encoded_length = 2;
    if (ret_col2)
      *ret_col2 = col;
  }
  if (length_px)
    *length_px = len;
  if (ret_col1)
    *ret_col1 = col;
  return encoded_length;
}


static inline int decode_pix_len (const uint8_t *pix, int *length_px)
{
  return decode_pix (pix, NULL, NULL, length_px, NULL);
}


static inline uint32_t color_diff (uint32_t a, uint32_t b)
{
  uint32_t sum_dist = 0;
  for (int c = 0; c < 4; c++)
  {
    sum_dist += ((a&0xff)-(b&0xff))*((a&0xff)-(b&0xff));
    a>>=8;
    b>>=8;
  }
  return sum_dist;
}

static inline int is_b_good_middle (uint32_t a, uint32_t b, uint32_t c, int color_budget)
{
  uint32_t lerped = ctx_lerp_RGBA8 (a, c, 127);

  if (a == c) return 0; // gradients are more expensive than not
  uint32_t sum_dist = color_diff (lerped, b);
  return (sum_dist < (GRADIENT_THRESHOLD*GRADIENT_THRESHOLD*3));
}

static inline int
ctx_CBRLE_recompress (uint8_t *cbrle, int size, int width, int pos, int level);

#define GRAYCOL(v) ((v) + (v) * 256 + (v) * 256 * 256 + (unsigned)255*256*256*256)
static const uint32_t hard_pal[8]={
        GRAYCOL(0*255/7),
        GRAYCOL(1*255/7),
        GRAYCOL(2*255/7),
        GRAYCOL(3*255/7),
        GRAYCOL(4*255/7),
        GRAYCOL(5*255/7),
        GRAYCOL(6*255/7),
        GRAYCOL(7*255/7)
};
#include <math.h>
#define pow2(a) ((a)*(a))
#define ctx_sqrtf sqrtf



static inline int
ctx_CBRLE_get_color_mask (int gen)
{
  switch (gen)
  {
    case 0:
    case 1:
    default:
      return 0xffffffff;
    case 2:
      return 0xfffefefe;
    case 3:
      return 0xfffcfcfc;
    case 4:
      return 0xfff8f8f8;
    case 5:
      return 0xfff0f0f0;
    case 6:
      return 0xffe0e0e0;
  }
  return 0xffe0e0e0;
}


static inline int
ctx_CBRLE_get_color_idx (uint8_t *cbrle, int size, int color_budget, uint32_t color, int gen)
{
  int found = 0;
  int colors = cbrle[2];
  int idx = 0;
    uint32_t threshold =
       (uint32_t)
       ctx_lerpf (COLOR_THRESHOLD_START*COLOR_THRESHOLD_START*3,
                  COLOR_THRESHOLD_END*COLOR_THRESHOLD_END*3, 
                  //((colors  / ( color_budget-1.0f))) );
                  ctx_sqrtf((colors  / ( color_budget-1.0f))) );

#if 0 // reduce color-depth of grays
  if (//gen > 0 &&
      (color&0xf0) == ((color >> 8) & 0xf0) &&
      (color&0xf0) == ((color >> 16) & 0xf0))
  {
    color  &= 0xfff0f0f0u;
  }
#endif

  color = color & ctx_CBRLE_get_color_mask (gen+2);

  uint32_t best_diff = 255*255*3;


#if PAL_GRAY_PREDEF // predefined 8 grays
  if (!found)
  {
    uint32_t diff;
    int best = -1;
    for (;idx < 8; idx++)
    if ((diff = color_diff (hard_pal[idx], color)) < best_diff)
    {
      best_diff = diff;
      best = idx;
    }
    idx = best;
    if (best_diff < threshold)
    {
      found = 1;
    }
  }
#endif

  if (!found)
  {
    uint32_t diff;
    int best = -1;
    for (;idx < colors; idx++)
    if ((diff = color_diff (((uint32_t*)(&cbrle[size-4-(idx)*4]))[0], color)) < best_diff)
    {
      best_diff = diff;
      best = idx;
    }
    if (best !=-1) idx = best + PAL_GRAY_OFFSET;

    /* the color diff threshold is dynamic, as
     * palette space gets tighter we are less eager to add*/
    if (best_diff > threshold && colors < color_budget-1) // didn't find - store new color
    {
      idx = colors++;
      ((uint32_t*)(&cbrle[size-4-idx*4]))[0] = color;
      cbrle[2] = colors;
      idx += PAL_GRAY_OFFSET;
    }
  }

  return idx;
}

static inline uint32_t
ctx_over_RGBA8 (uint32_t dst, uint32_t src, uint32_t cov);

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);


static inline uint32_t
ctx_CBRLE_idx_to_color (const uint8_t *cbrle, int size, int idx)
{
#if PAL_GRAY_PREDEF
  if (idx < 8)
#if 0
    return  (idx * 17 +
             idx * 17 * 256 +
             idx * 17 * 256 * 256 +
             (unsigned)255*256*256*256);
#else
     return hard_pal[idx];
#endif
  else
#endif
     return ((uint32_t*)(&cbrle[size-4-(idx-PAL_GRAY_OFFSET)*4]))[0];
}

static inline int
ctx_CBRLE_compute_color_budget (int width, int size)
{
  int color_budget = 256;
  //return color_budget;
  switch ((size*8/width)){
      case 1:
      case 2: color_budget  = 8;break;
      case 3: color_budget  = 8;break;
      case 4: color_budget  = 16;break;
      case 5: color_budget  = 32;break;
      case 6: color_budget  = 64;break;
      case 7: color_budget  = 128;break;
      case 8: color_budget  = 136;break;
      case 9: 
      case 10: color_budget = 196;break;
      case 11:
      case 12:
      case 13:
      case 14:
      case 15:
      case 16: color_budget = 256;break;
              break;
      case 17:case 18:case 19:case 20:case 21:case 22:case 23:
      case 24:
      case 32: color_budget = 256;break;
              break;
      default:
              color_budget = ctx_mini(256,size/2/4);
              break;
  }
  color_budget = ctx_maxi(color_budget,ctx_mini(256,size/5/4));
  return color_budget;
}

static inline void
ctx_CBRLE_compress (const uint8_t *rgba_in,
                     uint8_t       *cbrle,
                     int            width,
                     int            size,
                     int            skip,
                     int            count,
                     int            mode,
                     uint8_t       *coverage,
                     int            allow_recompress)
{
  const uint8_t *rgba = rgba_in;
  int pos;

  uint32_t prev_val;
  int recompress = 0;

  int repeats;
  int colors = 0;

  uint32_t src_rgba= ((uint32_t*)rgba_in)[0];
  int color_budget = ctx_CBRLE_compute_color_budget (width, size);

#if CBRLE_GRADIENT
  int in_gradient = 0;
#endif

  if (mode == 0)
  {
#if 0
  for (int round = 0; round < 1; round++)
  for (int i = 1; i < width-1; i++)
  {
    int g0 = rgba[(i-1)*4+1];
    int g1 = rgba[(i)*4+1];
    int g2 = rgba[(i+1)*4+1];

    if (abs(g0-g1) < abs(g1-g2))
    {
      for (int c = 0; c < 4; c++)
      {
        rgba[i*4+c]=(rgba[(i-1)*4+c]+rgba[i*4+c])/2;
      }
    }
    else if (abs(g0-g1) == abs(g1-g2))
    {
    }
    else
    {
      for (int c = 0; c < 4; c++)
      {
        rgba[i*4+c]=(rgba[(i+1)*4+c]+rgba[(i)*4+c])/2;
      }
    }
  }
#endif
  }

  uint8_t trailer[size+2];
  int trailer_size = 0;

  uint8_t copy[(mode>=1&&mode<=2)?count*2+2:1];
  int copy_size = 0;

#if 1
  if (cbrle[0] == 0 &&
      cbrle[1] == 0)
  {
    pos = 4;
    for (int i =0; i < width/63;i++)
    {
      pos = encode_pix (cbrle, pos, 0, 63, 0, 0);
    }
    ((uint16_t*)cbrle)[0]=pos;
    cbrle[2] = 0;
    cbrle[3] = 16;
  }
#endif

  rgba = rgba_in;

  pos = 4;
  prev_val = 0xc0ffee;
  repeats = 0;
  
  int trailer_start = 0;

  int x = 0;

  if (skip || width != count)
  {
    int original_length = ((uint16_t*)cbrle)[0];
    colors = cbrle[2];
    pos = 4;

    for (x = 0; x < skip + count;)
    {
       int repeat = 0;
       int codelen = decode_pix_len (&cbrle[pos], &repeat);
       if (x + repeat < skip + count && pos < original_length)
       {
         pos += codelen;
         x   += repeat;
       }
       else
       {
         uint8_t idx;
         uint8_t idx2;
         int gradient;
         decode_pix (&cbrle[pos], &idx, &idx2, &repeat, &gradient);
         
         trailer_start = pos + codelen;
         if (x + repeat == skip + count)
         {
           trailer_size = original_length - trailer_start;
           if (trailer_size > 0)
           {
             memcpy (&trailer[0], &cbrle[trailer_start], trailer_size);
           }
           else
           {
             trailer_size = 0;
           }
         }
         else
         {
           repeat -= (skip + count - x);

           if (repeat > 0)
           {
             trailer[0] = repeat + 128 + gradient * 64;
             trailer[1] = idx;
           }
           trailer_size = original_length - trailer_start;
           if (trailer_size > 0)
           {
             memcpy (&trailer[2], &cbrle[pos + codelen], trailer_size);
             if (repeat>0)trailer_size += 2;
           }
           else
           {
             trailer_size = 0;
           }
         }
         break;
       }
    }

    pos  = 4;
    rgba = rgba_in;

    for (x = 0; x < skip && pos < original_length;)
    {
       int     repeat = 0;
       uint8_t idx;
       uint8_t idx2;
       int     dec_repeat;
       int     gradient;
       //int len = decode_pix_len (&cbrle[pos], &repeat);
       int len = decode_pix (&cbrle[pos], &idx, &idx2, &dec_repeat, &gradient);
       if (x + dec_repeat < skip)
       {
         pos  += len;
         x    += dec_repeat;
         rgba += 4 * dec_repeat;
       }
       else
       {
         repeat = skip - x;

         if (repeat>0)
         {
           x += repeat;

           if (mode==CBRLE_MODE_COPY ||
               mode==CBRLE_MODE_OVER)
           {
             int mpos = 0;
             if (dec_repeat != repeat)
               mpos = encode_pix (copy, 0, idx, dec_repeat-repeat, gradient, 0);
             if (original_length)
             {
               if (trailer_start == 0) trailer_start = original_length;
               copy_size = trailer_start - (pos + len);
               if (copy_size <0)
                  fprintf (stderr, "%i: uh? cs:%i %i | %i %i %i\n", __LINE__,
                                  copy_size, (int)sizeof(copy),
                                  trailer_start, pos, len);
               if (copy_size<0)copy_size=0;
               copy_size = ctx_mini(copy_size, sizeof(copy));
               if (copy_size)
               {
                 memcpy (&copy[mpos], &cbrle[pos + len], copy_size);
               }
               copy_size += mpos;
             }
             else
             {
               copy_size = 0;
             }
           }
           else
           {
             rgba += 4 * repeat;
           }
           gradient = 0;
           pos += encode_pix (cbrle, pos, idx, repeat, gradient, 0);
         }
         else
         {
           if (mode != CBRLE_MODE_SET)
           {
             int mpos = 0;
             copy_size = trailer_start - (pos + len);
             fprintf (stderr, "csb:%i %i\n", copy_size, (int)sizeof(copy));
             memcpy (&copy[mpos], &cbrle[pos + len], copy_size);
           }
         }
  
         break;
       }
    }
  }
  else
  {
    colors = 0;
    cbrle[0]=0; // length
    cbrle[1]=0; // length
    cbrle[2]=0; // pal size
    cbrle[3]=0; // alpha
  }

#if CBRLE_GRADIENT
  int prev_in_gradient = 0;
#endif

  if (mode == CBRLE_MODE_OVER)
  {
     uint32_t si_ga      = (src_rgba & 0xff00ff00) >> 8;
     uint32_t si_rb      = src_rgba & 0x00ff00ff;
     uint32_t si_a       = si_ga >> 16;

     for (int mpos = 0; mpos < copy_size && x < width && count > 0; )
     {
       uint8_t offsetA = 0;
       uint8_t offset2 = 0;
       int repeat = 0;
       int gradient = 0;
       mpos += decode_pix (&copy[mpos], &offsetA, &offset2, &repeat, &gradient);

       uint32_t color = ctx_CBRLE_idx_to_color (cbrle, size, offsetA);

       repeat = ctx_mini (repeat, count);
       if (repeat)
       {
         do {
            int part = 0;
            int cov = coverage[0];
            while (coverage[0]==cov && count >0 && repeat > 0 && x < width)
            {
              x        ++;
              coverage ++;
              part     ++;
              count    --;
              repeat   --;
            }

            {
         uint32_t composited = ctx_over_RGBA8_2 (color, si_ga, si_rb, si_a, cov);
         int idx = ctx_CBRLE_get_color_idx (cbrle, size,
                              ctx_mini(color_budget,  size-pos-colors*4 - 2), // XXX expensive?
                              composited, recompress);
         colors = cbrle[2];

            pos += encode_pix  (cbrle, pos, idx, part, gradient, 1);
            }
         } while (repeat > 0);
       }
     }
  }
  else if (mode == CBRLE_MODE_COPY)
  {
     uint32_t si_ga      = (src_rgba & 0xff00ff00) >> 8;
     uint32_t si_rb      = src_rgba & 0x00ff00ff;
     for (int mpos = 0; mpos < copy_size && x < width && count > 0; )
     {
       uint8_t offsetA = 0;
       uint8_t offset2 = 0;
       int repeat = 0;
       int gradient = 0;
       mpos += decode_pix (&copy[mpos], &offsetA, &offset2, &repeat, &gradient);

       uint32_t color = ctx_CBRLE_idx_to_color (cbrle, size, offsetA);

       repeat = ctx_mini (repeat, count);
       if (repeat)
       {
         colors = cbrle[2];
         do {
            int part = 0;
            int cov = coverage[0];
            while (coverage[0]==cov && count >0 && repeat > 0 && x < width)
            {
              x++;
              coverage++;
              part++;
              count --;
              repeat--;
            }

            {
         uint32_t composited;
         composited = ctx_lerp_RGBA8_2 (color, si_ga, si_rb, cov);
         int idx = ctx_CBRLE_get_color_idx (cbrle, size, ctx_mini(color_budget,  size-pos-colors*4 - 8), composited, recompress);
            pos += encode_pix  (cbrle, pos, idx, part, gradient, 1);
            }
         } while (repeat > 0);
       }
     }
  }
  else
  if (mode == CBRLE_MODE_SET_COLOR)
  {
     int idx = ctx_CBRLE_get_color_idx (cbrle, size, ctx_mini(color_budget,  size-pos-colors*4 ), src_rgba, recompress);
     while (count > 0)
     {
        int repeat = ctx_mini (count, 63);
        pos += encode_pix  (cbrle, pos, idx, repeat, 0, 1);
        count -= repeat;
        x += repeat;
     }
  }
  else
  {

  for (; x < width && count--; x++)
    {
      uint32_t val = ((uint32_t*)rgba)[0] & 0xffffffff;
      // NOTE: we could probably drop precision to known lower precision reached here,
      //       but it requires more global state
#if CBRLE_GRADIENT
      uint32_t next_val = 0x23ff00ff;

      if (x + 1 < width && count>1)
        next_val = ((uint32_t*)rgba)[1] & 0xffffffffu;
#endif

      if (pos > size - BORDER - trailer_size - colors * 4)
      {
        ((uint16_t*)cbrle)[0]=pos;

        if (allow_recompress)
        do {
          pos = ctx_CBRLE_recompress (cbrle, size, width, pos, recompress);
          colors = cbrle[2];
          recompress ++;
        }
        while (pos > size - BORDER - trailer_size - colors * 4

                       
                        && recompress < MAX_RECOMPRESS);
        if (recompress >3) color_budget = 0;
        if (pos > size - BORDER - trailer_size - colors * 4)
           goto done;
      }

      if (val != prev_val || repeats>=63)
      {
        if (repeats)
        {

#if CBRLE_GRADIENT
          if ((( repeats == 1 && in_gradient == 0) ||
                ( repeats < MAX_GRADIENT_LENGTH && in_gradient == 1)) 

              && is_b_good_middle (prev_val, val, next_val, color_budget)
          )
          {
            in_gradient = 1;
          }
          else
#endif
          if (repeats) // got incoming pixels
                            // of color to store
          {
            int idx = ctx_CBRLE_get_color_idx (cbrle, size, ctx_mini(color_budget,  size-pos-colors*4-BORDER ), prev_val, recompress);
            colors = cbrle[2];

            pos += encode_pix (cbrle, pos, idx, repeats,
                            
#if CBRLE_GRADIENT
                      ((prev_in_gradient==1) && (in_gradient == 1))
#else
                      0
#endif
                      
                      
                      , 1);

#if CBRLE_GRADIENT
            prev_in_gradient = in_gradient;
            in_gradient = 0;
#endif
            repeats = 0;
          }
        }
      }
      repeats++;
      prev_val = val;
      if (!mode)
        rgba +=4;
    }

    if (repeats && pos  < size - colors * 4 - BORDER)
    {
          int idx = ctx_CBRLE_get_color_idx (cbrle, size, ctx_mini(color_budget,  size-pos-colors*4-BORDER ), prev_val, recompress);
          colors = cbrle[2];
  
          if (repeats && pos + 4 < size - colors * 4 - BORDER)
          {
            pos += encode_pix (cbrle, pos, idx, repeats, 0, 1);
            repeats = 0;
          }
    }
    }

    if (trailer_size)
    {
       for (int i = 0; i < trailer_size;i++)
       {

      if (pos > size - trailer_size - BORDER - colors * 4)
      {
        ((uint16_t*)cbrle)[0]=pos;
        cbrle[2] = colors;

        if (allow_recompress)
        do {
          pos = ctx_CBRLE_recompress (cbrle, size, width, pos, recompress);
          colors = cbrle[2];
          recompress ++;
        }
        while (pos > size - trailer_size - BORDER - colors * 4 && recompress < MAX_RECOMPRESS);
        if (pos > size - trailer_size - BORDER - colors * 4)
           goto done;
      }

         cbrle[pos++] = trailer[i];
       }
    }
done:
    ((uint16_t*)cbrle)[0]=pos;
    cbrle[2] = colors;
    //cbrle[3] = 16;
}

static inline void
_ctx_CBRLE_decompress (const uint8_t *cbrle, uint8_t *rgba8, int width, int size, int skip, int count)
{
  int x = 0;
  int pos = 4;
  uint32_t pixA = 0;
#if CBRLE_GRADIENT
  uint32_t prev_pix = 0;
#endif
  const uint8_t *codepix=cbrle+4;

  int length = ((uint16_t*)cbrle)[0];
  //int colors = cbrle[2]; 

  for (x = 0; x < skip;)
  {
     int repeat = 0;
     if (pos < length)
     {
     int len = decode_pix_len (&cbrle[pos], &repeat);
     if (x + repeat < skip)
     {
       pos += len;
       codepix += len;
       x+=repeat;
     }
     else
       break;
     }
     else 
       x++;
  }
  //x=skip;

  for (; pos < length && x < width; )
  {
    uint8_t offsetA = 0;
    uint8_t offset2 = 0;
    int repeat = 0;
    int gradient = 0;
    int codelen = decode_pix (codepix, &offsetA, &offset2, &repeat, &gradient);
    //fprintf (stderr, "{%i r%i%s}", offsetA, repeat, gradient?"g":"");

    pixA = ctx_CBRLE_idx_to_color (cbrle, size, offsetA);

#if CBRLE_GRADIENT
    if (gradient)
    {
      for (int i = 0; i < repeat && x < width && count--; i++)
      {
        int has_head = 1;
        int has_tail = 0;
        float dt = (i+has_head+0.0f) / (repeat+ has_head + has_tail-1.0f);
        ((uint32_t*)(&rgba8[(x++)*4]))[0] = ctx_lerp_RGBA8 (prev_pix, pixA, (uint8_t)(dt*255.0f));
        //((uint32_t*)(&rgba8[(x++)*4]))[0] = (int)(dt * 255) | 0xff000000;
      }
    }
    else
#endif
    {
       if (offsetA != offset2 && repeat == 2)
       {
         uint32_t pixB = ctx_CBRLE_idx_to_color (cbrle, size, offset2);
         ((uint32_t*)(&rgba8[(x++)*4]))[0] = pixA;
         ((uint32_t*)(&rgba8[(x++)*4]))[0] = pixB;
       }
       else
       {
         for (int i = 0; i < repeat && x < width && count--; i++)
           ((uint32_t*)(&rgba8[(x++)*4]))[0] = pixA;
       }
    }
#if CBRLE_GRADIENT
    prev_pix = pixA;
#endif

    codepix += codelen;
    pos += codelen;
  }
  //fprintf (stderr, "\n");

  // replicate last value
  while (x < width && count--)
    ((uint32_t*)(&rgba8[(x++)*4]))[0] = prev_pix;
}


static inline int
ctx_CBRLE_recompress_sort_pal (uint8_t *cbrle, int size, int width, int length, int no)
{
  uint8_t temp[width*4 + 32];
  int colors = cbrle[2];
  //fprintf (stderr, "{%i %i %i}", size, colors, no);
  uint32_t mask = ctx_CBRLE_get_color_mask (no);
    for (int i = size - colors * 4; i < size-4; i+=4)
    {
      uint32_t *pix = (uint32_t*)(&cbrle[i]);
      pix[i] &= mask;
    }

  _ctx_CBRLE_decompress (cbrle, temp, width, size, 0, width);
  memset(cbrle, 0, size);
  ctx_CBRLE_compress (temp, cbrle, width, size, 0, width, CBRLE_MODE_SET, NULL, 0);
  return ((uint16_t*)cbrle)[0];
}


static inline int
ctx_CBRLE_recompress_drop_bits (uint8_t *cbrle, int size, int width, int length, int no)
{
  uint8_t temp[width*4 + 32];
  int colors = cbrle[2];
  //fprintf (stderr, "{%i %i %i}", size, colors, no);
  uint32_t mask = ctx_CBRLE_get_color_mask (no);
    for (int i = size - colors * 4; i < size-4; i+=4)
    {
      uint32_t *pix = (uint32_t*)(&cbrle[i]);
      pix[i] &= mask;
    }
  _ctx_CBRLE_decompress (cbrle, temp, width, size, 0, width);
  memset(cbrle, 0, size);
  ctx_CBRLE_compress (temp, cbrle, width, size, 0, width, CBRLE_MODE_SET, NULL, 0);
  return ((uint16_t*)cbrle)[0];
}

static inline int
ctx_CBRLE_recompress_merge_pairs (uint8_t *cbrle, int size, int width, int length)
{
  uint8_t temp[width*4];
   // drop horizontal resolution
      _ctx_CBRLE_decompress (cbrle, temp, width, size, 0, width);
      for (int i = 0; i < width-1; i++)
      if ((rand()%100<MERGE_CHANCE)
       &&  color_diff ( ((uint32_t*)(&temp[i*4]))[0],
                        ((uint32_t*)(&temp[(i+1)*4]))[0]) < MERGE_THRESHOLD*MERGE_THRESHOLD*3
         )
      for (int c = 0; c < 4; c++)
        temp[i*4+c]=temp[(i+1)*4+c];

      memset(cbrle, 0, size);
      ctx_CBRLE_compress (temp, cbrle, width, size, 0, width, CBRLE_MODE_SET, NULL, 0);

  return ((uint16_t*)cbrle)[0];
}

static inline int
ctx_CBRLE_recompress_smoothen (uint8_t *cbrle, int size, int width, int length)
{
  uint8_t temp[width*4];
  _ctx_CBRLE_decompress (cbrle, temp, width, size, 0, width);

#if 0
  for (int round = 0; round < 2; round++)
  for (int i = 1; i < width-1; i++)
  {
    for (int c = 0; c < 4; c++)
    {
      temp[i*4+c]=(uint8_t)(temp[(i+1)*4+c]*0.10f+temp[i*4+c]*0.8f+temp[(i-1)*4+c]*0.10f);
    }
  }
#endif

#if 1
  for (int round = 0; round < 2; round++)
  for (int i = 1; i < width-1; i++)
  {
    int g0 = temp[(i-1)*4+1];
    int g1 = temp[(i)*4+1];
    int g2 = temp[(i+1)*4+1];

    if (abs(g0-g1) < abs(g1-g2))
    {
      for (int c = 0; c < 4; c++)
      {
        temp[i*4+c]=(temp[(i-1)*4+c]+temp[i*4+c])/2;
      }
    }
    else if (abs(g0-g1) == abs(g1-g2))
    {
#if 0
      for (int c = 0; c < 4; c++)
      {
        temp[i*4+c]=(temp[(i+1)*4+c]+temp[i*4+c]+temp[(i-1)*4+c])/3;
      }
#endif
    }
    else
    {
      for (int c = 0; c < 4; c++)
      {
        temp[i*4+c]=(temp[(i+1)*4+c]+temp[(i)*4+c])/2;
      }
    }
  }
#else
#endif

  memset(cbrle, 0, size);
  ctx_CBRLE_compress (temp, cbrle, width, size, 0, width, CBRLE_MODE_SET, NULL, 0);

  return ((uint16_t*)cbrle)[0];
}

static inline int
ctx_CBRLE_recompress (uint8_t *cbrle, int size, int width, int length, int no)
{
  //uint8_t temp[width*4];
      length = ((uint16_t*)cbrle)[0];

      //if (no>0)
      //fprintf (stderr, "len: %i cols:%i i:%i\n", length, cbrle[2], no);

#if 0
//ctx_CBRLE_recompress_smoothen (cbrle, size, width, length);
  ctx_CBRLE_recompress_smoothen (cbrle, size, width, length);
  if (((uint16_t*)cbrle)[0] < length - 8 || no == 0)
  {
//  fprintf (stderr, "rlen: %i cols:%i i:%i\n", ((uint16_t*)cbrle)[0],
//                      cbrle[2], no);
    return ((uint16_t*)cbrle)[0];
  }
#endif
#if 1
  if (no == 0)
  {
    ctx_CBRLE_recompress_smoothen (cbrle, size, width, length);
    if (((uint16_t*)cbrle)[0] < length -8)
      return ((uint16_t*)cbrle)[0];
  }
#endif

#if 0
  ctx_CBRLE_recompress_drop_bits (cbrle, size, width, length, no);
  if (((uint16_t*)cbrle)[0] < length -8)
    return ((uint16_t*)cbrle)[0];
#endif


#if 0
  ctx_CBRLE_recompress_merge_pairs (cbrle, size, width, length);
  if (((uint16_t*)cbrle)[0] < length - 8 || no < 2)
  {
    return ((uint16_t*)cbrle)[0];
  }
#endif

  return ((uint16_t*)cbrle)[0];
}


#endif
#endif
#ifndef CTX_DRAWLIST_H
#define CTX_DRAWLIST_H

static int
ctx_conts_for_entry (CtxEntry *entry);
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 commands[4] = {{cmd,{{0}}}};\
  ctx_process (ctx, &commands[0]);}while(0) \

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

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

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

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


#if CTX_BITPACK_PACKER
static unsigned 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, unsigned 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 {
#if CTX_32BIT_SEGMENTS
  uint32_t code;
#else
  uint16_t code;
#endif
  union {
#if CTX_32BIT_SEGMENTS
   int32_t s16[4];
#else
   int16_t s16[4]; 
#endif
   uint32_t u32[2];
  } data;
  int32_t val;
  int32_t delta;
} CtxSegment;
#pragma pack(pop)



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 inline 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 *) ctx_malloc (item_size * new_size);
      memcpy (ne, drawlist->entries, drawlist->size * item_size );
      ctx_free (drawlist->entries);
      drawlist->entries = ne;
      //drawlist->entries = (CtxEntry*)ctx_malloc (drawlist->entries, item_size * new_size);
    }
  else
    {
      //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size);
      drawlist->entries = (CtxEntry *) ctx_malloc (item_size * new_size);
    }
  drawlist->size = new_size;
    }
  //fprintf (stderr, "drawlist %p is %d\n", drawlist, drawlist->size);
#endif
}


static inline int
ctx_edgelist_add_single (CtxDrawlist *drawlist, CtxEntry *entry)
{
  int ret = drawlist->count;

  if (CTX_UNLIKELY(ret >= CTX_MAX_EDGE_LIST_SIZE- 20))
    {
      return 0;
    }
  if (CTX_UNLIKELY(ret + 2 >= drawlist->size))
    {
      int new_ = ctx_maxi (drawlist->size * 2, ret + 1024);
      new_ = ctx_mini (CTX_MAX_EDGE_LIST_SIZE, new_);
      ctx_edgelist_resize (drawlist, new_);
    }

  ((CtxSegment*)(drawlist->entries))[ret] = *(CtxSegment*)entry;
  drawlist->count++;
  return ret;
}


#endif


#if CTX_COMPOSITE

#define CTX_FULL_AA 15
#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));
  uint32_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
}

inline static void
ctx_RGBA8_associate_global_alpha (uint8_t *u8, uint8_t global_alpha)
{
  uint32_t val = *((uint32_t*)(u8));
  uint32_t a = (u8[3] * global_alpha + 255) >> 8;
  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);
}

inline static uint32_t
ctx_RGBA8_associate_global_alpha_u32 (uint32_t val, uint8_t global_alpha)
{
  uint32_t a = ((val>>24) * global_alpha + 255) >> 8;
  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;
  return  g|rb|(a << CTX_RGBA8_A_SHIFT);
}

CTX_INLINE static void
ctx_RGBA8_associate_alpha_probably_opaque (uint8_t *u8)
{
  uint32_t a = u8[3];//val>>24;//u8[3];
  if (CTX_UNLIKELY(a!=255))
  {
    u8[0] = (u8[0] * a + 255) >> 8;
    u8[1] = (u8[1] * a + 255) >> 8;
    u8[2] = (u8[2] * a + 255) >> 8;
  }
}

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

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

inline static int ctx_grad_index_i (CtxRasterizer *rasterizer, int v)
{
  v = v >> 8;
  return ctx_maxi (0, ctx_mini (rasterizer->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;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  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] = (int)(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 = (int)((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
      rgba[3]=(rgba[3]*global_alpha_u8+255)>>8;
      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;
  }
  rgba[3]=(rgba[3]*global_alpha_u8+255)>>8;
  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*)(&rasterizer->gradient_cache_u8[ctx_grad_index(rasterizer, 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)
{
  // XXX : todo  make the number of element dynamic depending on length of gradient
  // in device coordinates.

  if (rasterizer->gradient_cache_valid)
    return;
  

  {
    CtxSource *source = &rasterizer->state->gstate.source_fill;
    float length = 100;
    if (source->type == CTX_SOURCE_LINEAR_GRADIENT)
    {
       length = source->linear_gradient.length;
    }
    else
    if (source->type == CTX_SOURCE_RADIAL_GRADIENT)
    {
       length = ctx_maxf (source->radial_gradient.r1, source->radial_gradient.r0);
    }
  //  length = CTX_GRADIENT_CACHE_ELEMENTS;
  {
     float u = length; float v = length;
     const CtxMatrix *m = &rasterizer->state->gstate.transform;
     //CtxMatrix *transform = &source->transform;
     //
     //  combine with above source transform?
     _ctx_matrix_apply_transform (m, &u, &v);
     length = ctx_maxf (u, v);
  }
  
    rasterizer->gradient_cache_elements = ctx_mini ((int)length, CTX_GRADIENT_CACHE_ELEMENTS);
  }

  for (int u = 0; u < rasterizer->gradient_cache_elements; u++)
  {
    float v = u / (rasterizer->gradient_cache_elements - 1.0f);
    _ctx_fragment_gradient_1d_RGBA8 (rasterizer, v, 0.0f, &rasterizer->gradient_cache_u8[u][0]);
    //*((uint32_t*)(&rasterizer->gradient_cache_u8_a[u][0]))= *((uint32_t*)(&rasterizer->gradient_cache_u8[u][0]));
    //memcpy(&rasterizer->gradient_cache_u8_a[u][0], &rasterizer->gradient_cache_u8[u][0], 4);
    //ctx_RGBA8_associate_alpha (&rasterizer->gradient_cache_u8_a[u][0]);
  }
  rasterizer->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] = (int)(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 = (int)((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)
{
  float global_alpha = rasterizer->state->gstate.global_alpha_f;
  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 = (int)((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); }
      rgba[3] *= global_alpha;
      return;
    }
  else
    {
      color = & (g->stops[g->n_stops-1].color);
    }
  ctx_color_get_rgba (rasterizer->state, color, rgba);
  rgba[3] *= global_alpha;
}
#endif

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

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

  int u = (int)x;
  int v = (int)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.9f;
      float dy = (y-(int)(y)) * 255.9f;
      uint8_t dxb = (uint8_t)dx;
      uint8_t dyb = (uint8_t)dy;

      switch (bpp)
      {
      case 1:
        rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dxb),
                               ctx_lerp_u8 (src10[0], src11[0], dxb), dyb);
        rgba[3] = global_alpha_u8;
        break;
      case 2:
        rgba[0] = rgba[1] = rgba[2] = ctx_lerp_u8 (ctx_lerp_u8 (src00[0], src01[0], dxb),
                               ctx_lerp_u8 (src10[0], src11[0], dxb), dyb);
        rgba[3] = ctx_lerp_u8 (ctx_lerp_u8 (src00[1], src01[1], dxb),
                               ctx_lerp_u8 (src10[1], src11[1], dxb), dyb);
        rgba[3] = (rgba[3] * global_alpha_u8) / 255;
        break;
      case 3:
      for (int c = 0; c < bpp; c++)
        { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dxb),
                                 ctx_lerp_u8 (src10[c], src11[c], dxb), dyb);
                
        }
        rgba[3]=global_alpha_u8;
        break;
      break;
      case 4:
      for (int c = 0; c < bpp; c++)
        { rgba[c] = ctx_lerp_u8 (ctx_lerp_u8 (src00[c], src01[c], dxb),
                                 ctx_lerp_u8 (src10[c], src11[c], dxb), dyb);
                
        }
        rgba[3] = (rgba[3] * global_alpha_u8) / 255;
      }

      }
      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] = global_alpha_u8;
            break;
          case 2:
            for (int c = 0; c < 3; c++)
              { rgba[c] = src[0]; }
            rgba[3] = src[1];
            rgba[3] = (rgba[3] * global_alpha_u8) / 255;
            break;
          case 3:
            for (int c = 0; c < 3; c++)
              { rgba[c] = src[c]; }
            rgba[3] = global_alpha_u8;
            break;
          case 4:
            for (int c = 0; c < 4; c++)
              { rgba[c] = src[c]; }
            rgba[3] = (rgba[3] * global_alpha_u8) / 255;
            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);
    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

#if 0
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;
    }
}
#endif

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.0f) ralpha = 1.0f/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, float z,
                                   void *out, int count, float dx, float dy, float dz)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  int width = buffer->width;
  int height = buffer->height;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
  int dim = (int)((1.0f / factor) / 3);

  int i = 0;

  for (; i < count && (x - dim< 0 || y - dim < 0 || x + dim >= height || y + dim >= height); i++)
  {
    *((uint32_t*)(rgba))=0;
    rgba += 4;
    x += dx;
    y += dy;
  }

  for (; i < count && !(
       x - dim < 0 || y - dim < 0 ||
       x + dim >= width ||
       y + dim >= height); i++)
  {

  int u = (int)x;
  int v = (int)y;
    {
      int bpp = 3;
      rgba[3]=global_alpha_u8; // gets lost
          uint64_t sum[4]={0,0,0,0};
          int count = 0;

          {
            for (int ov = - dim; ov <= dim; ov++)
            {
              uint8_t *src = (uint8_t *) buffer->data + bpp * ((v+ov) * width + (u - dim));
              for (int ou = - dim; ou <= dim; ou++)
              {
                for (int c = 0; c < bpp; c++)
                  sum[c] += src[c];
                count ++;
                src += bpp;
              }

            }
          }

          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;
  }

  for (; i < count; i++)
  {
    *((uint32_t*)(rgba))= 0;
    rgba += 4;
  }
}

#define CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(frag) \
static void \
frag##_swap_red_green (CtxRasterizer *rasterizer,\
                       float x, float y, float z,\
                       void *out, int count, float dx, float dy, float dz)\
{\
  frag (rasterizer, x, y, z, out, count, dx, dy, dz);\
  ctx_fragment_swap_red_green_u8 (out, count);\
}



static inline void
ctx_RGBA8_apply_global_alpha_and_associate (CtxRasterizer *rasterizer,
                                         uint8_t *buf, int count)
{
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) buf;
  if (global_alpha_u8 != 255)
  {
    for (int i = 0; i < count; i++)
    {
      ctx_RGBA8_associate_global_alpha (rgba, global_alpha_u8);
      rgba += 4;
    }
  }
  else
  {
    for (int i = 0; i < count; i++)
    {
      ctx_RGBA8_associate_alpha_probably_opaque (rgba);
      rgba += 4;
    }
  }
}

#if CTX_FRAGMENT_SPECIALIZE

static void
ctx_fragment_image_rgb8_RGBA8_nearest (CtxRasterizer *rasterizer,
                                       float x, float y, float z,
                                       void *out, int scount,
                                       float dx, float dy, float dz);
static inline void
ctx_fragment_image_rgb8_RGBA8_bi (CtxRasterizer *rasterizer,
                                  float x, float y, float z,
                                  void *out, int scount,
                                  float dx, float dy, float dz)
{
  ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer,
                                         x, y, z,
                                         out, scount,
                                         dx, dy, dz);
  return;
}

static void
ctx_fragment_image_rgb8_RGBA8_nearest (CtxRasterizer *rasterizer,
                                       float x, float y, float z,
                                       void *out, int scount,
                                       float dx, float dy, float dz)
{
  unsigned int count = scount;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  unsigned int i = 0;
  uint8_t *data = ((uint8_t*)buffer->data);

  int yi_delta = (int)(dy * 65536);
  int xi_delta = (int)(dx * 65536);
  int zi_delta = (int)(dz * 65536);
  int32_t yi = (int)(y * 65536);
  int32_t xi = (int)(x * 65536);
  int32_t zi = (int)(z * 65536);
  {
    int32_t u1 = xi + xi_delta* (count-1);
    int32_t v1 = yi + yi_delta* (count-1);
    int32_t z1 = zi + zi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      float z_recip = (z1!=0) * (1.0f/z1);
      if ((u1*z_recip) <0 ||
          (v1*z_recip) <0 ||
          (u1*z_recip) >= (bwidth) - 1 ||
          (v1*z_recip) >= (bheight) - 1)
      {
        *edst-- = 0;
        count --;
        u1 -= xi_delta;
        v1 -= yi_delta;
        z1 -= zi_delta;
      }
      else break;
    }
  }

  for (i= 0; i < count; i ++)
  {
    float z_recip = (zi!=0) * (1.0f/zi);
    int u = (int)(xi * z_recip);
    int v = (int)(yi * z_recip);
    if ( u  <= 0 || v  <= 0 || u+1 >= bwidth-1 || v+1 >= bheight-1)
    {
      *((uint32_t*)(rgba))= 0;
    }
    else
      break;
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;
  }

  while (i < count)
  {
    float z_recip = (zi!=0) * (1.0f/zi);
    int u = (int)(xi * z_recip);
    int v = (int)(yi * z_recip);
    for (unsigned int c = 0; c < 3; c++)
      rgba[c] = data[(bwidth *v +u)*3+c];
    rgba[3] = global_alpha_u8;
    ctx_RGBA8_associate_alpha_probably_opaque (rgba);
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;
    i++;
  }
}



CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgb8_RGBA8_box)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgb8_RGBA8_bi)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgb8_RGBA8_nearest)


static inline void
ctx_fragment_image_rgb8_RGBA8 (CtxRasterizer *rasterizer,
                               float x,
                               float y,
                               float z,
                               void *out, int count, float dx, float dy, float dz)
{
  if (rasterizer->swap_red_green)
  {
    if (rasterizer->state->gstate.image_smoothing)
    {
      float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
      if (factor <= 0.50f)
        ctx_fragment_image_rgb8_RGBA8_box_swap_red_green (rasterizer,x,y,z,out,count,dx,dy,dz);
  #if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
      else if (factor > 0.99f && factor < 1.01f)
        ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer,x,y,z,
                                                            out,count,dx,dy,dz);
  #endif
      else
        ctx_fragment_image_rgb8_RGBA8_bi_swap_red_green (rasterizer,x,y,z,
                                                         out,count, dx, dy, dz);
    }
    else
    {
      ctx_fragment_image_rgb8_RGBA8_nearest_swap_red_green (rasterizer,x,y,z,
                                                            out,count,dx,dy,dz);
    }
  }
  else
  {
    if (rasterizer->state->gstate.image_smoothing)
    {
      float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
      if (factor <= 0.50f)
        ctx_fragment_image_rgb8_RGBA8_box (rasterizer,x,y,z,out,
                                           count,dx,dy,dz);
  #if CTX_ALWAYS_USE_NEAREST_FOR_SCALE1
      else if (factor > 0.99f && factor < 1.01f)
        ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer, x, y, z, out, count, dx, dy, dz);
  #endif
      else
        ctx_fragment_image_rgb8_RGBA8_bi (rasterizer,x,y,z,out,count,dx,dy,dz);
    }
    else
    {
        ctx_fragment_image_rgb8_RGBA8_nearest (rasterizer,x,y,z,out,
                                               count,dx,dy, dz);
    }
  }
}


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

static void
ctx_fragment_image_rgba8_RGBA8_box (CtxRasterizer *rasterizer,
                                    float x, float y, float z,
                                    void *out, int count, float dx, float dy, float dz)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  int width = buffer->width;
  int height = buffer->height;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
  int dim = (int)((1.0f / factor) / 3);

  int i = 0;

  for (; i < count && (x - dim< 0 || y - dim < 0 || x + dim >= height || y + dim >= height); i++)
  {
    *((uint32_t*)(rgba))=0;
    rgba += 4;
    x += dx;
    y += dy;
  }

  for (; i < count && !(
       x - dim < 0 || y - dim < 0 ||
       x + dim >= width ||
       y + dim >= height); i++)
  {

  int u = (int)x;
  int v = (int)y;
    {
      int bpp = 4;
          uint64_t sum[4]={0,0,0,0};
          int count = 0;

          {
            for (int ov = - dim; ov <= dim; ov++)
            {
              uint8_t *src = (uint8_t *) buffer->data + bpp * ((v+ov) * width + (u - dim));
              for (int ou = - dim; ou <= dim; ou++)
              {
                for (int c = 0; c < bpp; c++)
                  sum[c] += src[c];
                count ++;
                src += bpp;
              }

            }
          }

          int recip = 65536/count;
          for (int c = 0; c < bpp; c++)
            rgba[c] = sum[c] * recip >> 16;
          rgba[3]=rgba[3]*global_alpha_u8/255; // gets lost
          ctx_RGBA8_associate_alpha_probably_opaque (rgba);
    }
    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 void
ctx_fragment_image_rgba8_RGBA8_nearest_copy (CtxRasterizer *rasterizer,
                                             float x, float y, float z,
                                             void *out, int scount, float dx, float dy, float dz)
{
  unsigned int count = scount;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = 
     g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  uint32_t *dst = (uint32_t*)out;
#if 0
  for (int i = 0; i < scount; i++)
          dst[i] = (255<<24)+128;
  return;
#endif
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  int u = (int)x;
  int v = (int)y;

  uint32_t *src = ((uint32_t*)buffer->data) + bwidth * v + u;
  if (CTX_UNLIKELY(!(v >= 0 && v < bheight)))
  {
    for (unsigned i = 0 ; i < count; i++)
      *dst++ = 0;
    return;
  }

#if 1
  int pre = ctx_mini(ctx_maxi(-u,0), count);
  memset (dst, 0, pre);
  dst +=pre;
  count-=pre;
  src+=pre;
  u+=pre;
#else
  while (count && !(u >= 0))
  {
    *dst++ = 0;
    src ++;
    u++;
    count--;
  }
#endif

  int limit = ctx_mini (count, bwidth - u);
  if (limit>0)
  {
    memcpy (dst, src, limit * 4);
    dst += limit;
  }
  memset (dst, 0, count - limit);

//ctx_RGBA8_apply_global_alpha_and_associate (rasterizer, (uint8_t*)out, count);
}

static void
ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat (CtxRasterizer *rasterizer,
                                                    float x, float y, float z,
                                                    void *out, int count, float dx, float dy, float dz)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = 
     g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  uint32_t *dst = (uint32_t*)out;
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  int u = (int)x;
  int v = (int)y;
  if (v < 0) v += bheight * 8192;
  if (u < 0) u += bwidth * 8192;
  v %= bheight;
  u %= bwidth;

  uint32_t *src = ((uint32_t*)buffer->data) + bwidth * v;

  while (count)
  {
     int chunk = ctx_mini (bwidth - u, count);
     memcpy (dst, src + u, chunk * 4);
     dst += chunk;
     count -= chunk;
     u = (u + chunk) % bwidth;
  }
}

static inline int
_ctx_coords_restrict (CtxExtend extend,
                      int *u, int *v,
                      int bwidth, int bheight)
{
  switch (extend)
  {
    case CTX_EXTEND_REPEAT:
      if(u)
      {
         while (*u < 0) *u += bwidth * 4096;   // XXX need better way to do this
         *u  %= bwidth;
      }
      if(v)
      {
        while (*v < 0) *v += bheight * 4096;
        *v  %= bheight;
      }
      return 1;
    case CTX_EXTEND_REFLECT:
      if (u)
      {
      while (*u < 0) *u += bwidth * 4096;   // XXX need better way to do this
      *u  %= (bwidth*2);

      *u = (*u>=bwidth) * (bwidth*2 - *u) +
           (*u<bwidth) * *u;
      }

      if (v)
      {
      while (*v < 0) *v += bheight * 4096;
      *v  %= (bheight*2);
      *v = (*v>=bheight) * (bheight*2 - *v) +
           (*v<bheight) * *v;
      }

      return 1;
    case CTX_EXTEND_PAD:
      if (u)*u = ctx_mini (ctx_maxi (*u, 0), bwidth-1);
      if (v)*v = ctx_mini (ctx_maxi (*v, 0), bheight-1);
      return 1;
    case CTX_EXTEND_NONE:
      if (u)
      {
      //*u  %= bwidth;
      if (*u < 0 || *u >= bwidth) return 0;
      }
      if (v)
      {
      //*v  %= bheight;
      if (*v < 0 || *v >= bheight) return 0;
      }
      return 1;
  }
  return 0;
}

static void
ctx_fragment_image_rgba8_RGBA8_nearest_affine (CtxRasterizer *rasterizer,
                                               float x, float y, float z,
                                               void *out, int scount, float dx, float dy, float dz)
{
  unsigned int count = scount;
  //uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  CtxExtend extend = rasterizer->state->gstate.extend;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  unsigned int i = 0;
  uint32_t *data = ((uint32_t*)buffer->data);

  int yi_delta = (int)(dy * 65536);
  int xi_delta = (int)(dx * 65536);
  int32_t yi = (int)(y * 65536);
  int32_t xi = (int)(x * 65536);
  switch (extend){
          case CTX_EXTEND_NONE:
                  {

    int32_t u1 = xi + xi_delta* (count-1);
    int32_t v1 = yi + yi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      if ((u1>>16) <0 ||
          (v1>>16) <0 ||
          (u1>>16) >= (bwidth) - 1 ||
          (v1>>16) >= (bheight) - 1)
      {
        *edst-- = 0;
        count --;
        u1 -= xi_delta;
        v1 -= yi_delta;
      }
      else break;
    }

  for (i= 0; i < count; i ++)
  {
    int u = xi >> 16;
    int v = yi >> 16;
    if ( u  <= 0 || v  <= 0 || u+1 >= bwidth-1 || v+1 >= bheight-1)
    {
      *((uint32_t*)(rgba))= 0;
    }
    else
      break;
    xi += xi_delta;
    yi += yi_delta;
    rgba += 4;
  }

  while (i < count)
  {
    int u = xi >> 16;
    int v = yi >> 16;
    //((uint32_t*)(&rgba[0]))[0] =
    //  ctx_RGBA8_associate_global_alpha_u32 (data[bwidth *v +u], global_alpha_u8);
    ((uint32_t*)(&rgba[0]))[0] = data[bwidth *v +u];
    xi += xi_delta;
    yi += yi_delta;
    rgba += 4;
    i++;
  }
                  }
  break;
          default:
    while (i < count)
    {
      int u = xi >> 16;
      int v = yi >> 16;
      _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
      //((uint32_t*)(&rgba[0]))[0] =
      //  ctx_RGBA8_associate_global_alpha_u32 (data[bwidth *v +u], global_alpha_u8);
      ((uint32_t*)(&rgba[0]))[0] = data[bwidth *v +u];
      xi += xi_delta;
      yi += yi_delta;
      rgba += 4;
      i++;
    }
    break;
  }
}



static void
ctx_fragment_image_rgba8_RGBA8_nearest_scale (CtxRasterizer *rasterizer,
                                              float x, float y, float z,
                                              void *out, int scount, float dx, float dy, float dz)
{
  unsigned int count = scount;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = NULL;
  CtxExtend  extend = rasterizer->state->gstate.extend;
  uint32_t *src = NULL;
  buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  int ideltax = (int)(dx * 65536);
  uint32_t *dst = (uint32_t*)out;
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  int bbheight = bheight << 16;
  int bbwidth  = bwidth << 16;
//  x += 0.5f;
//  y += 0.5f;

  src = (uint32_t*)buffer->data;
  //if (!src){ fprintf (stderr, "eeek bailing in nearest fragment\n"); return;};

  {
    unsigned int i = 0;
    int32_t ix = (int)(x * 65536);
    int32_t iy = (int)(y * 65536);

    if (extend == CTX_EXTEND_NONE)
    {
    int32_t u1 = ix + ideltax * (count-1);
    int32_t v1 = iy;
    uint32_t *edst = ((uint32_t*)out)+count - 1;
    for (; i < count; )
    {
      if (u1 <0 || v1 < 0 || u1 >= bbwidth || v1 >= bbheight)
      {
        *edst-- = 0;
        count --;
        u1 -= ideltax;
      }
      else break;
    }

    for (i = 0; i < count; i ++)
    {
      if (ix < 0 || iy < 0 || ix >= bbwidth  || iy >= bbheight)
      {
        *dst++ = 0;
        x += dx;
        ix += ideltax;
      }
      else break;
    }

      int v = iy >> 16;
      int u = ix >> 16;
      int o = (v)*bwidth;
      for (; i < count; i ++)
      {
        u = ix >> 16;
        *dst++ = src[o + (u)];
        ix += ideltax;
      }
    }
    else
    {

      int v = iy >> 16;
      int u = ix >> 16;
      _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
      int o = (v)*bwidth;
      for (; i < count; i ++)
      {
        u = ix >> 16;
        _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
        *dst++ = src[o + (u)];
        ix += ideltax;
      }
    }
  }
//  ctx_RGBA8_apply_global_alpha_and_associate (rasterizer, (uint8_t*)out, count);
}

static void
ctx_fragment_image_rgba8_RGBA8_nearest_generic (CtxRasterizer *rasterizer,
                                                float x, float y, float z,
                                                void *out, int scount, float dx, float dy, float dz)
{
  unsigned int count = scount;
  //uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  CtxExtend extend = rasterizer->state->gstate.extend;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  unsigned int i = 0;
  uint32_t *data = ((uint32_t*)buffer->data);

  int yi_delta = (int)(dy * 65536);
  int xi_delta = (int)(dx * 65536);
  int zi_delta = (int)(dz * 65536);
  int32_t yi = (int)(y * 65536);
  int32_t xi = (int)(x * 65536);
  int32_t zi = (int)(z * 65536);
  switch (extend){
          case CTX_EXTEND_NONE:
                  {

    int32_t u1 = xi + xi_delta* (count-1);
    int32_t v1 = yi + yi_delta* (count-1);
    int32_t z1 = zi + zi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      float z_recip = (z1!=0) * (1.0f/z1);
      if ((u1*z_recip) <0 ||
          (v1*z_recip) <0 ||
          (u1*z_recip) >= (bwidth) - 1 ||
          (v1*z_recip) >= (bheight) - 1)
      {
        *edst-- = 0;
        count --;
        u1 -= xi_delta;
        v1 -= yi_delta;
        z1 -= zi_delta;
      }
      else break;
    }

  for (i= 0; i < count; i ++)
  {
    float z_recip = (zi!=0) * (1.0f/zi);
    int u = (int)(xi * z_recip);
    int v = (int)(yi * z_recip);
    if ( u  <= 0 || v  <= 0 || u+1 >= bwidth-1 || v+1 >= bheight-1)
    {
      *((uint32_t*)(rgba))= 0;
    }
    else
      break;
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;
  }

  while (i < count)
  {
    float z_recip = (zi!=0) * (1.0f/zi);
    int u = (int)(xi * z_recip);
    int v = (int)(yi * z_recip);
    //((uint32_t*)(&rgba[0]))[0] =
    //  ctx_RGBA8_associate_global_alpha_u32 (data[bwidth *v +u], global_alpha_u8);
    ((uint32_t*)(&rgba[0]))[0] = data[bwidth *v +u];
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;
    i++;
  }
                  }
  break;
  default:
    while (i < count)
    {
      float z_recip = (zi!=0) * (1.0f/zi);
      int u = (int)(xi * z_recip);
      int v = (int)(yi * z_recip);
      _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
      //((uint32_t*)(&rgba[0]))[0] =
      //  ctx_RGBA8_associate_global_alpha_u32 (data[bwidth *v +u], global_alpha_u8);
      ((uint32_t*)(&rgba[0]))[0] = data[bwidth *v +u];
      xi += xi_delta;
      yi += yi_delta;
      zi += zi_delta;
      rgba += 4;
      i++;
    }
    break;
  }
}

static void
ctx_fragment_image_rgba8_RGBA8_nearest (CtxRasterizer *rasterizer,
                                   float x, float y, float z,
                                   void *out, int icount, float dx, float dy, float dz)
{
  unsigned int count = icount;
  CtxExtend extend = rasterizer->state->gstate.extend;
  if (z == 1.0f && dz == 0.0f) // this also catches other constant z!
  {
    if (dy == 0.0f && dx == 1.0f && extend == CTX_EXTEND_NONE)
      ctx_fragment_image_rgba8_RGBA8_nearest_copy (rasterizer, x, y, z, out, count, dx, dy, dz);
    else
      ctx_fragment_image_rgba8_RGBA8_nearest_affine (rasterizer, x, y, z, out, count, dx, dy, dz);
  }
  else
  {
    ctx_fragment_image_rgba8_RGBA8_nearest_generic (rasterizer, x, y, z, out, count, dx, dy, dz);
  }
}


static inline void
ctx_fragment_image_rgba8_RGBA8_bi_scale (CtxRasterizer *rasterizer,
                                         float x, float y, float z,
                                         void *out, int scount, float dx, float dy, float dz)
{
    uint32_t count = scount;
    x -= 0.5f;
    y -= 0.5f;
    uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
    uint8_t *rgba = (uint8_t *) out;
    CtxSource *g = &rasterizer->state->gstate.source_fill;
    CtxExtend  extend = rasterizer->state->gstate.extend;
    CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
    const int bwidth = buffer->width;
    const int bheight = buffer->height;
    unsigned int i = 0;

    if (!extend)
    {
    if (!(y >= 0 && y < bheight))
    {
      uint32_t *dst = (uint32_t*)rgba;
      for (i = 0 ; i < count; i++)
        *dst++ = 0;
      return;
    }
    }

    //x+=1; // XXX off by one somewhere? ,, needed for alignment with nearest

    int32_t yi = (int)(y * 65536);
    int32_t xi = (int)(x * 65536);

    int xi_delta = (int)(dx * 65536);

    if (!extend)
    {
    int32_t u1 = xi + xi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      if (u1 <0 || u1 +65536 >= (bwidth<<16))
    {
      *edst-- = 0;
      count --;
      u1 -= xi_delta;
    }
    else break;
  }
    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 v = yi >> 16;


  int dv = (yi >> 8) & 0xff;

  int u = xi >> 16;

  int v1 = v+1;

  _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
  _ctx_coords_restrict (extend, NULL, &v1, bwidth, bheight);

  uint32_t *data = ((uint32_t*)buffer->data) + bwidth * v;
  uint32_t *ndata = ((uint32_t*)buffer->data) + bwidth * v1;

  if (!extend && v1 > bheight-1) ndata = data;

  if (extend)
  {
    if (xi_delta == 65536)
    {
      uint32_t *src0 = data, *src1 = ndata;
      uint32_t s1_ga = 0, s1_rb = 0;
      int du = (xi >> 8) & 0xff;

      src0 = data + u;
      src1 = ndata + u;
      ctx_lerp_RGBA8_split (src0[0],src1[0], dv, &s1_ga, &s1_rb);
  
      for (; i < count; i ++)
      {
        uint32_t s0_ga = s1_ga;
        uint32_t s0_rb = s1_rb;
        _ctx_coords_restrict (extend, &u, NULL, bwidth, bheight);
        ctx_lerp_RGBA8_split (src0[1],src1[1], dv, &s1_ga, &s1_rb);
        ((uint32_t*)(&rgba[0]))[0] = 
          ctx_RGBA8_associate_global_alpha_u32 (
                  ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, du), global_alpha_u8);
        rgba += 4;
        u++;
        src0 ++;
        src1 ++;
      }
    }
    else
    {
      uint32_t s0_ga = 0, s1_ga = 0, s0_rb = 0, s1_rb = 0;
      int prev_u = -1000;
      for (; (i < count); i++)
      {
        if (CTX_LIKELY(prev_u == u))
        {
        }
        else if (prev_u == u-1)
        {
          s0_ga = s1_ga;
          s0_rb = s1_rb;
          ctx_lerp_RGBA8_split (data[u+1],ndata[u+1], dv, &s1_ga, &s1_rb);
          prev_u++;
        }
        else
        {
          ctx_lerp_RGBA8_split (data[u],ndata[u], dv, &s0_ga, &s0_rb);
          ctx_lerp_RGBA8_split (data[u+1],ndata[u+1], dv, &s1_ga, &s1_rb);
          prev_u = u;
        }
        ((uint32_t*)(&rgba[0]))[0] = 
          ctx_RGBA8_associate_global_alpha_u32 (
                  ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, (xi>>8)), global_alpha_u8);
        xi += xi_delta;
        rgba += 4;
        u = xi >> 16;
        _ctx_coords_restrict (extend, &u, NULL, bwidth, bheight);
      }
    }
  
  }
  else
  {
    if (xi_delta == 65536)
    {
      uint32_t *src0 = data, *src1 = ndata;
      uint32_t s1_ga = 0, s1_rb = 0;
      int du = (xi >> 8) & 0xff;
  
      src0 = data + u;
      src1 = ndata + u;
      ctx_lerp_RGBA8_split (src0[0],src1[0], dv, &s1_ga, &s1_rb);
  
      for (; i < count; i ++)
      {
        uint32_t s0_ga = s1_ga;
        uint32_t s0_rb = s1_rb;
        ctx_lerp_RGBA8_split (src0[1],src1[1], dv, &s1_ga, &s1_rb);
        ((uint32_t*)(&rgba[0]))[0] = 
          ctx_RGBA8_associate_global_alpha_u32 (
                  ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, du), global_alpha_u8);
        rgba += 4;
        u++;
        src0 ++;
        src1 ++;
      }
    }
    else
    {
      uint32_t s0_ga = 0, s1_ga = 0, s0_rb = 0, s1_rb = 0;
      int prev_u = -1000;
      for (; (i < count); i++)
      {
        if (CTX_LIKELY(prev_u == u))
        {
        }
        else if (prev_u == u-1)
        {
          s0_ga = s1_ga;
          s0_rb = s1_rb;
          ctx_lerp_RGBA8_split (data[u+1],ndata[u+1], dv, &s1_ga, &s1_rb);
          prev_u++;
        }
        else
        {
          ctx_lerp_RGBA8_split (data[u],ndata[u], dv, &s0_ga, &s0_rb);
          ctx_lerp_RGBA8_split (data[u+1],ndata[u+1], dv, &s1_ga, &s1_rb);
          prev_u = u;
        }
        ((uint32_t*)(&rgba[0]))[0] = 
          ctx_RGBA8_associate_global_alpha_u32 (
                  ctx_lerp_RGBA8_merge (s0_ga, s0_rb, s1_ga, s1_rb, (xi>>8)), global_alpha_u8);
        xi += xi_delta;
        rgba += 4;
        u = xi >> 16;
      }
    }
  }
}


static inline void
ctx_fragment_image_rgba8_RGBA8_bi_affine (CtxRasterizer *rasterizer,
                                          float x, float y, float z,
                                          void *out, int scount,
                                          float dx, float dy, float dz)
{
        x-=0.5f;
        y-=0.5f;
  uint32_t count = scount;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  CtxExtend extend = rasterizer->state->gstate.extend;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  unsigned int i = 0;
  uint32_t *data = ((uint32_t*)buffer->data);

  int yi_delta = (int)(dy * 65536);
  int xi_delta = (int)(dx * 65536);
  int32_t yi = (int)(y * 65536);
  int32_t xi = (int)(x * 65536);

  if (extend == CTX_EXTEND_NONE)
  {
    int32_t u1 = xi + xi_delta* (count-1);
    int32_t v1 = yi + yi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      if ((u1>>16) <0 ||
          (v1>>16) <0 ||
          (u1>>16) >= (bwidth) - 1 ||
          (v1>>16) >= (bheight) - 1)
      {
        *edst-- = 0;
        count --;
        u1 -= xi_delta;
        v1 -= yi_delta;
      }
      else break;
    }

  for (i= 0; i < count; i ++)
  {
    int u = xi >> 16;
    int v = yi >> 16;
    if ( u  <= 0 || v  <= 0 || u+1 >= bwidth-1 || v+1 >= bheight-1)
    {
      *((uint32_t*)(rgba))= 0;
    }
    else
      break;
    xi += xi_delta;
    yi += yi_delta;
    rgba += 4;
  }
  }

  uint32_t *src00=data;
  uint32_t *src01=data;
  uint32_t *src10=data;
  uint32_t *src11=data;

  while (i < count)
  {
    int du = xi >> 8;
    int u = du >> 8;
    int dv = yi >> 8;
    int v = dv >> 8;
    if (CTX_UNLIKELY(u < 0 || v < 0 || u+1 >= bwidth || v+1 >=bheight)) // default to next sample down and to right
    {
      int u1 = u + 1;
      int v1 = v + 1;

      _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
      _ctx_coords_restrict (extend, &u1, &v1, bwidth, bheight);

      src00 = data  + bwidth * v + u;
      src01 = data  + bwidth * v + u1;
      src10 = data  + bwidth * v1 + u;
      src11 = data  + bwidth * v1 + u1;
    }
    else 
    {
      src00 = data  + bwidth * v + u;
      src01 = src00 + 1;
      src10 = src00 + bwidth;
      src11 = src01 + bwidth;
    }
    ((uint32_t*)(&rgba[0]))[0] =
        ctx_RGBA8_associate_global_alpha_u32 (
            ctx_bi_RGBA8 (*src00,*src01,*src10,*src11, du,dv), global_alpha_u8);
    xi += xi_delta;
    yi += yi_delta;
    rgba += 4;

    i++;
  }
}


static inline void
ctx_fragment_image_rgba8_RGBA8_bi_generic (CtxRasterizer *rasterizer,
                                           float x, float y, float z,
                                           void *out, int scount,
                                           float dx, float dy, float dz)
{
        x-=0.5f;
        y-=0.5f;
  uint32_t count = scount;
  uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  CtxExtend extend = rasterizer->state->gstate.extend;
  const int bwidth = buffer->width;
  const int bheight = buffer->height;
  unsigned int i = 0;
  uint32_t *data = ((uint32_t*)buffer->data);

  int yi_delta = (int)(dy * 65536);
  int xi_delta = (int)(dx * 65536);
  int zi_delta = (int)(dz * 65536);
  int32_t yi = (int)(y * 65536);
  int32_t xi = (int)(x * 65536);
  int32_t zi = (int)(z * 65536);
  if (extend == CTX_EXTEND_NONE) {
    int32_t u1 = xi + xi_delta* (count-1);
    int32_t v1 = yi + yi_delta* (count-1);
    int32_t z1 = zi + zi_delta* (count-1);
    uint32_t *edst = ((uint32_t*)out)+(count-1);
    for (; i < count; )
    {
      float z_recip = (z1!=0) * (1.0f/z1);
      if ((u1*z_recip) <0 ||
          (v1*z_recip) <0 ||
          (u1*z_recip) >= (bwidth) - 1 ||
          (v1*z_recip) >= (bheight) - 1)
      {
        *edst-- = 0;
        count --;
        u1 -= xi_delta;
        v1 -= yi_delta;
        z1 -= zi_delta;
      }
      else break;
    }

  for (i= 0; i < count; i ++)
  {
    float z_recip = (zi!=0) * (1.0f/zi);
    int u = (int)(xi * z_recip);
    int v = (int)(yi * z_recip);
    if ( u  <= 0 || v  <= 0 || u+1 >= bwidth-1 || v+1 >= bheight-1)
    {
      *((uint32_t*)(rgba))= 0;
    }
    else
      break;
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;
  }
  }

  uint32_t *src00=data;
  uint32_t *src01=data;
  uint32_t *src10=data;
  uint32_t *src11=data;

  while (i < count)
  {
    float zr = (zi!=0)*(1.0f/zi) * 256;
    int du = (int)(xi * zr);
    int u = du >> 8;
    int dv = (int)(yi * zr);
    int v = dv >> 8;
    if (CTX_UNLIKELY(u < 0 || v < 0 || u+1 >= bwidth || v+1 >=bheight)) // default to next sample down and to right
    {
      int u1 = u + 1;
      int v1 = v + 1;

      _ctx_coords_restrict (extend, &u, &v, bwidth, bheight);
      _ctx_coords_restrict (extend, &u1, &v1, bwidth, bheight);

      src00 = data  + bwidth * v + u;
      src01 = data  + bwidth * v + u1;
      src10 = data  + bwidth * v1 + u;
      src11 = data  + bwidth * v1 + u1;
    }
    else 
    {
      src00 = data  + bwidth * v + u;
      src01 = src00 + 1;
      src10 = src00 + bwidth;
      src11 = src01 + bwidth;
    }
    ((uint32_t*)(&rgba[0]))[0] =
        ctx_RGBA8_associate_global_alpha_u32 (
            ctx_bi_RGBA8 (*src00,*src01,*src10,*src11, du,dv), global_alpha_u8);
    xi += xi_delta;
    yi += yi_delta;
    zi += zi_delta;
    rgba += 4;

    i++;
  }
}


static void
ctx_fragment_image_rgba8_RGBA8_bi (CtxRasterizer *rasterizer,
                                   float x, float y, float z,
                                   void *out, int icount, float dx, float dy, float dz)
{
  unsigned int count = icount;
  if (dy == 0.0f && dx > 0.0f && z==1.0f && dz==0.0f) 
  {
    ctx_fragment_image_rgba8_RGBA8_bi_scale (rasterizer, x, y, z, out, count, dx, dy, dz);
  }
  else if (z == 1.0f && dz == 0.0f)
    ctx_fragment_image_rgba8_RGBA8_bi_affine (rasterizer, x, y, z, out, count, dx, dy, dz);
  else
  {
    ctx_fragment_image_rgba8_RGBA8_bi_generic (rasterizer, x, y, z, out, count, dx, dy, dz);
  }
}
#endif

#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, float z,
                                         void *out, int count, float dx, float dy, float dz)
{
  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;

  if (!src)
          return;

  {
    int i = 0;

    float  u1 = x + dx * (count-1);
    float  v1 = y + dy * (count-1);
    uint32_t *edst = ((uint32_t*)out)+count - 1;
    for (; i < count; )
    {
      if (u1 <0 || v1 < 0 || u1 >= bwidth || v1 >= bheight)
      {
        *edst-- = 0;
        count --;
        u1 -= dx;
        v1 -= dy;
      }
      else break;
    }

    for (; i < count; i ++)
    {
      int u = (int)x;
      int v = (int)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;
    }

    // XXX this is incorrect- but fixes some bug!
    int ix = 65536;//x * 65536;
    int iy = (int)(y * 65536);

    int ideltax = (int)(dx * 65536);
    int ideltay = (int)(dy * 65536);

    if (ideltay == 0)
    {
      int u = ix >> 16;
      int v = iy >> 16;

      uint32_t y  = v * bwidth;
      uint32_t uv = (v / 2) * bwidth_div_2;

      if (v >= 0 && v < bheight)
      while (i < count)// && u >= 0 && u+1 < bwidth)
      {
        *((uint32_t*)(rgba))= ctx_yuv_to_rgba32 (src[y+u],
                        src[u_offset+uv+u/2], src[v_offset+uv+u/2]);
#if 0
#if CTX_DITHER
       ctx_dither_rgba_u8 (rgba, x, y, rasterizer->format->dither_red_blue,
                           rasterizer->format->dither_green);
#endif
#endif

        ix += ideltax;
        rgba += 4;
        u = ix >> 16;
        i++;
      }
    }
    else
    {
      int u = ix >> 16;
      int v = iy >> 16;

      while (i < count)// && 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],
                        src[u_offset+uv], src[v_offset+uv]);
#if 0
#if CTX_DITHER
       ctx_dither_rgba_u8 (rgba, x+i, y, rasterizer->format->dither_red_blue,
                           rasterizer->format->dither_green);
#endif
#endif

        ix += ideltax;
        iy += ideltay;
        rgba += 4;
        u = ix >> 16;
        v = iy >> 16;
        i++;
      }
    }

    for (; i < count; i++)
    {
      *((uint32_t*)(rgba))= 0;
      rgba += 4;
    }
  }

  if (rasterizer->state->gstate.global_alpha_u8 != 255)
    ctx_RGBA8_apply_global_alpha_and_associate (rasterizer, (uint8_t*)out, count);
}

#if CTX_FRAGMENT_SPECIALIZE

CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_box)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_bi)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest)

CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest_copy)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest_scale)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest_affine)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_nearest_generic)

CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_bi_scale)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_bi_affine)
CTX_DECLARE_SWAP_RED_GREEN_FRAGMENT(ctx_fragment_image_rgba8_RGBA8_bi_generic)

static inline void
ctx_fragment_image_rgba8_RGBA8 (CtxRasterizer *rasterizer,
                                float x, float y, float z,
                                void *out, int count, float dx, float dy, float dz)
{
  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, z, out, count, dx, dy, dz);
      else
        ctx_fragment_image_rgba8_RGBA8_box (rasterizer, x, y, z, out, count, dx, dy, dz);
    }
#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, z, out, count, dx, dy, dz);
      else
        ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, z, out, count, dx, dy, dz);
    }
#endif
    else
    {
      if (rasterizer->swap_red_green)
        ctx_fragment_image_rgba8_RGBA8_bi_swap_red_green (rasterizer, x, y, z, out, count, dx, dy, dz);
      else
        ctx_fragment_image_rgba8_RGBA8_bi (rasterizer, x, y, z, out, count, dx, dy, dz);
    }
  }
  else
  {
    if (rasterizer->swap_red_green)
      ctx_fragment_image_rgba8_RGBA8_nearest_swap_red_green (rasterizer, x, y, z, out, count, dx, dy, dz);
    else
      ctx_fragment_image_rgba8_RGBA8_nearest (rasterizer, x, y, z, out, count, dx, dy, dz);
  }
  //ctx_fragment_swap_red_green_u8 (out, count);
#if 0
#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
#endif
}
#endif

static void
ctx_fragment_image_gray1_RGBA8 (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer;
  for (int i = 0; i < count; i ++)
  {
  int u = (int)x;
  int v = (int)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, float z, void *out, int count, float dx, float dy, float dz)
{
  uint8_t *rgba = (uint8_t *) out;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
#if CTX_DITHER
  int scan = rasterizer->scanline / CTX_FULL_AA;
  int ox = (int)x;
#endif
  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*)&rasterizer->gradient_cache_u8[ctx_grad_index(rasterizer, 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, ox+i, scan, 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, float z, void *out, int count, float dx, float dy, float dz)
{
#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+i, 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;
  int scan = rasterizer->scanline / CTX_FULL_AA;
  int ox = (int)x;
#endif

  u0 *= linear_gradient_dx;
  v0 *= linear_gradient_dy;
  ud *= linear_gradient_dx;
  vd *= linear_gradient_dy;

#if CTX_GRADIENT_CACHE
  int vv = (int)(((u0 + v0) - linear_gradient_start) * (rasterizer->gradient_cache_elements-1) * 256);
  int ud_plus_vd = (int)((ud + vd) * (rasterizer->gradient_cache_elements-1) * 256);
#else
  float vv = ((u0 + v0) - linear_gradient_start);
  float ud_plus_vd = (ud + vd);
#endif

  for (int i = 0; i < count ; i++)
  {
#if CTX_GRADIENT_CACHE
  uint32_t*rgbap = ((uint32_t*)(&rasterizer->gradient_cache_u8[ctx_grad_index_i (rasterizer, 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, ox+i, scan, 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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, float z, void *out, int count, float dx, float dy, float dz)
{
  float *rgba = (float *) out;
  float  in[4];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  ctx_color_get_rgba (rasterizer->state, &g->color, in);
  for (int c = 0; c < 3; c++)
    in[c] *= in[3];
  while (count--)
  {
    for (int c = 0; c < 4; c++)
      rgba[c] = in[c];
    rgba += 4;
  }
}


static void ctx_fragment_image_RGBAF (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  float *outf = (float *) out;
  uint8_t rgba[4 * count];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  switch (buffer->format->bpp)
    {
#if CTX_FRAGMENT_SPECIALIZE
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
#endif
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);       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 inline int
ctx_matrix_no_perspective (CtxMatrix *matrix)
{
  if (fabsf(matrix->m[2][0]) >0.001f) return 0;
  if (fabsf(matrix->m[2][1]) >0.001f) return 0;
  if (fabsf(matrix->m[2][2] - 1.0f)>0.001f) return 0;
  return 1;
}

/* for multiples of 90 degree rotations, we return no rotation */
static inline int
ctx_matrix_no_skew_or_rotate (CtxMatrix *matrix)
{
  if (fabsf(matrix->m[0][1]) >0.001f) return 0;
  if (fabsf(matrix->m[1][0]) >0.001f) return 0;
  return ctx_matrix_no_perspective (matrix);
}


static CtxFragment ctx_rasterizer_get_fragment_RGBA8 (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  switch (gstate->source_fill.type)
    {
      case CTX_SOURCE_TEXTURE:
      {
        CtxBuffer *buffer = g->texture.buffer;
        if (buffer)
          buffer = buffer->color_managed?buffer->color_managed:buffer;
        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
#if CTX_FRAGMENT_SPECIALIZE
        switch (buffer->format->bpp)
          {
            case 1: return ctx_fragment_image_gray1_RGBA8;
#if 1
            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;
#endif
            case 32:
              {
                CtxMatrix *transform = &gstate->source_fill.transform;
                CtxExtend extend = rasterizer->state->gstate.extend;
                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 && extend == CTX_EXTEND_NONE)
                  {
                    if (rasterizer->swap_red_green)
                      return ctx_fragment_image_rgba8_RGBA8_nearest_copy_swap_red_green;
                    return ctx_fragment_image_rgba8_RGBA8_nearest_copy;
                  }
#endif
                  else
                  {
                    if (rasterizer->swap_red_green)
                    {
                      if (ctx_matrix_no_perspective (transform))
                      {
                        if (ctx_matrix_no_skew_or_rotate (transform))
                        {
                          if (ctx_fabsf (transform->m[0][0] - 1.0f) < 0.001f &&
                              ctx_fabsf (transform->m[1][1] - 1.0f) < 0.001f &&
                              ctx_fmod1f (transform->m[0][2]) < 0.001f &&
                              ctx_fmod1f (transform->m[1][2]) < 0.001f)
                          {
                            if (extend == CTX_EXTEND_NONE)
                              return ctx_fragment_image_rgba8_RGBA8_nearest_copy_swap_red_green;
                            else if (extend == CTX_EXTEND_REPEAT)
                              return ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat_swap_red_green;
                          }
                          return ctx_fragment_image_rgba8_RGBA8_bi_scale_swap_red_green;
                        }
                        return ctx_fragment_image_rgba8_RGBA8_bi_affine_swap_red_green;
                      }
                      return ctx_fragment_image_rgba8_RGBA8_bi_generic_swap_red_green;
                    }

                    if (ctx_matrix_no_perspective (transform))
                    {
                      if (ctx_matrix_no_skew_or_rotate (transform))
                      {
                        if (ctx_fabsf (transform->m[0][0] - 1.0f) < 0.001f &&
                            ctx_fabsf (transform->m[1][1] - 1.0f) < 0.001f &&
                            ctx_fmod1f (transform->m[0][2]) < 0.001f &&
                            ctx_fmod1f (transform->m[1][2]) < 0.001f)
                        {
                          if (extend == CTX_EXTEND_NONE)
                            return ctx_fragment_image_rgba8_RGBA8_nearest_copy;
                          else if (extend == CTX_EXTEND_REPEAT)
                            return ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat;
                        }
                        return ctx_fragment_image_rgba8_RGBA8_bi_scale;
                      }
                      return ctx_fragment_image_rgba8_RGBA8_bi_affine;
                    }
                    return ctx_fragment_image_rgba8_RGBA8_bi_generic;
                  }
                }
                else
                {
                  if (rasterizer->swap_red_green)
                  {
                    if (ctx_matrix_no_perspective (transform))
                    {
                      if (ctx_matrix_no_skew_or_rotate (transform))
                      {
                        if (ctx_fabsf (transform->m[0][0] - 1.0f) < 0.001f &&
                            ctx_fabsf (transform->m[1][1] - 1.0f) < 0.001f)
                        {
                           return ctx_fragment_image_rgba8_RGBA8_nearest_copy_swap_red_green;
                         if (extend == CTX_EXTEND_NONE)
                           return ctx_fragment_image_rgba8_RGBA8_nearest_copy_swap_red_green;
                         else if (extend == CTX_EXTEND_REPEAT)
                           return ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat_swap_red_green;
                        }
                        return ctx_fragment_image_rgba8_RGBA8_nearest_scale_swap_red_green;
                      }
                      return ctx_fragment_image_rgba8_RGBA8_nearest_affine_swap_red_green;
                    }
                    return ctx_fragment_image_rgba8_RGBA8_nearest_generic_swap_red_green;
                  }
                  if (ctx_matrix_no_perspective (transform))
                  {
                    if (ctx_matrix_no_skew_or_rotate (transform))
                    {
                      if (ctx_fabsf (transform->m[0][0] - 1.0f) < 0.001f &&
                          ctx_fabsf (transform->m[1][1] - 1.0f) < 0.001f)
                      {
                         if (extend == CTX_EXTEND_NONE)
                           return ctx_fragment_image_rgba8_RGBA8_nearest_copy;
                         else if (extend == CTX_EXTEND_REPEAT)
                           return ctx_fragment_image_rgba8_RGBA8_nearest_copy_repeat;
                      }
                      return ctx_fragment_image_rgba8_RGBA8_nearest_scale;
                    }
                    return ctx_fragment_image_rgba8_RGBA8_nearest_affine;
                  }
                  return ctx_fragment_image_rgba8_RGBA8_nearest_generic;
                }
              }
            default: return ctx_fragment_image_RGBA8;
          }
#else
          return ctx_fragment_image_RGBA8;
#endif
      }

      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 inline void
ctx_init_uv (CtxRasterizer *rasterizer,
             int x0,
             int y0,
             float *u0, float *v0, float *w0, float *ud, float *vd, float *wd)
             //float *u0, float *v0, float *w0, float *ud, float *vd, float *wd)
{
  CtxMatrix *transform = &rasterizer->state->gstate.source_fill.transform;
  *u0 = transform->m[0][0] * (x0 + 0.0f) +
        transform->m[0][1] * (y0 + 0.0f) +
        transform->m[0][2];
  *v0 = transform->m[1][0] * (x0 + 0.0f) +
        transform->m[1][1] * (y0 + 0.0f) +
        transform->m[1][2];
  *w0 = transform->m[2][0] * (x0 + 0.0f) +
        transform->m[2][1] * (y0 + 0.0f) +
        transform->m[2][2];
  *ud = transform->m[0][0];
  *vd = transform->m[1][0];
  *wd = transform->m[2][0];
}

static inline 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;
      float w0 = 1; float wd = 0;
      ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);
      while (count--)
      {
        uint8_t cov = *coverage;
        if (CTX_UNLIKELY(cov == 0))
        {
          u0+=ud;
          v0+=vd;
        }
        else
        {
          rasterizer->fragment (rasterizer, u0, v0, w0, src, 1, ud, vd, wd);
          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 inline 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--)
  {
    uint8_t cov = *coverage++;
    for (int c = 0; c < components; c++)
      dst[c] =  ((((tsrc[c] * cov)) + (dst[c] * (((((255+(tsrc[components-1] * cov))>>8))^255 ))))>>8);
    dst+=components;
  }
}

static inline 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)
{
  uint32_t *ttsrc = (uint32_t*)tsrc;
  uint32_t *ddst  = (uint32_t*)dst;
  while (count--)
  {
     uint32_t si_ga = ((*ttsrc) & 0xff00ff00) >> 8;
     uint32_t si_rb = (*ttsrc++) & 0x00ff00ff;
     uint32_t si_a  = si_ga >> 16;
     uint32_t racov = si_a^255;
     *(ddst) =
     (((si_rb*255+0xff00ff+(((*ddst)&0x00ff00ff)*racov))>>8)&0x00ff00ff)|
     ((si_ga*255+0xff00ff+((((*ddst)&0xff00ff00)>>8)*racov))&0xff00ff00);
     ddst++;
  }
}

static inline void
ctx_RGBA8_source_copy_normal_buf (CTX_COMPOSITE_ARGUMENTS, uint8_t *__restrict__ tsrc)
{
  uint32_t *ttsrc = (uint32_t*)tsrc;
  uint32_t *ddst  = (uint32_t*)dst;
  while (count--)
  {
    *ddst=ctx_lerp_RGBA8 (*ddst, *(ttsrc++), *(coverage++));
    ddst++;
  }
}

static inline void
ctx_RGBA8_source_over_normal_fragment (CTX_COMPOSITE_ARGUMENTS)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  float w0 = 1; float wd = 0;
  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);
  uint8_t _tsrc[4 * (count)];
  rasterizer->fragment (rasterizer, u0, v0, w0, &_tsrc[0], count, ud, vd, wd);
  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, int scanlines)
{
  CtxMatrix *transform = &rasterizer->state->gstate.source_fill.transform;
  int scan = rasterizer->scanline /CTX_FULL_AA;

  if (CTX_LIKELY(ctx_matrix_no_perspective (transform)))
  {
    float u0, v0, ud, vd, w0, wd;
    ctx_init_uv (rasterizer, x0, scan, &u0, &v0, &w0, &ud, &vd, &wd);
    for (int y = 0; y < scanlines; y++)
    {
      uint8_t _tsrc[4 * count];
      rasterizer->fragment (rasterizer, u0, v0, w0, &_tsrc[0], count, ud, vd, wd);
      ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
                          dst, src, x0, coverage, count, &_tsrc[0]);
      u0 -= vd;
      v0 += ud;
      dst += rasterizer->blit_stride;
    }
  }
  else
  {
    for (int y = 0; y < scanlines; y++)
    {
      uint8_t _tsrc[4 * count];
      float u0, v0, ud, vd, w0, wd;
      ctx_init_uv (rasterizer, x0, scan+y, &u0, &v0, &w0, &ud, &vd, &wd);
      rasterizer->fragment (rasterizer, u0, v0, w0, &_tsrc[0], count, ud, vd, wd);
      ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
                          dst, src, x0, coverage, count, &_tsrc[0]);
      dst += rasterizer->blit_stride;
    }
  }
}

static inline void
ctx_RGBA8_source_copy_normal_fragment (CTX_COMPOSITE_ARGUMENTS)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  float w0 = 1; float wd = 0;
  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);
  uint8_t _tsrc[4 * (count)];
  rasterizer->fragment (rasterizer, u0, v0, w0, &_tsrc[0], count, ud, vd, wd);
  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 inline 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 = (int)(ctx_sqrtf(b[c]/255.0f) * 255.4f);
    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 (int)(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 (int)(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];
    memcpy (src, rasterizer->color, 4);
    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;
    float w0 = 1; float wd = 0;
    src = &tsrc[0];

    ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);
    fragment (rasterizer, u0, v0, w0, src, count, ud, vd, wd);
    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)


#if CTX_INLINED_NORMAL_RGBA8

ctx_u8_porter_duff(RGBA8, 4,color,   rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_GRADIENTS
ctx_u8_porter_duff(RGBA8, 4,linear_gradient, ctx_fragment_linear_gradient_RGBA8, rasterizer->state->gstate.blend_mode)
ctx_u8_porter_duff(RGBA8, 4,radial_gradient, ctx_fragment_radial_gradient_RGBA8, rasterizer->state->gstate.blend_mode)
#endif
ctx_u8_porter_duff(RGBA8, 4,image,           ctx_fragment_image_RGBA8,           rasterizer->state->gstate.blend_mode)
#endif


static inline void
ctx_RGBA8_nop (CTX_COMPOSITE_ARGUMENTS)
{
}


static inline void
ctx_setup_native_color (CtxRasterizer *rasterizer)
{
  if (rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR)
  {
    rasterizer->format->from_comp (rasterizer, 0,
      &rasterizer->color[0],
      &rasterizer->color_native,
      1);
  }
}

static void
ctx_setup_apply_coverage (CtxRasterizer *rasterizer)
{
  rasterizer->apply_coverage = rasterizer->format->apply_coverage ?
                               rasterizer->format->apply_coverage :
                               rasterizer->comp_op;
}

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, 1,rasterizer->color, 1, 0,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 CTX_INLINED_NORMAL_RGBA8
  if (gstate->compositing_mode == CTX_COMPOSITE_CLEAR)
    rasterizer->comp_op = ctx_RGBA8_clear_normal;
  else
    switch (gstate->blend_mode)
    {
      case CTX_BLEND_NORMAL:
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_RGBA8_copy_normal;
          if (gstate->source_fill.type == CTX_SOURCE_COLOR)
            rasterizer->comp = CTX_COV_PATH_RGBA8_COPY;

        }
        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_RGBA8_source_over_normal_color;
              if ( ((float*)rasterizer->color)[3] >= 0.999f)
                rasterizer->comp = CTX_COV_PATH_RGBA8_COPY;
            }
            else
            {
              rasterizer->comp_op = ctx_RGBAF_porter_duff_color_normal;
            }
            break;
#if CTX_GRADIENTS
          case CTX_SOURCE_LINEAR_GRADIENT:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_linear_gradient_normal;
            break;
          case CTX_SOURCE_RADIAL_GRADIENT:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_radial_gradient_normal;
            break;
#endif
          case CTX_SOURCE_TEXTURE:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_image_normal;
            break;
          default:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_generic_normal;
            break;
        }
        break;
      default:
        switch (gstate->source_fill.type)
        {
          case CTX_SOURCE_COLOR:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_color;
            break;
#if CTX_GRADIENTS
          case CTX_SOURCE_LINEAR_GRADIENT:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_linear_gradient;
            break;
          case CTX_SOURCE_RADIAL_GRADIENT:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_radial_gradient;
            break;
#endif
          case CTX_SOURCE_TEXTURE:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_image;
            break;
          default:
            rasterizer->comp_op = ctx_RGBA8_porter_duff_generic;
            break;
        }
        break;
    }

#else

  if (gstate->source_fill.type == CTX_SOURCE_COLOR)
    {

      if (blend_mode == CTX_BLEND_NORMAL)
      {
        if(compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp_op = ctx_RGBA8_source_copy_normal_color;
          rasterizer->comp = CTX_COV_PATH_RGBA8_COPY;
        }
        else if (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_RGBA8_COPY;
          }
          else
          {
            rasterizer->comp_op = ctx_RGBA8_source_over_normal_color;
            rasterizer->comp = CTX_COV_PATH_RGBA8_OVER;
          }
        }
      }
      else if (compositing_mode == CTX_COMPOSITE_CLEAR)
      {
        rasterizer->comp_op = ctx_RGBA8_clear_normal;
      }
  }
  else if (blend_mode == CTX_BLEND_NORMAL)
  {
    if(compositing_mode == CTX_COMPOSITE_SOURCE_OVER)
    {
       rasterizer->comp_op = ctx_RGBA8_source_over_normal_fragment;
       rasterizer->comp = CTX_COV_PATH_RGBA8_OVER_FRAGMENT;
    }
    else if (compositing_mode == CTX_COMPOSITE_COPY)
    {
       rasterizer->comp_op = ctx_RGBA8_source_copy_normal_fragment;
       rasterizer->comp = CTX_COV_PATH_RGBA8_COPY_FRAGMENT;
    }
  }
#endif
  ctx_setup_apply_coverage (rasterizer);
}


static inline void
ctx_setup_RGB (CtxRasterizer *rasterizer)
{
  ctx_setup_RGBA8 (rasterizer);
  ctx_setup_native_color (rasterizer);

  rasterizer->comp = CTX_COV_PATH_FALLBACK;
}


#if CTX_ENABLE_CBRLE
static void
ctx_setup_CBRLE (CtxRasterizer *rasterizer)
{
  ctx_setup_RGBA8 (rasterizer);
  //ctx_setup_native_color (rasterizer);

#if 0
  if (rasterizer->comp == CTX_COV_PATH_RGBA8_COPY)
    rasterizer->comp = CTX_COV_PATH_CBRLE_COPY;
  else
#endif
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

#if CTX_ENABLE_RGB332
static void
ctx_setup_RGB332 (CtxRasterizer *rasterizer)
{
  ctx_setup_RGBA8 (rasterizer);
  ctx_setup_native_color (rasterizer);

  if (rasterizer->comp == CTX_COV_PATH_RGBA8_COPY)
    rasterizer->comp = CTX_COV_PATH_RGB332_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

#if CTX_ENABLE_RGB565
static void
ctx_setup_RGB565 (CtxRasterizer *rasterizer)
{
  ctx_setup_RGBA8 (rasterizer);
  ctx_setup_native_color (rasterizer);

  if (rasterizer->comp == CTX_COV_PATH_RGBA8_COPY)
    rasterizer->comp = CTX_COV_PATH_RGB565_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

#if CTX_ENABLE_RGB8
static void
ctx_setup_RGB8 (CtxRasterizer *rasterizer)
{
  ctx_setup_RGBA8 (rasterizer);
  ctx_setup_native_color (rasterizer);

  if (rasterizer->comp == CTX_COV_PATH_RGBA8_COPY)
    rasterizer->comp = CTX_COV_PATH_RGB8_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

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 inline 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;
  float w0 = 1; float wd = 0;

  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);

  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.0f-covf) + srcf[c]*covf;
    dstf += components;
    coverage ++;
  }
}

static inline 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.0f - ctx_u8_to_float (cov);
      for (int c = 0; c < components; c++)
        { dstf[c] = (dstf[c] * ralpha); }
    }
    coverage ++;
#endif
    dstf += components;
  }
}


static inline 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; c++)
      dstf[c] = srcf[c]*fcov + dstf[c] * ralpha;
    coverage ++;
    dstf+= components;
  }
}

static inline 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; 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 inline 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;
  
  if (rasterizer->state->gstate.source_fill.type == CTX_SOURCE_COLOR)
  {
    float tsrc[components];

    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      
        ))))
      {
        coverage ++;
        dstf+=components;
        continue;
      }
#endif
      memcpy (tsrc, rasterizer->color, sizeof(tsrc));

      if (blend != CTX_BLEND_NORMAL)
        ctx_float_blend (components, blend, dstf, tsrc, tsrc);
      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;
    }
  }
  else
  {
    float tsrc[components];
    float u0 = 0; float v0 = 0;
    float ud = 0; float vd = 0;
    float w0 = 1; float wd = 0;
    for (int c = 0; c < components; c++) tsrc[c] = 0.0f;
    ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA, &u0, &v0, &w0, &ud, &vd, &wd);

    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, w0, tsrc, 1, ud, vd, wd);
      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,1, rasterizer->color, 1, 0,0,0);
      if (gstate->global_alpha_u8 != 255)
        for (int c = 0; c < components; c ++)
          ((float*)rasterizer->color)[c] *= gstate->global_alpha_f;

      if (rasterizer->format->from_comp)
        rasterizer->format->from_comp (rasterizer, 0,
          &rasterizer->color[0],
          &rasterizer->color_native,
          1);
    }
  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;
          if (gstate->source_fill.type == CTX_SOURCE_COLOR)
            rasterizer->comp = CTX_COV_PATH_RGBAF_COPY;

        }
        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;
              if ( ((float*)rasterizer->color)[3] >= 0.999f)
                rasterizer->comp = CTX_COV_PATH_RGBAF_COPY;
            }
            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;
            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
  ctx_setup_apply_coverage (rasterizer);
}

#endif
#if CTX_ENABLE_GRAYAF

#if CTX_GRADIENTS
static void
ctx_fragment_linear_gradient_GRAYAF (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  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.0f, 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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, float z, void *out, int count, float dx, float dy, float dz)
{
  uint8_t rgba[4*count];
  float rgbaf[4*count];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  switch (buffer->format->bpp)
    {
#if CTX_FRAGMENT_SPECIALIZE
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
#endif
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);       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;
      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;

      if (rasterizer->format->from_comp)
        rasterizer->format->from_comp (rasterizer, 0,
          &rasterizer->color[0],
          &rasterizer->color_native,
          1);
    }
  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;
            }
            else
            {
              rasterizer->comp_op = ctx_GRAYAF_porter_duff_color_normal;
            }
            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;
            break;
          default:
            rasterizer->comp_op = ctx_GRAYAF_porter_duff_generic;
            break;
        }
        break;
    }
#endif
  ctx_setup_apply_coverage (rasterizer);
}

#endif
#if CTX_ENABLE_GRAYF

static void
ctx_composite_GRAYF (CTX_COMPOSITE_ARGUMENTS)
{
  float *dstf = (float*)dst;

  float temp[count*2];
  for (unsigned 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 (unsigned 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
static inline void
ctx_composite_direct (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
  //
  //
  rasterizer->comp_op (rasterizer, dst, rasterizer->color, x0, coverage, count);
}

#if CTX_ENABLE_CMYKAF

static void
ctx_fragment_other_CMYKAF (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  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, z, rgba, count, dx, dy, dz);
        break;
      case CTX_SOURCE_COLOR:
        ctx_fragment_color_RGBAF (rasterizer, x, y, z, rgba, count, dx, dy, dz);
        break;
#if CTX_GRADIENTS
      case CTX_SOURCE_LINEAR_GRADIENT:
        ctx_fragment_linear_gradient_RGBAF (rasterizer, x, y, z, rgba, count, dx, dy, dz);
        break;
      case CTX_SOURCE_RADIAL_GRADIENT:
        ctx_fragment_radial_gradient_RGBAF (rasterizer, x, y, z, rgba, count, dx, dy, dz);
        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, float z, void *out, int count, float dx, float dy, float dz)
{
  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;
      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;

      if (rasterizer->format->from_comp)
        rasterizer->format->from_comp (rasterizer, 0,
          &rasterizer->color[0],
          &rasterizer->color_native,
          1);
    }
  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;
  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;
              else if (((float*)rasterizer->color)[components-1] == 1.0f)
              {
                rasterizer->comp_op = ctx_CMYKAF_source_copy_normal_color;
                rasterizer->comp = CTX_COV_PATH_CMYKAF_COPY;
              }
              else
                rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal;
            }
            else
            {
              rasterizer->comp_op = ctx_CMYKAF_porter_duff_color_normal;
            }
            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;
            break;
          default:
            rasterizer->comp_op = ctx_CMYKAF_porter_duff_generic;
            break;
        }
        break;
    }
#else

    if (gstate->blend_mode == CTX_BLEND_NORMAL &&
        gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp = CTX_COV_PATH_CMYKAF_COPY;
        }
        else if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER &&
                 rasterizer->color[components-1] == 255)
        {
          rasterizer->comp = CTX_COV_PATH_CMYKAF_COPY;
        }
    }
#endif
  ctx_setup_apply_coverage (rasterizer);
}

static void
ctx_setup_CMYKA8 (CtxRasterizer *rasterizer)
{
  ctx_setup_CMYKAF (rasterizer);

  if (rasterizer->comp == CTX_COV_PATH_CMYKAF_COPY)
    rasterizer->comp = CTX_COV_PATH_CMYKA8_COPY;
}

static void
ctx_setup_CMYK8 (CtxRasterizer *rasterizer)
{
  ctx_setup_CMYKAF (rasterizer);
  if (rasterizer->comp == CTX_COV_PATH_CMYKAF_COPY)
    rasterizer->comp = CTX_COV_PATH_CMYK8_COPY;
}

#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.0f - c;
      m = 1.0f - m;
      y = 1.0f - y;
      k = 1.0f - 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 *graya, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  while (count--)
    {
      int bitno = x&7;
      if (bitno == 0 && count >= 7)
      {
        if (*pixel == 0)
        {
          for (int i = 0; i < 8; i++)
          {
            *graya++ = 0; *graya++ = 255;
          }
          x+=8; count-=7; pixel++;
          continue;
        }
        else if (*pixel == 0xff)
        {
          for (int i = 0; i < 8 * 2; i++)
          {
            *graya++ = 255;
          }
          x+=8; count-=7; pixel++;
          continue;
        }
      }
      *graya++ = 255 * ((*pixel) & (1<<bitno));
      *graya++ = 255;
      pixel+= (bitno ==7);
      x++;
    }
}

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;
  while (count--)
    {
      int gray = rgba[0];
      int bitno = x&7;
      if (gray >= 128)
        *pixel |= (1<<bitno);
      else
        *pixel &= (~ (1<<bitno));
      pixel+= (bitno==7);
      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;
  uint32_t *dst = (uint32_t*)rgba;
  while (count--)
    {
      int bitno = x&7;

      if ((bitno) == 0 && count >=7)
      {
        /* special case some bit patterns when decoding */
        if (*pixel == 0)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          x+=8; count-=7; pixel++;
          continue;
        }
        else if (*pixel == 0xff)
        {
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=8; count-=7; pixel++;
          continue;
        }
        else if (*pixel == 0x0f)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=8; count-=7; pixel++;
          continue;
        }
        else if (*pixel == 0xfc)
        {
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          x+=8; count-=7; pixel++;
          continue;
        }
        else if (*pixel == 0x3f)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=8; count-=7; pixel++;
          continue;
        }
      }
      *dst++=0xff000000 + 0x00ffffff * ((*pixel & (1<< bitno ) )!=0);
      pixel += (bitno ==7);
      x++;
    }
}

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;
  while (count--)
    {
      int gray = ctx_u8_color_rgb_to_gray (rasterizer->state, rgba);
      int bitno = x&7;
      //gray += ctx_dither_mask_a (x, rasterizer->scanline/aa, 0, 127);
      if (gray >= 128)
        *pixel |= (1<< bitno);
      else
        *pixel &= (~ (1<< bitno));
      pixel+= (bitno ==7);
      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--)
    {
      uint8_t val = (((*pixel) >> ( (x&3) <<1)) & 3) * 85;
      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 = ctx_sadd8 (val, 40) >> 6;
      *pixel = (*pixel & (~ (3 << ( (x&3) <<1) ) ))
                      | ( (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;
  uint32_t *dst = (uint32_t*)rgba;
  while (count--)
    {
      int bitno = x & 3;
      if ((bitno) == 0 && count >=3)
      {
        /* special case some bit patterns when decoding */
        if (*pixel == 0)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0xff)
        {
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0x55)
        {
          *dst++ = 0xff555555;
          *dst++ = 0xff555555;
          *dst++ = 0xff555555;
          *dst++ = 0xff555555;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0xaa)
        {
          *dst++ = 0xffaaaaaa;
          *dst++ = 0xffaaaaaa;
          *dst++ = 0xffaaaaaa;
          *dst++ = 0xffaaaaaa;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0x0f)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xff000000;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0xfc)
        {
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xff000000;
          x+=4; count-=3; pixel++;
          continue;
        }
        else if (*pixel == 0x3f)
        {
          *dst++ = 0xff000000;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          *dst++ = 0xffffffff;
          x+=4; count-=3; pixel++;
          continue;
        }
      }
      {
        uint8_t val = (((*pixel) >> ( (bitno) <<1)) & 3) * 85;
        *dst = val + val * 256u + val * 256u * 256u + 255u * 256u * 256u * 256u;
        if (bitno==3)
          { pixel+=1; }
        x++;
        dst++;
      }
    }
}

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) ) ))
                      | ( (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, float z, void *out, int count, float dx, float dy, float dz)
{
  CtxSource *g = &rasterizer->state->gstate.source_fill;
        uint8_t *dst = (uint8_t*)out;
#if CTX_DITHER
  int scan = rasterizer->scanline / CTX_FULL_AA;
  int ox = (int)x;
#endif
  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.0f, rgba);
    ctx_rgba_to_graya_u8 (rasterizer->state, rgba, dst);
   
  }

#if CTX_DITHER
  ctx_dither_graya_u8 ((uint8_t*)dst, ox + i, scan, rasterizer->format->dither_red_blue,
                      rasterizer->format->dither_green);
#endif
  dst += 2;
  x += dx;
  y += dy;
  }
}

static void
ctx_fragment_radial_gradient_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  uint8_t *dst = (uint8_t*)out;
#if CTX_DITHER
  int scan = rasterizer->scanline / CTX_FULL_AA;
  int ox = (int)x;
#endif

  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, ox+i, scan, 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, float z, void *out, int count, float dx, float dy, float dz)
{
  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, (uint8_t*)&pix);
  for (int i = 0; i <count; i++)
  {
    dst[i]=pix;
  }
}

static void ctx_fragment_image_GRAYA8 (CtxRasterizer *rasterizer, float x, float y, float z, void *out, int count, float dx, float dy, float dz)
{
  uint8_t rgba[4*count];
  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  switch (buffer->format->bpp)
    {
#if CTX_FRAGMENT_SPECIALIZE
      case 1:  ctx_fragment_image_gray1_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
      case 24: ctx_fragment_image_rgb8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);  break;
      case 32: ctx_fragment_image_rgba8_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz); break;
#endif
      default: ctx_fragment_image_RGBA8 (rasterizer, x, y, z, rgba, count, dx, dy, dz);       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,generic, rasterizer->fragment, rasterizer->state->gstate.blend_mode)

#if CTX_INLINED_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)
{
#if 1
  ctx_u8_source_over_normal_color (2, rasterizer, dst, rasterizer->color, x0, coverage, count);
#else
  uint8_t tsrc[5];
  *((uint32_t*)tsrc) = *((uint32_t*)src);

  while (count--)
  {
    uint32_t cov = *coverage++;
    uint32_t common =(((((255+(tsrc[1] * cov))>>8))^255 ));
    dst[0] =  ((((tsrc[0] * cov)) + (dst[0] * common ))>>8);
    dst[1] =  ((((tsrc[1] * cov)) + (dst[1] * common ))>>8);
    dst+=2;
  }
#endif
}

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, 1,rasterizer->color, 1, 0,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 (rasterizer->format->from_comp)
        rasterizer->format->from_comp (rasterizer, 0,
          &rasterizer->color[0],
          &rasterizer->color_native,
          1);
    }

#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;
          rasterizer->comp = CTX_COV_PATH_GRAYA8_COPY;
        }
        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;
                rasterizer->comp = CTX_COV_PATH_GRAYA8_COPY;
              }
              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;
    }
#else
    if (gstate->blend_mode == CTX_BLEND_NORMAL &&
        gstate->source_fill.type == CTX_SOURCE_COLOR)
    {
        if (gstate->compositing_mode == CTX_COMPOSITE_COPY)
        {
          rasterizer->comp = CTX_COV_PATH_GRAYA8_COPY;
        }
        else if (gstate->compositing_mode == CTX_COMPOSITE_SOURCE_OVER &&
                 rasterizer->color[components-1] == 255)
        {
          rasterizer->comp = CTX_COV_PATH_GRAYA8_COPY;
        }
    }
#endif
  ctx_setup_apply_coverage (rasterizer);
}

#if CTX_ENABLE_GRAY4
static void
ctx_setup_GRAY4 (CtxRasterizer *rasterizer)
{
  ctx_setup_GRAYA8 (rasterizer);
  if (rasterizer->comp == CTX_COV_PATH_GRAYA8_COPY)
    rasterizer->comp = CTX_COV_PATH_GRAY4_COPY;
  else
  rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

#if CTX_ENABLE_GRAY2
static void
ctx_setup_GRAY2 (CtxRasterizer *rasterizer)
{
  ctx_setup_GRAYA8 (rasterizer);
  if (rasterizer->comp == CTX_COV_PATH_GRAYA8_COPY)
    rasterizer->comp = CTX_COV_PATH_GRAY2_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

#if CTX_ENABLE_GRAY1
static void
ctx_setup_GRAY1 (CtxRasterizer *rasterizer)
{
  ctx_setup_GRAYA8 (rasterizer);
  if (rasterizer->comp == CTX_COV_PATH_GRAYA8_COPY)
    rasterizer->comp = CTX_COV_PATH_GRAY1_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}
#endif

static void
ctx_setup_GRAY8 (CtxRasterizer *rasterizer)
{
  ctx_setup_GRAYA8 (rasterizer);
  if (rasterizer->comp == CTX_COV_PATH_GRAYA8_COPY)
    rasterizer->comp = CTX_COV_PATH_GRAY8_COPY;
  else
    rasterizer->comp = CTX_COV_PATH_FALLBACK;
}

#endif

#endif

inline static void
ctx_332_unpack (uint8_t pixel,
                uint8_t *red,
                uint8_t *green,
                uint8_t *blue)
{
  *green = (((pixel >> 2) & 7)*255)/7;
  *red   = (((pixel >> 5) & 7)*255)/7;
  *blue  = ((((pixel & 3) << 1) | ((pixel >> 2) & 1))*255)/7;
}

static inline uint8_t
ctx_332_pack (uint8_t red,
              uint8_t green,
              uint8_t blue)
{
  return ((ctx_sadd8(red,15) >> 5) << 5)
        |((ctx_sadd8(green,15) >> 5) << 2)
        |(ctx_sadd8(blue,15) >> 6);
}
#if CTX_ENABLE_RGB332

static inline uint8_t
ctx_888_to_332 (uint32_t in)
{
  uint8_t *rgb=(uint8_t*)(&in);
  return ctx_332_pack (rgb[0],rgb[1],rgb[2]);
}

static inline uint32_t
ctx_332_to_888 (uint8_t in)
{
  uint32_t ret = 0;
  uint8_t *rgba=(uint8_t*)&ret;
  ctx_332_unpack (in,
                  &rgba[0],
                  &rgba[1],
                  &rgba[2]);
  rgba[3] = 255;
  return ret;
}

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;
    }
}

static void
ctx_composite_RGB332 (CTX_COMPOSITE_ARGUMENTS)
{
#if 1
  if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color))
  {
    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    = ctx_332_to_888 (*((uint8_t*)dst));
      uint32_t di_ga = ((di & 0xff00ff00) >> 8);
      uint32_t di_rb = (di & 0x00ff00ff);
      *((uint8_t*)(dst)) =
      ctx_888_to_332((((si_rb * cov + 0xff00ff + di_rb * rcov) & 0xff00ff00) >> 8)  |
       ((si_ga * cov + 0xff00ff + di_ga * rcov) & 0xff00ff00));
       dst+=1;
    }
    return;
  }
#endif
  uint8_t pixels[count * 4];
  ctx_RGB332_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_RGBA8_to_RGB332 (rasterizer, x0, &pixels[0], dst, count);
}

#endif
static inline uint16_t
ctx_565_pack (uint8_t  red,
              uint8_t  green,
              uint8_t  blue,
              const int      byteswap)
{
#if 0
  // is this extra precision warranted?
  // for 332 it gives more pure white..
  // it might be the case also for generic 565
  red = ctx_sadd8 (red, 4);
  green = ctx_sadd8 (green, 3);
  blue = ctx_sadd8 (blue, 4);
#endif

  uint32_t c = (red >> 3) << 11;
  c |= (green >> 2) << 5;
  c |= blue >> 3;
  if (byteswap)
    { return (c>>8) | (c<<8); } /* swap bytes */
  return c;
}

 // ARGB - 
#if CTX_ENABLE_CBRLE


static inline void
ctx_RGBA8_to_CBRLE (CtxRasterizer *rasterizer, int u, const uint8_t *rgba, void *buf, int count)
{
  uint8_t *pixel = (uint8_t *) buf;
  int width = rasterizer->blit_width;
  int format_bits = rasterizer->format->bpp;
  pixel-= (u *format_bits)/8;
  int size = (width * format_bits)/8;

  ctx_CBRLE_compress (&rgba[-u*4], pixel, width, size, u, count, CBRLE_MODE_SET, NULL, 1);
}

static inline void
ctx_CBRLE_to_RGBA8 (CtxRasterizer *rasterizer, int u, const void *buf, uint8_t *rgba, int count)
{
  const uint8_t *pixel = (uint8_t *) buf;
  int width = rasterizer->blit_width;
  uint8_t temp[width * 4];
  int format_bits = rasterizer->format->bpp;
  pixel-= (u *format_bits)/8;
  int size = (width * format_bits)/8;

  _ctx_CBRLE_decompress (pixel, &temp[0], width, size, u, ctx_mini(count, width-u));//count);

  uint32_t *src = (uint32_t*)&temp[4*u];
  while (count--)
  {
      ((uint32_t*)rgba)[0] = *src++;
      rgba += 4;
  }
}


static void
ctx_composite_CBRLE (CTX_COMPOSITE_ARGUMENTS)
{
  uint8_t *pixel = (uint8_t*)dst;
  int format_bits = rasterizer->format->bpp;
  pixel-= (x0 *format_bits)/8;

#if 0
  if (pixel[0] || pixel[1])
  {

  if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color))
  {
    int width = rasterizer->blit_width;
    int size = (width * format_bits)/8;
    ctx_CBRLE_compress (rasterizer->color, pixel, width, size, x0, count,
                         CBRLE_MODE_OVER, coverage, 1);
    return;
  }
  else if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_copy_normal_color) &&
           getenv("CTX_CBRLE_COPY"))
  {
    int width = rasterizer->blit_width;
    int size = (width * format_bits)/8;
    ctx_CBRLE_compress (rasterizer->color, pixel, width, size, x0, count, 
                         CBRLE_MODE_COPY, coverage, 1);
    return;
  }
  }

#endif

  uint8_t pixels[count * 4];
  ctx_CBRLE_to_RGBA8 (rasterizer, x0, dst, &pixels[0], count);
  rasterizer->comp_op (rasterizer, &pixels[0], rasterizer->color, x0, coverage, count);
  ctx_RGBA8_to_CBRLE (rasterizer, x0, &pixels[0], dst, count);
}

#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; }
  uint8_t b  =  (byteswapped & 31) <<3;
  uint8_t g  = ( (byteswapped>>5) & 63) <<2;
  uint8_t r  = ( (byteswapped>>11) & 31) <<3;

#if 0
  *blue  = (b > 248) * 255 + (b <= 248) * b;
  *green = (g > 248) * 255 + (g <= 248) * g;
  *red   = (r > 248) * 255 + (r <= 248) * r;
#else
  *blue = b;
  *green = g;
  *red = r;
#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; }
  uint32_t b   = (byteswapped & 31) <<3;
  uint32_t g = ( (byteswapped>>5) & 63) <<2;
  uint32_t r   = ( (byteswapped>>11) & 31) <<3;
#if 0
  b = (b > 248) * 255 + (b <= 248) * b;
  g = (g > 248) * 255 + (g <= 248) * g;
  r = (r > 248) * 255 + (r <= 248) * r;
#endif

  return r +  (g << 8) + (b << 16) + (0xff << 24);
}


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);
  //rgba[3]=255;
  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--)
    {
      // XXX : checking the raw value for alpha before unpack will be faster
      ((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 1
  if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color))
  {
    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    = ctx_565_to_888 (*((uint16_t*)dst), 0);
        uint32_t di_ga = ((di & 0xff00ff00) >> 8);
        uint32_t di_rb = (di & 0x00ff00ff);
        *((uint16_t*)(dst)) =
        ctx_888_to_565((((si_rb * cov + 0xff00ff + di_rb * rcov) & 0xff00ff00) >> 8)  |
         ((si_ga * cov + 0xff00ff + di_ga * rcov) & 0xff00ff00), 0);
         dst+=2;
    }
    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)
{
#if 1
  if (CTX_LIKELY(rasterizer->comp_op == ctx_RGBA8_source_over_normal_color))
  {
    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    = ctx_565_to_888 (*((uint16_t*)dst), 1);
      uint32_t di_ga = ((di & 0xff00ff00) >> 8);
      uint32_t di_rb = (di & 0x00ff00ff);
      *((uint16_t*)(dst)) =
      ctx_888_to_565((((si_rb * cov + 0xff00ff + di_rb * rcov) & 0xff00ff00) >> 8)  |
       ((si_ga * cov + 0xff00ff + di_ga * rcov) & 0xff00ff00), 1);
       dst+=2;
    }
    return;
  }
#endif

  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 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;
}

static inline void ctx_span_set_colorb  (uint32_t *dst_pix, uint32_t val, int count)
{
  while(count--)
    *dst_pix++=val;
}

static inline void ctx_span_set_colorbu (uint32_t *dst_pix, uint32_t val, unsigned int count)
{
  while(count--)
    *dst_pix++=val;
}

static inline void ctx_span_set_color_x4 (uint32_t *dst_pix, uint32_t *val, int count)
{
  if (count>0)
  while(count--)
  {
    *dst_pix++=val[0];
    *dst_pix++=val[1];
    *dst_pix++=val[2];
    *dst_pix++=val[3];
  }
}

#if CTX_FAST_FILL_RECT

static inline void ctx_RGBA8_image_rgba8_RGBA8_bi_scaled_fill_rect (CtxRasterizer *rasterizer, int x0, int y0, int x1, int y1, int copy)
{
  CtxExtend extend = rasterizer->state->gstate.extend;
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  float w0 = 1; float wd = 0;
  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA,&u0, &v0, &w0, &ud, &vd, &wd);
  u0-=0.5f;
  v0-=0.5f;

  uint8_t *dst = ( (uint8_t *) rasterizer->buf);
  int blit_stride = rasterizer->blit_stride;
  dst += (y0 - rasterizer->blit_y) * blit_stride;
  dst += (x0) * rasterizer->format->bpp/8;

  unsigned int width = x1-x0+1;
  unsigned int height = y1-y0+1;

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;

  int bwidth = buffer->width;
  int bheight = buffer->height;
  uint8_t tsrc[copy?1:width*4]; /* unused when not copy */

  //uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint32_t *data = ((uint32_t*)buffer->data);
  uint32_t rb_row[2][width*2];
  //uint32_t ga_row[2][width];

  int32_t row_u = (int)(u0 * 65536);
  int32_t row_v = (int)(v0 * 65536);
  int   ui_delta = (int)(ud * 65536);
  int   vi_delta = (int)(vd * 65536);

  int iter = 0;

  int loaded_v = -1;
  int top      = iter % 2;


  { // preload previous row for first row
    int32_t ui  = row_u;
    int32_t vi  = row_v;
    unsigned int xa=0;
      for (unsigned int x = 0; x < width; x++, xa+=2)
      {
        int u = ui >> 16;
        int v = vi >> 16;
        int u1 = u + 1;
        uint32_t  blank = 0;
        uint32_t *src0 = &blank;
        uint32_t *src1 = src0;
    
        if (CTX_LIKELY(_ctx_coords_restrict (extend, &u, &v, bwidth, bheight)))
        {
          src0 = data + u + bwidth * (v);
        }
        if (CTX_LIKELY(_ctx_coords_restrict (extend, &u1, &v, bwidth, bheight)))
        {
          src1 = data + u1 + bwidth * (v);
        }
    
        ctx_lerp_RGBA8_split (*src0, *src1, ui>>8, &rb_row[!top][xa],
                                                   &rb_row[!top][xa+1]);
        ui += ui_delta;
        vi += vi_delta;
      }
    }

  { // scale/translate only
    for (unsigned int y = 0; y < height; y++)
    {
       int top     = iter % 2;
       int32_t ui = row_u;
       int32_t vi = row_v;
       int v =  (vi >> 16) + 1;
       uint8_t dv = ((row_v)>> 8);
  
       if (v != loaded_v)
       {
         loaded_v = v;
         unsigned int xa=0;

           for (unsigned int x = 0; x < width; x++, xa+=2)
           {
             int u = ui >> 16;
             int u1 = u+1;
             uint32_t  blank = 0;
             uint32_t *src0 = &blank;
             uint32_t *src1 = src0;


        if (CTX_LIKELY(_ctx_coords_restrict (extend, &u, &v, bwidth, bheight)))
        {
          src0 = data + u + bwidth * (v);
        }
        if (CTX_LIKELY(_ctx_coords_restrict (extend, &u1, &v, bwidth, bheight)))
        {
          src1 = data + u1 + bwidth * (v);
        }

             ctx_lerp_RGBA8_split (*src0, *src1, ui>>8, &rb_row[top][xa], &rb_row[top][xa+1]);
             ui += ui_delta;
           }
         iter++;
         top    = iter % 2;
       }
       
       {
         uint32_t*dst_i = copy?(uint32_t*)dst:(uint32_t*)tsrc;
         int ntop = !top;
         for (unsigned int xa = 0; xa < width * 2; xa+=2)
         {
            *dst_i ++ =
            ctx_lerp_RGBA8_merge (rb_row[top][xa], rb_row[top][xa+1], 
                                  rb_row[ntop][xa], rb_row[ntop][xa+1],
                                  dv);
         }
         if (!copy)
         ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
            dst, NULL, x0, NULL, width, &tsrc[0]);
       }
       row_u -= vi_delta;
       row_v += ui_delta;
       dst += blit_stride;
    }
  }
}

static inline void ctx_RGBA8_image_rgba8_RGBA8_bi_affine_fill_rect (CtxRasterizer *rasterizer, int x0, int y0, int x1, int y1, int copy)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  float w0 = 1; float wd = 0;
  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA,&u0, &v0, &w0, &ud, &vd, &wd);
  u0-=0.5f;
  v0-=0.5f;

  uint8_t *dst = ( (uint8_t *) rasterizer->buf);
  int blit_stride = rasterizer->blit_stride;
  dst += (y0 - rasterizer->blit_y) * blit_stride;
  dst += (x0) * rasterizer->format->bpp/8;

  unsigned int width = x1-x0+1;
  unsigned int height = y1-y0+1;

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;

  int bwidth = buffer->width;
  int bheight = buffer->height;
  uint8_t tsrc[copy?1:width*4]; /* unused when not copy */

  //uint8_t global_alpha_u8 = rasterizer->state->gstate.global_alpha_u8;
  uint32_t *data = ((uint32_t*)buffer->data);
  uint32_t rb_row[2][width*2];
  //uint32_t ga_row[2][width];

  uint32_t row_u = (int)(u0 * 65536);
  uint32_t row_v = (int)(v0 * 65536);
  int   ui_delta = (int)(ud * 65536);
  int   vi_delta = (int)(vd * 65536);

  int iter = 0;

  int loaded_v = -1;
  int top      = iter % 2;


  { // preload previous row for first row
    uint32_t ui  = row_u;
    uint32_t vi  = row_v;
    unsigned int xa=0;
      for (unsigned int x = 0; x < width; x++, xa+=2)
      {
        int u = ui >> 16;
        int v = vi >> 16;
        uint32_t  blank = 0;
        uint32_t *src0 = &blank;
        uint32_t *src1 = src0;
    
        if (CTX_LIKELY (v >= 0 && v < bheight))
        {
          if (CTX_LIKELY (u >= 0 && u + 1 < bwidth))
          {
            src0 = data + u + bwidth * (v);
            src1 = src0 + 1;
          }
          else
          {
            if (u >= 0 && u < bwidth)
              src0 = data + u + bwidth * (v);
            if (u + 1>= 0 && u + 1 < bwidth)
              src1 = data + (u+1) + bwidth * (v);
          }
        }
    
        ctx_lerp_RGBA8_split (*src0, *src1, ui>>8, &rb_row[!top][xa], &rb_row[!top][xa+1]);
        ui += ui_delta;
        vi += vi_delta;
      }
    }

  for (unsigned int y = 0; y < height; y++)
  {
     int top     = iter % 2;
     uint32_t ui = row_u;
     uint32_t vi = row_v;

     int v =  (vi >> 16) + 1;
     uint8_t dv = ((row_v)>> 8);

     if (v != loaded_v)
     {
       loaded_v = v;
       unsigned int xa=0;
       for (unsigned int x = 0; x < width; x++, xa+=2)
       {
         int u = ui >> 16;
         v =  (vi >> 16) + 1;
         uint32_t  blank = 0;
         uint32_t *src0 = &blank;
         uint32_t *src1 = src0;
         if (CTX_LIKELY (v >= 0 && v < bheight))
         {
           if (CTX_LIKELY(u >= 0 && u + 1 < bwidth))
           {
             src0 = data + u + bwidth * (v);
             src1 = src0 + 1;
           }
           else
           {
             if (u >= 0 && u < bwidth)
               src0 = data + u + bwidth * (v);
             if (u + 1>= 0 && u + 1 < bwidth)
               src1 = src0 + 1;
           }
         }
         ctx_lerp_RGBA8_split (*src0, *src1, ui>>8, &rb_row[top][xa],
                                                    &rb_row[top][xa+1]);
         ui += ui_delta;
         vi += vi_delta;
       }
       iter++;
       top    = iter % 2;
     }
     
     {
       uint32_t*dst_i = copy?(uint32_t*)dst:(uint32_t*)tsrc;
       int ntop = !top;
       for (unsigned int xa = 0; xa < width * 2; xa+=2)
       {
          *dst_i ++ =
          ctx_lerp_RGBA8_merge (rb_row[top][xa], rb_row[top][xa+1], 
                                rb_row[ntop][xa], rb_row[ntop][xa+1],
                                dv);
       }
       if (!copy)
       ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
          dst, NULL, x0, NULL, width, &tsrc[0]);
     }
     row_u -= vi_delta;
     row_v += ui_delta;
     dst += blit_stride;
  }
}

#if 0
static inline void ctx_RGBA8_image_rgba8_RGBA8_nearest_fill_rect_copy (CtxRasterizer *rasterizer, int x0, int y0, int x1, int y1, int copy)
{
  float u0 = 0; float v0 = 0;
  float ud = 0; float vd = 0;
  float w0 = 1; float wd = 0;
  ctx_init_uv (rasterizer, x0, rasterizer->scanline/CTX_FULL_AA,&u0, &v0, &w0, &ud, &vd, &wd);

  uint32_t *dst = ( (uint32_t *) rasterizer->buf);
  int blit_stride = rasterizer->blit_stride/4;
  dst += (y0 - rasterizer->blit_y) * blit_stride;
  dst += (x0);

  unsigned int width = x1-x0+1;
  unsigned int height = y1-y0+1;

  //CtxSource *g = &rasterizer->state->gstate.source_fill;
  //CtxBuffer *buffer = g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;

  CtxSource *g = &rasterizer->state->gstate.source_fill;
  CtxBuffer *buffer = 
     g->texture.buffer->color_managed?g->texture.buffer->color_managed:g->texture.buffer;
  int bwidth  = buffer->width;
  int bheight = buffer->height;
  int u = x0;// + 0.5f;
  int v = y0;// + 0.5f;

  uint32_t *src = ((uint32_t*)buffer->data) + bwidth * v + u;

  int pre = ctx_mini(ctx_maxi(-u,0), width);

  width-=pre;
  u+=pre;

  int core = ctx_mini (width, bwidth - u);

  if (copy)
  {
    if (core>0)
    {
      uint32_t *t_dst = dst;
      for (unsigned int y = 0; y < height; y++)
      {
         if (CTX_LIKELY((v >= 0 && v < bheight)))
         {
           memcpy (t_dst, src + pre, core * 4);
         }
         v++;
         src += bwidth;
         t_dst += blit_stride;
      }
    }
  }
  else
  {
    if (core>0)
    {
      uint32_t *t_dst = dst;
      for (unsigned int y = 0; y < height; y++)
      {
         if (CTX_LIKELY((v >= 0 && v < bheight)))
         {
           ctx_RGBA8_source_over_normal_full_cov_buf (rasterizer,
               (uint8_t*)t_dst, NULL, x0+pre, NULL, core, (uint8_t*)src);
         }
         v++;
         src += bwidth;
         t_dst += blit_stride;
      }
    }
  }
}
#endif


static void
ctx_composite_fill_rect_aligned (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 - 1);
  y0 = ctx_maxi (y0, blit_y);
  y1 = ctx_mini (y1, blit_y + blit_height - 1);

  int width = x1 - x0 + 1;
  int height= y1 - y0 + 1;
  //
  if (CTX_UNLIKELY (width <=0 || height <= 0))
    return;

  CtxCovPath comp = rasterizer->comp;
  uint8_t *dst;

  // this could be done here, but is not used
  // by a couple of the cases
#define INIT_ENV do {\
  rasterizer->scanline = y0 * CTX_FULL_AA; \
  dst = ( (uint8_t *) rasterizer->buf); \
  dst += (y0 - blit_y) * blit_stride; \
  dst += (x0 * rasterizer->format->bpp)/8;}while(0);

//if (CTX_UNLIKELY(width <=0 || height <= 0))
//  return;
  if (cov == 255)
  {
    switch (comp)
    {
    case CTX_COV_PATH_RGBA8_COPY:
    {
      uint32_t color;
      memcpy (&color, (uint32_t*)rasterizer->color, sizeof (color));
      INIT_ENV;
      if (CTX_UNLIKELY(width == 1))
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          *dst_i = color;
          dst += blit_stride;
        }
      }
      else
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
#if 0
          uint32_t *dst_pix = (uint32_t*)&dst[0];
          int count = width;
          while(count--)
            *dst_pix++=color;
#else
          ctx_span_set_colorbu ((uint32_t*)&dst[0], color, width);
#endif
          dst += blit_stride;
        }
      }
      return;
    }
    case CTX_COV_PATH_RGBAF_COPY:
    case CTX_COV_PATH_GRAY8_COPY:
    case CTX_COV_PATH_GRAYA8_COPY:
    case CTX_COV_PATH_GRAYAF_COPY:
    case CTX_COV_PATH_CMYKAF_COPY:
    case CTX_COV_PATH_RGB565_COPY:
    case CTX_COV_PATH_RGB332_COPY:
    case CTX_COV_PATH_RGB8_COPY:
    case CTX_COV_PATH_CMYK8_COPY:
    case CTX_COV_PATH_CMYKA8_COPY:
    {
      uint8_t *color = (uint8_t*)&rasterizer->color_native;
      unsigned int bytes = rasterizer->format->bpp/8;
      INIT_ENV;

      switch (bytes)
      {
        case 1:
          {
          uint8_t col = *color;
          if (width == 1)
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
            *dst = col;
            dst += blit_stride;
          }
          else
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
#if 0
            uint8_t *dst_i = (uint8_t*)&dst[0];
            for (int x = 0; x < width; x++) *dst_i++ = col;
#else
            memset (dst, col, width);
#endif
            dst += blit_stride;
          }
          }
          break;
        case 2:
          {
            uint16_t val = ((uint16_t*)color)[0];
            for (unsigned int y = y0; y <= (unsigned)y1; y++)
            {
              uint16_t *dst_i = (uint16_t*)&dst[0];
              for (int x = 0; x < width; x++)
                 *dst_i++ = val;
              dst += blit_stride;
            }
          }
          break;
        case 3:
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
            uint8_t *dst_i = (uint8_t*)&dst[0];
            for (int x = 0; x < width; x++)
                for (unsigned int b = 0; b < 3; b++) *dst_i++ = color[b];
            dst += blit_stride;
          }
          break;
        case 4:
          {
            uint32_t val = ((uint32_t*)color)[0];
            if (width == 1)
            for (unsigned int y = y0; y <= (unsigned)y1; y++)
            {
              *((uint32_t*)&dst[0]) = val;
              dst += blit_stride;
            }
            else
            for (unsigned int y = y0; y <= (unsigned)y1; y++)
            {
              //uint32_t *dst_i = (uint32_t*)&dst[0];
              ctx_span_set_colorbu ((uint32_t*)&dst[0], val, width);
              dst += blit_stride;
            }
          }
          break;
        case 5:
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
            uint8_t *dst_i = (uint8_t*)&dst[0];
            for (int x = 0; x < width; x++)
               for (unsigned int b = 0; b < 5; b++) *dst_i++ = color[b];
            dst += blit_stride;
          }
          break;
        case 16:
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
            uint8_t *dst_i = (uint8_t*)&dst[0];
            for (int x = 0; x < width; x++)for (unsigned int b = 0; b < 16; b++) *dst_i++ = color[b];
            dst += blit_stride;
          }
          break;
        default:
          for (unsigned int y = y0; y <= (unsigned)y1; y++)
          {
            uint8_t *dst_i = (uint8_t*)&dst[0];
            for (int x = 0; x < width; x++)
              for (unsigned int b = 0; b < bytes; b++)
                *dst_i++ = color[b];
            dst += blit_stride;
          }
      }
      return;
    }
    case CTX_COV_PATH_RGBA8_OVER:
    {
      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];
      INIT_ENV;

      if (CTX_UNLIKELY(width == 1))
      {
        for (unsigned int y = y0; y <= (unsigned)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;
        }
      }
      else
      {
        for (unsigned int y = y0; y <= (unsigned)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;
    }
    case CTX_COV_PATH_RGBA8_COPY_FRAGMENT:
    {
      CtxFragment fragment = rasterizer->fragment;
      CtxMatrix *transform = &rasterizer->state->gstate.source_fill.transform;
      CtxExtend extend = rasterizer->state->gstate.extend;
      INIT_ENV;

      if (fragment == ctx_fragment_image_rgba8_RGBA8_bi_scale)
      {
        ctx_RGBA8_image_rgba8_RGBA8_bi_scaled_fill_rect (rasterizer, x0, y0, x1,
y1, 1);
        return;
      }
      else if (fragment == ctx_fragment_image_rgba8_RGBA8_bi_affine && extend == CTX_EXTEND_NONE)
      {
        ctx_RGBA8_image_rgba8_RGBA8_bi_affine_fill_rect (rasterizer, x0, y0, x1,
y1, 1);
        return;
      }

      if (CTX_LIKELY(ctx_matrix_no_perspective (transform)))
      {
        int scan = rasterizer->scanline/CTX_FULL_AA;
        float u0, v0, ud, vd, w0, wd;
        ctx_init_uv (rasterizer, x0, scan, &u0, &v0, &w0, &ud, &vd, &wd);
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          fragment (rasterizer, u0, v0, w0, &dst[0], width, ud, vd, wd);
          u0 -= vd;
          v0 += ud;
          dst += blit_stride;
        }
      }
      else
      {
        int scan = rasterizer->scanline/CTX_FULL_AA;
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          float u0, v0, ud, vd, w0, wd;
          ctx_init_uv (rasterizer, x0, scan + y-y0, &u0, &v0, &w0, &ud, &vd, &wd);
          fragment (rasterizer, u0, v0, w0, &dst[0], width, ud, vd, wd);
          dst += blit_stride;
        }
      }
      return;
    }
    case CTX_COV_PATH_RGBA8_OVER_FRAGMENT:
    {
      CtxFragment fragment = rasterizer->fragment;
      CtxExtend extend = rasterizer->state->gstate.extend;
#if 0
      if (fragment == ctx_fragment_image_rgba8_RGBA8_nearest_copy)
      {
        ctx_RGBA8_image_rgba8_RGBA8_nearest_fill_rect_copy (rasterizer, x0, y0, x1, y1, 0);
        return;
      }
      else
#endif
      if (fragment == ctx_fragment_image_rgba8_RGBA8_bi_scale)
      {
        ctx_RGBA8_image_rgba8_RGBA8_bi_scaled_fill_rect (rasterizer, x0, y0, x1,
y1, 0);
        return;
      }
      else if (fragment == ctx_fragment_image_rgba8_RGBA8_bi_affine && extend == CTX_EXTEND_NONE)
      {
        ctx_RGBA8_image_rgba8_RGBA8_bi_affine_fill_rect (rasterizer, x0, y0, x1,
y1, 0);
        return;
      }

      INIT_ENV;
      ctx_RGBA8_source_over_normal_full_cov_fragment (rasterizer,
                         &dst[0], NULL, x0, NULL, width, y1-y0+1);
      return;
    }
    break;
    default:
    break;
    }
  }
  else
  {
    switch (comp)
    {
    case CTX_COV_PATH_RGBA8_COPY:
    {
      uint32_t color;
      memcpy (&color, (uint32_t*)rasterizer->color, sizeof (color));
      INIT_ENV;
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          for (unsigned int i = 0; i < (unsigned)width; i++)
          {
            dst_i[i] = ctx_lerp_RGBA8 (dst_i[i], color, cov);
          }
          dst += blit_stride;
        }
        return;
      }
    }
    case CTX_COV_PATH_RGBAF_COPY:
    {
      float *color = ((float*)rasterizer->color);
      float covf = cov / 255.0f;
      INIT_ENV;
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          float *dst_f = (float*)&dst[0];
          for (unsigned int i = 0; i < (unsigned)width; i++)
          {
            for (unsigned int c = 0; c < 4; c++)
              dst_f[i*4+c] = ctx_lerpf (dst_f[i*4+c], color[c], covf);
          }
          dst += blit_stride;
        }
        return;
      }
    }
    case CTX_COV_PATH_RGBA8_OVER:
    {
      uint32_t color;
      memcpy (&color, (uint32_t*)rasterizer->color, sizeof (color));
      INIT_ENV;
      if (width == 1)
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          *dst_i = ctx_over_RGBA8 (*dst_i, color, cov);
          dst += blit_stride;
        }
      }
      else
      {
        for (unsigned int y = y0; y <= (unsigned)y1; y++)
        {
          uint32_t *dst_i = (uint32_t*)&dst[0];
          for (unsigned int i = 0; i < (unsigned)width; i++)
          {
            dst_i[i] = ctx_over_RGBA8 (dst_i[i], color, cov);
          }
          dst += blit_stride;
        }
      }
      return;
    }
    break;
    default:
    break;
    }
  }

  INIT_ENV;
#undef INIT_ENV


  /* fallback */
  {
    uint8_t coverage[width];
    memset (coverage, cov, sizeof (coverage) );
    uint8_t *rasterizer_src = rasterizer->color;
    void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;

    for (unsigned int y = y0; y <= (unsigned)y1; y++)
    {
      apply_coverage (rasterizer, &dst[0], rasterizer_src, x0, coverage, width);
      rasterizer->scanline += CTX_FULL_AA;
      dst += blit_stride;
    }
  }
}

void
CTX_SIMD_SUFFIX (ctx_composite_fill_rect) (CtxRasterizer *rasterizer,
                          float          x0,
                          float          y0,
                          float          x1,
                          float          y1,
                          uint8_t        cov);

void
CTX_SIMD_SUFFIX (ctx_composite_fill_rect) (CtxRasterizer *rasterizer,
                          float          x0,
                          float          y0,
                          float          x1,
                          float          y1,
                          uint8_t        cov)
{
  if((ctx_fmod1f (x0) < 0.01f || ctx_fmod1f(x0) > 0.99f) &&
     (ctx_fmod1f (y0) < 0.01f || ctx_fmod1f(y0) > 0.99f) &&
     (ctx_fmod1f (x1) < 0.01f || ctx_fmod1f(x1) > 0.99f) &&
     (ctx_fmod1f (y1) < 0.01f || ctx_fmod1f(y1) > 0.99f))
  {
    /* best-case scenario axis aligned rectangle */
    ctx_composite_fill_rect_aligned (rasterizer, (int)x0, (int)y0, (int)(x1-1), (int)(y1-1), 255);
    return;
  }

  int blit_x = rasterizer->blit_x;
  int blit_y = rasterizer->blit_y;
  int blit_stride = rasterizer->blit_stride;
  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;
  uint8_t *rasterizer_src = rasterizer->color;
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                       int x, uint8_t *coverage, unsigned int count) =
    rasterizer->apply_coverage;

  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 = (int)(255-ctx_fmod1f (x0) * 255);
  uint8_t top  = (int)(255-ctx_fmod1f (y0) * 255);
  uint8_t right  = (int)(ctx_fmod1f (x1) * 255);
  uint8_t bottom = (int)(ctx_fmod1f (y1) * 255);

  x0 = ctx_floorf (x0);
  y0 = ctx_floorf (y0);
  x1 = ctx_floorf (x1+7/8.0f);
  y1 = ctx_floorf (y1+15/15.0f);

  int has_top    = (top < 255);
  int has_bottom = (bottom <255);
  int has_right  = (right >0);
  int has_left   = (left >0);

  if (x1 >= blit_x + blit_width) has_right = 0;
  if (y1 >= blit_y + blit_height) has_bottom = 0;

  int width = (int)(x1 - x0);

  if ((width >0))
  {
     uint8_t *dst = ( (uint8_t *) rasterizer->buf);
     uint8_t coverage[width+2];
     uint32_t x0i = (int)x0+has_left;
     uint32_t x1i = (int)x1-has_right;
     uint32_t y0i = (int)y0+has_top;
     uint32_t y1i = (int)y1-has_bottom;
     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) >> 8;
       }
       for (unsigned int x = x0i; x < x1i; x++)
         coverage[i++] = top;
       if (has_right)
         coverage[i++]= (top * right + 255) >> 8;

       apply_coverage (rasterizer, dst, rasterizer_src, (int)x0, coverage, width);
       dst += blit_stride;
     }

  if (y1-y0-has_top-has_bottom > 0)
  {
    if (has_left)
      ctx_composite_fill_rect_aligned (rasterizer, (int)x0, y0i,
                                                   (int)x0, y1i-1, left);
    if (has_right)
      ctx_composite_fill_rect_aligned (rasterizer, (int)x1-1, y0i,
                                                   (int)x1-1, y1i-1, right);

    if (width - has_left - has_right > 0)
      ctx_composite_fill_rect_aligned (rasterizer, x0i,y0i,
                                          x1i-1,y1i-1,255);

    dst += blit_stride * (y1i-y0i);
  }
    if (has_bottom)
    {
      int i = 0;
      if (has_left)
        coverage[i++] = (bottom * left + 255) >> 8;
      for (unsigned int x = x0i; x < x1i; x++)
        coverage[i++] = bottom;
      coverage[i++]= (bottom * right + 255) >> 8;

      apply_coverage (rasterizer,dst, rasterizer_src, (int)x0, coverage, width);
    }
  }
}

void
CTX_SIMD_SUFFIX(ctx_composite_stroke_rect) (CtxRasterizer *rasterizer,
                           float          x0,
                           float          y0,
                           float          x1,
                           float          y1,
                           float          line_width);

void
CTX_SIMD_SUFFIX(ctx_composite_stroke_rect) (CtxRasterizer *rasterizer,
                           float          x0,
                           float          y0,
                           float          x1,
                           float          y1,
                           float          line_width)
{
      float lwmod = ctx_fmod1f (line_width);
      int lw = (int)ctx_floorf (line_width + 0.5f);
      int is_compat_even = (lw % 2 == 0) && (lwmod < 0.1f); // only even linewidths implemented properly
      int is_compat_odd = (lw % 2 == 1) && (lwmod < 0.1f); // only even linewidths implemented properly

      float off_x = 0;
      float off_y = 0;

      if (is_compat_odd)
      {
        off_x = 0.5f;
        off_y = (CTX_FULL_AA/2)*1.0f / (CTX_FULL_AA);
      }

      if((is_compat_odd || is_compat_even) &&

     ((ctx_fmod1f (x0-off_x) < 0.01f || ctx_fmod1f(x0-off_x) > 0.99f) &&
     (ctx_fmod1f (y0-off_y) < 0.01f || ctx_fmod1f(y0-off_y) > 0.99f) &&
     (ctx_fmod1f (x1-off_x) < 0.01f || ctx_fmod1f(x1-off_x) > 0.99f) &&
     (ctx_fmod1f (y1-off_y) < 0.01f || ctx_fmod1f(y1-off_y) > 0.99f)))


      {
        int bw = lw/2+1;
        int bwb = lw/2;

        if (is_compat_even)
        {
          bw = lw/2;
        }
        /* top */
        ctx_composite_fill_rect_aligned (rasterizer,
                                         (int)x0-bwb, (int)y0-bwb,
                                         (int)x1+bw-1, (int)y0+bw-1, 255);
        /* bottom */
        ctx_composite_fill_rect_aligned (rasterizer,
                                         (int)x0-bwb, (int)y1-bwb,
                                         (int)x1-bwb-1, (int)y1+bw-1, 255);

        /* left */
        ctx_composite_fill_rect_aligned (rasterizer,
                                         (int)x0-bwb, (int)y0+1,
                                         (int)x0+bw-1, (int)y1-bwb, 255);
        /* right */
        ctx_composite_fill_rect_aligned (rasterizer,
                                         (int)x1-bwb, (int)y0+1,
                                         (int)x1+bw-1, (int)y1+bw-1, 255);
      }
      else
      {
        float hw = line_width/2;


        /* top */
        ctx_composite_fill_rect (rasterizer,
                                 x0+hw, y0-hw,
                                 x1-hw, y0+hw, 255);
        /* bottom */
        ctx_composite_fill_rect (rasterizer,
                                 x0+hw, y1-hw,
                                 x1-hw, y1+hw, 255);

        /* left */
        ctx_composite_fill_rect (rasterizer,
                                 x0-hw, y0+hw,
                                 x0+hw, y1-hw, 255);
        /* right */

        ctx_composite_fill_rect (rasterizer,
                                 x1-hw, y0+hw,
                                 x1+hw, y1-hw, 255);

        /* corners */

        ctx_composite_fill_rect (rasterizer,
                                 x0-hw, y0-hw,
                                 x0+hw, y0+hw, 255);
        ctx_composite_fill_rect (rasterizer,
                                 x1-hw, y1-hw,
                                 x1+hw, y1+hw, 255);
        ctx_composite_fill_rect (rasterizer,
                                 x1-hw, y0-hw,
                                 x1+hw, y0+hw, 255);
        ctx_composite_fill_rect (rasterizer,
                                 x0-hw, y1-hw,
                                 x0+hw, y1+hw, 255);
      }
}


#endif

static void
CTX_SIMD_SUFFIX (ctx_composite_setup) (CtxRasterizer *rasterizer)
{
  if (CTX_UNLIKELY (rasterizer->comp_op==NULL))
  {
#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);
#if 0
      rasterizer->state->gstate.source_fill.transform_inv =
                           rasterizer->state->gstate.source_fill.transform;
#endif
      ctx_matrix_invert (&rasterizer->state->gstate.source_fill.transform);

#if 0
      if (!rasterizer->state->gstate.source_fill.texture.buffer->color_managed)
      {
        _ctx_texture_prepare_color_management (rasterizer->state,
        rasterizer->state->gstate.source_fill.texture.buffer);
      }
#endif
      break;
  }
#endif
#endif
  }
    rasterizer->format->setup (rasterizer);

}


CtxPixelFormatInfo CTX_SIMD_SUFFIX(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_RGB8,
  },
#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_GRAY1,
#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_RGB,
#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_GRAY2,
#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_RGB,
#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_GRAY4,
#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_RGB,
#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_GRAY8,
#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_RGB,
#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_RGB,
#endif
  },
#endif
#if CTX_ENABLE_RGB332
  {
    CTX_FORMAT_RGB332, 3, 8, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_RGB332_to_RGBA8,  ctx_RGBA8_to_RGB332,
    ctx_composite_RGB332, ctx_setup_RGB332,
  },
#endif
#if CTX_ENABLE_CBRLE
  {
    CTX_FORMAT_CBRLE_1, 4, 1, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_3, 4, 3, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_2, 4, 2, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_4, 4, 4, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_5, 4, 5, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_6, 4, 6, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_7, 4, 7, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_8, 4, 8, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_9, 4, 9, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_10, 4, 10, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_11, 4, 11, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_12, 4, 12, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_13, 4, 13, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_14, 4, 14, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_15, 4, 15, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_16, 4, 16, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },

  {
    CTX_FORMAT_CBRLE_17, 4, 17, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_18, 4, 18, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_19, 4, 19, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_20, 4, 20, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_21, 4, 21, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_22, 4, 22, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },

  {
    CTX_FORMAT_CBRLE_23, 4, 23, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },


  {
    CTX_FORMAT_CBRLE_24, 4, 24, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE_32, 4, 32, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
  {
    CTX_FORMAT_CBRLE, 4, 32, 4, 12, 12, CTX_FORMAT_RGBA8,
    ctx_CBRLE_to_RGBA8,  ctx_RGBA8_to_CBRLE,
    ctx_composite_CBRLE, ctx_setup_CBRLE,
  },
#endif
#if CTX_ENABLE_RGB565
  {
    CTX_FORMAT_RGB565, 3, 16, 4, 16, 32, CTX_FORMAT_RGBA8,
    ctx_RGB565_to_RGBA8,  ctx_RGBA8_to_RGB565,
    ctx_composite_RGB565, ctx_setup_RGB565,
  },
#endif
#if CTX_ENABLE_RGB565_BYTESWAPPED
  {
    CTX_FORMAT_RGB565_BYTESWAPPED, 3, 16, 4, 16, 32, CTX_FORMAT_RGBA8,
    ctx_RGB565_BS_to_RGBA8,
    ctx_RGBA8_to_RGB565_BS,
    ctx_composite_RGB565_BS, ctx_setup_RGB565,
  },
#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_CMYKA8,
  },
#endif
#if CTX_ENABLE_CMYK8
  {
    CTX_FORMAT_CMYK8, 5, 32, 4 * 5, 0, 0, CTX_FORMAT_CMYKAF,
    NULL, NULL, ctx_composite_CMYK8, ctx_setup_CMYK8,
  },
#endif
#if CTX_ENABLE_YUV420
  {
    CTX_FORMAT_YUV420, 1, 8, 4, 0, 0, CTX_FORMAT_RGBA8,
    NULL, NULL, ctx_composite_convert, ctx_setup_RGB,
  },
#endif
  {
    CTX_FORMAT_NONE
  }
};




#endif // CTX_COMPOSITE

#endif // CTX_IMPLEMENTATION

#if CTX_IMPLEMENTATION || CTX_SIMD_BUILD
#if CTX_COMPOSITE 

#define CTX_AA_HALFSTEP2   (CTX_FULL_AA/2)
#define CTX_AA_HALFSTEP    ((CTX_FULL_AA/2)+1)

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 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 = scanline + CTX_FULL_AA;
  int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA;
  CtxSegment *segments = &((CtxSegment*)(rasterizer->edge_list.entries))[0];
  int *edges = rasterizer->edges;
  for (unsigned 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
  // perhaps 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 *__restrict__ segments = &((CtxSegment*)(rasterizer->edge_list.entries))[0];
  unsigned int active_edges = rasterizer->active_edges;
  unsigned int pending_edges = rasterizer->pending_edges;
  unsigned int pending_base = CTX_MAX_EDGES-pending_edges;
  for (unsigned int i = 0; i < active_edges; i++)
    {
      CtxSegment *segment = segments + rasterizer->edges[i];
      segment->val += segment->delta * count;
    }
  for (unsigned int i = 0; i < pending_edges; i++)
    {
      CtxSegment *segment = segments + rasterizer->edges[pending_base+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
*/
inline static void ctx_edge2_insertion_sort (CtxSegment *segments, int *entries, unsigned int count)
{
  for(unsigned 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;
   }
}

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;
}

inline static void ctx_edge2_insertion_sort2 (CtxSegment *segments, int *entries, unsigned int count)
{
  for(unsigned 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 *__restrict__ entries = (CtxSegment*)&rasterizer->edge_list.entries[0];
  int *edges = rasterizer->edges;
  unsigned int pending_edges   = rasterizer->pending_edges;
  rasterizer->horizontal_edges = 0;
  rasterizer->ending_edges     = 0;
  for (unsigned int i = 0; i < pending_edges; i++)
    {
      if (entries[rasterizer->edges[CTX_MAX_EDGES-1-i]].data.s16[1] - 1 <= rasterizer->scanline &&
          rasterizer->active_edges < CTX_MAX_EDGES-2)
        {
          unsigned int no = rasterizer->active_edges;
          rasterizer->active_edges++;
          edges[no] = edges[CTX_MAX_EDGES-1-i];
          edges[CTX_MAX_EDGES-1-i] =
            edges[CTX_MAX_EDGES-1-pending_edges + 1];
          pending_edges--;
          i--;
        }
    }
  int limit3 = CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA;
  int scanline = rasterizer->scanline;
  int next_scanline = scanline + CTX_FULL_AA;
  unsigned int edge_pos = rasterizer->edge_pos;
  unsigned int edge_count = rasterizer->edge_list.count;
  while ((edge_pos < edge_count &&
         (miny=entries[edge_pos].data.s16[1]-1)  <= next_scanline))
    {
      if (rasterizer->active_edges < CTX_MAX_EDGES-2 &&
      entries[edge_pos].data.s16[3]-1 /* (maxy) */  >= scanline)
        {
          int dy = (entries[edge_pos].data.s16[3] - 1 - miny);
          if (dy)
            {
              int yd = scanline - miny;
              unsigned int no = rasterizer->active_edges;
              rasterizer->active_edges++;
              unsigned 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 &&
                  pending_edges < CTX_MAX_PENDING-1)
              {
                  /* 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
                  */
                    edges[CTX_MAX_EDGES-1-pending_edges] =
                    rasterizer->edges[no];
                    pending_edges++;
                    rasterizer->active_edges--;
              }
            }
          else
            rasterizer->horizontal_edges ++;
        }
      edge_pos++;
    }
    rasterizer->pending_edges = pending_edges;
    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, unsigned int minx, unsigned int maxx, uint8_t *coverage, int *first_col, int *last_col)
{
#if CTX_ENABLE_SHADOW_BLUR
  if (CTX_UNLIKELY(rasterizer->in_shadow))
  {
    float radius = rasterizer->state->gstate.shadow_blur;
    unsigned 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 (unsigned int x = dim/2; x < maxx-minx + 1 - dim/2; x ++)
        for (unsigned int u = 0; u < dim; u ++)
        {
          temp[x] += coverage[minx+x+u-dim/2] * rasterizer->kernel[u] * 256;
        }
      for (unsigned 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))
  {
  int scanline     = rasterizer->scanline - CTX_FULL_AA; // we do the
                                                 // post process after
                                                 // coverage generation icnrement
    /* 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 (unsigned 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
    }
  }
#endif
}

#define CTX_EDGE(no)      entries[edges[no]]
#define CTX_EDGE_YMIN     (segment->data.s16[1]-1)

#define UPDATE_PARITY \
        if (CTX_LIKELY(scanline!=CTX_EDGE_YMIN))\
        { \
          if (is_winding)\
             parity = parity + -1+2*(segment->code == CTX_EDGE_FLIPPED);\
          else\
             parity = 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,
                                  const uint8_t  fraction)
{
  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 (CTX_UNLIKELY (first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if (CTX_UNLIKELY (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 (CTX_UNLIKELY (first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if (CTX_UNLIKELY (last > maxx))
          {
            last = maxx;
            grayend=255;
          }

          graystart = (graystart&0xff) ^ 255;
          grayend   = (grayend & 0xff);

          coverage[first] += graystart;
          coverage[last]  += grayend;
          if (first + 1< last)
              memset(&coverage[first+1], 255, last-(first+1));
        }
   }
}


inline static void
ctx_rasterizer_generate_coverage_apply (CtxRasterizer *rasterizer,
                                        int            minx,
                                        int            maxx,
                                        uint8_t* __restrict__ coverage,
                                        int            is_winding,
                                        CtxCovPath     comp)
{
  CtxSegment *entries = (CtxSegment*)(&rasterizer->edge_list.entries[0]);
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;
  uint8_t *rasterizer_src = rasterizer->color;
  int *edges          = rasterizer->edges;
  int scanline        = rasterizer->scanline;
  const int bpp       = rasterizer->format->bpp;
  int active_edges    = rasterizer->active_edges;
  int parity          = 0;
#if CTX_RASTERIZER_SWITCH_DISPATCH
  uint32_t *src_pixp;
  uint32_t src_pix, si_ga, si_rb, si_ga_full, si_rb_full, si_a;
  if (comp != CTX_COV_PATH_FALLBACK &&
      comp != CTX_COV_PATH_RGBA8_COPY_FRAGMENT &&
      comp != CTX_COV_PATH_RGBA8_OVER_FRAGMENT)
  {
    src_pixp   = ((uint32_t*)rasterizer->color);
    src_pix    = src_pixp[0];
    si_ga      = ((uint32_t*)rasterizer->color)[1];
    si_rb      = ((uint32_t*)rasterizer->color)[2];
    si_ga_full = ((uint32_t*)rasterizer->color)[3];
    si_rb_full = ((uint32_t*)rasterizer->color)[4];
    si_a       = src_pix >> 24;
  }
  else
  {
    src_pix    =
    si_ga      =
    si_rb      =
    si_ga_full =
    si_rb_full =
    si_a       = 0;
    src_pixp = &src_pix;
  }
#endif

  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 first = graystart >> 8;
          int grayend = x1 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256);
          int last = grayend >> 8;

          if (CTX_UNLIKELY(first < minx))
          { 
            graystart = 0;
            first = minx;
          }
          if (CTX_UNLIKELY(last > maxx))
          {
             last = maxx;
             grayend=255;
          }
          graystart = (graystart&0xff) ^ 255;

          grayend     = (grayend & 0xff);

          if (accumulated)
          {
            if (accumulator_x == first)
            {
              graystart += accumulated;
            }
            else
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)/8]);
              switch (comp)
              {
#if CTX_RASTERIZER_SWITCH_DISPATCH
                case CTX_COV_PATH_RGBA8_COPY:
                  *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated);
                  break;
                case CTX_COV_PATH_RGBA8_OVER:
                  *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated);
                  break;
#endif
                default:
                  apply_coverage (rasterizer, (uint8_t*)dst_pix, rasterizer_src, accumulator_x, &accumulated, 1);
              }
            }
            accumulated = 0;
          }

          if (first < last)
          {
            switch (comp)
            {
#if CTX_RASTERIZER_SWITCH_DISPATCH
              case CTX_COV_PATH_RGBA8_COPY:
              {
                uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)/8]);
                *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, graystart);

                dst_pix++;
                ctx_span_set_colorb (dst_pix, src_pix, last - first - 1);
              }
              break;
#if CTX_ENABLE_CBRLE
              case CTX_COV_PATH_CBRLE_COPY:
              {
              uint8_t* dsts = (uint8_t*)(&dst[(first *bpp)/8]);
              uint8_t  startcov = graystart;
              apply_coverage (rasterizer, (uint8_t*)dsts, rasterizer_src, first, &startcov, 1);

              if (last-(first+1) > 0)
              ctx_CBRLE_compress (rasterizer->color,
                                   dst,
                                   rasterizer->blit_width,
                                   rasterizer->blit_stride,
                                   first+1, last-(first+1), 
                                   CBRLE_MODE_SET_COLOR, NULL, 1);

              }
              break;
#endif
            case CTX_COV_PATH_RGB8_COPY:
            case CTX_COV_PATH_RGBAF_COPY:
            case CTX_COV_PATH_RGB565_COPY:
            case CTX_COV_PATH_RGB332_COPY:
            case CTX_COV_PATH_GRAYA8_COPY:
            case CTX_COV_PATH_GRAYAF_COPY:
            case CTX_COV_PATH_CMYKAF_COPY:
            case CTX_COV_PATH_GRAY8_COPY:
            case CTX_COV_PATH_CMYKA8_COPY:
            case CTX_COV_PATH_CMYK8_COPY:
            {
              uint8_t* dsts = (uint8_t*)(&dst[(first *bpp)/8]);
              uint8_t  startcov = graystart;
              apply_coverage (rasterizer, (uint8_t*)dsts, rasterizer_src, first, &startcov, 1);
              uint8_t* dst_i = (uint8_t*)dsts;
              uint8_t *color = ((uint8_t*)&rasterizer->color_native);
              unsigned int bytes = rasterizer->format->bpp/8;
              dst_i+=bytes;

              unsigned int count = last-(first+1);//  (last - post) - (first+pre) + 1;

              //for (int i = first + pre; i <= last - post; i++)
              if (CTX_LIKELY(count>0))
              switch (bytes)
              {
                case 1:
#if 1
                  memset (dst_i, color[0], count);
#else
                  while (count--)
                  {
                    dst_i[0] = color[0];
                    dst_i++;
                  }
#endif
                  break;
                case 2:
                  {
                    uint16_t val = ((uint16_t*)color)[0];
                    while (count--)
                    {
                      ((uint16_t*)dst_i)[0] = val;
                      dst_i+=2;
                    }
                  }
                  break;
                case 4:
                  {
                    uint32_t val = ((uint32_t*)color)[0];
                    ctx_span_set_colorb ((uint32_t*)dst, val, count);
                  }
                  break;
                case 16:
                  ctx_span_set_color_x4 ((uint32_t*)dst, (uint32_t*)color, count);
                  break;
                case 3:
                 while (count--)
                 {
                   *dst_i ++ = color[0];
                   *dst_i ++ = color[1];
                   *dst_i ++ = color[2];
                 }
                 break;
                case 5:
                 while (count--)
                 {
                   *dst_i ++ = color[0];
                   *dst_i ++ = color[1];
                   *dst_i ++ = color[2];
                   *dst_i ++ = color[3];
                   *dst_i ++ = color[4];
                 }
                 break;
                default:
                 while (count--)
                 {
                   for (unsigned int b = 0; b < bytes; b++)
                     *dst_i++ = color[b];
                 }
                  break;
               }
            }
              break;

#if CTX_ENABLE_GRAY4
              case CTX_COV_PATH_GRAY4_COPY:
              {
                uint8_t* dstp = (uint8_t*)(&dst[(first *bpp)/8]);
                uint8_t *srcp = (uint8_t*)src_pixp;
                uint8_t  startcov = graystart;
                apply_coverage (rasterizer, (uint8_t*)dstp, rasterizer_src, first, &startcov, 1);
                dstp = (uint8_t*)(&dst[((first+1)*bpp)/8]);
                unsigned int count = last - first - 1;
                int val = srcp[0]/17;

                uint8_t val_x2 = val + (val << 4);
                {
                  int x = first + 1;
                  for (unsigned int i = 0; i < count && x & 1; count--)
                  {
                     int bitno = x & 1;
                     *dstp &= ~(15<<(bitno*4));
                     *dstp |= (val<<(bitno*4));
                     dstp += (bitno == 1);
                     x++;
                  }

                  for (unsigned int i = 0; i < count && count>2; count-=2, x+=2, dstp++)
                     *dstp = val_x2;

                  for (unsigned int i = 0; i < count; count--)
                  {
                     int bitno = x & 1;
                     *dstp &= ~(15<<(bitno*4));
                     *dstp |= (val<<(bitno*4));
                     dstp += (bitno == 1);
                     x++;
                  }
                }
              }
              break;
#endif

#if CTX_ENABLE_GRAY2
              case CTX_COV_PATH_GRAY2_COPY:
              {
                uint8_t* dstp = (uint8_t*)(&dst[(first *bpp)/8]);
                uint8_t *srcp = (uint8_t*)src_pixp;
                uint8_t  startcov = graystart;
                apply_coverage (rasterizer, (uint8_t*)dstp, rasterizer_src, first, &startcov, 1);
                dstp = (uint8_t*)(&dst[((first+1)*bpp)/8]);
                unsigned int count = last - first - 1;
                int val = srcp[0]/85; 

                uint8_t val_x4 = val + (val << 2) + (val << 4) + (val << 6);
                {
                  int x = first + 1;
                  for (unsigned int i = 0; i < count && x & 3; count--)
                  {
                     int bitno = x & 3;
                     *dstp &= ~(3<<(bitno*2));
                     *dstp |= (val<<(bitno*2));
                     dstp += (bitno == 3);
                     x++;
                  }

                  for (unsigned int i = 0; i < count && count>4; count-=4, x+=4, dstp++)
                     *dstp = val_x4;

                  for (unsigned int i = 0; i < count; count--)
                  {
                     int bitno = x & 3;
                     *dstp &= ~(3<<(bitno*2));
                     *dstp |= (val<<(bitno*2));
                     dstp += (bitno == 3);
                     x++;
                  }
                }
              }
              break;
#endif

#if CTX_ENABLE_GRAY1
              case CTX_COV_PATH_GRAY1_COPY:
              {
                uint8_t* dstp = (uint8_t*)(&dst[(first *bpp)/8]);
                uint8_t *srcp = (uint8_t*)src_pixp;
                uint8_t  startcov = graystart;
                apply_coverage (rasterizer, (uint8_t*)dstp, rasterizer_src, first, &startcov, 1);
                dstp = (uint8_t*)(&dst[((first+1)*bpp)/8]);
                unsigned int count = last - first - 1;
                if (srcp[0]>=127)
                {
                  int x = first + 1;
                  for (unsigned int i = 0; i < count && x & 7; count--)
                  {
                     int bitno = x & 7;
                     *dstp |= (1<<bitno);
                     dstp += (bitno == 7);
                     x++;
                  }

                  for (unsigned int i = 0; i < count && count>8; count-=8)
                  {
                     *dstp = 255;
                     dstp++;
                     x+=8;
                  }

                  for (unsigned int i = 0; i < count; i++)
                  {
                     int bitno = x & 7;
                     *dstp |= (1<<bitno);
                     dstp += (bitno == 7);
                     x++;
                  }
                }
                else
                {
                  unsigned int x = first + 1;
                  for (unsigned int i = 0; i < count && x & 7; count--)
                  {
                     int bitno = x & 7;
                     *dstp &= ~(1<<bitno);
                     dstp += (bitno == 7);
                     x++;
                  }

                  for (unsigned int i = 0; i < count && count>8; count-=8)
                  {
                     *dstp = 0;
                     dstp++;
                     x+=8;
                  }

                  for (unsigned int i = 0; i < count; i++)
                  {
                     int bitno = x & 7;
                     *dstp &= ~(1<<bitno);
                     dstp += (bitno == 7);
                     x++;
                  }

                }
              }
              break;
#endif

            case CTX_COV_PATH_RGBA8_OVER:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)/8]);
              *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, graystart);
              dst_pix++;
              for (unsigned int i = first + 1; i < (unsigned)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_RGBA8_COPY_FRAGMENT:
            {
              float u0 = 0; float v0 = 0;
              float ud = 0; float vd = 0;
              float w0 = 1; float wd = 0;
              uint8_t gs = graystart;
              ctx_RGBA8_source_copy_normal_fragment (rasterizer, &dst[(first * bpp)/8], NULL, first, &gs, 1);
              ctx_init_uv (rasterizer, first+1, scanline/CTX_FULL_AA,&u0, &v0, &w0, &ud, &vd, &wd);
              rasterizer->fragment (rasterizer, u0, v0, w0, &dst[((first+1)*bpp)/8], last-first-1, ud, vd, wd);
            }
            break;
              case CTX_COV_PATH_RGBA8_OVER_FRAGMENT:
            {
              uint8_t gs = graystart;
              ctx_RGBA8_source_over_normal_fragment (rasterizer, &dst[(first * bpp)/8], NULL, first, &gs, 1);
              ctx_RGBA8_source_over_normal_full_cov_fragment (rasterizer,
                                                     &dst[((first+1)*bpp)/8], NULL, first + 1, NULL, last-first-1, 1);
            }
            break;
#endif
              default:
            {
#if static_OPAQUE
              uint8_t *opaque = &rasterizer->opaque[0];
#else
              uint8_t opaque[last-first];
              memset (opaque, 255, sizeof (opaque));
#endif
              opaque[0] = graystart;
              apply_coverage (rasterizer,
                              &dst[(first * bpp)/8],
                              rasterizer_src, first, opaque, last-first);

#if static_OPAQUE
              opaque[0] = 255;
#endif
            }
            }
            accumulated = grayend;
          }
          else if (first == last)
          {
            accumulated = (graystart-(grayend^255));
          }
          accumulator_x = last;
        }
   }

   if (accumulated)
   {
     uint32_t* dst_pix = (uint32_t*)(&dst[(accumulator_x*bpp)/8]);
     switch (comp)
     {
#if CTX_RASTERIZER_SWITCH_DISPATCH
       case CTX_COV_PATH_RGBA8_COPY:
         *dst_pix = ctx_lerp_RGBA8_2(*dst_pix, si_ga, si_rb, accumulated);
         break;
       case CTX_COV_PATH_RGBA8_OVER:
         *dst_pix = ctx_over_RGBA8_2(*dst_pix, si_ga, si_rb, si_a, accumulated);
         break;
#endif
       default:
         apply_coverage (rasterizer, (uint8_t*)dst_pix, rasterizer_src,
                         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;

          if (CTX_UNLIKELY (first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if (CTX_UNLIKELY (last > maxx))
          {
            last = maxx;
            grayend=255;
          }
          graystart = (graystart&0xff) ^ 255;
          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;
            }
            else
            {
              unsigned int u0 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_mini (x0_start, x0_end)));
              unsigned int u1 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_maxi (x0_start, x0_end)));

              int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
              int count = 0;

              int mod = ((u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)^255) *
                         (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255);
              int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV)/255);

              int recip = 65536/sum;
              for (unsigned int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
              {
                coverage[us + count] += ((u - u0 + mod) * recip)>>16;
                count++;
              }
              pre = (us+count-1)-first+1;
            }
  
            if (abs(delta1) < CTX_RASTERIZER_AA_SLOPE_LIMIT3_FAST_AA)
            {
               coverage[last] += grayend;
            }
            else
            {
              unsigned int u0 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_mini (x1_start, x1_end)));
              unsigned int u1 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_maxi (x1_start, x1_end)));

              int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
              int count = 0;
              int mod = ((((u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)^255)+64) *
                    (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255));
              int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 5/4)/255);
              int recip = 65536 / sum;
              for (unsigned int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
              {
                coverage[us + count] += (((u - u0 + mod) * recip)>>16) ^ 255;
                count++;
              }
              post = last-us+1;
            }
            for (int i = first + pre; i <= last - post; i++)
              coverage[i] = 255;
          }
          else if (first == last)
          {
            coverage[last]+=(graystart-(grayend^255));
          }
        }
   }
}


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;
  int  active_edges   = rasterizer->active_edges;
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;
  uint8_t *rasterizer_src = rasterizer->color;
  int  parity         = 0;

#if CTX_RASTERIZER_SWITCH_DISPATCH
  uint32_t *src_pixp;
  uint32_t src_pix, si_ga, si_rb, si_ga_full, si_rb_full, si_a;
  if (comp != CTX_COV_PATH_FALLBACK &&
      comp != CTX_COV_PATH_RGBA8_COPY_FRAGMENT &&
      comp != CTX_COV_PATH_RGBA8_OVER_FRAGMENT)
  {
    src_pixp   = ((uint32_t*)rasterizer->color);
    src_pix    = src_pixp[0];
    si_ga      = ((uint32_t*)rasterizer->color)[1];
    si_rb      = ((uint32_t*)rasterizer->color)[2];
    si_ga_full = ((uint32_t*)rasterizer->color)[3];
    si_rb_full = ((uint32_t*)rasterizer->color)[4];
    si_a  = src_pix >> 24;
  }
  else
  {
    src_pix    =
    si_ga      =
    si_rb      =
    si_ga_full =
    si_rb_full =
    si_a  = 0;
    src_pixp = &src_pix;
  }
#endif

  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;

          if (CTX_UNLIKELY (first < minx))
          { 
            first = minx;
            graystart=0;
          }
          if (CTX_UNLIKELY (last > maxx))
          {
            last = maxx;
            grayend=255;
          }
          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
          {
            unsigned int u0 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_mini (x0_start, x0_end)));
            unsigned int u1 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_maxi (x0_start, x0_end)));

            int mod = ((u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)^255) *
                    (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;
            int recip = 65536/ sum;
            for (unsigned int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
            {
              coverage[us + count] += ((u - u0 + mod) * recip)>>16;
              count++;
            }
            pre = (us+count-1)-first+1;

            accumulated_x0 = ctx_mini (accumulated_x0, us);
            accumulated_x1 = us + count - 1;
          }

          if (accumulated_x1-accumulated_x0>=0)
          {
             switch (comp)
             {
#if CTX_RASTERIZER_SWITCH_DISPATCH
                case CTX_COV_PATH_RGBA8_OVER:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)/8];
                  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_RGBA8_COPY:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)/8];
                  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;
                case CTX_COV_PATH_RGB8_COPY:
                {
                  uint8_t *dst_i = (uint8_t*)&dst[((accumulated_x0) * bpp)/8];
                  uint8_t *srcp = (uint8_t*)src_pixp;
                  for (int i = 0; i < accumulated_x1-accumulated_x0+1; i++)
                  {
                    for (int c = 0; c < 3; c++)
                      dst_i[c] = ctx_lerp_u8 (dst_i[c], srcp[c], coverage[accumulated_x0+i]);
                    dst_i +=3;
                  }
                }
                  break;
#endif
                default:
                apply_coverage (rasterizer,
                          &dst[((accumulated_x0) * bpp)/8],
                          rasterizer_src,
                          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
          {
            unsigned int u0 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_mini (x1_start, x1_end)));
            unsigned int u1 = ctx_mini (maxx_, ctx_maxi (minx_, ctx_maxi (x1_start, x1_end)));

            int us = u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV);
            int count = 0;

            int mod = ((((u0 / (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/256) % 256)^255) +64) *
                    (CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV/255));
            int sum = ((u1-u0+CTX_RASTERIZER_EDGE_MULTIPLIER * CTX_SUBDIV * 5/4)/255);

            int recip = 65536/ sum;
            for (unsigned int u = u0; u < u1; u+= CTX_RASTERIZER_EDGE_MULTIPLIER*CTX_SUBDIV)
            {
              coverage[us + count] = (((u - u0 + mod)*recip)>>16)^255;
              count++;
            }
            post = last-us+1;

            accumulated_x1 = us + count;
            accumulated_x0 = us;
          }
          switch (comp)
          {
#if CTX_RASTERIZER_SWITCH_DISPATCH
            case CTX_COV_PATH_RGBAF_COPY:
            case CTX_COV_PATH_GRAY8_COPY:
            case CTX_COV_PATH_RGB8_COPY:
            case CTX_COV_PATH_GRAYA8_COPY:
            case CTX_COV_PATH_GRAYAF_COPY:
            case CTX_COV_PATH_CMYKAF_COPY:
            case CTX_COV_PATH_RGB565_COPY:
            case CTX_COV_PATH_RGB332_COPY:
            case CTX_COV_PATH_CMYK8_COPY:
            case CTX_COV_PATH_CMYKA8_COPY:
            {
              uint8_t* dsts = (uint8_t*)(&dst[(first *bpp)/8]);
              uint8_t* dst_i = (uint8_t*)dsts;
              uint8_t* color = ((uint8_t*)&rasterizer->color_native);
              unsigned int bytes = rasterizer->format->bpp/8;
              dst_i+=pre*bytes;

              int scount = (last - post) - (first+pre) + 1;
              unsigned int count = scount;

              //for (int i = first + pre; i <= last - post; i++)
              if (CTX_LIKELY(scount>0))
              switch (bytes)
              {
                case 1:
#if 1
                  memset (dst_i, color[0], count);
#else
                  while (count--)
                  {
                    dst_i[0] = color[0];
                    dst_i++;
                  }
#endif
                  break;
                case 2:
                  {
                    uint16_t val = ((uint16_t*)color)[0];
                    while (count--)
                    {
                      ((uint16_t*)dst_i)[0] = val;
                      dst_i+=2;
                    }
                  }
                  break;
                case 4:
                  {
                    uint32_t val = ((uint32_t*)color)[0];
                    while (count--)
                    {
                      ((uint32_t*)dst_i)[0] = val;
                      dst_i+=4;
                    }
                  }
                  break;
                case 16:
                  ctx_span_set_color_x4 ((uint32_t*)dst, (uint32_t*)color, count);
                  break;
                case 3:
                 while (count--)
                 {
                   *dst_i++ = color[0];
                   *dst_i++ = color[1];
                   *dst_i++ = color[2];
                 }
                 break;
                case 5:
                 while (count--)
                 {
                   *dst_i++ = color[0];
                   *dst_i++ = color[1];
                   *dst_i++ = color[2];
                   *dst_i++ = color[3];
                   *dst_i++ = color[4];
                 }
                 break;
                default:
                 while (count--)
                 {
                   for (unsigned int b = 0; b < bytes; b++)
                     *dst_i++ = color[b];
                 }
                  break;
               }
             }
             break;
#if CTX_ENABLE_CBRLE
            case CTX_COV_PATH_CBRLE_COPY:
              {
              int count = (last - post) - (first+pre) + 1;
              if (count>0)
              ctx_CBRLE_compress (rasterizer->color,
                                   dst,
                                   rasterizer->blit_width,
                                   rasterizer->blit_stride,
                                   pre, count,
                                   CBRLE_MODE_SET_COLOR, NULL, 1);

              }
              break;
#endif

            case CTX_COV_PATH_RGBA8_COPY:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)/8]);
              dst_pix+=pre;
              ctx_span_set_color (dst_pix, src_pix, last-first-pre-post + 1);
            }
            break;


            case CTX_COV_PATH_RGBA8_OVER:
            {
              uint32_t* dst_pix = (uint32_t*)(&dst[(first *bpp)/8]);
              dst_pix+=pre;
              int scount = (last - post) - (first + pre) + 1;
              if (scount > 0)
              {
                unsigned int count = scount;
                while (count--)
                {
                  *dst_pix = ctx_over_RGBA8_full_2(*dst_pix, si_ga_full, si_rb_full, si_a);
                  dst_pix++;
                }
              }
            }
            break;
            case CTX_COV_PATH_RGBA8_COPY_FRAGMENT:
            {
              int width = last-first-pre-post+1;
              if (width>0)
              {
                float u0 = 0; float v0 = 0;
                float ud = 0; float vd = 0;
                float w0 = 1; float wd = 0;
                ctx_init_uv (rasterizer, first+pre, rasterizer->scanline/CTX_FULL_AA,&u0, &v0, &w0, &ud, &vd, &wd);
                rasterizer->fragment (rasterizer, u0, v0, w0, &dst[(first+pre)*bpp/8],
                                      width, ud, vd, wd);
              }
            }
            break;
            case CTX_COV_PATH_RGBA8_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)/8],
                               NULL,
                               first + pre,
                               NULL,
                               width, 1);
              }
            break;
#endif
            default:
              {
                int width = last-first-pre-post+1;
                if (width > 0)
                {
#if static_OPAQUE
                uint8_t *opaque = &rasterizer->opaque[0];
#else
                uint8_t opaque[width];
                memset (opaque, 255, sizeof (opaque));
#endif
                apply_coverage (rasterizer,
                            &dst[((first + pre) * bpp)/8],
                            rasterizer_src,
                            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)
             {
#if CTX_RASTERIZER_SWITCH_DISPATCH
                case CTX_COV_PATH_RGBA8_OVER:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)/8];
                  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_RGBA8_COPY:
                {
                  uint32_t *dst_i = (uint32_t*)&dst[((accumulated_x0) * bpp)/8];
                  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;
#endif
                default:
                apply_coverage (rasterizer,
                          &dst[((accumulated_x0) * bpp)/8],
                          rasterizer_src,
                          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->has_shape       =   
  rasterizer->has_prev        =   
  rasterizer->edge_list.count =    // ready for new edges
  rasterizer->edge_pos        =   
  rasterizer->scanline        = 0;
  if (CTX_LIKELY(!rasterizer->preserve))
  {
    rasterizer->scan_min      =
    rasterizer->col_min       = 50000000;
    rasterizer->scan_max      =
    rasterizer->col_max       = -50000000;
  }
  //rasterizer->comp_op       = NULL; // keep comp_op cached 
  //     between rasterizations where rendering attributes are
  //     nonchanging
}

static void
ctx_rasterizer_rasterize_edges2 (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               )
{
  rasterizer->pending_edges   =   
  rasterizer->active_edges    =   0;
  //rasterizer->scanline        = 0;
  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;
  uint8_t   real_fraction = 255/real_aa;
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;
  uint8_t *rasterizer_src = rasterizer->color;

  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 (CTX_UNLIKELY (minx >= maxx))
    {
      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;

  rasterizer->scan_min -= (rasterizer->scan_min % CTX_FULL_AA);
#if CTX_SHAPE_CACHE
  if (shape)
    {
      coverage_size = shape->width;
      coverage = &shape->data[0];
      scan_start = rasterizer->scan_min;
      scan_end   = rasterizer->scan_max;
    }
  else
#endif
  {
     coverage_size = sizeof (_coverage);
     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 */
    return;
  }

  rasterizer->horizontal_edges =
    rasterizer->needs_aa3  =
    rasterizer->needs_aa5  =
    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;)
    {

    if (rasterizer->active_edges == 0 && rasterizer->pending_edges == 0)
    { /* no edges */
      ctx_rasterizer_feed_edges (rasterizer, 0);
      ctx_rasterizer_increment_edges (rasterizer, CTX_FULL_AA);
      dst += blit_stride;
#if CTX_SHAPE_CACHE
      if (shape)
      {
        memset (coverage, 0, coverage_size);
        coverage += shape->width;
      }
#endif
      rasterizer->prev_active_edges = rasterizer->active_edges;
      continue;
    }
    else if (real_aa != 1 && ( (rasterizer->horizontal_edges!=0) 
          || (rasterizer->active_edges != rasterizer->prev_active_edges)
          || (rasterizer->active_edges + rasterizer->pending_edges == rasterizer->ending_edges)
          ))
    { /* 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, real_fraction);
          ctx_rasterizer_increment_edges (rasterizer, increment);
        }
    }
    else if (rasterizer->needs_aa3 == 0)
    {
      if (! avoid_direct)
      { /* can generate with direct rendering to target (we're not using shape cache) */
        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
      { /* cheap fully correct AA, to coverage mask / clipping */
        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))
    { /* the scanline transitions does not contain multiple intersections - each aa segment is a linear ramp */
      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);
      if (real_aa == 1)
      {
        for (int x = minx; x <= maxx; x ++)
          coverage[x] = coverage[x] > 127?255:0;
      }
    }
    else
    { /* determine level of oversampling based on lowest steepness edges */
      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);
      uint8_t fraction = 255/aa;
      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, fraction);
        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
  {
    apply_coverage (rasterizer,
                         &dst[(minx * rasterizer->format->bpp) /8],
                         rasterizer_src,
                         minx,
                         coverage,
                         maxx-minx+ 1);
  }
#if CTX_SHAPE_CACHE
  else
  {
    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;)
     {
       apply_coverage (rasterizer,
                       &dst[ (startx * rasterizer->format->bpp) /8],
                       rasterizer_src, 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;)
     {
       apply_coverage (rasterizer,
                       &dst[ (startx * rasterizer->format->bpp) /8],
                       rasterizer_src,
                       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;)
     {
       apply_coverage (rasterizer,
                       &dst[ (maxx * rasterizer->format->bpp) /8],
                       rasterizer_src, 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 this crashes under valgrind/asan
     if(0)for (rasterizer->scanline = scan_end; rasterizer->scanline/CTX_FULL_AA < gscan_end-1;)
     {
       apply_coverage (rasterizer,
                       &dst[ (startx * rasterizer->format->bpp) /8],
                       rasterizer_src,
                       0,
                       nocoverage, clipw-1);

       rasterizer->scanline += CTX_FULL_AA;
       dst += blit_stride;
     }
#endif
  }
}


#if CTX_INLINE_FILL_RULE
void
CTX_SIMD_SUFFIX (ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               );
#else

void
CTX_SIMD_SUFFIX (ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               );
#endif


#if CTX_INLINE_FILL_RULE
void
CTX_SIMD_SUFFIX (ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               )
{
  if (fill_rule)
  {
    ctx_rasterizer_rasterize_edges2 (rasterizer, 1
#if CTX_SHAPE_CACHE
                    ,shape
#endif
                    );
  }
  else
  {
    ctx_rasterizer_rasterize_edges2 (rasterizer, 0
#if CTX_SHAPE_CACHE
                    ,shape
#endif
                    );
  }
}
#else

void
CTX_SIMD_SUFFIX (ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule 
#if CTX_SHAPE_CACHE
                                ,CtxShapeEntry *shape
#endif
                               )
{
    ctx_rasterizer_rasterize_edges2 (rasterizer, fill_rule
#if CTX_SHAPE_CACHE
                    ,shape
#endif
                    );
}

#endif



extern CtxPixelFormatInfo *ctx_pixel_formats;
void CTX_SIMD_SUFFIX(ctx_simd_setup)(void);
void CTX_SIMD_SUFFIX(ctx_simd_setup)(void)
{
  ctx_pixel_formats         = CTX_SIMD_SUFFIX(ctx_pixel_formats);
  ctx_composite_setup       = CTX_SIMD_SUFFIX(ctx_composite_setup);
  ctx_rasterizer_rasterize_edges = CTX_SIMD_SUFFIX(ctx_rasterizer_rasterize_edges);
#if CTX_FAST_FILL_RECT
  ctx_composite_fill_rect   = CTX_SIMD_SUFFIX(ctx_composite_fill_rect);
  ctx_composite_stroke_rect = CTX_SIMD_SUFFIX(ctx_composite_stroke_rect);
#endif
}


#endif
#endif
#if CTX_IMPLEMENTATION
#if CTX_RASTERIZER


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 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 < CTX_MAX_GRADIENT_STOPS-1) //we'll keep overwriting the last when out of stops
    { gradient->n_stops++; }
}

static inline void ctx_rasterizer_update_inner_point (CtxRasterizer *rasterizer, int x, int y)
{
  rasterizer->scan_min = ctx_mini (y, rasterizer->scan_min);
  rasterizer->scan_max = ctx_maxi (y, rasterizer->scan_max);
  rasterizer->col_min = ctx_mini (x, rasterizer->col_min);
  rasterizer->col_max = ctx_maxi (x, rasterizer->col_max);
  rasterizer->inner_x = x;
  rasterizer->inner_y = y;
}

static inline int ctx_rasterizer_add_point (CtxRasterizer *rasterizer, int x1, int y1)
{
  CtxSegment entry = {CTX_EDGE, {{0,}},0,0};

  entry.data.s16[0]=rasterizer->inner_x;
  entry.data.s16[1]=rasterizer->inner_y;

  entry.data.s16[2]=x1;
  entry.data.s16[3]=y1;

  ctx_rasterizer_update_inner_point (rasterizer, x1, y1);

  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


#if CTX_SHAPE_CACHE
float ctx_shape_cache_rate = 0.0;
int   _ctx_shape_cache_enabled = CTX_SHAPE_CACHE_DEFAULT;

//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;
  {
    static int i = 0;
    i++;
    if (i>256)
      {
        if (ctx_shape_cache_hits+ctx_shape_cache_misses)
        {
          ctx_shape_cache_rate = 
                0.5f * ctx_shape_cache_rate +
                0.5f * (ctx_shape_cache_hits * 100.0f  / (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;

  CtxShapeEntry *entry = rasterizer->shape_cache.entries[entry_no];
  if (entry)
    {
      int old_size = sizeof (CtxShapeEntry) + entry->width + entry->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)
      {
         rasterizer->shape_cache.size -= old_size;
         rasterizer->shape_cache.size += (old_size-size); // slack/leaked
      }
      else
      {
        rasterizer->shape_cache.entries[entry_no] = NULL;
        rasterizer->shape_cache.size -= entry->width * entry->height;
        rasterizer->shape_cache.size -= sizeof (CtxShapeEntry);
        ctx_free (entry);
        entry = NULL;
      }
    }

  if (!entry)
    entry = rasterizer->shape_cache.entries[entry_no] = (CtxShapeEntry *) ctx_calloc (size, 1);

  rasterizer->shape_cache.size += size;

  ctx_shape_cache_misses ++;
  entry->hash   = hash;
  entry->width  = width;
  entry->height = height;
  entry->uses = 0;
  return entry;
}

#endif

static uint32_t ctx_rasterizer_poly_to_edges (CtxRasterizer *rasterizer)
{
#if CTX_SHAPE_CACHE
  int x = 0;
  int y = 0;
#endif
  unsigned 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 (unsigned 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;
    }
}

//#define MIN_Y -100
//#define MAX_Y 3800
//#define MIN_X -100
//#define MAX_X 3600*10

static inline void ctx_rasterizer_move_to (CtxRasterizer *rasterizer, float x, float y)
{
  int tx = 0, ty = 0;

  rasterizer->x        = x;
  rasterizer->y        = y;
  rasterizer->first_x  = x;
  rasterizer->first_y  = y;
  rasterizer->has_prev = -1;
  _ctx_user_to_device_prepped (rasterizer->state, x,y, &tx, &ty);

  tx -= rasterizer->blit_x * CTX_SUBDIV;
  ctx_rasterizer_update_inner_point (rasterizer, tx, ty);
}

static inline void
ctx_rasterizer_line_to_fixed (CtxRasterizer *rasterizer, int x, int y)
{
  rasterizer->has_shape = 1;
  //  XXX we avoid doing this for the cases where we initially have fixed point
  //  need a separate call for doing this at end of beziers and arcs
  //if (done)
  //{
  //     rasterizer->y         = y*1.0/CTX_FULL_AA;
  //     rasterizer->x         = x*1.0/CTX_SUBDIV;
  //}
  int tx = 0, ty = 0;
  _ctx_user_to_device_prepped_fixed (rasterizer->state, x, y, &tx, &ty);
  tx -= rasterizer->blit_x * CTX_SUBDIV;

  ctx_rasterizer_add_point (rasterizer, tx, ty);

  if (CTX_UNLIKELY(rasterizer->has_prev<=0))
    {
      CtxSegment *entry = & ((CtxSegment*)rasterizer->edge_list.entries)[rasterizer->edge_list.count-1];
      entry->code = CTX_NEW_EDGE;
      rasterizer->has_prev = 1;
    }
}

static inline void
ctx_rasterizer_line_to (CtxRasterizer *rasterizer, float x, float y)
{
  int tx = 0, ty = 0;
  rasterizer->has_shape = 1;
  rasterizer->y         = y;
  rasterizer->x         = x;

  _ctx_user_to_device_prepped (rasterizer->state, x, y, &tx, &ty);
  tx -= rasterizer->blit_x * CTX_SUBDIV;

  ctx_rasterizer_add_point (rasterizer, tx, ty);

  if (CTX_UNLIKELY(rasterizer->has_prev<=0))
    {
      CtxSegment *entry = & ((CtxSegment*)rasterizer->edge_list.entries)[rasterizer->edge_list.count-1];
      entry->code = CTX_NEW_EDGE;
      rasterizer->has_prev = 1;
    }
}

CTX_INLINE static float
ctx_bezier_sample_1d (float x0, float x1, float x2, float x3, float dt)
{
  return ctx_lerpf (
      ctx_lerpf (ctx_lerpf (x0, x1, dt),
                 ctx_lerpf (x1, x2, dt), dt),
      ctx_lerpf (ctx_lerpf (x1, x2, dt),
                 ctx_lerpf (x2, x3, dt), dt), dt);
}

CTX_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;
  float lx, ly, dx, dy;
  ctx_bezier_sample (ox, oy, x0, y0, x1, y1, x2, y2, t, &x, &y);
  lx = ctx_lerpf (sx, ex, t);
  ly = ctx_lerpf (sy, ey, t);
  dx = lx - x;
  dy = ly - y;
  if (iteration < 6 && (dx*dx+dy*dy) > tolerance)
  {
    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);
  }
}

#define CTX_FIX_SCALE 1024
#define CTX_FIX_SHIFT 10
                      

CTX_INLINE static int
ctx_lerp_fixed (int v0, int v1, int dx)
{
  return v0 + (((v1-v0) * dx) >> CTX_FIX_SHIFT);
}



CTX_INLINE static int
ctx_bezier_sample_1d_fixed (int x0, int x1, int x2, int x3, int dt)
{
  return ctx_lerp_fixed (
      ctx_lerp_fixed (ctx_lerp_fixed (x0, x1, dt),
                 ctx_lerp_fixed (x1, x2, dt), dt),
      ctx_lerp_fixed (ctx_lerp_fixed (x1, x2, dt),
                 ctx_lerp_fixed (x2, x3, dt), dt), dt);
}

CTX_INLINE static void
ctx_bezier_sample_fixed (int x0, int y0,
                         int x1, int y1,
                         int x2, int y2,
                         int x3, int y3,
                         int dt, int *x, int *y)
{
  *x = ctx_bezier_sample_1d_fixed (x0, x1, x2, x3, dt);
  *y = ctx_bezier_sample_1d_fixed (y0, y1, y2, y3, dt);
}

static inline void
ctx_rasterizer_bezier_divide_fixed (CtxRasterizer *rasterizer,
                                    int ox, int oy,
                                    int x0, int y0,
                                    int x1, int y1,
                                    int x2, int y2,
                                    int sx, int sy,
                                    int ex, int ey,
                                    int s,
                                    int e,
                                    int iteration,
                                    int tolerance)
{
  int t = (s + e) / 2;
  int x, y;
  int lx, ly, dx, dy;
  ctx_bezier_sample_fixed (ox, oy, x0, y0, x1, y1, x2, y2, t, &x, &y);
  lx = ctx_lerp_fixed (sx, ex, t);
  ly = ctx_lerp_fixed (sy, ey, t);
  dx = lx - x;
  dy = ly - y;
  if (iteration < 6 && ((dx*dx+dy*dy))  > tolerance)
  {
    iteration++;
    ctx_rasterizer_bezier_divide_fixed (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2,
                                  sx, sy, x, y, s, t, iteration,
                                  tolerance);
    ctx_rasterizer_line_to_fixed (rasterizer, x, y);
    ctx_rasterizer_bezier_divide_fixed (rasterizer, ox, oy, x0, y0, x1, y1, x2, y2,
                                  x, y, ex, ey, t, e, iteration,
                                  tolerance);
  }
}

static void
ctx_rasterizer_curve_to (CtxRasterizer *rasterizer,
                         float x0, float y0,
                         float x1, float y1,
                         float x2, float y2)
{
#if CTX_32BIT_SEGMENTS
  float tolerance = 0.125f/ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
#else
  float tolerance = 0.5f/ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
#endif
  float ox = rasterizer->state->x;
  float oy = rasterizer->state->y;

  tolerance = tolerance * tolerance;

  if(1){

#if CTX_AVOID_CLIPPED_SUBDIVISION
          // XXX need sporting to fixed point
  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);
  
  float coords[4][2]={{minx,miny},
                      {maxx,miny},
                      {maxx,maxy},
                      {minx,maxy}};
  for (int i = 0; i < 4; i++)
  {
    _ctx_user_to_device (rasterizer->state, &coords[i][0], &coords[i][1]);
  }
  minx = maxx = coords[0][0];
  miny = maxy = coords[0][1];
  for (int i = 1; i < 4; i++)
  {
    minx = ctx_minf (minx, coords[i][0]);
    miny = ctx_minf (miny, coords[i][1]);
    maxx = ctx_maxf (minx, coords[i][0]);
    maxy = ctx_maxf (miny, coords[i][1]);
  }

    if( (maxx-minx) + (maxy-miny) < 0.66f ||
        (minx > rasterizer->blit_x + rasterizer->blit_width) ||
        (miny > rasterizer->blit_y + rasterizer->blit_height) ||
        (maxx < rasterizer->blit_x) ||
        (maxy < rasterizer->blit_y) )
    {
    }
    else
#endif
    {
#if 1
        ctx_rasterizer_bezier_divide_fixed (rasterizer,
            (int)(ox * CTX_FIX_SCALE), (int)(oy * CTX_FIX_SCALE), (int)(x0 * CTX_FIX_SCALE), (int)(y0 * CTX_FIX_SCALE),
            (int)(x1 * CTX_FIX_SCALE), (int)(y1 * CTX_FIX_SCALE), (int)(x2 * CTX_FIX_SCALE), (int)(y2 * CTX_FIX_SCALE),
            (int)(ox * CTX_FIX_SCALE), (int)(oy * CTX_FIX_SCALE), (int)(x2 * CTX_FIX_SCALE), (int)(y2 * CTX_FIX_SCALE),
            0, CTX_FIX_SCALE, 0, (int)(tolerance * CTX_FIX_SCALE * CTX_FIX_SCALE));
#else
        ctx_rasterizer_bezier_divide (rasterizer,
                                      ox, oy, x0, y0,
                                      x1, y1, x2, y2,
                                      ox, oy, x2, y2,
                                      0.0f, 1.0f, 0, tolerance);
#endif
    }
  }
  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)
    {
#if CTX_32BIT_SEGMENTS
      // not really, but we want to avoid building/linking fprintf
      // for firmwares which are likely to not have this set
      //fprintf (stderr, "ctx tex fail %p %s %i\n", rasterizer->texture_source, eid, no);
#endif
      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.0f, 0.0f);
  if (!rasterizer->state->gstate.source_fill.texture.buffer->color_managed)
  {
    _ctx_texture_prepare_color_management (rasterizer->state,
    rasterizer->state->gstate.source_fill.texture.buffer);
  }
  _ctx_texture_unlock ();
}


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;
}



static void
ctx_rasterizer_fill (CtxRasterizer *rasterizer)
{
  unsigned int preserved_count =
          (rasterizer->preserve&&rasterizer->edge_list.count)?
             rasterizer->edge_list.count:1;
  int blit_x = rasterizer->blit_x;
  int blit_y = rasterizer->blit_y;
  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;
#if CTX_SHAPE_CACHE
  int blit_stride = rasterizer->blit_stride;
#endif

  CtxSegment temp[preserved_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 (unsigned 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))
    {
    }
  else
  {
    ctx_composite_setup (rasterizer);

    rasterizer->state->ink_min_x = ctx_mini (rasterizer->state->ink_min_x, rasterizer->col_min / CTX_SUBDIV);
    rasterizer->state->ink_max_x = ctx_maxi (rasterizer->state->ink_min_x, rasterizer->col_max / CTX_SUBDIV);
    rasterizer->state->ink_min_y = ctx_mini (rasterizer->state->ink_min_y, rasterizer->scan_min / CTX_FULL_AA);
    rasterizer->state->ink_max_y = ctx_maxi (rasterizer->state->ink_max_y, rasterizer->scan_max / CTX_FULL_AA);

#if CTX_FAST_FILL_RECT
  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
         )
       {
         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);

         if (x1 > x0 && y1 > y0)
         {
           ctx_composite_fill_rect (rasterizer, x0, y0, x1, y1, 255);
           goto done;
         }
       }
    }
#endif

    ctx_rasterizer_finish_shape (rasterizer);

    uint32_t hash = ctx_rasterizer_poly_to_edges (rasterizer);
    if (hash){};

#if CTX_SHAPE_CACHE
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;
  uint8_t *rasterizer_src = rasterizer->color;
    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)
        {
          rasterizer->scanline = scan_min;
          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;
                    }
                    apply_coverage (rasterizer,
                                                 ( (uint8_t *) rasterizer->buf) + (y-blit_y) * blit_stride + ((int) (x0) * bpp)/8,
                                                 rasterizer_src,
                                                 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) ))
                {
                    apply_coverage (rasterizer,
                                    ( (uint8_t *) rasterizer->buf) + (y-blit_y) * blit_stride + (int) ((x0) * bpp)/8, rasterizer_src,
                                    x0,
                                    &shape->data[shape->width * (int) (y-ymin) + xo],
                                    ewidth );
                }
               rasterizer->scanline += CTX_FULL_AA;
            }
          }
         }
        }
      }
    else
#endif
    {
            
    ctx_rasterizer_rasterize_edges (rasterizer, rasterizer->state->gstate.fill_rule
#if CTX_SHAPE_CACHE
                                    , NULL
#endif
                                   );
    }
  }
#if CTX_FAST_FILL_RECT
done:
#endif
  if (CTX_UNLIKELY(rasterizer->preserve))
    {
      memcpy (rasterizer->edge_list.entries, temp, sizeof (temp) );
      rasterizer->edge_list.count = preserved_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_TERM
#if CTX_BRAILLE_TEXT
  float font_size = 0;
  int ch = 1;
  int cw = 1;

  if (rasterizer->term_glyphs)
  {
    float tx = 0;
    font_size = rasterizer->state->gstate.font_size;

    ch = (int)ctx_term_get_cell_height (rasterizer->backend.ctx);
    cw = (int)ctx_term_get_cell_width (rasterizer->backend.ctx);

    _ctx_user_to_device_distance (rasterizer->state, &tx, &font_size);
  }
  if (rasterizer->term_glyphs && !stroke &&
      fabsf (font_size - ch) < 0.5f)
  {
    float tx = rasterizer->x;
    float ty = rasterizer->y;
    _ctx_user_to_device (rasterizer->state, &tx, &ty);
    int col = (int)(tx / cw + 1);
    int row = (int)(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
#endif
  _ctx_glyph (rasterizer->backend.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_TERM
#if CTX_BRAILLE_TEXT
  float font_size = 0;
  if (rasterizer->term_glyphs)
  {
    float tx = 0;
    font_size = rasterizer->state->gstate.font_size;
    _ctx_user_to_device_distance (rasterizer->state, &tx, &font_size);
  }
  int   ch = (int)ctx_term_get_cell_height (rasterizer->backend.ctx);
  int   cw = (int)ctx_term_get_cell_width (rasterizer->backend.ctx);

  if (rasterizer->term_glyphs && !stroke &&
      fabsf (font_size - ch) < 0.5f)
  {
    float tx = rasterizer->x;
    float ty = rasterizer->y;
    _ctx_user_to_device (rasterizer->state, &tx, &ty);
    int col = (int)(tx / cw + 1);
    int row = (int)(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
#endif
  {
    _ctx_text (rasterizer->backend.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->backend.ctx, font_name);
}

static void
ctx_rasterizer_arc (CtxRasterizer *rasterizer,
                    float          x,
                    float          y,
                    float          radius,
                    float          start_angle,
                    float          end_angle,
                    int            anticlockwise)
{
  float factor = ctx_matrix_get_scale (&rasterizer->state->gstate.transform);
  int full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS;
  full_segments = (int)(factor * radius * CTX_PI * 2 / 4.0f);
  if (full_segments > CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS)
    { full_segments = CTX_RASTERIZER_MAX_CIRCLE_SEGMENTS; }
  if (full_segments < 42) full_segments = 42;
  float step = CTX_PI*2.0f/full_segments;
  int steps;

  if (end_angle < -30.0f)
    end_angle = -30.0f;
  if (start_angle < -30.0f)
    start_angle = -30.0f;
  if (end_angle > 30.0f)
    end_angle = 30.0f;
  if (start_angle > 30.0f)
    start_angle = 30.0f;

  if (radius <= 0.0001f)
          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 )  )
    {
      steps = full_segments - 1;
    }
  else
#endif
    {
      if (anticlockwise)
      steps = (int)((start_angle - end_angle) / (CTX_PI*2) * full_segments);
      else
      steps = (int)((end_angle - start_angle) / (CTX_PI*2) * full_segments);
   // 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)
{
  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);
}

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

#if CTX_STROKE_1PX

static void
ctx_rasterizer_pset (CtxRasterizer *rasterizer, int x, int y, uint8_t cov)
{
  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);

  int blit_stride = rasterizer->blit_stride;
  int pitch = rasterizer->format->bpp / 8;

  uint8_t *dst = ( (uint8_t *) rasterizer->buf) + y * blit_stride + x * pitch;
  rasterizer->apply_coverage (rasterizer, dst, rasterizer->color, x, &cov, 1);
}


static inline void
ctx_rasterizer_stroke_1px_segment (CtxRasterizer *rasterizer,
                                   float x0, float y0,
                                   float x1, float y1)
{
  void (*apply_coverage)(CtxRasterizer *r, uint8_t *dst, uint8_t *src,
                         int x, uint8_t *coverage, unsigned int count) =
      rasterizer->apply_coverage;
  uint8_t *rasterizer_src = rasterizer->color;
  int pitch = rasterizer->format->bpp / 8;
  int blit_stride = rasterizer->blit_stride;

  x1 += 0.5f;
  y1 += 0.5f;
  x0 += 0.5f;
  y0 += 0.5f;

  float dxf = (x1 - x0);
  float dyf = (y1 - y0);
  int tx = (x0)* 65536;
  int ty = (y0)* 65536;

  int blit_width = rasterizer->blit_width;
  int blit_height = rasterizer->blit_height;

  if (dxf*dxf>dyf*dyf)
  {
    int length = abs((int)dxf);
    int dy = (dyf * 65536)/(length);
    int x = tx >> 16;

    if (dxf < 0.0f)
    {
      ty = (y1)* 65536;
      x = (x1); 
      dy *= -1;
    }
    int i = 0;
    int sblit_height = blit_height << 16;

    for (; i < length && x < 0; ++i, ++x, ty += dy);
    for (; i < length && x < blit_width && (ty<0 || (ty>=sblit_height+1))
         ; ++i, ++x, ty += dy);

    for (; i < length && x < blit_width && (ty<65536 || (ty>=sblit_height))
         ; ++i, ++x, ty += dy)
    {
      int y = ty>>16;
      int ypos = (ty >> 8) & 0xff;

      ctx_rasterizer_pset (rasterizer, x, y-1, 255-ypos);
      ctx_rasterizer_pset (rasterizer, x, y, ypos);
    }

      {
       for (; i < length && x < blit_width && (ty>65536 && (ty<sblit_height))
            ; ++i, ++x, ty += dy)
       {
         uint8_t *dst = ( (uint8_t *) rasterizer->buf)
                        + ((ty>>16)-1) * blit_stride + x * pitch;
         uint8_t ypos = (ty >> 8) & 0xff;
         uint8_t rcov=255-ypos;
         apply_coverage (rasterizer, dst, rasterizer_src, x, &rcov, 1);
         dst += blit_stride;
         apply_coverage (rasterizer, dst, rasterizer_src, x, &ypos, 1);
       }
      }

    for (; i < length; ++i, ++x, ty += dy)
    {
      int y = ty>>16;
      int ypos = (ty >> 8) & 0xff;
      ctx_rasterizer_pset (rasterizer, x, y-1, 255-ypos);
      ctx_rasterizer_pset (rasterizer, x, y, ypos);
    }

  }
  else
  {
    int length = abs((int)dyf);
    int dx = (dxf * 65536)/(length);
    int y = ty >> 16;

    if (dyf < 0.0f)
    {
      tx = (x1)* 65536;
      y = (y1); 
      dx *= -1;
    }
    int i = 0;

    int sblit_width = blit_width << 16;

    for (; i < length && y < 0; ++i, ++y, tx += dx);

    for (; i < length && y < blit_height && (tx<0 || (tx>=sblit_width+1))
         ; ++i, ++y, tx += dx);
    for (; i < length && y < blit_height && (tx<65536 || (tx>=sblit_width))
         ; ++i, ++y, tx += dx)
    {
      int x = tx>>16;
      int xpos = (tx >> 8) & 0xff;
      ctx_rasterizer_pset (rasterizer, x-1, y, 255-xpos);
      ctx_rasterizer_pset (rasterizer, x, y, xpos);
    }

      {
       for (; i < length && y < blit_height && (tx>65536 && (tx<sblit_width))
            ; ++i, ++y, tx += dx)
       {
         int x = tx>>16;
         uint8_t *dst = ( (uint8_t *) rasterizer->buf)
                       + y * blit_stride + (x-1) * pitch;
         int xpos = (tx >> 8) & 0xff;
         uint8_t cov[2]={255-xpos, xpos};
         apply_coverage (rasterizer, dst, rasterizer_src, x, cov, 2);
       }
      }
    for (; i < length; ++i, ++y, tx += dx)
    {
      int x = tx>>16;
      int xpos = (tx >> 8) & 0xff;
      ctx_rasterizer_pset (rasterizer, x-1, y, 255-xpos);
      ctx_rasterizer_pset (rasterizer, x, y, xpos);
    }
  }
}

static inline 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 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 (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 / CTX_FULL_AA;
              started = 1;
              start = i;
            }
          x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
          y = entry->data.s16[3] * 1.0f / CTX_FULL_AA;
          
          ctx_rasterizer_stroke_1px_segment (rasterizer, prev_x, prev_y, x, y);
          prev_x = x;
          prev_y = y;
        }
      end = i-1;
foo:
      start = end+1;
    }
  ctx_rasterizer_reset (rasterizer);
}

#endif

static void
ctx_rasterizer_stroke (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  CtxSource source_backup;
  int count = rasterizer->edge_list.count;
  if (count == 0)
    return;
  int preserved = rasterizer->preserve;
  float factor = ctx_matrix_get_scale (&gstate->transform);
  float line_width = gstate->line_width * factor;
  if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL)
  {
    source_backup = gstate->source_fill;
    gstate->source_fill = rasterizer->state->gstate.source_stroke;
  }

  rasterizer->comp_op = NULL;
  ctx_composite_setup (rasterizer);

#if CTX_STROKE_1PX
  if ((gstate->line_width * factor <= 0.0f &&
       gstate->line_width * factor > -10.0f)

     ||(    gstate->line_width * factor >= 0.99f 
         && gstate->line_width * factor <= 1.01f 
         && gstate->n_dashes  == 0
        )
     )
  {
    ctx_rasterizer_stroke_1px (rasterizer);
    if (preserved)
    {
      rasterizer->preserve = 0;
    }
    else
    {
      rasterizer->edge_list.count = 0;
    }
    if (gstate->source_stroke.type != CTX_SOURCE_INHERIT_FILL)
      gstate->source_fill = source_backup;

    return;
  }
#endif

  CtxSegment temp[count]; /* copy of already built up path's poly line  */
  memcpy (temp, rasterizer->edge_list.entries, sizeof (temp) );

#if CTX_FAST_FILL_RECT
  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 &&
          (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 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;

        ctx_composite_stroke_rect (rasterizer, x0, y0, x1, y1, line_width);

        goto done;
       }
    }
#endif
  
    {
    {
      if (line_width < 5.0f)
      {
      factor *= 0.89f; /* 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);
      //gstate->transform_type = 0;
      _ctx_transform_prime (rasterizer->state);
      float prev_x = 0.0f;
      float prev_y = 0.0f;
      float half_width_x = line_width/2;
      float half_width_y = half_width_x;

      if (CTX_UNLIKELY(line_width <= 0.0f))
        { // makes 0 width be 1px in user-space; 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 (entry->code == CTX_NEW_EDGE)
                {
                  if (CTX_LIKELY(started))
                    {
                      end = i - 1;
                      goto foo;
                    }
                  prev_x = entry->data.s16[0] / CTX_SUBDIV;
                  prev_y = entry->data.s16[1] / CTX_FULL_AA;
                  started = 1;
                  start = i;
                }
              x = entry->data.s16[2] / CTX_SUBDIV;
              y = entry->data.s16[3] / CTX_FULL_AA;
              float dx = x - prev_x;
              float dy = y - prev_y;
              float length = ctx_fast_hypotf (dx, dy);
              if (length>0.001f)
                {
                  float recip_length = 1.0f/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 / CTX_FULL_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 / CTX_FULL_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_reverse (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 / CTX_FULL_AA;
                      ctx_rasterizer_rectangle_reverse (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 / CTX_FULL_AA;
                  has_prev = 1;
                }
              ctx_rasterizer_rectangle_reverse (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 / CTX_FULL_AA;
                      ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*2, 0, 1);
                      ctx_rasterizer_finish_shape (rasterizer);
                    }
                  x = entry->data.s16[2] * 1.0f / CTX_SUBDIV;
                  y = entry->data.s16[3] * 1.0f / CTX_FULL_AA;
                  has_prev = 1;
                }
              ctx_rasterizer_move_to (rasterizer, x, y);
              ctx_rasterizer_arc (rasterizer, x, y, half_width_x, CTX_PI*2, 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 / CTX_FULL_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;
      //gstate->transform_type = 0;
      _ctx_transform_prime (rasterizer->state);
    }
  }
#if CTX_FAST_FILL_RECT
done:
#endif
  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_destroy (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)
{
  unsigned 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;

  float coords[6][2];

  for (unsigned 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] / CTX_SUBDIV;
          prev_y = entry->data.s16[1] / CTX_FULL_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 / CTX_FULL_AA;
      if (x < minx) { minx = (int)x; }
      if (y < miny) { miny = (int)y; }
      if (x > maxx) { maxx = (int)x; }
      if (y > maxy) { maxy = (int)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);
  }

  {

  float prev_x = 0;
  float prev_y = 0;

    Ctx *ctx = ctx_new_for_framebuffer (clip_buffer->data, blit_width, blit_height,
       blit_width,
       CTX_CLIP_FORMAT);

  for (unsigned 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 / CTX_FULL_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 / CTX_FULL_AA;
      ctx_line_to (ctx, x, y);
    }
    ctx_gray (ctx, 1.0f);
    ctx_fill (ctx);
    ctx_destroy (ctx);
  }

  int maybe_rect = 1;
  rasterizer->clip_rectangle = 0;

  if (CTX_CLIP_FORMAT == CTX_FORMAT_GRAY1)
  {
    unsigned int count = blit_width * blit_height / 8;
    for (unsigned 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_destroy (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, SQZ_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->backend.ctx->texture[0], path);
  ctx_rasterizer_set_texture (rasterizer, 0, x, y);
}
#endif

static void
ctx_rasterizer_rectangle_reverse (CtxRasterizer *rasterizer,
                                  float x,
                                  float y,
                                  float width,
                                  float height)
{
  ctx_rasterizer_move_to (rasterizer, x, y);
  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, 0);
  //ctx_rasterizer_rel_line_to (rasterizer, width/2, 0);
  ctx_rasterizer_finish_shape (rasterizer);
}

static 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 inline float
ctx_gaussian (float x, float mu, float sigma)
{
  float a = ( x- mu) / sigma;
  return ctx_expf (-0.5f * a * a);
}

static inline 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 (Ctx *ctx, CtxCommand *command);

#if CTX_COMPOSITING_GROUPS
static void
ctx_rasterizer_start_group (CtxRasterizer *rasterizer) /* add a radius? */
{
  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->backend.ctx, (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;

  Ctx *ctx = rasterizer->backend.ctx;

  CtxCompositingMode comp = rasterizer->state->gstate.compositing_mode;
  CtxBlend blend = rasterizer->state->gstate.blend_mode;
  CtxExtend extend = rasterizer->state->gstate.extend;
  float global_alpha = rasterizer->state->gstate.global_alpha_f;
  // fetch compositing, blending, global alpha
  ctx_rasterizer_process (ctx, (CtxCommand*)&restore_command);
  ctx_rasterizer_process (ctx, (CtxCommand*)&save_command);
  CtxEntry set_state[4]=
  {
    ctx_u32 (CTX_COMPOSITING_MODE, comp,  0),
    ctx_u32 (CTX_BLEND_MODE,       blend, 0),
    ctx_u32 (CTX_EXTEND,          extend, 0),
    ctx_f  (CTX_GLOBAL_ALPHA,     global_alpha, 0.0)
  };
  ctx_rasterizer_process (ctx, (CtxCommand*)&set_state[0]);
  ctx_rasterizer_process (ctx, (CtxCommand*)&set_state[1]);
  ctx_rasterizer_process (ctx, (CtxCommand*)&set_state[2]);
  ctx_rasterizer_process (ctx, (CtxCommand*)&set_state[3]);
  if (no == 0)
  {
    rasterizer->buf = rasterizer->saved_buf;
  }
  else
  {
    rasterizer->buf = rasterizer->group[no-1]->data;
  }
  // XXX use texture_source ?
   ctx_texture_init (ctx, ".ctx-group", 
                  rasterizer->blit_width, 
                  rasterizer->blit_height,
                                         
                  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 = ctx_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 (ctx, (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 (ctx, (CtxCommand*)commands);
  }
  {
    CtxEntry commands[1] = { ctx_void (CTX_FILL) };
    ctx_rasterizer_process (ctx, (CtxCommand*)commands);
  }
  //ctx_texture_release (rasterizer->backend.ctx, ".ctx-group");
  ctx_buffer_destroy (rasterizer->group[no]);
  rasterizer->group[no] = 0;
  ctx_rasterizer_process (ctx, (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);
  Ctx *ctx = rasterizer->backend.ctx;

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->backend.ctx, SQZ_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 (ctx, (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 (ctx, (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
      }
  }
  //ctx_free (kernel);
  ctx_rasterizer_process (ctx, (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);
  Ctx *ctx = rasterizer->backend.ctx;

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->backend.ctx, SQZ_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 (ctx, (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 (ctx, (CtxCommand*)&set_color_command);
        ctx_rasterizer_process (ctx, (CtxCommand*)&move_to_command);
        rasterizer->in_shadow=1;
        ctx_rasterizer_text (rasterizer, str, 0);
        rasterizer->in_shadow=0;
      }
  }
  ctx_rasterizer_process (ctx, (CtxCommand*)&restore_command);
  move_to_command[0].data.f[0] = x;
  move_to_command[0].data.f[1] = y;
  ctx_rasterizer_process (ctx, (CtxCommand*)&move_to_command);
}

static void
ctx_rasterizer_shadow_fill (CtxRasterizer *rasterizer)
{
  CtxColor color;
  Ctx *ctx = rasterizer->backend.ctx;
  CtxEntry save_command = ctx_void(CTX_SAVE);

  float rgba[4] = {0, 0, 0, 1.0};
  if (ctx_get_color (rasterizer->backend.ctx, SQZ_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 (ctx, (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 (ctx, (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 (ctx, (CtxCommand*)&restore_command);
}
#endif

static void
ctx_rasterizer_line_dash (CtxRasterizer *rasterizer, unsigned int count, float *dashes)
{
  if (!dashes)
  {
    rasterizer->state->gstate.n_dashes = 0;
    return;
  }
  count = CTX_MIN(count, CTX_MAX_DASHES);
  rasterizer->state->gstate.n_dashes = count;
  memcpy(&rasterizer->state->gstate.dashes[0], dashes, count * sizeof(float));
  for (unsigned 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 (Ctx *ctx, CtxCommand *command)
{
  CtxEntry      *entry      = &command->entry;
  CtxRasterizer *rasterizer = (CtxRasterizer *) ctx->backend;
  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->backend.ctx, SQZ_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:
        if (ctx->bail) break;
        ctx_rasterizer_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_LINE_TO:
        if (ctx->bail) break;
        ctx_rasterizer_rel_line_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_MOVE_TO:
        if (ctx->bail) break;
        ctx_rasterizer_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_REL_MOVE_TO:
        if (ctx->bail) break;
        ctx_rasterizer_rel_move_to (rasterizer, c->c.x0, c->c.y0);
        break;
      case CTX_CURVE_TO:
        if (ctx->bail) break;
        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:
        if (ctx->bail) break;
        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:
        if (ctx->bail) break;
        ctx_rasterizer_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_REL_QUAD_TO:
        if (ctx->bail) break;
        ctx_rasterizer_rel_quad_to (rasterizer, c->c.x0, c->c.y0, c->c.x1, c->c.y1);
        break;
      case CTX_ARC:
        if (ctx->bail) break;
        ctx_rasterizer_arc (rasterizer, c->arc.x, c->arc.y, c->arc.radius, c->arc.angle1, c->arc.angle2, (int)c->arc.direction);
        break;
      case CTX_RECTANGLE:
        if (ctx->bail) break;
        ctx_rasterizer_rectangle (rasterizer, c->rectangle.x, c->rectangle.y,
                                  c->rectangle.width, c->rectangle.height);
        break;
      case CTX_ROUND_RECTANGLE:
        if (ctx->bail) break;
        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),
                        ctx_arg_float (6), ctx_arg_float (7),
                        ctx_arg_float (8));
        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->gradient_cache_valid = 0;
        rasterizer->comp_op = NULL;
        break;
      case CTX_RADIAL_GRADIENT:
        ctx_state_gradient_clear_stops (state);
        rasterizer->gradient_cache_valid = 0;
        rasterizer->comp_op = NULL;
        break;
#endif
      case CTX_PRESERVE:
        rasterizer->preserve = 1;
        break;
      case CTX_COLOR:
      case CTX_COMPOSITING_MODE:
      case CTX_BLEND_MODE:
      case CTX_EXTEND:
        rasterizer->comp_op = NULL;
        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 (unsigned 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 == SQZ_clip)
          {
            clear_clip = 1;
          }
        }
        /* FALLTHROUGH */
      case CTX_ROTATE:
      case CTX_SCALE:
      case CTX_APPLY_TRANSFORM:
      case CTX_TRANSLATE:
      case CTX_IDENTITY:
        /* FALLTHROUGH */
      case CTX_SAVE:
        rasterizer->comp_op = NULL;
        ctx_interpret_transforms (state, entry, NULL);
        if (clear_clip)
        {
          ctx_rasterizer_clip_reset (rasterizer);
          for (unsigned 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 == SQZ_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 (rasterizer->edge_list.count == 0)break;
#if CTX_ENABLE_SHADOW_BLUR
        if (state->gstate.shadow_blur > 0.0f &&
            !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);

          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);
      //state->gstate.transform_type = 0;
      _ctx_transform_prime (state);
      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 / CTX_FULL_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 / CTX_FULL_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 / CTX_FULL_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;
        //state->gstate.transform_type = 0;
        _ctx_transform_prime (state);
        }
        ctx_rasterizer_stroke (rasterizer);
        }
        ctx_rasterizer_reset (rasterizer);

        break;
      case CTX_FONT:
        ctx_rasterizer_set_font (rasterizer, ctx_arg_string() );
        break;
      case CTX_TEXT:
        if (ctx->bail)
        {
          _ctx_text (rasterizer->backend.ctx, ctx_arg_string(), 0, 0);
          break;
        }

        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:
        if (ctx->bail) break;
        {
        uint32_t unichar = entry[0].data.u32[0];
        uint32_t stroke = unichar &  ((uint32_t)1<<31);
        if (stroke) unichar -= stroke;
        ctx_rasterizer_glyph (rasterizer, entry[0].data.u32[0], stroke);
        }
        break;
      case CTX_PAINT:
        // XXX simplify this with a special case
        ctx_rasterizer_rectangle (rasterizer, -1000.0, -1000.0, 11000, 11000);
        ctx_rasterizer_fill (rasterizer);
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_FILL:
        if (!ctx->bail)
        {
          if (rasterizer->edge_list.count == 0)break;
#if CTX_ENABLE_SHADOW_BLUR
        if (state->gstate.shadow_blur > 0.0f &&
            !rasterizer->in_text)
          ctx_rasterizer_shadow_fill (rasterizer);
#endif
        ctx_rasterizer_fill (rasterizer);
        }
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_START_FRAME:
      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);
}


//static CtxFont *ctx_fonts;
void
ctx_rasterizer_deinit (CtxRasterizer *rasterizer)
{
  //rasterizer->fonts = ctx_fonts;
  ctx_drawlist_deinit (&rasterizer->edge_list);
#if CTX_ENABLE_CLIP
  if (rasterizer->clip_buffer)
  {
    ctx_buffer_destroy (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])
    {
      ctx_free (rasterizer->shape_cache.entries[i]);
      rasterizer->shape_cache.entries[i] = NULL;
    }
#endif
}

void
ctx_rasterizer_destroy (CtxRasterizer *rasterizer)
{
  ctx_rasterizer_deinit (rasterizer);
  ctx_free (rasterizer);
}

CtxAntialias ctx_get_antialias (Ctx *ctx)
{
#if CTX_EVENTS
  if (ctx_backend_is_tiled (ctx))
  {
     CtxTiled *fb = (CtxTiled*)(ctx->backend);
     return fb->antialias;
  }
#endif
  if (ctx_backend_type (ctx) != CTX_BACKEND_RASTERIZER) return CTX_ANTIALIAS_DEFAULT;

  switch (((CtxRasterizer*)(ctx->backend))->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;
  }
}

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;
  }
}

void
ctx_set_antialias (Ctx *ctx, CtxAntialias antialias)
{
#if CTX_TERMINAL_EVENTS
  if (ctx_backend_is_tiled (ctx))
  {
     CtxTiled *fb = (CtxTiled*)(ctx->backend);
     fb->antialias = antialias;
#if CTX_THREADS
     for (int i = 0; i < _ctx_max_threads; i++)
#else
     int i = 0;
#endif
     {
       ctx_set_antialias (fb->host[i], antialias);
     }
     return;
  }
#endif
  if (ctx_backend_type (ctx) != CTX_BACKEND_RASTERIZER) return;

  ((CtxRasterizer*)(ctx->backend))->aa = 
     _ctx_antialias_to_aa (antialias);
  ((CtxRasterizer*)(ctx->backend))->fast_aa = 0;
  if (antialias == CTX_ANTIALIAS_DEFAULT||
      antialias == CTX_ANTIALIAS_FAST)
    ((CtxRasterizer*)(ctx->backend))->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_destroy (rasterizer->clip_buffer);
#endif
  if (rasterizer->edge_list.size)
    ctx_drawlist_deinit (&rasterizer->edge_list);
#if CTX_SHAPE_CACHE
  memset (rasterizer, 0, sizeof (CtxRasterizer) - sizeof (CtxShapeCache));
#else
  memset (rasterizer, 0, sizeof (CtxRasterizer));
#endif
  CtxBackend *backend = (CtxBackend*)rasterizer;
  backend->type = CTX_BACKEND_RASTERIZER;
  backend->process = ctx_rasterizer_process;
  backend->destroy = (CtxDestroyNotify)ctx_rasterizer_destroy;
  backend->ctx     = ctx;
  rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST;
  rasterizer->state       = state;
  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);

#if CTX_GRADIENTS
#if CTX_GRADIENT_CACHE
  rasterizer->gradient_cache_elements = CTX_GRADIENT_CACHE_ELEMENTS;
  rasterizer->gradient_cache_valid = 0;
#endif
#endif

#if static_OPAQUE
  memset (rasterizer->opaque, 255, sizeof (rasterizer->opaque));
#endif

  return rasterizer;
}

Ctx *
ctx_new_for_buffer (CtxBuffer *buffer)
{
  Ctx *ctx = _ctx_new_drawlist (buffer->width, buffer->height);
  ctx_set_backend (ctx,
                    ctx_rasterizer_init ( (CtxRasterizer *) ctx_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_drawlist (width, height);
  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_backend (ctx, r);
  if (pixel_format == CTX_FORMAT_GRAY1) // XXX we get some bugs without it..
  {                                     //     something is going amiss with offsets
    ctx_set_antialias (ctx, CTX_ANTIALIAS_NONE);
  }
  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 *) ctx_malloc (sizeof (CtxState) );
  CtxRasterizer *rasterizer = (CtxRasterizer *) ctx_malloc (sizeof (CtxBackend) );
  ctx_rasterizer_init (rasterizer, state, data, x, y, width, height,
                       stride, pixel_format, CTX_ANTIALIAS_DEFAULT);
}
#endif

#else

#endif


void
ctx_state_gradient_clear_stops (CtxState *state)
{
  state->gradient.n_stops = 0;
}


/****  end of engine ****/
//#include "miniz.h"
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **************************************************************************/



typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1];
typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];
typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- zlib-style API's */

mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
{
    mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16);
    size_t block_len = buf_len % 5552;
    if (!ptr)
        return MZ_ADLER32_INIT;
    while (buf_len)
    {
        for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
        {
            s1 += ptr[0], s2 += s1;
            s1 += ptr[1], s2 += s1;
            s1 += ptr[2], s2 += s1;
            s1 += ptr[3], s2 += s1;
            s1 += ptr[4], s2 += s1;
            s1 += ptr[5], s2 += s1;
            s1 += ptr[6], s2 += s1;
            s1 += ptr[7], s2 += s1;
        }
        for (; i < block_len; ++i)
            s1 += *ptr++, s2 += s1;
        s1 %= 65521U, s2 %= 65521U;
        buf_len -= block_len;
        block_len = 5552;
    }
    return (s2 << 16) + s1;
}

/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */
#if 0
    mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
    {
        static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
                                               0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
        mz_uint32 crcu32 = (mz_uint32)crc;
        if (!ptr)
            return MZ_CRC32_INIT;
        crcu32 = ~crcu32;
        while (buf_len--)
        {
            mz_uint8 b = *ptr++;
            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
        }
        return ~crcu32;
    }
#elif defined(USE_EXTERNAL_MZCRC)
/* If USE_EXTERNAL_CRC is defined, an external module will export the
 * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version.
 * Depending on the impl, it may be necessary to ~ the input/output crc values.
 */
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len);
#else
/* Faster, but larger CPU cache footprint.
 */
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
{
    static const mz_uint32 s_crc_table[256] =
        {
          0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
          0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
          0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
          0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
          0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
          0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
          0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
          0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
          0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
          0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
          0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
          0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
          0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
          0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
          0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
          0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
          0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
          0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
          0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
          0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
          0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
          0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
          0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
          0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
          0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
          0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
          0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
          0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
          0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
          0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
          0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
          0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
          0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
          0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
          0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
          0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
          0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
        };

    mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF;
    const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr;

    while (buf_len >= 4)
    {
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF];
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF];
        pByte_buf += 4;
        buf_len -= 4;
    }

    while (buf_len)
    {
        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
        ++pByte_buf;
        --buf_len;
    }

    return ~crc32;
}
#endif

void mz_free(void *p)
{
    MZ_FREE(p);
}

MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size)
{
    (void)opaque, (void)items, (void)size;
    return MZ_MALLOC(items * size);
}
MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address)
{
    (void)opaque, (void)address;
    MZ_FREE(address);
}
MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size)
{
    (void)opaque, (void)address, (void)items, (void)size;
    return MZ_REALLOC(address, items * size);
}

const char *mz_version(void)
{
    return MZ_VERSION;
}

#ifndef MINIZ_NO_ZLIB_APIS

#ifndef MINIZ_NO_DEFLATE_APIS

int mz_deflateInit(mz_streamp pStream, int level)
{
    return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
}

int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
{
    tdefl_compressor *pComp;
    mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);

    if (!pStream)
        return MZ_STREAM_ERROR;
    if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))
        return MZ_PARAM_ERROR;

    pStream->data_type = 0;
    pStream->adler = MZ_ADLER32_INIT;
    pStream->msg = NULL;
    pStream->reserved = 0;
    pStream->total_in = 0;
    pStream->total_out = 0;
    if (!pStream->zalloc)
        pStream->zalloc = miniz_def_alloc_func;
    if (!pStream->zfree)
        pStream->zfree = miniz_def_free_func;

    pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
    if (!pComp)
        return MZ_MEM_ERROR;

    pStream->state = (struct mz_internal_state *)pComp;

    if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
    {
        mz_deflateEnd(pStream);
        return MZ_PARAM_ERROR;
    }

    return MZ_OK;
}

int mz_deflateReset(mz_streamp pStream)
{
    if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree))
        return MZ_STREAM_ERROR;
    pStream->total_in = pStream->total_out = 0;
    tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags);
    return MZ_OK;
}

int mz_deflate(mz_streamp pStream, int flush)
{
    size_t in_bytes, out_bytes;
    mz_ulong orig_total_in, orig_total_out;
    int mz_status = MZ_OK;

    if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))
        return MZ_STREAM_ERROR;
    if (!pStream->avail_out)
        return MZ_BUF_ERROR;

    if (flush == MZ_PARTIAL_FLUSH)
        flush = MZ_SYNC_FLUSH;

    if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
        return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;

    orig_total_in = pStream->total_in;
    orig_total_out = pStream->total_out;
    for (;;)
    {
        tdefl_status defl_status;
        in_bytes = pStream->avail_in;
        out_bytes = pStream->avail_out;

        defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);

        pStream->next_out += (mz_uint)out_bytes;
        pStream->avail_out -= (mz_uint)out_bytes;
        pStream->total_out += (mz_uint)out_bytes;

        if (defl_status < 0)
        {
            mz_status = MZ_STREAM_ERROR;
            break;
        }
        else if (defl_status == TDEFL_STATUS_DONE)
        {
            mz_status = MZ_STREAM_END;
            break;
        }
        else if (!pStream->avail_out)
            break;
        else if ((!pStream->avail_in) && (flush != MZ_FINISH))
        {
            if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
                break;
            return MZ_BUF_ERROR; /* Can't make forward progress without some input.
 */
        }
    }
    return mz_status;
}

int mz_deflateEnd(mz_streamp pStream)
{
    if (!pStream)
        return MZ_STREAM_ERROR;
    if (pStream->state)
    {
        pStream->zfree(pStream->opaque, pStream->state);
        pStream->state = NULL;
    }
    return MZ_OK;
}

mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
{
    (void)pStream;
    /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */
    return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
}

int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
{
    int status;
    mz_stream stream;
    memset(&stream, 0, sizeof(stream));

    /* In case mz_ulong is 64-bits (argh I hate longs). */
    if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU)
        return MZ_PARAM_ERROR;

    stream.next_in = pSource;
    stream.avail_in = (mz_uint32)source_len;
    stream.next_out = pDest;
    stream.avail_out = (mz_uint32)*pDest_len;

    status = mz_deflateInit(&stream, level);
    if (status != MZ_OK)
        return status;

    status = mz_deflate(&stream, MZ_FINISH);
    if (status != MZ_STREAM_END)
    {
        mz_deflateEnd(&stream);
        return (status == MZ_OK) ? MZ_BUF_ERROR : status;
    }

    *pDest_len = stream.total_out;
    return mz_deflateEnd(&stream);
}

int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
    return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
}

mz_ulong mz_compressBound(mz_ulong source_len)
{
    return mz_deflateBound(NULL, source_len);
}

#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/

#ifndef MINIZ_NO_INFLATE_APIS

typedef struct
{
    tinfl_decompressor m_decomp;
    mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;
    int m_window_bits;
    mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
    tinfl_status m_last_status;
} inflate_state;

int mz_inflateInit2(mz_streamp pStream, int window_bits)
{
    inflate_state *pDecomp;
    if (!pStream)
        return MZ_STREAM_ERROR;
    if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))
        return MZ_PARAM_ERROR;

    pStream->data_type = 0;
    pStream->adler = 0;
    pStream->msg = NULL;
    pStream->total_in = 0;
    pStream->total_out = 0;
    pStream->reserved = 0;
    if (!pStream->zalloc)
        pStream->zalloc = miniz_def_alloc_func;
    if (!pStream->zfree)
        pStream->zfree = miniz_def_free_func;

    pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
    if (!pDecomp)
        return MZ_MEM_ERROR;

    pStream->state = (struct mz_internal_state *)pDecomp;

    tinfl_init(&pDecomp->m_decomp);
    pDecomp->m_dict_ofs = 0;
    pDecomp->m_dict_avail = 0;
    pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
    pDecomp->m_first_call = 1;
    pDecomp->m_has_flushed = 0;
    pDecomp->m_window_bits = window_bits;

    return MZ_OK;
}

int mz_inflateInit(mz_streamp pStream)
{
    return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
}

int mz_inflateReset(mz_streamp pStream)
{
    inflate_state *pDecomp;
    if (!pStream)
        return MZ_STREAM_ERROR;

    pStream->data_type = 0;
    pStream->adler = 0;
    pStream->msg = NULL;
    pStream->total_in = 0;
    pStream->total_out = 0;
    pStream->reserved = 0;

    pDecomp = (inflate_state *)pStream->state;

    tinfl_init(&pDecomp->m_decomp);
    pDecomp->m_dict_ofs = 0;
    pDecomp->m_dict_avail = 0;
    pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
    pDecomp->m_first_call = 1;
    pDecomp->m_has_flushed = 0;
    /* pDecomp->m_window_bits = window_bits */;

    return MZ_OK;
}

int mz_inflate(mz_streamp pStream, int flush)
{
    inflate_state *pState;
    mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
    size_t in_bytes, out_bytes, orig_avail_in;
    tinfl_status status;

    if ((!pStream) || (!pStream->state))
        return MZ_STREAM_ERROR;
    if (flush == MZ_PARTIAL_FLUSH)
        flush = MZ_SYNC_FLUSH;
    if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))
        return MZ_STREAM_ERROR;

    pState = (inflate_state *)pStream->state;
    if (pState->m_window_bits > 0)
        decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
    orig_avail_in = pStream->avail_in;

    first_call = pState->m_first_call;
    pState->m_first_call = 0;
    if (pState->m_last_status < 0)
        return MZ_DATA_ERROR;

    if (pState->m_has_flushed && (flush != MZ_FINISH))
        return MZ_STREAM_ERROR;
    pState->m_has_flushed |= (flush == MZ_FINISH);

    if ((flush == MZ_FINISH) && (first_call))
    {
        /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */
        decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
        in_bytes = pStream->avail_in;
        out_bytes = pStream->avail_out;
        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
        pState->m_last_status = status;
        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tinfl_get_adler32(&pState->m_decomp);
        pStream->next_out += (mz_uint)out_bytes;
        pStream->avail_out -= (mz_uint)out_bytes;
        pStream->total_out += (mz_uint)out_bytes;

        if (status < 0)
            return MZ_DATA_ERROR;
        else if (status != TINFL_STATUS_DONE)
        {
            pState->m_last_status = TINFL_STATUS_FAILED;
            return MZ_BUF_ERROR;
        }
        return MZ_STREAM_END;
    }
    /* flush != MZ_FINISH then we must assume there's more input. */
    if (flush != MZ_FINISH)
        decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;

    if (pState->m_dict_avail)
    {
        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
        pStream->next_out += n;
        pStream->avail_out -= n;
        pStream->total_out += n;
        pState->m_dict_avail -= n;
        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
        return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
    }

    for (;;)
    {
        in_bytes = pStream->avail_in;
        out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;

        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
        pState->m_last_status = status;

        pStream->next_in += (mz_uint)in_bytes;
        pStream->avail_in -= (mz_uint)in_bytes;
        pStream->total_in += (mz_uint)in_bytes;
        pStream->adler = tinfl_get_adler32(&pState->m_decomp);

        pState->m_dict_avail = (mz_uint)out_bytes;

        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
        pStream->next_out += n;
        pStream->avail_out -= n;
        pStream->total_out += n;
        pState->m_dict_avail -= n;
        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);

        if (status < 0)
            return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */
        else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
            return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */
        else if (flush == MZ_FINISH)
        {
            /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */
            if (status == TINFL_STATUS_DONE)
                return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
            /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */
            else if (!pStream->avail_out)
                return MZ_BUF_ERROR;
        }
        else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
            break;
    }

    return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}

int mz_inflateEnd(mz_streamp pStream)
{
    if (!pStream)
        return MZ_STREAM_ERROR;
    if (pStream->state)
    {
        pStream->zfree(pStream->opaque, pStream->state);
        pStream->state = NULL;
    }
    return MZ_OK;
}
int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len)
{
    mz_stream stream;
    int status;
    memset(&stream, 0, sizeof(stream));

    /* In case mz_ulong is 64-bits (argh I hate longs). */
    if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU)
        return MZ_PARAM_ERROR;

    stream.next_in = pSource;
    stream.avail_in = (mz_uint32)*pSource_len;
    stream.next_out = pDest;
    stream.avail_out = (mz_uint32)*pDest_len;

    status = mz_inflateInit(&stream);
    if (status != MZ_OK)
        return status;

    status = mz_inflate(&stream, MZ_FINISH);
    *pSource_len = *pSource_len - stream.avail_in;
    if (status != MZ_STREAM_END)
    {
        mz_inflateEnd(&stream);
        return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
    }
    *pDest_len = stream.total_out;

    return mz_inflateEnd(&stream);
}

int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
    return mz_uncompress2(pDest, pDest_len, pSource, &source_len);
}

#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/

const char *mz_error(int err)
{
    static struct
    {
        int m_err;
        const char *m_pDesc;
    } s_error_descs[] =
        {
          { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
        };
    mz_uint i;
    for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i)
        if (s_error_descs[i].m_err == err)
            return s_error_descs[i].m_pDesc;
    return NULL;
}

#endif /*MINIZ_NO_ZLIB_APIS */

#ifdef __cplusplus
}
#endif

/*
  This is free and unencumbered software released into the public domain.

  Anyone is free to copy, modify, publish, use, compile, sell, or
  distribute this software, either in source code form or as a compiled
  binary, for any purpose, commercial or non-commercial, and by any
  means.

  In jurisdictions that recognize copyright laws, the author or authors
  of this software dedicate any and all copyright interest in the
  software to the public domain. We make this dedication for the benefit
  of the public at large and to the detriment of our heirs and
  successors. We intend this dedication to be an overt act of
  relinquishment in perpetuity of all present and future rights to this
  software under copyright law.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.

  For more information, please refer to <http://unlicense.org/>
*/
/**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **************************************************************************/



#ifndef MINIZ_NO_DEFLATE_APIS

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- Low-level Compression (independent from all decompression API's) */

/* Purposely making these tables static for faster init and thread safety. */
static const mz_uint16 s_tdefl_len_sym[256] =
    {
      257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272,
      273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276,
      277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
      279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280,
      281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281,
      282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282,
      283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283,
      284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285
    };

static const mz_uint8 s_tdefl_len_extra[256] =
    {
      0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 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, 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,
      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, 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, 0
    };

static const mz_uint8 s_tdefl_small_dist_sym[512] =
    {
      0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11,
      11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13,
      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
      14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
      14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
      15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17
    };

static const mz_uint8 s_tdefl_small_dist_extra[512] =
    {
      0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 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, 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, 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, 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 const mz_uint8 s_tdefl_large_dist_sym[128] =
    {
      0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
      26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
      28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
    };

static const mz_uint8 s_tdefl_large_dist_extra[128] =
    {
      0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
      12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13
    };

/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */
typedef struct
{
    mz_uint16 m_key, m_sym_index;
} tdefl_sym_freq;
static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1)
{
    mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2];
    tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1;
    MZ_CLEAR_ARR(hist);
    for (i = 0; i < num_syms; i++)
    {
        mz_uint freq = pSyms0[i].m_key;
        hist[freq & 0xFF]++;
        hist[256 + ((freq >> 8) & 0xFF)]++;
    }
    while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256]))
        total_passes--;
    for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
    {
        const mz_uint32 *pHist = &hist[pass << 8];
        mz_uint offsets[256], cur_ofs = 0;
        for (i = 0; i < 256; i++)
        {
            offsets[i] = cur_ofs;
            cur_ofs += pHist[i];
        }
        for (i = 0; i < num_syms; i++)
            pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
        {
            tdefl_sym_freq *t = pCur_syms;
            pCur_syms = pNew_syms;
            pNew_syms = t;
        }
    }
    return pCur_syms;
}

/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */
static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)
{
    int root, leaf, next, avbl, used, dpth;
    if (n == 0)
        return;
    else if (n == 1)
    {
        A[0].m_key = 1;
        return;
    }
    A[0].m_key += A[1].m_key;
    root = 0;
    leaf = 2;
    for (next = 1; next < n - 1; next++)
    {
        if (leaf >= n || A[root].m_key < A[leaf].m_key)
        {
            A[next].m_key = A[root].m_key;
            A[root++].m_key = (mz_uint16)next;
        }
        else
            A[next].m_key = A[leaf++].m_key;
        if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key))
        {
            A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key);
            A[root++].m_key = (mz_uint16)next;
        }
        else
            A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);
    }
    A[n - 2].m_key = 0;
    for (next = n - 3; next >= 0; next--)
        A[next].m_key = A[A[next].m_key].m_key + 1;
    avbl = 1;
    used = dpth = 0;
    root = n - 2;
    next = n - 1;
    while (avbl > 0)
    {
        while (root >= 0 && (int)A[root].m_key == dpth)
        {
            used++;
            root--;
        }
        while (avbl > used)
        {
            A[next--].m_key = (mz_uint16)(dpth);
            avbl--;
        }
        avbl = 2 * used;
        dpth++;
        used = 0;
    }
}

/* Limits canonical Huffman code table's max code size. */
enum
{
    TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32
};
static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)
{
    int i;
    mz_uint32 total = 0;
    if (code_list_len <= 1)
        return;
    for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++)
        pNum_codes[max_code_size] += pNum_codes[i];
    for (i = max_code_size; i > 0; i--)
        total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));
    while (total != (1UL << max_code_size))
    {
        pNum_codes[max_code_size]--;
        for (i = max_code_size - 1; i > 0; i--)
            if (pNum_codes[i])
            {
                pNum_codes[i]--;
                pNum_codes[i + 1] += 2;
                break;
            }
        total--;
    }
}

static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)
{
    int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE];
    mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1];
    MZ_CLEAR_ARR(num_codes);
    if (static_table)
    {
        for (i = 0; i < table_len; i++)
            num_codes[d->m_huff_code_sizes[table_num][i]]++;
    }
    else
    {
        tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;
        int num_used_syms = 0;
        const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];
        for (i = 0; i < table_len; i++)
            if (pSym_count[i])
            {
                syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i];
                syms0[num_used_syms++].m_sym_index = (mz_uint16)i;
            }

        pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1);
        tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);

        for (i = 0; i < num_used_syms; i++)
            num_codes[pSyms[i].m_key]++;

        tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);

        MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]);
        MZ_CLEAR_ARR(d->m_huff_codes[table_num]);
        for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
            for (l = num_codes[i]; l > 0; l--)
                d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);
    }

    next_code[1] = 0;
    for (j = 0, i = 2; i <= code_size_limit; i++)
        next_code[i] = j = ((j + num_codes[i - 1]) << 1);

    for (i = 0; i < table_len; i++)
    {
        mz_uint rev_code = 0, code, code_size;
        if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0)
            continue;
        code = next_code[code_size]++;
        for (l = code_size; l > 0; l--, code >>= 1)
            rev_code = (rev_code << 1) | (code & 1);
        d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;
    }
}

#define TDEFL_PUT_BITS(b, l)                                       \
    do                                                             \
    {                                                              \
        mz_uint bits = b;                                          \
        mz_uint len = l;                                           \
        MZ_ASSERT(bits <= ((1U << len) - 1U));                     \
        d->m_bit_buffer |= (bits << d->m_bits_in);                 \
        d->m_bits_in += len;                                       \
        while (d->m_bits_in >= 8)                                  \
        {                                                          \
            if (d->m_pOutput_buf < d->m_pOutput_buf_end)           \
                *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \
            d->m_bit_buffer >>= 8;                                 \
            d->m_bits_in -= 8;                                     \
        }                                                          \
    }                                                              \
    MZ_MACRO_END

#define TDEFL_RLE_PREV_CODE_SIZE()                                                                                       \
    {                                                                                                                    \
        if (rle_repeat_count)                                                                                            \
        {                                                                                                                \
            if (rle_repeat_count < 3)                                                                                    \
            {                                                                                                            \
                d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
                while (rle_repeat_count--)                                                                               \
                    packed_code_sizes[num_packed_code_sizes++] = prev_code_size;                                         \
            }                                                                                                            \
            else                                                                                                         \
            {                                                                                                            \
                d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1);                                        \
                packed_code_sizes[num_packed_code_sizes++] = 16;                                                         \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3);                           \
            }                                                                                                            \
            rle_repeat_count = 0;                                                                                        \
        }                                                                                                                \
    }

#define TDEFL_RLE_ZERO_CODE_SIZE()                                                         \
    {                                                                                      \
        if (rle_z_count)                                                                   \
        {                                                                                  \
            if (rle_z_count < 3)                                                           \
            {                                                                              \
                d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count);  \
                while (rle_z_count--)                                                      \
                    packed_code_sizes[num_packed_code_sizes++] = 0;                        \
            }                                                                              \
            else if (rle_z_count <= 10)                                                    \
            {                                                                              \
                d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1);          \
                packed_code_sizes[num_packed_code_sizes++] = 17;                           \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3);  \
            }                                                                              \
            else                                                                           \
            {                                                                              \
                d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1);          \
                packed_code_sizes[num_packed_code_sizes++] = 18;                           \
                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \
            }                                                                              \
            rle_z_count = 0;                                                               \
        }                                                                                  \
    }

static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };

static void tdefl_start_dynamic_block(tdefl_compressor *d)
{
    int num_lit_codes, num_dist_codes, num_bit_lengths;
    mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
    mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;

    d->m_huff_count[0][256] = 1;

    tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);
    tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);

    for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--)
        if (d->m_huff_code_sizes[0][num_lit_codes - 1])
            break;
    for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--)
        if (d->m_huff_code_sizes[1][num_dist_codes - 1])
            break;

    memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
    memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
    total_code_sizes_to_pack = num_lit_codes + num_dist_codes;
    num_packed_code_sizes = 0;
    rle_z_count = 0;
    rle_repeat_count = 0;

    memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);
    for (i = 0; i < total_code_sizes_to_pack; i++)
    {
        mz_uint8 code_size = code_sizes_to_pack[i];
        if (!code_size)
        {
            TDEFL_RLE_PREV_CODE_SIZE();
            if (++rle_z_count == 138)
            {
                TDEFL_RLE_ZERO_CODE_SIZE();
            }
        }
        else
        {
            TDEFL_RLE_ZERO_CODE_SIZE();
            if (code_size != prev_code_size)
            {
                TDEFL_RLE_PREV_CODE_SIZE();
                d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1);
                packed_code_sizes[num_packed_code_sizes++] = code_size;
            }
            else if (++rle_repeat_count == 6)
            {
                TDEFL_RLE_PREV_CODE_SIZE();
            }
        }
        prev_code_size = code_size;
    }
    if (rle_repeat_count)
    {
        TDEFL_RLE_PREV_CODE_SIZE();
    }
    else
    {
        TDEFL_RLE_ZERO_CODE_SIZE();
    }

    tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);

    TDEFL_PUT_BITS(2, 2);

    TDEFL_PUT_BITS(num_lit_codes - 257, 5);
    TDEFL_PUT_BITS(num_dist_codes - 1, 5);

    for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--)
        if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]])
            break;
    num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1));
    TDEFL_PUT_BITS(num_bit_lengths - 4, 4);
    for (i = 0; (int)i < num_bit_lengths; i++)
        TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);

    for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;)
    {
        mz_uint code = packed_code_sizes[packed_code_sizes_index++];
        MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);
        TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
        if (code >= 16)
            TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
    }
}

static void tdefl_start_static_block(tdefl_compressor *d)
{
    mz_uint i;
    mz_uint8 *p = &d->m_huff_code_sizes[0][0];

    for (i = 0; i <= 143; ++i)
        *p++ = 8;
    for (; i <= 255; ++i)
        *p++ = 9;
    for (; i <= 279; ++i)
        *p++ = 7;
    for (; i <= 287; ++i)
        *p++ = 8;

    memset(d->m_huff_code_sizes[1], 5, 32);

    tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);
    tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);

    TDEFL_PUT_BITS(1, 2);
}

static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
    mz_uint flags;
    mz_uint8 *pLZ_codes;
    mz_uint8 *pOutput_buf = d->m_pOutput_buf;
    mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;
    mz_uint64 bit_buffer = d->m_bit_buffer;
    mz_uint bits_in = d->m_bits_in;

#define TDEFL_PUT_BITS_FAST(b, l)                    \
    {                                                \
        bit_buffer |= (((mz_uint64)(b)) << bits_in); \
        bits_in += (l);                              \
    }

    flags = 1;
    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)
    {
        if (flags == 1)
            flags = *pLZ_codes++ | 0x100;

        if (flags & 1)
        {
            mz_uint s0, s1, n0, n1, sym, num_extra_bits;
            mz_uint match_len = pLZ_codes[0];
            match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));
            pLZ_codes += 3;

            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

            /* This sequence coaxes MSVC into using cmov's vs. jmp's. */
            s0 = s_tdefl_small_dist_sym[match_dist & 511];
            n0 = s_tdefl_small_dist_extra[match_dist & 511];
            s1 = s_tdefl_large_dist_sym[match_dist >> 8];
            n1 = s_tdefl_large_dist_extra[match_dist >> 8];
            sym = (match_dist < 512) ? s0 : s1;
            num_extra_bits = (match_dist < 512) ? n0 : n1;

            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
        }
        else
        {
            mz_uint lit = *pLZ_codes++;
            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

            if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
            {
                flags >>= 1;
                lit = *pLZ_codes++;
                MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
                TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

                if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
                {
                    flags >>= 1;
                    lit = *pLZ_codes++;
                    MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
                    TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
                }
            }
        }

        if (pOutput_buf >= d->m_pOutput_buf_end)
            return MZ_FALSE;

        memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64));
        pOutput_buf += (bits_in >> 3);
        bit_buffer >>= (bits_in & ~7);
        bits_in &= 7;
    }

#undef TDEFL_PUT_BITS_FAST

    d->m_pOutput_buf = pOutput_buf;
    d->m_bits_in = 0;
    d->m_bit_buffer = 0;

    while (bits_in)
    {
        mz_uint32 n = MZ_MIN(bits_in, 16);
        TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);
        bit_buffer >>= n;
        bits_in -= n;
    }

    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

    return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#else
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
    mz_uint flags;
    mz_uint8 *pLZ_codes;

    flags = 1;
    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)
    {
        if (flags == 1)
            flags = *pLZ_codes++ | 0x100;
        if (flags & 1)
        {
            mz_uint sym, num_extra_bits;
            mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));
            pLZ_codes += 3;

            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
            TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

            if (match_dist < 512)
            {
                sym = s_tdefl_small_dist_sym[match_dist];
                num_extra_bits = s_tdefl_small_dist_extra[match_dist];
            }
            else
            {
                sym = s_tdefl_large_dist_sym[match_dist >> 8];
                num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];
            }
            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
            TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
        }
        else
        {
            mz_uint lit = *pLZ_codes++;
            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
            TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
        }
    }

    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

    return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */

static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)
{
    if (static_block)
        tdefl_start_static_block(d);
    else
        tdefl_start_dynamic_block(d);
    return tdefl_compress_lz_codes(d);
}

static const mz_uint s_tdefl_num_probes[11];

static int tdefl_flush_block(tdefl_compressor *d, int flush)
{
    mz_uint saved_bit_buf, saved_bits_in;
    mz_uint8 *pSaved_output_buf;
    mz_bool comp_block_succeeded = MZ_FALSE;
    int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;
    mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;

    d->m_pOutput_buf = pOutput_buf_start;
    d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;

    MZ_ASSERT(!d->m_output_flush_remaining);
    d->m_output_flush_ofs = 0;
    d->m_output_flush_remaining = 0;

    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);
    d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);

    if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))
    {
        const mz_uint8 cmf = 0x78;
        mz_uint8 flg, flevel = 3;
        mz_uint header, i, n = sizeof(s_tdefl_num_probes) / sizeof(mz_uint);

        /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */
        for (i = 0; i < n; i++)
            if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break;

        if (i < 2)
            flevel = 0;
        else if (i < 6)
            flevel = 1;
        else if (i == 6)
            flevel = 2;

        header = cmf << 8 | (flevel << 6);
        header += 31 - (header % 31);
        flg = header & 0xFF;

        TDEFL_PUT_BITS(cmf, 8);
        TDEFL_PUT_BITS(flg, 8);
    }

    TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);

    pSaved_output_buf = d->m_pOutput_buf;
    saved_bit_buf = d->m_bit_buffer;
    saved_bits_in = d->m_bits_in;

    if (!use_raw_block)
        comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));

    /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */
    if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&
        ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size))
    {
        mz_uint i;
        d->m_pOutput_buf = pSaved_output_buf;
        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
        TDEFL_PUT_BITS(0, 2);
        if (d->m_bits_in)
        {
            TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
        }
        for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)
        {
            TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);
        }
        for (i = 0; i < d->m_total_lz_bytes; ++i)
        {
            TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);
        }
    }
    /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */
    else if (!comp_block_succeeded)
    {
        d->m_pOutput_buf = pSaved_output_buf;
        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
        tdefl_compress_block(d, MZ_TRUE);
    }

    if (flush)
    {
        if (flush == TDEFL_FINISH)
        {
            if (d->m_bits_in)
            {
                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
            }
            if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER)
            {
                mz_uint i, a = d->m_adler32;
                for (i = 0; i < 4; i++)
                {
                    TDEFL_PUT_BITS((a >> 24) & 0xFF, 8);
                    a <<= 8;
                }
            }
        }
        else
        {
            mz_uint i, z = 0;
            TDEFL_PUT_BITS(0, 3);
            if (d->m_bits_in)
            {
                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
            }
            for (i = 2; i; --i, z ^= 0xFFFF)
            {
                TDEFL_PUT_BITS(z & 0xFFFF, 16);
            }
        }
    }

    MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);

    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);

    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
    d->m_pLZ_flags = d->m_lz_code_buf;
    d->m_num_flags_left = 8;
    d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes;
    d->m_total_lz_bytes = 0;
    d->m_block_index++;

    if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)
    {
        if (d->m_pPut_buf_func)
        {
            *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
            if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))
                return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);
        }
        else if (pOutput_buf_start == d->m_output_buf)
        {
            int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));
            memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);
            d->m_out_buf_ofs += bytes_to_copy;
            if ((n -= bytes_to_copy) != 0)
            {
                d->m_output_flush_ofs = bytes_to_copy;
                d->m_output_flush_remaining = n;
            }
        }
        else
        {
            d->m_out_buf_ofs += n;
        }
    }

    return d->m_output_flush_remaining;
}

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
#ifdef MINIZ_UNALIGNED_USE_MEMCPY
static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p)
{
	mz_uint16 ret;
	memcpy(&ret, p, sizeof(mz_uint16));
	return ret;
}
static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p)
{
	mz_uint16 ret;
	memcpy(&ret, p, sizeof(mz_uint16));
	return ret;
}
#else
#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p)
#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p)
#endif
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
    const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q;
    mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s);
    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
    if (max_match_len <= match_len)
        return;
    for (;;)
    {
        for (;;)
        {
            if (--num_probes_left == 0)
                return;
#define TDEFL_PROBE                                                                             \
    next_probe_pos = d->m_next[probe_pos];                                                      \
    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \
        return;                                                                                 \
    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                       \
    if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01)                \
        break;
            TDEFL_PROBE;
            TDEFL_PROBE;
            TDEFL_PROBE;
        }
        if (!dist)
            break;
        q = (const mz_uint16 *)(d->m_dict + probe_pos);
        if (TDEFL_READ_UNALIGNED_WORD2(q) != s01)
            continue;
        p = s;
        probe_len = 32;
        do
        {
        } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
                 (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
        if (!probe_len)
        {
            *pMatch_dist = dist;
            *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN);
            break;
        }
        else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len)
        {
            *pMatch_dist = dist;
            if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len)
                break;
            c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);
        }
    }
}
#else
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
    const mz_uint8 *s = d->m_dict + pos, *p, *q;
    mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];
    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
    if (max_match_len <= match_len)
        return;
    for (;;)
    {
        for (;;)
        {
            if (--num_probes_left == 0)
                return;
#define TDEFL_PROBE                                                                               \
    next_probe_pos = d->m_next[probe_pos];                                                        \
    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist))   \
        return;                                                                                   \
    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                         \
    if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \
        break;
            TDEFL_PROBE;
            TDEFL_PROBE;
            TDEFL_PROBE;
        }
        if (!dist)
            break;
        p = s;
        q = d->m_dict + probe_pos;
        for (probe_len = 0; probe_len < max_match_len; probe_len++)
            if (*p++ != *q++)
                break;
        if (probe_len > match_len)
        {
            *pMatch_dist = dist;
            if ((*pMatch_len = match_len = probe_len) == max_match_len)
                return;
            c0 = d->m_dict[pos + match_len];
            c1 = d->m_dict[pos + match_len - 1];
        }
    }
}
#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
#ifdef MINIZ_UNALIGNED_USE_MEMCPY
static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p)
{
	mz_uint32 ret;
	memcpy(&ret, p, sizeof(mz_uint32));
	return ret;
}
#else
#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p)
#endif
static mz_bool tdefl_compress_fast(tdefl_compressor *d)
{
    /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */
    mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;
    mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;
    mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;

    while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))
    {
        const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;
        mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
        mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);
        d->m_src_buf_left -= num_bytes_to_process;
        lookahead_size += num_bytes_to_process;

        while (num_bytes_to_process)
        {
            mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);
            memcpy(d->m_dict + dst_pos, d->m_pSrc, n);
            if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));
            d->m_pSrc += n;
            dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;
            num_bytes_to_process -= n;
        }

        dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);
        if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE))
            break;

        while (lookahead_size >= 4)
        {
            mz_uint cur_match_dist, cur_match_len = 1;
            mz_uint8 *pCur_dict = d->m_dict + cur_pos;
            mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF;
            mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;
            mz_uint probe_pos = d->m_hash[hash];
            d->m_hash[hash] = (mz_uint16)lookahead_pos;

            if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))
            {
                const mz_uint16 *p = (const mz_uint16 *)pCur_dict;
                const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);
                mz_uint32 probe_len = 32;
                do
                {
                } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
                         (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
                cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);
                if (!probe_len)
                    cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;

                if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)))
                {
                    cur_match_len = 1;
                    *pLZ_code_buf++ = (mz_uint8)first_trigram;
                    *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
                    d->m_huff_count[0][(mz_uint8)first_trigram]++;
                }
                else
                {
                    mz_uint32 s0, s1;
                    cur_match_len = MZ_MIN(cur_match_len, lookahead_size);

                    MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));

                    cur_match_dist--;

                    pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);
#ifdef MINIZ_UNALIGNED_USE_MEMCPY
					memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist));
#else
                    *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;
#endif
                    pLZ_code_buf += 3;
                    *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);

                    s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];
                    s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];
                    d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;

                    d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;
                }
            }
            else
            {
                *pLZ_code_buf++ = (mz_uint8)first_trigram;
                *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
                d->m_huff_count[0][(mz_uint8)first_trigram]++;
            }

            if (--num_flags_left == 0)
            {
                num_flags_left = 8;
                pLZ_flags = pLZ_code_buf++;
            }

            total_lz_bytes += cur_match_len;
            lookahead_pos += cur_match_len;
            dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE);
            cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
            MZ_ASSERT(lookahead_size >= cur_match_len);
            lookahead_size -= cur_match_len;

            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
            {
                int n;
                d->m_lookahead_pos = lookahead_pos;
                d->m_lookahead_size = lookahead_size;
                d->m_dict_size = dict_size;
                d->m_total_lz_bytes = total_lz_bytes;
                d->m_pLZ_code_buf = pLZ_code_buf;
                d->m_pLZ_flags = pLZ_flags;
                d->m_num_flags_left = num_flags_left;
                if ((n = tdefl_flush_block(d, 0)) != 0)
                    return (n < 0) ? MZ_FALSE : MZ_TRUE;
                total_lz_bytes = d->m_total_lz_bytes;
                pLZ_code_buf = d->m_pLZ_code_buf;
                pLZ_flags = d->m_pLZ_flags;
                num_flags_left = d->m_num_flags_left;
            }
        }

        while (lookahead_size)
        {
            mz_uint8 lit = d->m_dict[cur_pos];

            total_lz_bytes++;
            *pLZ_code_buf++ = lit;
            *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
            if (--num_flags_left == 0)
            {
                num_flags_left = 8;
                pLZ_flags = pLZ_code_buf++;
            }

            d->m_huff_count[0][lit]++;

            lookahead_pos++;
            dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE);
            cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
            lookahead_size--;

            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
            {
                int n;
                d->m_lookahead_pos = lookahead_pos;
                d->m_lookahead_size = lookahead_size;
                d->m_dict_size = dict_size;
                d->m_total_lz_bytes = total_lz_bytes;
                d->m_pLZ_code_buf = pLZ_code_buf;
                d->m_pLZ_flags = pLZ_flags;
                d->m_num_flags_left = num_flags_left;
                if ((n = tdefl_flush_block(d, 0)) != 0)
                    return (n < 0) ? MZ_FALSE : MZ_TRUE;
                total_lz_bytes = d->m_total_lz_bytes;
                pLZ_code_buf = d->m_pLZ_code_buf;
                pLZ_flags = d->m_pLZ_flags;
                num_flags_left = d->m_num_flags_left;
            }
        }
    }

    d->m_lookahead_pos = lookahead_pos;
    d->m_lookahead_size = lookahead_size;
    d->m_dict_size = dict_size;
    d->m_total_lz_bytes = total_lz_bytes;
    d->m_pLZ_code_buf = pLZ_code_buf;
    d->m_pLZ_flags = pLZ_flags;
    d->m_num_flags_left = num_flags_left;
    return MZ_TRUE;
}
#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */

static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)
{
    d->m_total_lz_bytes++;
    *d->m_pLZ_code_buf++ = lit;
    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1);
    if (--d->m_num_flags_left == 0)
    {
        d->m_num_flags_left = 8;
        d->m_pLZ_flags = d->m_pLZ_code_buf++;
    }
    d->m_huff_count[0][lit]++;
}

static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)
{
    mz_uint32 s0, s1;

    MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));

    d->m_total_lz_bytes += match_len;

    d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);

    match_dist -= 1;
    d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);
    d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8);
    d->m_pLZ_code_buf += 3;

    *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80);
    if (--d->m_num_flags_left == 0)
    {
        d->m_num_flags_left = 8;
        d->m_pLZ_flags = d->m_pLZ_code_buf++;
    }

    s0 = s_tdefl_small_dist_sym[match_dist & 511];
    s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];
    d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;
    d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;
}

static mz_bool tdefl_compress_normal(tdefl_compressor *d)
{
    const mz_uint8 *pSrc = d->m_pSrc;
    size_t src_buf_left = d->m_src_buf_left;
    tdefl_flush flush = d->m_flush;

    while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))
    {
        mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;
        /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */
        if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))
        {
            mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;
            mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];
            mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);
            const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL;
            src_buf_left -= num_bytes_to_process;
            d->m_lookahead_size += num_bytes_to_process;
            while (pSrc != pSrc_end)
            {
                mz_uint8 c = *pSrc++;
                d->m_dict[dst_pos] = c;
                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
                hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
                d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
                d->m_hash[hash] = (mz_uint16)(ins_pos);
                dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
                ins_pos++;
            }
        }
        else
        {
            while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
            {
                mz_uint8 c = *pSrc++;
                mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
                src_buf_left--;
                d->m_dict[dst_pos] = c;
                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
                if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)
                {
                    mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;
                    mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
                    d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
                    d->m_hash[hash] = (mz_uint16)(ins_pos);
                }
            }
        }
        d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);
        if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
            break;

        /* Simple lazy/greedy parsing state machine. */
        len_to_move = 1;
        cur_match_dist = 0;
        cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1);
        cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
        if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))
        {
            if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))
            {
                mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];
                cur_match_len = 0;
                while (cur_match_len < d->m_lookahead_size)
                {
                    if (d->m_dict[cur_pos + cur_match_len] != c)
                        break;
                    cur_match_len++;
                }
                if (cur_match_len < TDEFL_MIN_MATCH_LEN)
                    cur_match_len = 0;
                else
                    cur_match_dist = 1;
            }
        }
        else
        {
            tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);
        }
        if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))
        {
            cur_match_dist = cur_match_len = 0;
        }
        if (d->m_saved_match_len)
        {
            if (cur_match_len > d->m_saved_match_len)
            {
                tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);
                if (cur_match_len >= 128)
                {
                    tdefl_record_match(d, cur_match_len, cur_match_dist);
                    d->m_saved_match_len = 0;
                    len_to_move = cur_match_len;
                }
                else
                {
                    d->m_saved_lit = d->m_dict[cur_pos];
                    d->m_saved_match_dist = cur_match_dist;
                    d->m_saved_match_len = cur_match_len;
                }
            }
            else
            {
                tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);
                len_to_move = d->m_saved_match_len - 1;
                d->m_saved_match_len = 0;
            }
        }
        else if (!cur_match_dist)
            tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);
        else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))
        {
            tdefl_record_match(d, cur_match_len, cur_match_dist);
            len_to_move = cur_match_len;
        }
        else
        {
            d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)];
            d->m_saved_match_dist = cur_match_dist;
            d->m_saved_match_len = cur_match_len;
        }
        /* Move the lookahead forward by len_to_move bytes. */
        d->m_lookahead_pos += len_to_move;
        MZ_ASSERT(d->m_lookahead_size >= len_to_move);
        d->m_lookahead_size -= len_to_move;
        d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE);
        /* Check if it's time to flush the current LZ codes to the internal output buffer. */
        if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
            ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))))
        {
            int n;
            d->m_pSrc = pSrc;
            d->m_src_buf_left = src_buf_left;
            if ((n = tdefl_flush_block(d, 0)) != 0)
                return (n < 0) ? MZ_FALSE : MZ_TRUE;
        }
    }

    d->m_pSrc = pSrc;
    d->m_src_buf_left = src_buf_left;
    return MZ_TRUE;
}

static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)
{
    if (d->m_pIn_buf_size)
    {
        *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
    }

    if (d->m_pOut_buf_size)
    {
        size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);
        memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);
        d->m_output_flush_ofs += (mz_uint)n;
        d->m_output_flush_remaining -= (mz_uint)n;
        d->m_out_buf_ofs += n;

        *d->m_pOut_buf_size = d->m_out_buf_ofs;
    }

    return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)
{
    if (!d)
    {
        if (pIn_buf_size)
            *pIn_buf_size = 0;
        if (pOut_buf_size)
            *pOut_buf_size = 0;
        return TDEFL_STATUS_BAD_PARAM;
    }

    d->m_pIn_buf = pIn_buf;
    d->m_pIn_buf_size = pIn_buf_size;
    d->m_pOut_buf = pOut_buf;
    d->m_pOut_buf_size = pOut_buf_size;
    d->m_pSrc = (const mz_uint8 *)(pIn_buf);
    d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;
    d->m_out_buf_ofs = 0;
    d->m_flush = flush;

    if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||
        (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf))
    {
        if (pIn_buf_size)
            *pIn_buf_size = 0;
        if (pOut_buf_size)
            *pOut_buf_size = 0;
        return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);
    }
    d->m_wants_to_finish |= (flush == TDEFL_FINISH);

    if ((d->m_output_flush_remaining) || (d->m_finished))
        return (d->m_prev_return_status = tdefl_flush_output_buffer(d));

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
    if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&
        ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&
        ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))
    {
        if (!tdefl_compress_fast(d))
            return d->m_prev_return_status;
    }
    else
#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */
    {
        if (!tdefl_compress_normal(d))
            return d->m_prev_return_status;
    }

    if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))
        d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);

    if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))
    {
        if (tdefl_flush_block(d, flush) < 0)
            return d->m_prev_return_status;
        d->m_finished = (flush == TDEFL_FINISH);
        if (flush == TDEFL_FULL_FLUSH)
        {
            MZ_CLEAR_ARR(d->m_hash);
            MZ_CLEAR_ARR(d->m_next);
            d->m_dict_size = 0;
        }
    }

    return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
}

tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)
{
    MZ_ASSERT(d->m_pPut_buf_func);
    return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);
}

tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    d->m_pPut_buf_func = pPut_buf_func;
    d->m_pPut_buf_user = pPut_buf_user;
    d->m_flags = (mz_uint)(flags);
    d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3;
    d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;
    d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;
    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
        MZ_CLEAR_ARR(d->m_hash);
    d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;
    d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;
    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
    d->m_pLZ_flags = d->m_lz_code_buf;
    *d->m_pLZ_flags = 0;
    d->m_num_flags_left = 8;
    d->m_pOutput_buf = d->m_output_buf;
    d->m_pOutput_buf_end = d->m_output_buf;
    d->m_prev_return_status = TDEFL_STATUS_OKAY;
    d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0;
    d->m_adler32 = 1;
    d->m_pIn_buf = NULL;
    d->m_pOut_buf = NULL;
    d->m_pIn_buf_size = NULL;
    d->m_pOut_buf_size = NULL;
    d->m_flush = TDEFL_NO_FLUSH;
    d->m_pSrc = NULL;
    d->m_src_buf_left = 0;
    d->m_out_buf_ofs = 0;
    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
        MZ_CLEAR_ARR(d->m_dict);
    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
    return TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)
{
    return d->m_prev_return_status;
}

mz_uint32 tdefl_get_adler32(tdefl_compressor *d)
{
    return d->m_adler32;
}

mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    tdefl_compressor *pComp;
    mz_bool succeeded;
    if (((buf_len) && (!pBuf)) || (!pPut_buf_func))
        return MZ_FALSE;
    pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
    if (!pComp)
        return MZ_FALSE;
    succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);
    succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);
    MZ_FREE(pComp);
    return succeeded;
}

typedef struct
{
    size_t m_size, m_capacity;
    mz_uint8 *m_pBuf;
    mz_bool m_expandable;
} tdefl_output_buffer;

static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)
{
    tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;
    size_t new_size = p->m_size + len;
    if (new_size > p->m_capacity)
    {
        size_t new_capacity = p->m_capacity;
        mz_uint8 *pNew_buf;
        if (!p->m_expandable)
            return MZ_FALSE;
        do
        {
            new_capacity = MZ_MAX(128U, new_capacity << 1U);
        } while (new_size > new_capacity);
        pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity);
        if (!pNew_buf)
            return MZ_FALSE;
        p->m_pBuf = pNew_buf;
        p->m_capacity = new_capacity;
    }
    memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len);
    p->m_size = new_size;
    return MZ_TRUE;
}

void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
    tdefl_output_buffer out_buf;
    MZ_CLEAR_OBJ(out_buf);
    if (!pOut_len)
        return MZ_FALSE;
    else
        *pOut_len = 0;
    out_buf.m_expandable = MZ_TRUE;
    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
        return NULL;
    *pOut_len = out_buf.m_size;
    return out_buf.m_pBuf;
}

size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
    tdefl_output_buffer out_buf;
    MZ_CLEAR_OBJ(out_buf);
    if (!pOut_buf)
        return 0;
    out_buf.m_pBuf = (mz_uint8 *)pOut_buf;
    out_buf.m_capacity = out_buf_len;
    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
        return 0;
    return out_buf.m_size;
}

static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };

/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */
mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
{
    mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
    if (window_bits > 0)
        comp_flags |= TDEFL_WRITE_ZLIB_HEADER;

    if (!level)
        comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
    else if (strategy == MZ_FILTERED)
        comp_flags |= TDEFL_FILTER_MATCHES;
    else if (strategy == MZ_HUFFMAN_ONLY)
        comp_flags &= ~TDEFL_MAX_PROBES_MASK;
    else if (strategy == MZ_FIXED)
        comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
    else if (strategy == MZ_RLE)
        comp_flags |= TDEFL_RLE_MATCHES;

    return comp_flags;
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */
#endif

/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at
 http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
 This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */
void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)
{
    /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */
    static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
    tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
    tdefl_output_buffer out_buf;
    int i, bpl = w * num_chans, y, z;
    mz_uint32 c;
    *pLen_out = 0;
    if (!pComp)
        return NULL;
    MZ_CLEAR_OBJ(out_buf);
    out_buf.m_expandable = MZ_TRUE;
    out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h);
    if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity)))
    {
        MZ_FREE(pComp);
        return NULL;
    }
    /* write dummy header */
    for (z = 41; z; --z)
        tdefl_output_buffer_putter(&z, 1, &out_buf);
    /* compress image data */
    tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);
    for (y = 0; y < h; ++y)
    {
        tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH);
        tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH);
    }
    if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE)
    {
        MZ_FREE(pComp);
        MZ_FREE(out_buf.m_pBuf);
        return NULL;
    }
    /* write real header */
    *pLen_out = out_buf.m_size - 41;
    {
        static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };
        mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d,
                                0x0a, 0x1a, 0x0a, 0x00, 0x00,
                                0x00, 0x0d, 0x49, 0x48, 0x44,
                                0x52, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x08,
                                0x00, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x00, 0x00, 0x00,
                                0x00, 0x00, 0x49, 0x44, 0x41,
                                0x54 };
        pnghdr[18] = (mz_uint8)(w >> 8);
        pnghdr[19] = (mz_uint8)w;
        pnghdr[22] = (mz_uint8)(h >> 8);
        pnghdr[23] = (mz_uint8)h;
        pnghdr[25] = chans[num_chans];
        pnghdr[33] = (mz_uint8)(*pLen_out >> 24);
        pnghdr[34] = (mz_uint8)(*pLen_out >> 16);
        pnghdr[35] = (mz_uint8)(*pLen_out >> 8);
        pnghdr[36] = (mz_uint8)*pLen_out;
        c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17);
        for (i = 0; i < 4; ++i, c <<= 8)
            ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24);
        memcpy(out_buf.m_pBuf, pnghdr, 41);
    }
    /* write footer (IDAT CRC-32, followed by IEND chunk) */
    if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf))
    {
        *pLen_out = 0;
        MZ_FREE(pComp);
        MZ_FREE(out_buf.m_pBuf);
        return NULL;
    }
    c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4);
    for (i = 0; i < 4; ++i, c <<= 8)
        (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24);
    /* compute final size of file, grab compressed data buffer and return */
    *pLen_out += 57;
    MZ_FREE(pComp);
    return out_buf.m_pBuf;
}
void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)
{
    /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */
    return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);
}

#ifndef MINIZ_NO_MALLOC
/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */
/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */
/* structure size and allocation mechanism. */
tdefl_compressor *tdefl_compressor_alloc(void)
{
    return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
}

void tdefl_compressor_free(tdefl_compressor *pComp)
{
    MZ_FREE(pComp);
}
#endif

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/
 /**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **************************************************************************/



#ifndef MINIZ_NO_INFLATE_APIS

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- Low-level Decompression (completely independent from all compression API's) */

#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
#define TINFL_MEMSET(p, c, l) memset(p, c, l)

#define TINFL_CR_BEGIN  \
    switch (r->m_state) \
    {                   \
        case 0:
#define TINFL_CR_RETURN(state_index, result) \
    do                                       \
    {                                        \
        status = result;                     \
        r->m_state = state_index;            \
        goto common_exit;                    \
        case state_index:;                   \
    }                                        \
    MZ_MACRO_END
#define TINFL_CR_RETURN_FOREVER(state_index, result) \
    do                                               \
    {                                                \
        for (;;)                                     \
        {                                            \
            TINFL_CR_RETURN(state_index, result);    \
        }                                            \
    }                                                \
    MZ_MACRO_END
#define TINFL_CR_FINISH }

#define TINFL_GET_BYTE(state_index, c)                                                                                                                           \
    do                                                                                                                                                           \
    {                                                                                                                                                            \
        while (pIn_buf_cur >= pIn_buf_end)                                                                                                                       \
        {                                                                                                                                                        \
            TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \
        }                                                                                                                                                        \
        c = *pIn_buf_cur++;                                                                                                                                      \
    }                                                                                                                                                            \
    MZ_MACRO_END

#define TINFL_NEED_BITS(state_index, n)                \
    do                                                 \
    {                                                  \
        mz_uint c;                                     \
        TINFL_GET_BYTE(state_index, c);                \
        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \
        num_bits += 8;                                 \
    } while (num_bits < (mz_uint)(n))
#define TINFL_SKIP_BITS(state_index, n)      \
    do                                       \
    {                                        \
        if (num_bits < (mz_uint)(n))         \
        {                                    \
            TINFL_NEED_BITS(state_index, n); \
        }                                    \
        bit_buf >>= (n);                     \
        num_bits -= (n);                     \
    }                                        \
    MZ_MACRO_END
#define TINFL_GET_BITS(state_index, b, n)    \
    do                                       \
    {                                        \
        if (num_bits < (mz_uint)(n))         \
        {                                    \
            TINFL_NEED_BITS(state_index, n); \
        }                                    \
        b = bit_buf & ((1 << (n)) - 1);      \
        bit_buf >>= (n);                     \
        num_bits -= (n);                     \
    }                                        \
    MZ_MACRO_END

/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */
/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */
/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */
/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */
#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree)                    \
    do                                                                         \
    {                                                                          \
        temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)];                \
        if (temp >= 0)                                                         \
        {                                                                      \
            code_len = temp >> 9;                                              \
            if ((code_len) && (num_bits >= code_len))                          \
                break;                                                         \
        }                                                                      \
        else if (num_bits > TINFL_FAST_LOOKUP_BITS)                            \
        {                                                                      \
            code_len = TINFL_FAST_LOOKUP_BITS;                                 \
            do                                                                 \
            {                                                                  \
                temp = pTree[~temp + ((bit_buf >> code_len++) & 1)];           \
            } while ((temp < 0) && (num_bits >= (code_len + 1)));              \
            if (temp >= 0)                                                     \
                break;                                                         \
        }                                                                      \
        TINFL_GET_BYTE(state_index, c);                                        \
        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits);                         \
        num_bits += 8;                                                         \
    } while (num_bits < 15);

/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */
/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */
/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */
/* The slow path is only executed at the very end of the input buffer. */
/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */
/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */
#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree)                                                                         \
    do                                                                                                                              \
    {                                                                                                                               \
        int temp;                                                                                                                   \
        mz_uint code_len, c;                                                                                                        \
        if (num_bits < 15)                                                                                                          \
        {                                                                                                                           \
            if ((pIn_buf_end - pIn_buf_cur) < 2)                                                                                    \
            {                                                                                                                       \
                TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree);                                                                \
            }                                                                                                                       \
            else                                                                                                                    \
            {                                                                                                                       \
                bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \
                pIn_buf_cur += 2;                                                                                                   \
                num_bits += 16;                                                                                                     \
            }                                                                                                                       \
        }                                                                                                                           \
        if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)                                                          \
            code_len = temp >> 9, temp &= 511;                                                                                      \
        else                                                                                                                        \
        {                                                                                                                           \
            code_len = TINFL_FAST_LOOKUP_BITS;                                                                                      \
            do                                                                                                                      \
            {                                                                                                                       \
                temp = pTree[~temp + ((bit_buf >> code_len++) & 1)];                                                                \
            } while (temp < 0);                                                                                                     \
        }                                                                                                                           \
        sym = temp;                                                                                                                 \
        bit_buf >>= code_len;                                                                                                       \
        num_bits -= code_len;                                                                                                       \
    }                                                                                                                               \
    MZ_MACRO_END

static void tinfl_clear_tree(tinfl_decompressor *r)
{
    if (r->m_type == 0)
        MZ_CLEAR_ARR(r->m_tree_0);
    else if (r->m_type == 1)
        MZ_CLEAR_ARR(r->m_tree_1);
    else
        MZ_CLEAR_ARR(r->m_tree_2);
}

tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)
{
    static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 };
    static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 };
    static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 };
    static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };
    static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
    static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 };

    mz_int16 *pTrees[3];
    mz_uint8 *pCode_sizes[3];

    tinfl_status status = TINFL_STATUS_FAILED;
    mz_uint32 num_bits, dist, counter, num_extra;
    tinfl_bit_buf_t bit_buf;
    const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
    mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL;
    size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;

    /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */
    if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start))
    {
        *pIn_buf_size = *pOut_buf_size = 0;
        return TINFL_STATUS_BAD_PARAM;
    }

    pTrees[0] = r->m_tree_0;
    pTrees[1] = r->m_tree_1;
    pTrees[2] = r->m_tree_2;
    pCode_sizes[0] = r->m_code_size_0;
    pCode_sizes[1] = r->m_code_size_1;
    pCode_sizes[2] = r->m_code_size_2;

    num_bits = r->m_num_bits;
    bit_buf = r->m_bit_buf;
    dist = r->m_dist;
    counter = r->m_counter;
    num_extra = r->m_num_extra;
    dist_from_out_buf_start = r->m_dist_from_out_buf_start;
    TINFL_CR_BEGIN

    bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0;
    r->m_z_adler32 = r->m_check_adler32 = 1;
    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
    {
        TINFL_GET_BYTE(1, r->m_zhdr0);
        TINFL_GET_BYTE(2, r->m_zhdr1);
        counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
        if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
            counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
        if (counter)
        {
            TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED);
        }
    }

    do
    {
        TINFL_GET_BITS(3, r->m_final, 3);
        r->m_type = r->m_final >> 1;
        if (r->m_type == 0)
        {
            TINFL_SKIP_BITS(5, num_bits & 7);
            for (counter = 0; counter < 4; ++counter)
            {
                if (num_bits)
                    TINFL_GET_BITS(6, r->m_raw_header[counter], 8);
                else
                    TINFL_GET_BYTE(7, r->m_raw_header[counter]);
            }
            if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8))))
            {
                TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED);
            }
            while ((counter) && (num_bits))
            {
                TINFL_GET_BITS(51, dist, 8);
                while (pOut_buf_cur >= pOut_buf_end)
                {
                    TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT);
                }
                *pOut_buf_cur++ = (mz_uint8)dist;
                counter--;
            }
            while (counter)
            {
                size_t n;
                while (pOut_buf_cur >= pOut_buf_end)
                {
                    TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT);
                }
                while (pIn_buf_cur >= pIn_buf_end)
                {
                    TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS);
                }
                n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
                TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n);
                pIn_buf_cur += n;
                pOut_buf_cur += n;
                counter -= (mz_uint)n;
            }
        }
        else if (r->m_type == 3)
        {
            TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
        }
        else
        {
            if (r->m_type == 1)
            {
                mz_uint8 *p = r->m_code_size_0;
                mz_uint i;
                r->m_table_sizes[0] = 288;
                r->m_table_sizes[1] = 32;
                TINFL_MEMSET(r->m_code_size_1, 5, 32);
                for (i = 0; i <= 143; ++i)
                    *p++ = 8;
                for (; i <= 255; ++i)
                    *p++ = 9;
                for (; i <= 279; ++i)
                    *p++ = 7;
                for (; i <= 287; ++i)
                    *p++ = 8;
            }
            else
            {
                for (counter = 0; counter < 3; counter++)
                {
                    TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]);
                    r->m_table_sizes[counter] += s_min_table_sizes[counter];
                }
                MZ_CLEAR_ARR(r->m_code_size_2);
                for (counter = 0; counter < r->m_table_sizes[2]; counter++)
                {
                    mz_uint s;
                    TINFL_GET_BITS(14, s, 3);
                    r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s;
                }
                r->m_table_sizes[2] = 19;
            }
            for (; (int)r->m_type >= 0; r->m_type--)
            {
                int tree_next, tree_cur;
                mz_int16 *pLookUp;
                mz_int16 *pTree;
                mz_uint8 *pCode_size;
                mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16];
                pLookUp = r->m_look_up[r->m_type];
                pTree = pTrees[r->m_type];
                pCode_size = pCode_sizes[r->m_type];
                MZ_CLEAR_ARR(total_syms);
                TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0]));
                tinfl_clear_tree(r);
                for (i = 0; i < r->m_table_sizes[r->m_type]; ++i)
                    total_syms[pCode_size[i]]++;
                used_syms = 0, total = 0;
                next_code[0] = next_code[1] = 0;
                for (i = 1; i <= 15; ++i)
                {
                    used_syms += total_syms[i];
                    next_code[i + 1] = (total = ((total + total_syms[i]) << 1));
                }
                if ((65536 != total) && (used_syms > 1))
                {
                    TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
                }
                for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
                {
                    mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index];
                    if (!code_size)
                        continue;
                    cur_code = next_code[code_size]++;
                    for (l = code_size; l > 0; l--, cur_code >>= 1)
                        rev_code = (rev_code << 1) | (cur_code & 1);
                    if (code_size <= TINFL_FAST_LOOKUP_BITS)
                    {
                        mz_int16 k = (mz_int16)((code_size << 9) | sym_index);
                        while (rev_code < TINFL_FAST_LOOKUP_SIZE)
                        {
                            pLookUp[rev_code] = k;
                            rev_code += (1 << code_size);
                        }
                        continue;
                    }
                    if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)]))
                    {
                        pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next;
                        tree_cur = tree_next;
                        tree_next -= 2;
                    }
                    rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
                    for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
                    {
                        tree_cur -= ((rev_code >>= 1) & 1);
                        if (!pTree[-tree_cur - 1])
                        {
                            pTree[-tree_cur - 1] = (mz_int16)tree_next;
                            tree_cur = tree_next;
                            tree_next -= 2;
                        }
                        else
                            tree_cur = pTree[-tree_cur - 1];
                    }
                    tree_cur -= ((rev_code >>= 1) & 1);
                    pTree[-tree_cur - 1] = (mz_int16)sym_index;
                }
                if (r->m_type == 2)
                {
                    for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);)
                    {
                        mz_uint s;
                        TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2);
                        if (dist < 16)
                        {
                            r->m_len_codes[counter++] = (mz_uint8)dist;
                            continue;
                        }
                        if ((dist == 16) && (!counter))
                        {
                            TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
                        }
                        num_extra = "\02\03\07"[dist - 16];
                        TINFL_GET_BITS(18, s, num_extra);
                        s += "\03\03\013"[dist - 16];
                        TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s);
                        counter += s;
                    }
                    if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
                    {
                        TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);
                    }
                    TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]);
                    TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
                }
            }
            for (;;)
            {
                mz_uint8 *pSrc;
                for (;;)
                {
                    if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
                    {
                        TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0);
                        if (counter >= 256)
                            break;
                        while (pOut_buf_cur >= pOut_buf_end)
                        {
                            TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT);
                        }
                        *pOut_buf_cur++ = (mz_uint8)counter;
                    }
                    else
                    {
                        int sym2;
                        mz_uint code_len;
#if TINFL_USE_64BIT_BITBUF
                        if (num_bits < 30)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 4;
                            num_bits += 32;
                        }
#else
                        if (num_bits < 15)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 2;
                            num_bits += 16;
                        }
#endif
                        if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
                            code_len = sym2 >> 9;
                        else
                        {
                            code_len = TINFL_FAST_LOOKUP_BITS;
                            do
                            {
                                sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)];
                            } while (sym2 < 0);
                        }
                        counter = sym2;
                        bit_buf >>= code_len;
                        num_bits -= code_len;
                        if (counter & 256)
                            break;

#if !TINFL_USE_64BIT_BITBUF
                        if (num_bits < 15)
                        {
                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
                            pIn_buf_cur += 2;
                            num_bits += 16;
                        }
#endif
                        if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
                            code_len = sym2 >> 9;
                        else
                        {
                            code_len = TINFL_FAST_LOOKUP_BITS;
                            do
                            {
                                sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)];
                            } while (sym2 < 0);
                        }
                        bit_buf >>= code_len;
                        num_bits -= code_len;

                        pOut_buf_cur[0] = (mz_uint8)counter;
                        if (sym2 & 256)
                        {
                            pOut_buf_cur++;
                            counter = sym2;
                            break;
                        }
                        pOut_buf_cur[1] = (mz_uint8)sym2;
                        pOut_buf_cur += 2;
                    }
                }
                if ((counter &= 511) == 256)
                    break;

                num_extra = s_length_extra[counter - 257];
                counter = s_length_base[counter - 257];
                if (num_extra)
                {
                    mz_uint extra_bits;
                    TINFL_GET_BITS(25, extra_bits, num_extra);
                    counter += extra_bits;
                }

                TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1);
                num_extra = s_dist_extra[dist];
                dist = s_dist_base[dist];
                if (num_extra)
                {
                    mz_uint extra_bits;
                    TINFL_GET_BITS(27, extra_bits, num_extra);
                    dist += extra_bits;
                }

                dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
                if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
                {
                    TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
                }

                pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);

                if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
                {
                    while (counter--)
                    {
                        while (pOut_buf_cur >= pOut_buf_end)
                        {
                            TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT);
                        }
                        *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
                    }
                    continue;
                }
#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
                else if ((counter >= 9) && (counter <= dist))
                {
                    const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
                    do
                    {
#ifdef MINIZ_UNALIGNED_USE_MEMCPY
						memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2);
#else
                        ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
                        ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
#endif
                        pOut_buf_cur += 8;
                    } while ((pSrc += 8) < pSrc_end);
                    if ((counter &= 7) < 3)
                    {
                        if (counter)
                        {
                            pOut_buf_cur[0] = pSrc[0];
                            if (counter > 1)
                                pOut_buf_cur[1] = pSrc[1];
                            pOut_buf_cur += counter;
                        }
                        continue;
                    }
                }
#endif
                while(counter>2)
                {
                    pOut_buf_cur[0] = pSrc[0];
                    pOut_buf_cur[1] = pSrc[1];
                    pOut_buf_cur[2] = pSrc[2];
                    pOut_buf_cur += 3;
                    pSrc += 3;
					counter -= 3;
                }
                if (counter > 0)
                {
                    pOut_buf_cur[0] = pSrc[0];
                    if (counter > 1)
                        pOut_buf_cur[1] = pSrc[1];
                    pOut_buf_cur += counter;
                }
            }
        }
    } while (!(r->m_final & 1));

    /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
    /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */
    TINFL_SKIP_BITS(32, num_bits & 7);
    while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
    {
        --pIn_buf_cur;
        num_bits -= 8;
    }
    bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits);
    MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */

    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
    {
        for (counter = 0; counter < 4; ++counter)
        {
            mz_uint s;
            if (num_bits)
                TINFL_GET_BITS(41, s, 8);
            else
                TINFL_GET_BYTE(42, s);
            r->m_z_adler32 = (r->m_z_adler32 << 8) | s;
        }
    }
    TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);

    TINFL_CR_FINISH

common_exit:
    /* As long as we aren't telling the caller that we NEED more input to make forward progress: */
    /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
    /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */
    if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS))
    {
        while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
        {
            --pIn_buf_cur;
            num_bits -= 8;
        }
    }
    r->m_num_bits = num_bits;
    r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits);
    r->m_dist = dist;
    r->m_counter = counter;
    r->m_num_extra = num_extra;
    r->m_dist_from_out_buf_start = dist_from_out_buf_start;
    *pIn_buf_size = pIn_buf_cur - pIn_buf_next;
    *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
    if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
    {
        const mz_uint8 *ptr = pOut_buf_next;
        size_t buf_len = *pOut_buf_size;
        mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16;
        size_t block_len = buf_len % 5552;
        while (buf_len)
        {
            for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
            {
                s1 += ptr[0], s2 += s1;
                s1 += ptr[1], s2 += s1;
                s1 += ptr[2], s2 += s1;
                s1 += ptr[3], s2 += s1;
                s1 += ptr[4], s2 += s1;
                s1 += ptr[5], s2 += s1;
                s1 += ptr[6], s2 += s1;
                s1 += ptr[7], s2 += s1;
            }
            for (; i < block_len; ++i)
                s1 += *ptr++, s2 += s1;
            s1 %= 65521U, s2 %= 65521U;
            buf_len -= block_len;
            block_len = 5552;
        }
        r->m_check_adler32 = (s2 << 16) + s1;
        if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32))
            status = TINFL_STATUS_ADLER32_MISMATCH;
    }
    return status;
}

/* Higher level helper functions. */
void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
    tinfl_decompressor decomp;
    void *pBuf = NULL, *pNew_buf;
    size_t src_buf_ofs = 0, out_buf_capacity = 0;
    *pOut_len = 0;
    tinfl_init(&decomp);
    for (;;)
    {
        size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size,
                                               (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
        if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))
        {
            MZ_FREE(pBuf);
            *pOut_len = 0;
            return NULL;
        }
        src_buf_ofs += src_buf_size;
        *pOut_len += dst_buf_size;
        if (status == TINFL_STATUS_DONE)
            break;
        new_out_buf_capacity = out_buf_capacity * 2;
        if (new_out_buf_capacity < 128)
            new_out_buf_capacity = 128;
        pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);
        if (!pNew_buf)
        {
            MZ_FREE(pBuf);
            *pOut_len = 0;
            return NULL;
        }
        pBuf = pNew_buf;
        out_buf_capacity = new_out_buf_capacity;
    }
    return pBuf;
}

size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
    tinfl_decompressor decomp;
    tinfl_status status;
    tinfl_init(&decomp);
    status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
    return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
}

int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
    int result = 0;
    tinfl_decompressor decomp;
    mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE);
    size_t in_buf_ofs = 0, dict_ofs = 0;
    if (!pDict)
        return TINFL_STATUS_FAILED;
    memset(pDict,0,TINFL_LZ_DICT_SIZE);
    tinfl_init(&decomp);
    for (;;)
    {
        size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;
        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
                                               (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
        in_buf_ofs += in_buf_size;
        if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
            break;
        if (status != TINFL_STATUS_HAS_MORE_OUTPUT)
        {
            result = (status == TINFL_STATUS_DONE);
            break;
        }
        dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);
    }
    MZ_FREE(pDict);
    *pIn_buf_size = in_buf_ofs;
    return result;
}

#ifndef MINIZ_NO_MALLOC
tinfl_decompressor *tinfl_decompressor_alloc(void)
{
    tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor));
    if (pDecomp)
        tinfl_init(pDecomp);
    return pDecomp;
}

void tinfl_decompressor_free(tinfl_decompressor *pDecomp)
{
    MZ_FREE(pDecomp);
}
#endif

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/
 /**************************************************************************
 *
 * Copyright 2013-2014 RAD Game Tools and Valve Software
 * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
 * Copyright 2016 Martin Raiber
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 **************************************************************************/


#ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef __cplusplus
extern "C" {
#endif

/* ------------------- .ZIP archive reading */

#ifdef MINIZ_NO_STDIO
#define MZ_FILE void *
#else
#include <sys/stat.h>

#if defined(_MSC_VER) || defined(__MINGW64__)

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static WCHAR* mz_utf8z_to_widechar(const char* str)
{
  int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
  WCHAR* wStr = (WCHAR*)malloc(reqChars * sizeof(WCHAR));
  MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, sizeof(WCHAR) * reqChars);
  return wStr;
}

static FILE *mz_fopen(const char *pFilename, const char *pMode)
{
  WCHAR* wFilename = mz_utf8z_to_widechar(pFilename);
  WCHAR* wMode = mz_utf8z_to_widechar(pMode);
  FILE* pFile = NULL;
  errno_t err = _wfopen_s(&pFile, wFilename, wMode);
  free(wFilename);
  free(wMode);
  return err ? NULL : pFile;
}

static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
{
  WCHAR* wPath = mz_utf8z_to_widechar(pPath);
  WCHAR* wMode = mz_utf8z_to_widechar(pMode);
  FILE* pFile = NULL;
  errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream);
  free(wPath);
  free(wMode);
  return err ? NULL : pFile;
}

static int mz_stat64(const char *path, struct __stat64 *buffer)
{
  WCHAR* wPath = mz_utf8z_to_widechar(path);
  int res = _wstat64(wPath, buffer);
  free(wPath);
  return res;
}

#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN mz_fopen
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 _ftelli64
#define MZ_FSEEK64 _fseeki64
#define MZ_FILE_STAT_STRUCT _stat64
#define MZ_FILE_STAT mz_stat64 
#define MZ_FFLUSH fflush
#define MZ_FREOPEN mz_freopen
#define MZ_DELETE_FILE remove

#elif defined(__MINGW32__) || defined(__WATCOMC__)
#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 _ftelli64
#define MZ_FSEEK64 _fseeki64
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove

#elif defined(__TINYC__)
#ifndef MINIZ_NO_TIME
#include <sys/utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftell
#define MZ_FSEEK64 fseek
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove

#elif defined(__USE_LARGEFILE64) /* gcc, clang */
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen64(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftello64
#define MZ_FSEEK64 fseeko64
#define MZ_FILE_STAT_STRUCT stat64
#define MZ_FILE_STAT stat64
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
#define MZ_DELETE_FILE remove

#elif defined(__APPLE__) || defined(__FreeBSD__)
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#define MZ_FTELL64 ftello
#define MZ_FSEEK64 fseeko
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(p, m, s) freopen(p, m, s)
#define MZ_DELETE_FILE remove

#else
//#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.")
#ifndef MINIZ_NO_TIME
#include <utime.h>
#endif
#define MZ_FOPEN(f, m) fopen(f, m)
#define MZ_FCLOSE fclose
#define MZ_FREAD fread
#define MZ_FWRITE fwrite
#ifdef __STRICT_ANSI__
#define MZ_FTELL64 ftell
#define MZ_FSEEK64 fseek
#else
#define MZ_FTELL64 ftello
#define MZ_FSEEK64 fseeko
#endif
#define MZ_FILE_STAT_STRUCT stat
#define MZ_FILE_STAT stat
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
#define MZ_DELETE_FILE remove
#endif /* #ifdef _MSC_VER */
#endif /* #ifdef MINIZ_NO_STDIO */

#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))

/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */
enum
{
    /* ZIP archive identifiers and record sizes */
    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50,
    MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50,
    MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
    MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30,
    MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46,
    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,

    /* ZIP64 archive identifier and record sizes */
    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50,
    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50,
    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56,
    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20,
    MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001,
    MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50,
    MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24,
    MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16,

    /* Central directory header record offsets */
    MZ_ZIP_CDH_SIG_OFS = 0,
    MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4,
    MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6,
    MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
    MZ_ZIP_CDH_METHOD_OFS = 10,
    MZ_ZIP_CDH_FILE_TIME_OFS = 12,
    MZ_ZIP_CDH_FILE_DATE_OFS = 14,
    MZ_ZIP_CDH_CRC32_OFS = 16,
    MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20,
    MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24,
    MZ_ZIP_CDH_FILENAME_LEN_OFS = 28,
    MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
    MZ_ZIP_CDH_COMMENT_LEN_OFS = 32,
    MZ_ZIP_CDH_DISK_START_OFS = 34,
    MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36,
    MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38,
    MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,

    /* Local directory header offsets */
    MZ_ZIP_LDH_SIG_OFS = 0,
    MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4,
    MZ_ZIP_LDH_BIT_FLAG_OFS = 6,
    MZ_ZIP_LDH_METHOD_OFS = 8,
    MZ_ZIP_LDH_FILE_TIME_OFS = 10,
    MZ_ZIP_LDH_FILE_DATE_OFS = 12,
    MZ_ZIP_LDH_CRC32_OFS = 14,
    MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18,
    MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
    MZ_ZIP_LDH_FILENAME_LEN_OFS = 26,
    MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
    MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3,

    /* End of central directory offsets */
    MZ_ZIP_ECDH_SIG_OFS = 0,
    MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4,
    MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6,
    MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
    MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10,
    MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12,
    MZ_ZIP_ECDH_CDIR_OFS_OFS = 16,
    MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,

    /* ZIP64 End of central directory locator offsets */
    MZ_ZIP64_ECDL_SIG_OFS = 0,                    /* 4 bytes */
    MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4,          /* 4 bytes */
    MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8,  /* 8 bytes */
    MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */

    /* ZIP64 End of central directory header offsets */
    MZ_ZIP64_ECDH_SIG_OFS = 0,                       /* 4 bytes */
    MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4,            /* 8 bytes */
    MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12,          /* 2 bytes */
    MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14,           /* 2 bytes */
    MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16,            /* 4 bytes */
    MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20,            /* 4 bytes */
    MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32,       /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40,                /* 8 bytes */
    MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48,                 /* 8 bytes */
    MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0,
    MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192,
    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11
};

typedef struct
{
    void *m_p;
    size_t m_size, m_capacity;
    mz_uint m_element_size;
} mz_zip_array;

struct mz_zip_internal_state_tag
{
    mz_zip_array m_central_dir;
    mz_zip_array m_central_dir_offsets;
    mz_zip_array m_sorted_central_dir_offsets;

    /* The flags passed in when the archive is initially opened. */
    mz_uint32 m_init_flags;

    /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */
    mz_bool m_zip64;

    /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */
    mz_bool m_zip64_has_extended_info_fields;

    /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */
    MZ_FILE *m_pFile;
    mz_uint64 m_file_archive_start_ofs;

    void *m_pMem;
    size_t m_mem_size;
    size_t m_mem_capacity;
};

#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size

#if defined(DEBUG) || defined(_DEBUG)
static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index)
{
    MZ_ASSERT(index < pArray->m_size);
    return index;
}
#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)]
#else
#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]
#endif

static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size)
{
    memset(pArray, 0, sizeof(mz_zip_array));
    pArray->m_element_size = element_size;
}

static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)
{
    pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
    memset(pArray, 0, sizeof(mz_zip_array));
}

static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)
{
    void *pNew_p;
    size_t new_capacity = min_new_capacity;
    MZ_ASSERT(pArray->m_element_size);
    if (pArray->m_capacity >= min_new_capacity)
        return MZ_TRUE;
    if (growing)
    {
        new_capacity = MZ_MAX(1, pArray->m_capacity);
        while (new_capacity < min_new_capacity)
            new_capacity *= 2;
    }
    if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity)))
        return MZ_FALSE;
    pArray->m_p = pNew_p;
    pArray->m_capacity = new_capacity;
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)
{
    if (new_capacity > pArray->m_capacity)
    {
        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing))
            return MZ_FALSE;
    }
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)
{
    if (new_size > pArray->m_capacity)
    {
        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing))
            return MZ_FALSE;
    }
    pArray->m_size = new_size;
    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)
{
    return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);
}

static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)
{
    size_t orig_size = pArray->m_size;
    if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE))
        return MZ_FALSE;
    if (n > 0)
        memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
    return MZ_TRUE;
}

#ifndef MINIZ_NO_TIME
static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date)
{
    struct tm tm;
    memset(&tm, 0, sizeof(tm));
    tm.tm_isdst = -1;
    tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900;
    tm.tm_mon = ((dos_date >> 5) & 15) - 1;
    tm.tm_mday = dos_date & 31;
    tm.tm_hour = (dos_time >> 11) & 31;
    tm.tm_min = (dos_time >> 5) & 63;
    tm.tm_sec = (dos_time << 1) & 62;
    return mktime(&tm);
}

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
{
#ifdef _MSC_VER
    struct tm tm_struct;
    struct tm *tm = &tm_struct;
    errno_t err = localtime_s(tm, &time);
    if (err)
    {
        *pDOS_date = 0;
        *pDOS_time = 0;
        return;
    }
#else
    struct tm *tm = localtime(&time);
#endif /* #ifdef _MSC_VER */

    *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
    *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
}
#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */

#ifndef MINIZ_NO_STDIO
#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime)
{
    struct MZ_FILE_STAT_STRUCT file_stat;

    /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */
    if (MZ_FILE_STAT(pFilename, &file_stat) != 0)
        return MZ_FALSE;

    *pTime = file_stat.st_mtime;

    return MZ_TRUE;
}
#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/

static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time)
{
    struct utimbuf t;

    memset(&t, 0, sizeof(t));
    t.actime = access_time;
    t.modtime = modified_time;

    return !utime(pFilename, &t);
}
#endif /* #ifndef MINIZ_NO_STDIO */
#endif /* #ifndef MINIZ_NO_TIME */

static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num)
{
    if (pZip)
        pZip->m_last_error = err_num;
    return MZ_FALSE;
}

static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags)
{
    (void)flags;
    if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!pZip->m_pAlloc)
        pZip->m_pAlloc = miniz_def_alloc_func;
    if (!pZip->m_pFree)
        pZip->m_pFree = miniz_def_free_func;
    if (!pZip->m_pRealloc)
        pZip->m_pRealloc = miniz_def_realloc_func;

    pZip->m_archive_size = 0;
    pZip->m_central_directory_file_ofs = 0;
    pZip->m_total_files = 0;
    pZip->m_last_error = MZ_ZIP_NO_ERROR;

    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
    pZip->m_pState->m_init_flags = flags;
    pZip->m_pState->m_zip64 = MZ_FALSE;
    pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE;

    pZip->m_zip_mode = MZ_ZIP_MODE_READING;

    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)
{
    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
    const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));
    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    mz_uint8 l = 0, r = 0;
    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pE = pL + MZ_MIN(l_len, r_len);
    while (pL < pE)
    {
        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
            break;
        pL++;
        pR++;
    }
    return (pL == pE) ? (l_len < r_len) : (l < r);
}

#define MZ_SWAP_UINT32(a, b) \
    do                       \
    {                        \
        mz_uint32 t = a;     \
        a = b;               \
        b = t;               \
    }                        \
    MZ_MACRO_END

/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */
static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
    const mz_zip_array *pCentral_dir = &pState->m_central_dir;
    mz_uint32 *pIndices;
    mz_uint32 start, end;
    const mz_uint32 size = pZip->m_total_files;

    if (size <= 1U)
        return;

    pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);

    start = (size - 2U) >> 1U;
    for (;;)
    {
        mz_uint64 child, root = start;
        for (;;)
        {
            if ((child = (root << 1U) + 1U) >= size)
                break;
            child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])));
            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
                break;
            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
            root = child;
        }
        if (!start)
            break;
        start--;
    }

    end = size - 1;
    while (end > 0)
    {
        mz_uint64 child, root = 0;
        MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
        for (;;)
        {
            if ((child = (root << 1U) + 1U) >= end)
                break;
            child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]));
            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
                break;
            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
            root = child;
        }
        end--;
    }
}

static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs)
{
    mz_int64 cur_file_ofs;
    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;

    /* Basic sanity checks - reject files which are too small */
    if (pZip->m_archive_size < record_size)
        return MZ_FALSE;

    /* Find the record by scanning the file from the end towards the beginning. */
    cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
    for (;;)
    {
        int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);

        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
            return MZ_FALSE;

        for (i = n - 4; i >= 0; --i)
        {
            mz_uint s = MZ_READ_LE32(pBuf + i);
            if (s == record_sig)
            {
                if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size)
                    break;
            }
        }

        if (i >= 0)
        {
            cur_file_ofs += i;
            break;
        }

        /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */
        if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size)))
            return MZ_FALSE;

        cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
    }

    *pOfs = cur_file_ofs;
    return MZ_TRUE;
}

static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags)
{
    mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0;
    mz_uint64 cdir_ofs = 0;
    mz_int64 cur_file_ofs = 0;
    const mz_uint8 *p;

    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
    mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
    mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32;

    mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32;

    mz_uint64 zip64_end_of_central_dir_ofs = 0;

    /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */
    if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))
        return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);

    /* Read and verify the end of central directory record. */
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
    {
        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
        {
            if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG)
            {
                zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
                if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
                    return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

                if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
                {
                    if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)
                    {
                        pZip->m_pState->m_zip64 = MZ_TRUE;
                    }
                }
            }
        }
    }

    pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);
    cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
    num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
    cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
    cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS);
    cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);

    if (pZip->m_pState->m_zip64)
    {
        mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS);
        mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS);
        mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
        mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS);
        mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS);

        if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

        if (zip64_total_num_of_disks != 1U)
            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

        /* Check for miniz's practical limits */
        if (zip64_cdir_total_entries > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries;

        if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk;

        /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */
        if (zip64_size_of_central_directory > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

        cdir_size = (mz_uint32)zip64_size_of_central_directory;

        num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS);

        cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS);

        cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS);
    }

    if (pZip->m_total_files != cdir_entries_on_this_disk)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

    if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

    if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    pZip->m_central_directory_file_ofs = cdir_ofs;

    if (pZip->m_total_files)
    {
        mz_uint i, n;
        /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */
        if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||
            (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        if (sort_central_dir)
        {
            if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))
                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

        /* Now create an index into the central directory file records, do some basic sanity checking on each record */
        p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
        for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
        {
            mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size;
            mz_uint64 comp_size, decomp_size, local_header_ofs;

            if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);

            if (sort_central_dir)
                MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;

            comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
            decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
            local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
            filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
            ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);

            if ((!pZip->m_pState->m_zip64_has_extended_info_fields) &&
                (ext_data_size) &&
                (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX))
            {
                /* Attempt to find zip64 extended information field in the entry's extra data */
                mz_uint32 extra_size_remaining = ext_data_size;

                if (extra_size_remaining)
                {
					const mz_uint8 *pExtra_data;
					void* buf = NULL;

					if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n)
					{
						buf = MZ_MALLOC(ext_data_size);
						if(buf==NULL)
							return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

						if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size)
						{
							MZ_FREE(buf);
							return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
						}

						pExtra_data = (mz_uint8*)buf;
					}
					else
					{
						pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size;
					}

                    do
                    {
                        mz_uint32 field_id;
                        mz_uint32 field_data_size;

						if (extra_size_remaining < (sizeof(mz_uint16) * 2))
						{
							MZ_FREE(buf);
							return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
						}

                        field_id = MZ_READ_LE16(pExtra_data);
                        field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));

						if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
						{
							MZ_FREE(buf);
							return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
						}

                        if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
                        {
                            /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */
                            pZip->m_pState->m_zip64 = MZ_TRUE;
                            pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE;
                            break;
                        }

                        pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
                        extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
                    } while (extra_size_remaining);

					MZ_FREE(buf);
                }
            }

            /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */
            if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX))
            {
                if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size))
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
            if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1)))
                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);

            if (comp_size != MZ_UINT32_MAX)
            {
                if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
            if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED)
                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

            if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            n -= total_header_size;
            p += total_header_size;
        }
    }

    if (sort_central_dir)
        mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);

    return MZ_TRUE;
}

void mz_zip_zero_struct(mz_zip_archive *pZip)
{
    if (pZip)
        MZ_CLEAR_PTR(pZip);
}

static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
{
    mz_bool status = MZ_TRUE;

    if (!pZip)
        return MZ_FALSE;

    if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    {
        if (set_last_error)
            pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER;

        return MZ_FALSE;
    }

    if (pZip->m_pState)
    {
        mz_zip_internal_state *pState = pZip->m_pState;
        pZip->m_pState = NULL;

        mz_zip_array_clear(pZip, &pState->m_central_dir);
        mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
        mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
        if (pState->m_pFile)
        {
            if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
            {
                if (MZ_FCLOSE(pState->m_pFile) == EOF)
                {
                    if (set_last_error)
                        pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED;
                    status = MZ_FALSE;
                }
            }
            pState->m_pFile = NULL;
        }
#endif /* #ifndef MINIZ_NO_STDIO */

        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
    }
    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;

    return status;
}

mz_bool mz_zip_reader_end(mz_zip_archive *pZip)
{
    return mz_zip_reader_end_internal(pZip, MZ_TRUE);
}
mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags)
{
    if ((!pZip) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_USER;
    pZip->m_archive_size = size;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);
    memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);
    return s;
}

mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags)
{
    if (!pMem)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY;
    pZip->m_archive_size = size;
    pZip->m_pRead = mz_zip_mem_read_func;
    pZip->m_pIO_opaque = pZip;
    pZip->m_pNeeds_keepalive = NULL;

#ifdef __cplusplus
    pZip->m_pState->m_pMem = const_cast<void *>(pMem);
#else
    pZip->m_pState->m_pMem = (void *)pMem;
#endif

    pZip->m_pState->m_mem_size = size;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);

    file_ofs += pZip->m_pState->m_file_archive_start_ofs;

    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
        return 0;

    return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)
{
    return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0);
}

mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size)
{
    mz_uint64 file_size;
    MZ_FILE *pFile;

    if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pFile = MZ_FOPEN(pFilename, "rb");
    if (!pFile)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    file_size = archive_size;
    if (!file_size)
    {
        if (MZ_FSEEK64(pFile, 0, SEEK_END))
        {
            MZ_FCLOSE(pFile);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
        }

        file_size = MZ_FTELL64(pFile);
    }

    /* TODO: Better sanity check archive_size and the # of actual remaining bytes */

    if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
    {
	MZ_FCLOSE(pFile);
        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
    }

    if (!mz_zip_reader_init_internal(pZip, flags))
    {
        MZ_FCLOSE(pFile);
        return MZ_FALSE;
    }

    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;
    pZip->m_pRead = mz_zip_file_read_func;
    pZip->m_pIO_opaque = pZip;
    pZip->m_pState->m_pFile = pFile;
    pZip->m_archive_size = file_size;
    pZip->m_pState->m_file_archive_start_ofs = file_start_ofs;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags)
{
    mz_uint64 cur_file_ofs;

    if ((!pZip) || (!pFile))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    cur_file_ofs = MZ_FTELL64(pFile);

    if (!archive_size)
    {
        if (MZ_FSEEK64(pFile, 0, SEEK_END))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);

        archive_size = MZ_FTELL64(pFile) - cur_file_ofs;

        if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
    }

    if (!mz_zip_reader_init_internal(pZip, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;
    pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;
    pZip->m_pState->m_pFile = pFile;
    pZip->m_archive_size = archive_size;
    pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs;

    if (!mz_zip_reader_read_central_dir(pZip, flags))
    {
        mz_zip_reader_end_internal(pZip, MZ_FALSE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

#endif /* #ifndef MINIZ_NO_STDIO */

static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index)
{
    if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files))
        return NULL;
    return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
}

mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint m_bit_flag;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
    return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0;
}

mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint bit_flag;
    mz_uint method;

    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
    bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);

    if ((method != 0) && (method != MZ_DEFLATED))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
        return MZ_FALSE;
    }

    if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
        return MZ_FALSE;
    }

    if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
        return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)
{
    mz_uint filename_len, attribute_mapping_id, external_attr;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    if (filename_len)
    {
        if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
            return MZ_TRUE;
    }

    /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */
    /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */
    /* FIXME: Remove this check? Is it necessary - we already check the filename. */
    attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8;
    (void)attribute_mapping_id;

    external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
    if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0)
    {
        return MZ_TRUE;
    }

    return MZ_FALSE;
}

static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data)
{
    mz_uint n;
    const mz_uint8 *p = pCentral_dir_header;

    if (pFound_zip64_extra_data)
        *pFound_zip64_extra_data = MZ_FALSE;

    if ((!p) || (!pStat))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Extract fields from the central directory record. */
    pStat->m_file_index = file_index;
    pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);
    pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
    pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);
    pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
    pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
#ifndef MINIZ_NO_TIME
    pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));
#endif
    pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);
    pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
    pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
    pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
    pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
    pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);

    /* Copy as much of the filename and comment as possible. */
    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
    memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
    pStat->m_filename[n] = '\0';

    n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS);
    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
    pStat->m_comment_size = n;
    memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n);
    pStat->m_comment[n] = '\0';

    /* Set some flags for convienance */
    pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index);
    pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index);
    pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index);

    /* See if we need to read any zip64 extended information fields. */
    /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */
    if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX)
    {
        /* Attempt to find zip64 extended information field in the entry's extra data */
        mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);

        if (extra_size_remaining)
        {
            const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);

            do
            {
                mz_uint32 field_id;
                mz_uint32 field_data_size;

                if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                field_id = MZ_READ_LE16(pExtra_data);
                field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));

                if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
                {
                    const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2;
                    mz_uint32 field_data_remaining = field_data_size;

                    if (pFound_zip64_extra_data)
                        *pFound_zip64_extra_data = MZ_TRUE;

                    if (pStat->m_uncomp_size == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_uncomp_size = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    if (pStat->m_comp_size == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_comp_size = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    if (pStat->m_local_header_ofs == MZ_UINT32_MAX)
                    {
                        if (field_data_remaining < sizeof(mz_uint64))
                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

                        pStat->m_local_header_ofs = MZ_READ_LE64(pField_data);
                        pField_data += sizeof(mz_uint64);
                        field_data_remaining -= sizeof(mz_uint64);
                    }

                    break;
                }

                pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
                extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
            } while (extra_size_remaining);
        }
    }

    return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)
{
    mz_uint i;
    if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)
        return 0 == memcmp(pA, pB, len);
    for (i = 0; i < len; ++i)
        if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))
            return MZ_FALSE;
    return MZ_TRUE;
}

static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)
{
    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    mz_uint8 l = 0, r = 0;
    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    pE = pL + MZ_MIN(l_len, r_len);
    while (pL < pE)
    {
        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
            break;
        pL++;
        pR++;
    }
    return (pL == pE) ? (int)(l_len - r_len) : (l - r);
}

static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
    const mz_zip_array *pCentral_dir = &pState->m_central_dir;
    mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
    const mz_uint32 size = pZip->m_total_files;
    const mz_uint filename_len = (mz_uint)strlen(pFilename);

    if (pIndex)
        *pIndex = 0;

    if (size)
    {
        /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */
        /* honestly the major expense here on 32-bit CPU's will still be the filename compare */
        mz_int64 l = 0, h = (mz_int64)size - 1;

        while (l <= h)
        {
            mz_int64 m = l + ((h - l) >> 1);
            mz_uint32 file_index = pIndices[(mz_uint32)m];

            int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
            if (!comp)
            {
                if (pIndex)
                    *pIndex = file_index;
                return MZ_TRUE;
            }
            else if (comp < 0)
                l = m + 1;
            else
                h = m - 1;
        }
    }

    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
}

int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)
{
    mz_uint32 index;
    if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index))
        return -1;
    else
        return (int)index;
}

mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex)
{
    mz_uint file_index;
    size_t name_len, comment_len;

    if (pIndex)
        *pIndex = 0;

    if ((!pZip) || (!pZip->m_pState) || (!pName))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* See if we can use a binary search */
    if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) &&
        (pZip->m_zip_mode == MZ_ZIP_MODE_READING) &&
        ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
    {
        return mz_zip_locate_file_binary_search(pZip, pName, pIndex);
    }

    /* Locate the entry by scanning the entire central directory */
    name_len = strlen(pName);
    if (name_len > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    comment_len = pComment ? strlen(pComment) : 0;
    if (comment_len > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    for (file_index = 0; file_index < pZip->m_total_files; file_index++)
    {
        const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
        mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
        const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
        if (filename_len < name_len)
            continue;
        if (comment_len)
        {
            mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);
            const char *pFile_comment = pFilename + filename_len + file_extra_len;
            if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags)))
                continue;
        }
        if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
        {
            int ofs = filename_len - 1;
            do
            {
                if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
                    break;
            } while (--ofs >= 0);
            ofs++;
            pFilename += ofs;
            filename_len -= ofs;
        }
        if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags)))
        {
            if (pIndex)
                *pIndex = file_index;
            return MZ_TRUE;
        }
    }

    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
}

static
mz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st)
{
    int status = TINFL_STATUS_DONE;
    mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    tinfl_decompressor inflator;

    if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (st) {
        file_stat = *st;
    } else
    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    /* Ensure supplied output buffer is large enough. */
    needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
    if (buf_size < needed_size)
        return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL);

    /* Read and parse the local directory entry. */
    cur_file_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data. */
        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0)
        {
            if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
                return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
        }
#endif

        return MZ_TRUE;
    }

    /* Decompress the file either directly from memory or from a file input buffer. */
    tinfl_init(&inflator);

    if (pZip->m_pState->m_pMem)
    {
        /* Read directly from the archive in memory. */
        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
        read_buf_size = read_buf_avail = file_stat.m_comp_size;
        comp_remaining = 0;
    }
    else if (pUser_read_buf)
    {
        /* Use a user provided read buffer. */
        if (!user_read_buf_size)
            return MZ_FALSE;
        pRead_buf = (mz_uint8 *)pUser_read_buf;
        read_buf_size = user_read_buf_size;
        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }
    else
    {
        /* Temporarily allocate a read buffer. */
        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
        if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }

    do
    {
        /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */
        size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
        if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
        {
            read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
            if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
            {
                status = TINFL_STATUS_FAILED;
                mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                break;
            }
            cur_file_ofs += read_buf_avail;
            comp_remaining -= read_buf_avail;
            read_buf_ofs = 0;
        }
        in_buf_size = (size_t)read_buf_avail;
        status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));
        read_buf_avail -= in_buf_size;
        read_buf_ofs += in_buf_size;
        out_buf_ofs += out_buf_size;
    } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);

    if (status == TINFL_STATUS_DONE)
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (out_buf_ofs != file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
            status = TINFL_STATUS_FAILED;
        }
#endif
    }

    if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);

    return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL);
}

mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return MZ_FALSE;
    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL);
}

mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)
{
    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL);
}

mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)
{
    return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
}

void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)
{
    mz_zip_archive_file_stat file_stat;
    mz_uint64 alloc_size;
    void *pBuf;

    if (pSize)
        *pSize = 0;

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return NULL;

    alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
    if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
    {
        mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
        return NULL;
    }

    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
    {
        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        return NULL;
    }

    if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
        return NULL;
    }

    if (pSize)
        *pSize = (size_t)alloc_size;
    return pBuf;
}

void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
    {
        if (pSize)
            *pSize = 0;
        return MZ_FALSE;
    }
    return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);
}

mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
    int status = TINFL_STATUS_DONE;
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
    mz_uint file_crc32 = MZ_CRC32_INIT;
#endif
    mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;
    mz_zip_archive_file_stat file_stat;
    void *pRead_buf = NULL;
    void *pWrite_buf = NULL;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;

    if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */
    cur_file_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    /* Decompress the file either directly from memory or from a file input buffer. */
    if (pZip->m_pState->m_pMem)
    {
        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
        read_buf_size = read_buf_avail = file_stat.m_comp_size;
        comp_remaining = 0;
    }
    else
    {
        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        read_buf_avail = 0;
        comp_remaining = file_stat.m_comp_size;
    }

    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data. */
        if (pZip->m_pState->m_pMem)
        {
            if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX))
                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

            if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)
            {
                mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                status = TINFL_STATUS_FAILED;
            }
            else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
            {
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);
#endif
            }

            cur_file_ofs += file_stat.m_comp_size;
            out_buf_ofs += file_stat.m_comp_size;
            comp_remaining = 0;
        }
        else
        {
            while (comp_remaining)
            {
                read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
                if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                    status = TINFL_STATUS_FAILED;
                    break;
                }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
                {
                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);
                }
#endif

                if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                    status = TINFL_STATUS_FAILED;
                    break;
                }

                cur_file_ofs += read_buf_avail;
                out_buf_ofs += read_buf_avail;
                comp_remaining -= read_buf_avail;
            }
        }
    }
    else
    {
        tinfl_decompressor inflator;
        tinfl_init(&inflator);

        if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            status = TINFL_STATUS_FAILED;
        }
        else
        {
            do
            {
                mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
                size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
                if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
                {
                    read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
                    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }
                    cur_file_ofs += read_buf_avail;
                    comp_remaining -= read_buf_avail;
                    read_buf_ofs = 0;
                }

                in_buf_size = (size_t)read_buf_avail;
                status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
                read_buf_avail -= in_buf_size;
                read_buf_ofs += in_buf_size;

                if (out_buf_size)
                {
                    if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);
#endif
                    if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)
                    {
                        mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                        status = TINFL_STATUS_FAILED;
                        break;
                    }
                }
            } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));
        }
    }

    if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (out_buf_ofs != file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (file_crc32 != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
            status = TINFL_STATUS_FAILED;
        }
#endif
    }

    if (!pZip->m_pState->m_pMem)
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);

    if (pWrite_buf)
        pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);

    return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);
}

mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
{
    mz_zip_reader_extract_iter_state *pState;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;

    /* Argument sanity check */
    if ((!pZip) || (!pZip->m_pState))
        return NULL;

    /* Allocate an iterator status structure */
    pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state));
    if (!pState)
    {
        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        return NULL;
    }

    /* Fetch file details */
    if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Encryption and patch files are not supported. */
    if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* This function only supports decompressing stored and deflate. */
    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED))
    {
        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Init state - save args */
    pState->pZip = pZip;
    pState->flags = flags;

    /* Init state - reset variables to defaults */
    pState->status = TINFL_STATUS_DONE;
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
    pState->file_crc32 = MZ_CRC32_INIT;
#endif
    pState->read_buf_ofs = 0;
    pState->out_buf_ofs = 0;
    pState->pRead_buf = NULL;
    pState->pWrite_buf = NULL;
    pState->out_blk_remain = 0;

    /* Read and parse the local directory entry. */
    pState->cur_file_ofs = pState->file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size)
    {
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
        return NULL;
    }

    /* Decompress the file either directly from memory or from a file input buffer. */
    if (pZip->m_pState->m_pMem)
    {
        pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs;
        pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size;
        pState->comp_remaining = pState->file_stat.m_comp_size;
    }
    else
    {
        if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
        {
            /* Decompression required, therefore intermediate read buffer required */
            pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
            if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size)))
            {
                mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
                pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
                return NULL;
            }
        }
        else
        {
            /* Decompression not required - we will be reading directly into user buffer, no temp buf required */
            pState->read_buf_size = 0;
        }
        pState->read_buf_avail = 0;
        pState->comp_remaining = pState->file_stat.m_comp_size;
    }

    if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
    {
        /* Decompression required, init decompressor */
        tinfl_init( &pState->inflator );

        /* Allocate write buffer */
        if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            if (pState->pRead_buf)
                pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf);
            pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
            return NULL;
        }
    }

    return pState;
}

mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
{
    mz_uint32 file_index;

    /* Locate file index by name */
    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
        return NULL;

    /* Construct iterator */
    return mz_zip_reader_extract_iter_new(pZip, file_index, flags);
}

size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size)
{
    size_t copied_to_caller = 0;

    /* Argument sanity check */
    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf))
        return 0;

    if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))
    {
        /* The file is stored or the caller has requested the compressed data, calc amount to return. */
        copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining );

        /* Zip is in memory....or requires reading from a file? */
        if (pState->pZip->m_pState->m_pMem)
        {
            /* Copy data to caller's buffer */
            memcpy( pvBuf, pState->pRead_buf, copied_to_caller );
            pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller;
        }
        else
        {
            /* Read directly into caller's buffer */
            if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller)
            {
                /* Failed to read all that was asked for, flag failure and alert user */
                mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
                pState->status = TINFL_STATUS_FAILED;
                copied_to_caller = 0;
            }
        }

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        /* Compute CRC if not returning compressed data only */
        if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
            pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller);
#endif

        /* Advance offsets, dec counters */
        pState->cur_file_ofs += copied_to_caller;
        pState->out_buf_ofs += copied_to_caller;
        pState->comp_remaining -= copied_to_caller;
    }
    else
    {
        do
        {
            /* Calc ptr to write buffer - given current output pos and block size */
            mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));

            /* Calc max output size - given current output pos and block size */
            size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));

            if (!pState->out_blk_remain)
            {
                /* Read more data from file if none available (and reading from file) */
                if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem))
                {
                    /* Calc read size */
                    pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining);
                    if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail)
                    {
                        mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
                        pState->status = TINFL_STATUS_FAILED;
                        break;
                    }

                    /* Advance offsets, dec counters */
                    pState->cur_file_ofs += pState->read_buf_avail;
                    pState->comp_remaining -= pState->read_buf_avail;
                    pState->read_buf_ofs = 0;
                }

                /* Perform decompression */
                in_buf_size = (size_t)pState->read_buf_avail;
                pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
                pState->read_buf_avail -= in_buf_size;
                pState->read_buf_ofs += in_buf_size;

                /* Update current output block size remaining */
                pState->out_blk_remain = out_buf_size;
            }

            if (pState->out_blk_remain)
            {
                /* Calc amount to return. */
                size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain );

                /* Copy data to caller's buffer */
                memcpy( (mz_uint8*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy );

#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
                /* Perform CRC */
                pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy);
#endif

                /* Decrement data consumed from block */
                pState->out_blk_remain -= to_copy;

                /* Inc output offset, while performing sanity check */
                if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size)
                {
                    mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
                    pState->status = TINFL_STATUS_FAILED;
                    break;
                }

                /* Increment counter of data copied to caller */
                copied_to_caller += to_copy;
            }
        } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) );
    }

    /* Return how many bytes were copied into user buffer */
    return copied_to_caller;
}

mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState)
{
    int status;

    /* Argument sanity check */
    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState))
        return MZ_FALSE;

    /* Was decompression completed and requested? */
    if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
    {
        /* Make sure the entire file was decompressed, and check its CRC. */
        if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size)
        {
            mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
            pState->status = TINFL_STATUS_FAILED;
        }
#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
        else if (pState->file_crc32 != pState->file_stat.m_crc32)
        {
            mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
            pState->status = TINFL_STATUS_FAILED;
        }
#endif
    }

    /* Free buffers */
    if (!pState->pZip->m_pState->m_pMem)
        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf);
    if (pState->pWrite_buf)
        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf);

    /* Save status */
    status = pState->status;

    /* Free context */
    pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState);

    return status == TINFL_STATUS_DONE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)
{
    (void)ofs;

    return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque);
}

mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)
{
    mz_bool status;
    mz_zip_archive_file_stat file_stat;
    MZ_FILE *pFile;

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    pFile = MZ_FOPEN(pDst_filename, "wb");
    if (!pFile)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);

    if (MZ_FCLOSE(pFile) == EOF)
    {
        if (status)
            mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);

        status = MZ_FALSE;
    }

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
    if (status)
        mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);
#endif

    return status;
}

mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);
}

mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags)
{
    mz_zip_archive_file_stat file_stat;

    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
        return MZ_FALSE;

    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
}

mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags)
{
    mz_uint32 file_index;
    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
        return MZ_FALSE;

    return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags);
}
#endif /* #ifndef MINIZ_NO_STDIO */

static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_uint32 *p = (mz_uint32 *)pOpaque;
    (void)file_ofs;
    *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n);
    return n;
}

mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
{
    mz_zip_archive_file_stat file_stat;
    mz_zip_internal_state *pState;
    const mz_uint8 *pCentral_dir_header;
    mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE;
    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    mz_uint64 local_header_ofs = 0;
    mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32;
    mz_uint64 local_header_comp_size, local_header_uncomp_size;
    mz_uint32 uncomp_crc32 = MZ_CRC32_INIT;
    mz_bool has_data_descriptor;
    mz_uint32 local_header_bit_flags;

    mz_zip_array file_data_array;
    mz_zip_array_init(&file_data_array, 1);

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (file_index > pZip->m_total_files)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    pCentral_dir_header = mz_zip_get_cdh(pZip, file_index);

    if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir))
        return MZ_FALSE;

    /* A directory or zero length file */
    if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size))
        return MZ_TRUE;

    /* Encryption and patch files are not supported. */
    if (file_stat.m_is_encrypted)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);

    /* This function only supports stored and deflate. */
    if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);

    if (!file_stat.m_is_supported)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);

    /* Read and parse the local directory entry. */
    local_header_ofs = file_stat.m_local_header_ofs;
    if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
    local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS);
    local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
    has_data_descriptor = (local_header_bit_flags & 8) != 0;

    if (local_header_filename_len != strlen(file_stat.m_filename))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE))
    {
        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        goto handle_failure;
    }

    if (local_header_filename_len)
    {
        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len)
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */
        if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0)
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }

    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
    {
        mz_uint32 extra_size_remaining = local_header_extra_len;
        const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p;

        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
            {
                mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                goto handle_failure;
            }

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
            {
                mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                goto handle_failure;
            }

            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);

                if (field_data_size < sizeof(mz_uint64) * 2)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                    goto handle_failure;
                }

                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64));

                found_zip64_ext_data_in_ldir = MZ_TRUE;
                break;
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);
    }

    /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */
    /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */
    if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32))
    {
        mz_uint8 descriptor_buf[32];
        mz_bool has_id;
        const mz_uint8 *pSrc;
        mz_uint32 file_crc32;
        mz_uint64 comp_size = 0, uncomp_size = 0;

        mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4;

        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s))
        {
            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            goto handle_failure;
        }

        has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID);
        pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf;

        file_crc32 = MZ_READ_LE32(pSrc);

        if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir))
        {
            comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32));
            uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64));
        }
        else
        {
            comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32));
            uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32));
        }

        if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size))
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }
    else
    {
        if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size))
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            goto handle_failure;
        }
    }

    mz_zip_array_clear(pZip, &file_data_array);

    if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0)
    {
        if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0))
            return MZ_FALSE;

        /* 1 more check to be sure, although the extract checks too. */
        if (uncomp_crc32 != file_stat.m_crc32)
        {
            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
            return MZ_FALSE;
        }
    }

    return MZ_TRUE;

handle_failure:
    mz_zip_array_clear(pZip, &file_data_array);
    return MZ_FALSE;
}

mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags)
{
    mz_zip_internal_state *pState;
    mz_uint32 i;

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    /* Basic sanity checks */
    if (!pState->m_zip64)
    {
        if (pZip->m_total_files > MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        if (pZip->m_archive_size > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }
    else
    {
        if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }

    for (i = 0; i < pZip->m_total_files; i++)
    {
        if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags)
        {
            mz_uint32 found_index;
            mz_zip_archive_file_stat stat;

            if (!mz_zip_reader_file_stat(pZip, i, &stat))
                return MZ_FALSE;

            if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index))
                return MZ_FALSE;

            /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */
            if (found_index != i)
                return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
        }

        if (!mz_zip_validate_file(pZip, i, flags))
            return MZ_FALSE;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr)
{
    mz_bool success = MZ_TRUE;
    mz_zip_archive zip;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    if ((!pMem) || (!size))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    mz_zip_zero_struct(&zip);

    if (!mz_zip_reader_init_mem(&zip, pMem, size, flags))
    {
        if (pErr)
            *pErr = zip.m_last_error;
        return MZ_FALSE;
    }

    if (!mz_zip_validate_archive(&zip, flags))
    {
        actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (!mz_zip_reader_end_internal(&zip, success))
    {
        if (!actual_err)
            actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (pErr)
        *pErr = actual_err;

    return success;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr)
{
    mz_bool success = MZ_TRUE;
    mz_zip_archive zip;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    if (!pFilename)
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    mz_zip_zero_struct(&zip);

    if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0))
    {
        if (pErr)
            *pErr = zip.m_last_error;
        return MZ_FALSE;
    }

    if (!mz_zip_validate_archive(&zip, flags))
    {
        actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (!mz_zip_reader_end_internal(&zip, success))
    {
        if (!actual_err)
            actual_err = zip.m_last_error;
        success = MZ_FALSE;
    }

    if (pErr)
        *pErr = actual_err;

    return success;
}
#endif /* #ifndef MINIZ_NO_STDIO */

/* ------------------- .ZIP archive writing */

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v)
{
    p[0] = (mz_uint8)v;
    p[1] = (mz_uint8)(v >> 8);
}
static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v)
{
    p[0] = (mz_uint8)v;
    p[1] = (mz_uint8)(v >> 8);
    p[2] = (mz_uint8)(v >> 16);
    p[3] = (mz_uint8)(v >> 24);
}
static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v)
{
    mz_write_le32(p, (mz_uint32)v);
    mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32));
}

#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))
#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))
#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v))

static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_zip_internal_state *pState = pZip->m_pState;
    mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);

    if (!n)
        return 0;

    /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */
    if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
        return 0;
    }

    if (new_size > pState->m_mem_capacity)
    {
        void *pNew_block;
        size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity);

        while (new_capacity < new_size)
            new_capacity *= 2;

        if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))
        {
            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            return 0;
        }

        pState->m_pMem = pNew_block;
        pState->m_mem_capacity = new_capacity;
    }
    memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);
    pState->m_mem_size = (size_t)new_size;
    return n;
}

static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
{
    mz_zip_internal_state *pState;
    mz_bool status = MZ_TRUE;

    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))
    {
        if (set_last_error)
            mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return MZ_FALSE;
    }

    pState = pZip->m_pState;
    pZip->m_pState = NULL;
    mz_zip_array_clear(pZip, &pState->m_central_dir);
    mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
    mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
    if (pState->m_pFile)
    {
        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
        {
            if (MZ_FCLOSE(pState->m_pFile) == EOF)
            {
                if (set_last_error)
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
                status = MZ_FALSE;
            }
        }

        pState->m_pFile = NULL;
    }
#endif /* #ifndef MINIZ_NO_STDIO */

    if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);
        pState->m_pMem = NULL;
    }

    pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
    return status;
}

mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags)
{
    mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0;

    if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
    {
        if (!pZip->m_pRead)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    if (pZip->m_file_offset_alignment)
    {
        /* Ensure user specified file offset alignment is a power of 2. */
        if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    if (!pZip->m_pAlloc)
        pZip->m_pAlloc = miniz_def_alloc_func;
    if (!pZip->m_pFree)
        pZip->m_pFree = miniz_def_free_func;
    if (!pZip->m_pRealloc)
        pZip->m_pRealloc = miniz_def_realloc_func;

    pZip->m_archive_size = existing_size;
    pZip->m_central_directory_file_ofs = 0;
    pZip->m_total_files = 0;

    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));

    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));

    pZip->m_pState->m_zip64 = zip64;
    pZip->m_pState->m_zip64_has_extended_info_fields = zip64;

    pZip->m_zip_type = MZ_ZIP_TYPE_USER;
    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)
{
    return mz_zip_writer_init_v2(pZip, existing_size, 0);
}

mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags)
{
    pZip->m_pWrite = mz_zip_heap_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_mem_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
        return MZ_FALSE;

    pZip->m_zip_type = MZ_ZIP_TYPE_HEAP;

    if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))
    {
        if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))
        {
            mz_zip_writer_end_internal(pZip, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }
        pZip->m_pState->m_mem_capacity = initial_allocation_size;
    }

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)
{
    return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0);
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);

    file_ofs += pZip->m_pState->m_file_archive_start_ofs;

    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
    {
        mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
        return 0;
    }

    return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)
{
    return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0);
}

mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags)
{
    MZ_FILE *pFile;

    pZip->m_pWrite = mz_zip_file_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
        return MZ_FALSE;

    if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb")))
    {
        mz_zip_writer_end(pZip);
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
    }

    pZip->m_pState->m_pFile = pFile;
    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;

    if (size_to_reserve_at_beginning)
    {
        mz_uint64 cur_ofs = 0;
        char buf[4096];

        MZ_CLEAR_ARR(buf);

        do
        {
            size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);
            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)
            {
                mz_zip_writer_end(pZip);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
            }
            cur_ofs += n;
            size_to_reserve_at_beginning -= n;
        } while (size_to_reserve_at_beginning);
    }

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags)
{
    pZip->m_pWrite = mz_zip_file_write_func;
    pZip->m_pNeeds_keepalive = NULL;

    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
        pZip->m_pRead = mz_zip_file_read_func;

    pZip->m_pIO_opaque = pZip;

    if (!mz_zip_writer_init_v2(pZip, 0, flags))
        return MZ_FALSE;

    pZip->m_pState->m_pFile = pFile;
    pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;

    return MZ_TRUE;
}
#endif /* #ifndef MINIZ_NO_STDIO */

mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
{
    mz_zip_internal_state *pState;

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (flags & MZ_ZIP_FLAG_WRITE_ZIP64)
    {
        /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */
        if (!pZip->m_pState->m_zip64)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    /* No sense in trying to write to an archive that's already at the support max size */
    if (pZip->m_pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);

        if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
    }

    pState = pZip->m_pState;

    if (pState->m_pFile)
    {
#ifdef MINIZ_NO_STDIO
        (void)pFilename;
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
#else
        if (pZip->m_pIO_opaque != pZip)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
        {
            if (!pFilename)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

            /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */
            if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile)))
            {
                /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */
                mz_zip_reader_end_internal(pZip, MZ_FALSE);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
            }
        }

        pZip->m_pWrite = mz_zip_file_write_func;
        pZip->m_pNeeds_keepalive = NULL;
#endif /* #ifdef MINIZ_NO_STDIO */
    }
    else if (pState->m_pMem)
    {
        /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */
        if (pZip->m_pIO_opaque != pZip)
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

        pState->m_mem_capacity = pState->m_mem_size;
        pZip->m_pWrite = mz_zip_heap_write_func;
        pZip->m_pNeeds_keepalive = NULL;
    }
    /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */
    else if (!pZip->m_pWrite)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Start writing new files at the archive's current central directory location. */
    /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */
    pZip->m_archive_size = pZip->m_central_directory_file_ofs;
    pZip->m_central_directory_file_ofs = 0;

    /* Clear the sorted central dir offsets, they aren't useful or maintained now. */
    /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */
    /* TODO: We could easily maintain the sorted central directory offsets. */
    mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets);

    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)
{
    return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0);
}

/* TODO: pArchive_name is a terrible name here! */
mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)
{
    return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);
}

typedef struct
{
    mz_zip_archive *m_pZip;
    mz_uint64 m_cur_archive_file_ofs;
    mz_uint64 m_comp_size;
} mz_zip_writer_add_state;

static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser)
{
    mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;
    if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)
        return MZ_FALSE;

    pState->m_cur_archive_file_ofs += len;
    pState->m_comp_size += len;
    return MZ_TRUE;
}

#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2)
#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3)
static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs)
{
    mz_uint8 *pDst = pBuf;
    mz_uint32 field_size = 0;

    MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
    MZ_WRITE_LE16(pDst + 2, 0);
    pDst += sizeof(mz_uint16) * 2;

    if (pUncomp_size)
    {
        MZ_WRITE_LE64(pDst, *pUncomp_size);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    if (pComp_size)
    {
        MZ_WRITE_LE64(pDst, *pComp_size);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    if (pLocal_header_ofs)
    {
        MZ_WRITE_LE64(pDst, *pLocal_header_ofs);
        pDst += sizeof(mz_uint64);
        field_size += sizeof(mz_uint64);
    }

    MZ_WRITE_LE16(pBuf + 2, field_size);

    return (mz_uint32)(pDst - pBuf);
}

static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)
{
    (void)pZip;
    memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);
    return MZ_TRUE;
}

static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst,
                                                       mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size,
                                                       mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
                                                       mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
                                                       mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
{
    (void)pZip;
    memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);
    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);
    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX));
    return MZ_TRUE;
}

static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size,
                                                const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size,
                                                mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
                                                mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
                                                mz_uint64 local_header_ofs, mz_uint32 ext_attributes,
                                                const char *user_extra_data, mz_uint user_extra_data_len)
{
    mz_zip_internal_state *pState = pZip->m_pState;
    mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;
    size_t orig_central_dir_size = pState->m_central_dir.m_size;
    mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];

    if (!pZip->m_pState->m_zip64)
    {
        if (local_header_ofs > 0xFFFFFFFF)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
    }

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))
        return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

    if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||
        (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &central_dir_ofs, 1)))
    {
        /* Try to resize the central directory array back into its original state. */
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    return MZ_TRUE;
}

static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)
{
    /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */
    if (*pArchive_name == '/')
        return MZ_FALSE;

    /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/

    return MZ_TRUE;
}

static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)
{
    mz_uint32 n;
    if (!pZip->m_file_offset_alignment)
        return 0;
    n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));
    return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1));
}

static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)
{
    char buf[4096];
    memset(buf, 0, MZ_MIN(sizeof(buf), n));
    while (n)
    {
        mz_uint32 s = MZ_MIN(sizeof(buf), n);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_file_ofs += s;
        n -= s;
    }
    return MZ_TRUE;
}

mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)
{
    return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0);
}

mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size,
                                    mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified,
                                    const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
{
    mz_uint16 method = 0, dos_time = 0, dos_date = 0;
    mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
    mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
    size_t archive_name_size;
    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
    tdefl_compressor *pComp = NULL;
    mz_bool store_data_uncompressed;
    mz_zip_internal_state *pState;
    mz_uint8 *pExtra_data = NULL;
    mz_uint32 extra_size = 0;
    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
    mz_uint16 bit_flags = 0;

    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;

    if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
        bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;

    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
        bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;

    level = level_and_flags & 0xF;
    store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if (pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
        }
        if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

    if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

#ifndef MINIZ_NO_TIME
    if (last_modified != NULL)
    {
        mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date);
    }
    else
    {
        MZ_TIME_T cur_time;
        time(&cur_time);
        mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date);
    }
#endif /* #ifndef MINIZ_NO_TIME */

	if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
	{
		uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size);
		uncomp_size = buf_size;
		if (uncomp_size <= 3)
		{
			level = 0;
			store_data_uncompressed = MZ_TRUE;
		}
	}

    archive_name_size = strlen(pArchive_name);
    if (archive_name_size > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!pState->m_zip64)
    {
        /* Bail early if the archive would obviously become too large */
        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size
			+ MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len +
			pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len
			+ MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

    if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
    {
        /* Set DOS Subdirectory attribute bit. */
        ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;

        /* Subdirectories cannot contain data. */
        if ((buf_size) || (uncomp_size))
            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
    }

    /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */
    if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    if ((!store_data_uncompressed) && (buf_size))
    {
        if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
    {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
        return MZ_FALSE;
    }

    local_dir_header_ofs += num_alignment_padding_bytes;
    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }
    cur_archive_file_ofs += num_alignment_padding_bytes;

    MZ_CLEAR_ARR(local_dir_header);

    if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
    {
        method = MZ_DEFLATED;
    }

    if (pState->m_zip64)
    {
        if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
        {
            pExtra_data = extra_data;
            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                               (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
        }

        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_archive_file_ofs += archive_name_size;

        if (pExtra_data != NULL)
        {
            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

            cur_archive_file_ofs += extra_size;
        }
    }
    else
    {
        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_archive_file_ofs += archive_name_size;
    }

	if (user_extra_data_len > 0)
	{
		if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
			return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

		cur_archive_file_ofs += user_extra_data_len;
	}

    if (store_data_uncompressed)
    {
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += buf_size;
        comp_size = buf_size;
    }
    else if (buf_size)
    {
        mz_zip_writer_add_state state;

        state.m_pZip = pZip;
        state.m_cur_archive_file_ofs = cur_archive_file_ofs;
        state.m_comp_size = 0;

        if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
            (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
            return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
        }

        comp_size = state.m_comp_size;
        cur_archive_file_ofs = state.m_cur_archive_file_ofs;
    }

    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
    pComp = NULL;

    if (uncomp_size)
    {
        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;

        MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR);

        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
        if (pExtra_data == NULL)
        {
            if (comp_size > MZ_UINT32_MAX)
                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
        }
        else
        {
            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
            return MZ_FALSE;

        cur_archive_file_ofs += local_dir_footer_size;
    }

    if (pExtra_data != NULL)
    {
        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
    }

    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment,
                                          comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
                                          user_extra_data_central, user_extra_data_central_len))
        return MZ_FALSE;

    pZip->m_total_files++;
    pZip->m_archive_size = cur_archive_file_ofs;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
                                const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
{
    mz_uint16 gen_flags;
    mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
    mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;
    mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0;
    size_t archive_name_size;
    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
    mz_uint8 *pExtra_data = NULL;
    mz_uint32 extra_size = 0;
    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
    mz_zip_internal_state *pState;
    mz_uint64 file_ofs = 0, cur_archive_header_file_ofs;

    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;
    level = level_and_flags & 0xF;

    gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;

    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
        gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;

    /* Sanity checks */
    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX))
    {
        /* Source file is too large for non-zip64 */
        /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        pState->m_zip64 = MZ_TRUE;
    }

    /* We could support this, but why? */
    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    if (pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
        }
    }

    archive_name_size = strlen(pArchive_name);
    if (archive_name_size > MZ_UINT16_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    if (!pState->m_zip64)
    {
        /* Bail early if the archive would obviously become too large */
        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE
			+ archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024
			+ MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF)
        {
            pState->m_zip64 = MZ_TRUE;
            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
        }
    }

#ifndef MINIZ_NO_TIME
    if (pFile_time)
    {
        mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date);
    }
#endif

    if (max_size <= 3)
        level = 0;

    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
    {
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
    }

    cur_archive_file_ofs += num_alignment_padding_bytes;
    local_dir_header_ofs = cur_archive_file_ofs;

    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }

    if (max_size && level)
    {
        method = MZ_DEFLATED;
    }

    MZ_CLEAR_ARR(local_dir_header);
    if (pState->m_zip64)
    {
        if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
        {
            pExtra_data = extra_data;
            if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)
                extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                               (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL,
                                                                (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
            else
                extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL,
                                                                   NULL,
                                                                   (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
        }

        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += archive_name_size;

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += extra_size;
    }
    else
    {
        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += sizeof(local_dir_header);

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_archive_file_ofs += archive_name_size;
    }

    if (user_extra_data_len > 0)
    {
        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        cur_archive_file_ofs += user_extra_data_len;
    }

    if (max_size)
    {
        void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);
        if (!pRead_buf)
        {
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!level)
        {
            while (1)
            {
                size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);
                if (n == 0)
                    break;

                if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))
                {
                    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                    return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                }
                if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)
                {
                    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                    return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
                }
                file_ofs += n;
                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
                cur_archive_file_ofs += n;
            }
            uncomp_size = file_ofs;
            comp_size = uncomp_size;
        }
        else
        {
            mz_bool result = MZ_FALSE;
            mz_zip_writer_add_state state;
            tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));
            if (!pComp)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            }

            state.m_pZip = pZip;
            state.m_cur_archive_file_ofs = cur_archive_file_ofs;
            state.m_comp_size = 0;

            if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
            }

            for (;;)
            {
                tdefl_status status;
                tdefl_flush flush = TDEFL_NO_FLUSH;

                size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);
                if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))
                {
                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
                    break;
                }

                file_ofs += n;
                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);

                if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque))
                    flush = TDEFL_FULL_FLUSH;

                if (n == 0)
                    flush = TDEFL_FINISH;

                status = tdefl_compress_buffer(pComp, pRead_buf, n, flush);
                if (status == TDEFL_STATUS_DONE)
                {
                    result = MZ_TRUE;
                    break;
                }
                else if (status != TDEFL_STATUS_OKAY)
                {
                    mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
                    break;
                }
            }

            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);

            if (!result)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
                return MZ_FALSE;
            }

            uncomp_size = file_ofs;
            comp_size = state.m_comp_size;
            cur_archive_file_ofs = state.m_cur_archive_file_ofs;
        }

        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
    }

    if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE))
    {
        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;

        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
        if (pExtra_data == NULL)
        {
            if (comp_size > MZ_UINT32_MAX)
                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
        }
        else
        {
            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
            return MZ_FALSE;

        cur_archive_file_ofs += local_dir_footer_size;
    }

    if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)
    {
        if (pExtra_data != NULL)
        {
            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                               (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
        }

        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header,
                                                   (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len),
                                                   (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, 
                                                    (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size,
                                                   uncomp_crc32, method, gen_flags, dos_time, dos_date))
            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);

        cur_archive_header_file_ofs = local_dir_header_ofs;

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        if (pExtra_data != NULL)
        {
            cur_archive_header_file_ofs += sizeof(local_dir_header);

            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
            {
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
            }

            cur_archive_header_file_ofs += archive_name_size;

            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size)
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

            cur_archive_header_file_ofs += extra_size;
        }
    }

    if (pExtra_data != NULL)
    {
        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
    }

    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size,
                                          uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
                                          user_extra_data_central, user_extra_data_central_len))
        return MZ_FALSE;

    pZip->m_total_files++;
    pZip->m_archive_size = cur_archive_file_ofs;

    return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO

static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
	MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque;
	mz_int64 cur_ofs = MZ_FTELL64(pSrc_file);

	if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET))))
		return 0;

	return MZ_FREAD(pBuf, 1, n, pSrc_file);
}

mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
	const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
{
	return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags,
		user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len);
}

mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
    MZ_FILE *pSrc_file = NULL;
    mz_uint64 uncomp_size = 0;
    MZ_TIME_T file_modified_time;
    MZ_TIME_T *pFile_time = NULL;
    mz_bool status;

    memset(&file_modified_time, 0, sizeof(file_modified_time));

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
    pFile_time = &file_modified_time;
    if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED);
#endif

    pSrc_file = MZ_FOPEN(pSrc_filename, "rb");
    if (!pSrc_file)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);

    MZ_FSEEK64(pSrc_file, 0, SEEK_END);
    uncomp_size = MZ_FTELL64(pSrc_file);
    MZ_FSEEK64(pSrc_file, 0, SEEK_SET);

    status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0);

    MZ_FCLOSE(pSrc_file);

    return status;
}
#endif /* #ifndef MINIZ_NO_STDIO */

static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, mz_uint32 ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start)
{
    /* + 64 should be enough for any new zip64 data */
    if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE);

    if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start))
    {
        mz_uint8 new_ext_block[64];
        mz_uint8 *pDst = new_ext_block;
        mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
        mz_write_le16(pDst + sizeof(mz_uint16), 0);
        pDst += sizeof(mz_uint16) * 2;

        if (pUncomp_size)
        {
            mz_write_le64(pDst, *pUncomp_size);
            pDst += sizeof(mz_uint64);
        }

        if (pComp_size)
        {
            mz_write_le64(pDst, *pComp_size);
            pDst += sizeof(mz_uint64);
        }

        if (pLocal_header_ofs)
        {
            mz_write_le64(pDst, *pLocal_header_ofs);
            pDst += sizeof(mz_uint64);
        }

        if (pDisk_start)
        {
            mz_write_le32(pDst, *pDisk_start);
            pDst += sizeof(mz_uint32);
        }

        mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2));

        if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    if ((pExt) && (ext_len))
    {
        mz_uint32 extra_size_remaining = ext_len;
        const mz_uint8 *pExtra_data = pExt;

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

            if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size))
                    return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);
    }

    return MZ_TRUE;
}

/* TODO: This func is now pretty freakin complex due to zip64, split it up? */
mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index)
{
    mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size;
    mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs;
    mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;
    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
    mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
    size_t orig_central_dir_size;
    mz_zip_internal_state *pState;
    void *pBuf;
    const mz_uint8 *pSrc_central_header;
    mz_zip_archive_file_stat src_file_stat;
    mz_uint32 src_filename_len, src_comment_len, src_ext_len;
    mz_uint32 local_header_filename_size, local_header_extra_len;
    mz_uint64 local_header_comp_size, local_header_uncomp_size;
    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;

    /* Sanity checks */
    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */
    if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    /* Get pointer to the source central dir header and crack it */
    if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index)))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);
    src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS);
    src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len;

    /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */
    if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX)
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);

    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

    if (!pState->m_zip64)
    {
        if (pZip->m_total_files == MZ_UINT16_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */
        if (pZip->m_total_files == MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }

    if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL))
        return MZ_FALSE;

    cur_src_file_ofs = src_file_stat.m_local_header_ofs;
    cur_dst_file_ofs = pZip->m_archive_size;

    /* Read the source archive's local dir header */
    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);

    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);

    cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

    /* Compute the total size we need to copy (filename+extra data+compressed data) */
    local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
    src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size;

    /* Try to find a zip64 extended information field */
    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
    {
        mz_zip_array file_data_array;
        const mz_uint8 *pExtra_data;
        mz_uint32 extra_size_remaining = local_header_extra_len;

        mz_zip_array_init(&file_data_array, 1);
        if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE))
        {
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
        {
            mz_zip_array_clear(pZip, &file_data_array);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        }

        pExtra_data = (const mz_uint8 *)file_data_array.m_p;

        do
        {
            mz_uint32 field_id, field_data_size, field_total_size;

            if (extra_size_remaining < (sizeof(mz_uint16) * 2))
            {
                mz_zip_array_clear(pZip, &file_data_array);
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            field_id = MZ_READ_LE16(pExtra_data);
            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
            field_total_size = field_data_size + sizeof(mz_uint16) * 2;

            if (field_total_size > extra_size_remaining)
            {
                mz_zip_array_clear(pZip, &file_data_array);
                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
            }

            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
            {
                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);

                if (field_data_size < sizeof(mz_uint64) * 2)
                {
                    mz_zip_array_clear(pZip, &file_data_array);
                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
                }

                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */

                found_zip64_ext_data_in_ldir = MZ_TRUE;
                break;
            }

            pExtra_data += field_total_size;
            extra_size_remaining -= field_total_size;
        } while (extra_size_remaining);

        mz_zip_array_clear(pZip, &file_data_array);
    }

    if (!pState->m_zip64)
    {
        /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */
        /* We also check when the archive is finalized so this doesn't need to be perfect. */
        mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) +
                                            pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64;

        if (approx_new_archive_size >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
    }

    /* Write dest archive padding */
    if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))
        return MZ_FALSE;

    cur_dst_file_ofs += num_alignment_padding_bytes;

    local_dir_header_ofs = cur_dst_file_ofs;
    if (pZip->m_file_offset_alignment)
    {
        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
    }

    /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */
    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

    cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

    /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */
    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining)))))
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

    while (src_archive_bytes_remaining)
    {
        n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining);
        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
        }
        cur_src_file_ofs += n;

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }
        cur_dst_file_ofs += n;

        src_archive_bytes_remaining -= n;
    }

    /* Now deal with the optional data descriptor */
    bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
    if (bit_flags & 8)
    {
        /* Copy data descriptor */
        if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir))
        {
            /* src is zip64, dest must be zip64 */

            /* name			uint32_t's */
            /* id				1 (optional in zip64?) */
            /* crc			1 */
            /* comp_size	2 */
            /* uncomp_size 2 */
            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6))
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            }

            n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5);
        }
        else
        {
            /* src is NOT zip64 */
            mz_bool has_id;

            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)
            {
                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
            }

            has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID);

            if (pZip->m_pState->m_zip64)
            {
                /* dest is zip64, so upgrade the data descriptor */
                const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0);
                const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor);
                const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32));
                const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2*sizeof(mz_uint32));

                mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID);
                mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32);
                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size);
                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size);

                n = sizeof(mz_uint32) * 6;
            }
            else
            {
                /* dest is NOT zip64, just copy it as-is */
                n = sizeof(mz_uint32) * (has_id ? 4 : 3);
            }
        }

        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
        {
            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
        }

        cur_src_file_ofs += n;
        cur_dst_file_ofs += n;
    }
    pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);

    /* Finally, add the new central dir header */
    orig_central_dir_size = pState->m_central_dir.m_size;

    memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);

    if (pState->m_zip64)
    {
        /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */
        const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len;
        mz_zip_array new_ext_block;

        mz_zip_array_init(&new_ext_block, sizeof(mz_uint8));

        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX);

        if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            return MZ_FALSE;
        }

        MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len))
        {
            mz_zip_array_clear(pZip, &new_ext_block);
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }

        mz_zip_array_clear(pZip, &new_ext_block);
    }
    else
    {
        /* sanity checks */
        if (cur_dst_file_ofs > MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        if (local_dir_header_ofs >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);

        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);

        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size))
        {
            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
        }
    }

    /* This shouldn't trigger unless we screwed up during the initial sanity checks */
    if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
    {
        /* TODO: Support central dirs >= 32-bits in size */
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
    }

    n = (mz_uint32)orig_central_dir_size;
    if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))
    {
        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
    }

    pZip->m_total_files++;
    pZip->m_archive_size = cur_dst_file_ofs;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)
{
    mz_zip_internal_state *pState;
    mz_uint64 central_dir_ofs, central_dir_size;
    mz_uint8 hdr[256];

    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    pState = pZip->m_pState;

    if (pState->m_zip64)
    {
        if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX)
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }
    else
    {
        if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX))
            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
    }

    central_dir_ofs = 0;
    central_dir_size = 0;
    if (pZip->m_total_files)
    {
        /* Write central directory */
        central_dir_ofs = pZip->m_archive_size;
        central_dir_size = pState->m_central_dir.m_size;
        pZip->m_central_directory_file_ofs = central_dir_ofs;
        if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += central_dir_size;
    }

    if (pState->m_zip64)
    {
        /* Write zip64 end of central directory header */
        mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size;

        MZ_CLEAR_ARR(hdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64));
        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */
        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE;

        /* Write zip64 end of central directory locator */
        MZ_CLEAR_ARR(hdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG);
        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr);
        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1);
        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE;
    }

    /* Write end of central directory record */
    MZ_CLEAR_ARR(hdr);
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);
    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size));
    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs));

    if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);

#ifndef MINIZ_NO_STDIO
    if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))
        return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
#endif /* #ifndef MINIZ_NO_STDIO */

    pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE;

    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
    return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize)
{
    if ((!ppBuf) || (!pSize))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    *ppBuf = NULL;
    *pSize = 0;

    if ((!pZip) || (!pZip->m_pState))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (pZip->m_pWrite != mz_zip_heap_write_func)
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    if (!mz_zip_writer_finalize_archive(pZip))
        return MZ_FALSE;

    *ppBuf = pZip->m_pState->m_pMem;
    *pSize = pZip->m_pState->m_mem_size;
    pZip->m_pState->m_pMem = NULL;
    pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;

    return MZ_TRUE;
}

mz_bool mz_zip_writer_end(mz_zip_archive *pZip)
{
    return mz_zip_writer_end_internal(pZip, MZ_TRUE);
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
    return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL);
}

mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr)
{
    mz_bool status, created_new_archive = MZ_FALSE;
    mz_zip_archive zip_archive;
    struct MZ_FILE_STAT_STRUCT file_stat;
    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;

    mz_zip_zero_struct(&zip_archive);
    if ((int)level_and_flags < 0)
        level_and_flags = MZ_DEFAULT_LEVEL;

    if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;
        return MZ_FALSE;
    }

    if (!mz_zip_writer_validate_archive_name(pArchive_name))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_FILENAME;
        return MZ_FALSE;
    }

    /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */
    /* So be sure to compile with _LARGEFILE64_SOURCE 1 */
    if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
    {
        /* Create a new archive. */
        if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;
            return MZ_FALSE;
        }

        created_new_archive = MZ_TRUE;
    }
    else
    {
        /* Append to an existing archive. */
        if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;
            return MZ_FALSE;
        }

        if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags))
        {
            if (pErr)
                *pErr = zip_archive.m_last_error;

            mz_zip_reader_end_internal(&zip_archive, MZ_FALSE);

            return MZ_FALSE;
        }
    }

    status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);
    actual_err = zip_archive.m_last_error;

    /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */
    if (!mz_zip_writer_finalize_archive(&zip_archive))
    {
        if (!actual_err)
            actual_err = zip_archive.m_last_error;

        status = MZ_FALSE;
    }

    if (!mz_zip_writer_end_internal(&zip_archive, status))
    {
        if (!actual_err)
            actual_err = zip_archive.m_last_error;

        status = MZ_FALSE;
    }

    if ((!status) && (created_new_archive))
    {
        /* It's a new archive and something went wrong, so just delete it. */
        int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
        (void)ignoredStatus;
    }

    if (pErr)
        *pErr = actual_err;

    return status;
}

void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr)
{
    mz_uint32 file_index;
    mz_zip_archive zip_archive;
    void *p = NULL;

    if (pSize)
        *pSize = 0;

    if ((!pZip_filename) || (!pArchive_name))
    {
        if (pErr)
            *pErr = MZ_ZIP_INVALID_PARAMETER;

        return NULL;
    }

    mz_zip_zero_struct(&zip_archive);
    if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
    {
        if (pErr)
            *pErr = zip_archive.m_last_error;

        return NULL;
    }

    if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index))
    {
        p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);
    }

    mz_zip_reader_end_internal(&zip_archive, p != NULL);

    if (pErr)
        *pErr = zip_archive.m_last_error;

    return p;
}

void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)
{
    return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL);
}

#endif /* #ifndef MINIZ_NO_STDIO */

#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */

/* ------------------- Misc utils */

mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID;
}

mz_zip_type mz_zip_get_type(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID;
}

mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num)
{
    mz_zip_error prev_err;

    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    prev_err = pZip->m_last_error;

    pZip->m_last_error = err_num;
    return prev_err;
}

mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip)
{
    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    return pZip->m_last_error;
}

mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip)
{
    return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR);
}

mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip)
{
    mz_zip_error prev_err;

    if (!pZip)
        return MZ_ZIP_INVALID_PARAMETER;

    prev_err = pZip->m_last_error;

    pZip->m_last_error = MZ_ZIP_NO_ERROR;
    return prev_err;
}

const char *mz_zip_get_error_string(mz_zip_error mz_err)
{
    switch (mz_err)
    {
        case MZ_ZIP_NO_ERROR:
            return "no error";
        case MZ_ZIP_UNDEFINED_ERROR:
            return "undefined error";
        case MZ_ZIP_TOO_MANY_FILES:
            return "too many files";
        case MZ_ZIP_FILE_TOO_LARGE:
            return "file too large";
        case MZ_ZIP_UNSUPPORTED_METHOD:
            return "unsupported method";
        case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
            return "unsupported encryption";
        case MZ_ZIP_UNSUPPORTED_FEATURE:
            return "unsupported feature";
        case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
            return "failed finding central directory";
        case MZ_ZIP_NOT_AN_ARCHIVE:
            return "not a ZIP archive";
        case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
            return "invalid header or archive is corrupted";
        case MZ_ZIP_UNSUPPORTED_MULTIDISK:
            return "unsupported multidisk archive";
        case MZ_ZIP_DECOMPRESSION_FAILED:
            return "decompression failed or archive is corrupted";
        case MZ_ZIP_COMPRESSION_FAILED:
            return "compression failed";
        case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
            return "unexpected decompressed size";
        case MZ_ZIP_CRC_CHECK_FAILED:
            return "CRC-32 check failed";
        case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
            return "unsupported central directory size";
        case MZ_ZIP_ALLOC_FAILED:
            return "allocation failed";
        case MZ_ZIP_FILE_OPEN_FAILED:
            return "file open failed";
        case MZ_ZIP_FILE_CREATE_FAILED:
            return "file create failed";
        case MZ_ZIP_FILE_WRITE_FAILED:
            return "file write failed";
        case MZ_ZIP_FILE_READ_FAILED:
            return "file read failed";
        case MZ_ZIP_FILE_CLOSE_FAILED:
            return "file close failed";
        case MZ_ZIP_FILE_SEEK_FAILED:
            return "file seek failed";
        case MZ_ZIP_FILE_STAT_FAILED:
            return "file stat failed";
        case MZ_ZIP_INVALID_PARAMETER:
            return "invalid parameter";
        case MZ_ZIP_INVALID_FILENAME:
            return "invalid filename";
        case MZ_ZIP_BUF_TOO_SMALL:
            return "buffer too small";
        case MZ_ZIP_INTERNAL_ERROR:
            return "internal error";
        case MZ_ZIP_FILE_NOT_FOUND:
            return "file not found";
        case MZ_ZIP_ARCHIVE_TOO_LARGE:
            return "archive is too large";
        case MZ_ZIP_VALIDATION_FAILED:
            return "validation failed";
        case MZ_ZIP_WRITE_CALLBACK_FAILED:
            return "write calledback failed";
	case MZ_ZIP_TOTAL_ERRORS:
            return "total errors";
        default:
            break;
    }

    return "unknown error";
}

/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */
mz_bool mz_zip_is_zip64(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return MZ_FALSE;

    return pZip->m_pState->m_zip64;
}

size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;

    return pZip->m_pState->m_central_dir.m_size;
}

mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)
{
    return pZip ? pZip->m_total_files : 0;
}

mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip)
{
    if (!pZip)
        return 0;
    return pZip->m_archive_size;
}

mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;
    return pZip->m_pState->m_file_archive_start_ofs;
}

MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip)
{
    if ((!pZip) || (!pZip->m_pState))
        return 0;
    return pZip->m_pState->m_pFile;
}

size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n)
{
    if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead))
        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);

    return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n);
}

mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
{
    mz_uint n;
    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
    if (!p)
    {
        if (filename_buf_size)
            pFilename[0] = '\0';
        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
        return 0;
    }
    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    if (filename_buf_size)
    {
        n = MZ_MIN(n, filename_buf_size - 1);
        memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
        pFilename[n] = '\0';
    }
    return n + 1;
}

mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
{
    return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);
}

mz_bool mz_zip_end(mz_zip_archive *pZip)
{
    if (!pZip)
        return MZ_FALSE;

    if (pZip->m_zip_mode == MZ_ZIP_MODE_READING)
        return mz_zip_reader_end(pZip);
#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
    else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))
        return mz_zip_writer_end(pZip);
#endif

    return MZ_FALSE;
}

#ifdef __cplusplus
}
#endif

#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/
/* 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,
                size_t      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*)ctx_calloc (bin_length + 4, 1);
  unsigned const char *p = bin2;
  unsigned int i;
  if (bin_length > 128 * 1024 * 1024) return;
  memcpy (bin2, bin, (size_t)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]);
   }
  ctx_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;
}


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


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 inline 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(expand_bitpack && 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_PAINT:
        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_END_FRAME:
        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_START_FRAME:
        case CTX_EXIT:
        case CTX_BEGIN_PATH:
        case CTX_CLOSE_PATH:
        case CTX_SAVE:
        case CTX_CLIP:
        case CTX_PRESERVE:
        case CTX_DEFINE_FONT:
        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_LINE_HEIGHT:
        case CTX_WRAP_LEFT:
        case CTX_WRAP_RIGHT:
        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;
#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;
      if(0)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 *) ctx_malloc (item_size * new_size);
      memcpy (ne, drawlist->entries, drawlist->size * item_size );
      ctx_free (drawlist->entries);
      drawlist->entries = ne;
      //drawlist->entries = (CtxEntry*)ctx_malloc (drawlist->entries, item_size * new_size);
    }
  else
    {
      //fprintf (stderr, "allocating for %p %d\n", drawlist, new_size);
      drawlist->entries = (CtxEntry *) ctx_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)
{
  unsigned 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;
}


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;
    }
#if 0
  for (unsigned int i = 0; i < length / sizeof (CtxEntry); i++)
    {
      ctx_drawlist_add_single (&ctx->drawlist, &entries[i]);
    }
#else
  CtxDrawlist dl;
  dl.entries = entries;
  dl.count = length/9;
  dl.size = length;
  dl.flags = CTX_DRAWLIST_DOESNT_OWN_ENTRIES;
  dl.bitpack_pos = 0;

  CtxIterator it;
  ctx_iterator_init (&it, &dl, 0, 0);
  CtxCommand *command;
 
  while ((command = ctx_iterator_next (&it)))
  {
     ctx_process (ctx, (CtxEntry*)command);
  }
#endif
  return 0;
}

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

const CtxEntry *ctx_get_drawlist (Ctx *ctx, int *count)
{
  if (count) *count = ctx->drawlist.count;
  return ctx->drawlist.entries;
}

void ctx_drawlist_force_count (Ctx *ctx, int count)
{
   if ((int)ctx->drawlist.count < count)
     return;
   ctx->drawlist.count = count;
}


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[4];
  entry[0].code = code;
  entry[0].data.u32[0] = u32[0];
  entry[0].data.u32[1] = u32[1];
  return ctx_drawlist_add_single (drawlist, &entry[0]);
}

int ctx_drawlist_add_data (CtxDrawlist *drawlist, const void *data, int length)
{
  CtxEntry entry[4] = {{CTX_DATA, {{0},}}};
  entry[0].data.u32[0] = 0;
  entry[0].data.u32[1] = 0;
  int ret = ctx_drawlist_add_single (drawlist, &entry[0]);
  if (CTX_UNLIKELY(!data)) { return -1; }
  int length_in_blocks;
  if (length <= 0) { length = ctx_strlen ( (char *) data) + 1; }
  length_in_blocks = length / sizeof (CtxEntry);
  length_in_blocks += (length % sizeof (CtxEntry) ) ?1:0;
  if ((signed)drawlist->count + length_in_blocks + 4 > drawlist->size)
    { ctx_drawlist_resize (drawlist, (int)(drawlist->count * 1.2 + length_in_blocks + 32)); }
  if (CTX_UNLIKELY((signed)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[4] = {{CTX_DATA_REV, {{0},}}};
    entry[0].data.u32[0] = length;
    entry[0].data.u32[1] = length_in_blocks;
    ctx_drawlist_add_single (drawlist, &entry[0]);

    /* 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 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;
}

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, ctx_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, ctx_strlen (string));
}

#if CTX_BITPACK_PACKER
static unsigned int
ctx_last_history (CtxDrawlist *drawlist)
{
  unsigned int last_history = 0;
  unsigned 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, unsigned int start_pos)
{
#if CTX_BITPACK
  unsigned 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);
    }

  unsigned int source = drawlist->bitpack_pos;
  unsigned 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 inline void
ctx_drawlist_compact (CtxDrawlist *drawlist)
{
#if CTX_BITPACK_PACKER
  unsigned 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_only_x (const CtxMatrix *m, float *x, float y_in)
{
  //float x_in = *x;
  //*x = ( (x_in * m->m[0][0]) + (y_in * m->m[1][0]) + m->m[2][0]);
  float y_res;
  _ctx_matrix_apply_transform (m, x, &y_res);
}

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

static inline int
determine_transform_type (const CtxMatrix *m)
{
  if (m->m[2][0] != 0.0f ||
      m->m[2][1] != 0.0f ||
      m->m[2][2] != 1.0f)
    return 3;
  if (m->m[0][1] != 0.0f ||
      m->m[1][0] != 0.0f)
    return 3;
  if (m->m[0][2] != 0.0f ||
      m->m[1][2] != 0.0f ||
      m->m[0][0] != 1.0f ||
      m->m[1][1] != 1.0f)
    return 2;
  return 1;
}

#define TRANSFORM_SHIFT (10)
#define TRANSFORM_SCALE (1<<TRANSFORM_SHIFT)

static inline void
_ctx_transform_prime (CtxState *state)
{
   state->gstate.transform_type = 
     determine_transform_type (&state->gstate.transform);

   for (int c = 0; c < 3; c++)
   {
     state->gstate.prepped_transform.m[0][c] =
             (int)(state->gstate.transform.m[0][c] * TRANSFORM_SCALE);
     state->gstate.prepped_transform.m[1][c] =
             (int)(state->gstate.transform.m[1][c] * TRANSFORM_SCALE);
     state->gstate.prepped_transform.m[2][c] =
             (int)(state->gstate.transform.m[2][c] * TRANSFORM_SCALE);
   }
}

static inline void
_ctx_matrix_apply_transform_perspective_fixed (const Ctx16f16Matrix *m, int x_in, int y_in,
                int *x_out, int *y_out)
{
  int w  = (((x_in * m->m[2][0] +
               y_in * m->m[2][1])>>TRANSFORM_SHIFT) +
                     (m->m[2][2]));
  int w_recip = w?TRANSFORM_SCALE / w:0;


  *x_out = ((((((x_in * m->m[0][0] +
               y_in * m->m[0][1])>>TRANSFORM_SHIFT) +
                     (m->m[0][2])) * w_recip)>>TRANSFORM_SHIFT) * CTX_SUBDIV) >> TRANSFORM_SHIFT;
  *y_out = ((((((x_in * m->m[1][0] +
               y_in * m->m[1][1])>>TRANSFORM_SHIFT) +
                     (m->m[1][2])) * w_recip)>>TRANSFORM_SHIFT) * CTX_FULL_AA) >> TRANSFORM_SHIFT;

}

static inline void
_ctx_matrix_apply_transform_affine_fixed (const Ctx16f16Matrix *m, int x_in, int y_in,
                int *x_out, int *y_out)
{
  *x_out = ((((x_in * m->m[0][0] +
               y_in * m->m[0][1])>>TRANSFORM_SHIFT) +
                     (m->m[0][2])) * CTX_SUBDIV) >>TRANSFORM_SHIFT;
  *y_out = ((((x_in * m->m[1][0] +
               y_in * m->m[1][1])>>TRANSFORM_SHIFT) +
                     (m->m[1][2])) * CTX_FULL_AA) >>TRANSFORM_SHIFT;
}

static inline void
_ctx_matrix_apply_transform_scale_translate_fixed (const Ctx16f16Matrix *m, int x_in, int y_in, int *x_out, int *y_out)
{
  *x_out = ((((x_in * m->m[0][0])>>TRANSFORM_SHIFT) +
                     (m->m[0][2])) * CTX_SUBDIV) >>TRANSFORM_SHIFT;
  *y_out = ((((y_in * m->m[1][1])>>TRANSFORM_SHIFT) +
                     (m->m[1][2])) * CTX_FULL_AA) >>TRANSFORM_SHIFT;
}

static inline void
_ctx_user_to_device_prepped_fixed (CtxState *state, int x, int y, int *x_out, int *y_out)
{
  switch (state->gstate.transform_type)
  {
    case 0:
      _ctx_transform_prime (state);
      _ctx_user_to_device_prepped_fixed (state, x, y, x_out, y_out);
      break;
    case 1:  // identity
      *x_out = (x * CTX_SUBDIV) / TRANSFORM_SCALE;
      *y_out = (y * CTX_FULL_AA) / TRANSFORM_SCALE;
      break;
    case 2:  // scale/translate
      _ctx_matrix_apply_transform_scale_translate_fixed (&state->gstate.prepped_transform, x, y, x_out, y_out);
      break;
    case 3:  // affine
      _ctx_matrix_apply_transform_affine_fixed (&state->gstate.prepped_transform, x, y, x_out, y_out);
      break;
    case 4:  // perspective
      _ctx_matrix_apply_transform_perspective_fixed (&state->gstate.prepped_transform, x, y, x_out, y_out);
      break;
  }
}

static inline void
_ctx_user_to_device_prepped (CtxState *state, float x, float y, int *x_out, int *y_out)
{
  int x_in = (int)(x * TRANSFORM_SCALE);
  int y_in = (int)(y * TRANSFORM_SCALE);
  _ctx_user_to_device_prepped_fixed (state, x_in, y_in, x_out, y_out);
}

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

static inline void
_ctx_matrix_apply_transform_distance (const CtxMatrix *m, float *x, float *y)
{
  float x0 = 0.0f;
  float y0 = 0.0f;
  float x1 = *x;
  float y1 = *y;

  _ctx_matrix_apply_transform (m, &x0, &y0);
  _ctx_matrix_apply_transform (m, &x1, &y1);
  *x = (x1-x0);
  *y = (y1-y0);
}

void
ctx_matrix_apply_transform_distance (const CtxMatrix *m, float *x, float *y)
{
  _ctx_matrix_apply_transform_distance (m, x, y);
}

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

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 inline void
_ctx_device_to_user (CtxState *state, float *x, float *y)
{
  CtxMatrix m = state->gstate.transform;
  ctx_matrix_invert (&m);
  _ctx_matrix_apply_transform (&m, x, y);
}

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

void ctx_device_to_user          (Ctx *ctx, float *x, float *y)
{
  _ctx_device_to_user (&ctx->state, x, y);
}

void ctx_device_to_user_distance (Ctx *ctx, float *x, float *y)
{
  _ctx_device_to_user_distance (&ctx->state, x, y);
}


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


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

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[0][2] = x;
  transform.m[1][0] = 0.0f;
  transform.m[1][1] = 1.0f;
  transform.m[1][2] = y;
  transform.m[2][0] = 0.0f;
  transform.m[2][1] = 0.0f;
  transform.m[2][2] = 1.0f;
  _ctx_matrix_multiply (matrix, matrix, &transform);
}

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[0][2] = 0.0f;
  transform.m[1][0] = 0.0f;
  transform.m[1][1] = y;
  transform.m[1][2] = 0.0f;
  transform.m[2][0] = 0.0f;
  transform.m[2][1] = 0.0f;
  transform.m[2][2] = 1.0;
  _ctx_matrix_multiply (matrix, matrix, &transform);
}


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[0][2] = 0;
  transform.m[1][0] = -val_sin;
  transform.m[1][1] = val_cos;
  transform.m[1][2] = 0;
  transform.m[2][0] = 0.0f;
  transform.m[2][1] = 0.0f;
  transform.m[2][2] = 1.0f;
  _ctx_matrix_multiply (matrix, matrix, &transform);
}

#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,
                     float c, float d, 
                     float e, float f, float g, float h, float i)
{
  CtxEntry command[5]=
  {
    ctx_f (CTX_APPLY_TRANSFORM, a, b),
    ctx_f (CTX_CONT,            c, d),
    ctx_f (CTX_CONT,            e, f),
    ctx_f (CTX_CONT,            g, h),
    ctx_f (CTX_CONT,            i, 0)
  };
  ctx_process (ctx, command);
}

void
ctx_get_transform  (Ctx *ctx, float *a, float *b,
                    float *c, float *d,
                    float *e, float *f,
                    float *g, float *h,
                    float *i)
{
  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[0][2]; }
  if (d) { *d = ctx->state.gstate.transform.m[1][0]; }
  if (e) { *e = ctx->state.gstate.transform.m[1][1]; }
  if (f) { *f = ctx->state.gstate.transform.m[1][2]; }
  if (g) { *g = ctx->state.gstate.transform.m[2][0]; }
  if (h) { *h = ctx->state.gstate.transform.m[2][1]; }
  if (i) { *i = ctx->state.gstate.transform.m[2][2]; }
}

void
ctx_source_transform (Ctx *ctx, float a, float b,  // hscale, hskew
                      float c, float d,  // vskew,  vscale
                      float e, float f,
                      float g, float h,
                      float i)  // htran,  vtran
{
  CtxEntry command[5]=
  {
    ctx_f (CTX_SOURCE_TRANSFORM, a, b),
    ctx_f (CTX_CONT,             c, d),
    ctx_f (CTX_CONT,             e, f),
    ctx_f (CTX_CONT,             g, h),
    ctx_f (CTX_CONT,             i, 0)
  };
  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[0][2],
    matrix->m[1][0], matrix->m[1][1], matrix->m[1][2],
    matrix->m[2][0], matrix->m[2][1], matrix->m[2][2]
    
    );
}

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

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--; }
}

static inline float
ctx_matrix_determinant (const CtxMatrix *m)
{
  float det = m->m[0][0] * (m->m[1][1] * m->m[2][2] -
                            m->m[1][2] * m->m[2][1])
              - m->m[0][1] * (m->m[1][0] * m->m[2][2] -
                              m->m [1][2] * m->m [2][0])
              + m->m[0][2] * (m->m[1][0] * m->m[2][1] -
                              m->m[1][1] * m->m[2][0]);
  return det;
}

void
ctx_matrix_invert (CtxMatrix *m)
{
  CtxMatrix t = *m;
  float c = 1.0f / ctx_matrix_determinant (m);

  m->m [0][0] = (t.m [1][1] * t.m [2][2] -
                   t.m [1][2] * t.m [2][1]) * c;
  m->m [1][0] = (t.m [1][2] * t.m [2][0] -
                   t.m [1][0] * t.m [2][2]) * c;
  m->m [2][0] = (t.m [1][0] * t.m [2][1] -
                   t.m [1][1] * t.m [2][0]) * c;

  m->m [0][1] = (t.m [0][2] * t.m [2][1] -
                   t.m [0][1] * t.m [2][2]) * c;
  m->m [1][1] = (t.m [0][0] * t.m [2][2] -
                   t.m [0][2] * t.m [2][0]) * c;
  m->m [2][1] = (t.m [0][1] * t.m [2][0] -
                   t.m [0][0] * t.m [2][1]) * c;

  m->m [0][2] = (t.m [0][1] * t.m [1][2] -
                   t.m [0][2] * t.m [1][1]) * c;
  m->m [1][2] = (t.m [0][2] * t.m [1][0] -
                   t.m [0][0] * t.m [1][2]) * c;
  m->m [2][2] = (t.m [0][0] * t.m [1][1] -
                   t.m [0][1] * t.m [1][0]) * c;
}

#endif
#if CTX_AUDIO

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

#if !__COSMOPOLITAN__

#include <pthread.h>
#if CTX_ALSA
#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
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_list && 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, old);
            ctx_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 && ctx_pcm_list)
    {
      int i;
      for (i = 0; i < c && ctx_pcm_cur_left; i ++)
      {
        if (ctx_pcm_list && ctx_pcm_cur_left)
        {
          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, old);
            ctx_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 ("\033\\", 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_backend_type (ctx) == CTX_BACKEND_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
     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 = ctx_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_backend_type (ctx) == CTX_BACKEND_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;
}

#else

void ctx_pcm_set_format (Ctx *ctx, CtxPCM format) { }
void ctx_pcm_set_sample_rate (Ctx *ctx, int sample_rate) { }
int ctx_pcm_get_sample_rate (Ctx *ctx) { return 48000; }
CtxPCM ctx_pcm_get_format (Ctx *ctx) { return CTX_S16S; }
int ctx_pcm_queue (Ctx *ctx, const int8_t *data, int frames) { return frames; }
float ctx_pcm_get_queued_length (Ctx *ctx) { return 0.0; }

#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 & ((unsigned)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 & ((unsigned)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

#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*)ctx_calloc (sizeof (CtxSHA1), 1);
  ctx_sha1_init (state);
  return state;
}
void ctx_sha1_free (CtxSHA1 *sha1)
{
  ctx_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
#ifdef CTX_X86_64

enum
{
  ARCH_X86_INTEL_FEATURE_MMX      = 1 << 23,
  ARCH_X86_INTEL_FEATURE_XMM      = 1 << 25,
  ARCH_X86_INTEL_FEATURE_XMM2     = 1 << 26,
};

enum
{
  ARCH_X86_INTEL_FEATURE_PNI      = 1 << 0,
  ARCH_X86_INTEL_FEATURE_SSSE3    = 1 << 9,
  ARCH_X86_INTEL_FEATURE_FMA      = 1 << 12,
  ARCH_X86_INTEL_FEATURE_SSE4_1   = 1 << 19,
  ARCH_X86_INTEL_FEATURE_SSE4_2   = 1 << 20,
  ARCH_X86_INTEL_FEATURE_MOVBE    = 1 << 22,
  ARCH_X86_INTEL_FEATURE_POPCNT   = 1 << 23,
  ARCH_X86_INTEL_FEATURE_XSAVE    = 1 << 26,
  ARCH_X86_INTEL_FEATURE_OSXSAVE  = 1 << 27,
  ARCH_X86_INTEL_FEATURE_AVX      = 1 << 28,
  ARCH_X86_INTEL_FEATURE_F16C     = 1 << 29
};

enum
{
  ARCH_X86_INTEL_FEATURE_BMI1     = 1 << 3,
  ARCH_X86_INTEL_FEATURE_BMI2     = 1 << 8,
  ARCH_X86_INTEL_FEATURE_AVX2     = 1 << 5,
};

#define cpuid(a,b,eax,ebx,ecx,edx)                     \
  __asm__("cpuid"                                           \
           : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) \
           : "0" (a), "2" (b)  )

/* returns x86_64 microarchitecture level
 *   0
 */
int
ctx_x86_64_level (void)
{
  int level = 0;
  uint32_t eax, ebx, ecx, edx;
  cpuid (1, 0, eax, ebx, ecx, edx);

  if ((edx & ARCH_X86_INTEL_FEATURE_MMX) == 0)   return level;
  if ((edx & ARCH_X86_INTEL_FEATURE_XMM) == 0)   return level;
  level = 1;

  if ((ecx & ARCH_X86_INTEL_FEATURE_SSSE3)==0)   return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_SSE4_1)==0)  return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_SSE4_2)==0)  return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_POPCNT)==0)  return level;
  level = 2;

  if ((ecx & ARCH_X86_INTEL_FEATURE_AVX)==0)     return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_OSXSAVE)==0) return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_XSAVE)==0)   return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_FMA)==0)     return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_F16C)==0)    return level;
  if ((ecx & ARCH_X86_INTEL_FEATURE_MOVBE)==0)   return level;

  cpuid (0, 0, eax, ebx, ecx, edx);
  if (eax >= 7)
  {
    cpuid (2, 0, eax, ebx, ecx, edx);
    if ((ebx & ARCH_X86_INTEL_FEATURE_AVX2)==0)  return level;
    if ((ebx & ARCH_X86_INTEL_FEATURE_BMI1)==0)  return level;
    if ((ebx & ARCH_X86_INTEL_FEATURE_BMI2)==0)  return level;
    level = 3; 
  }
  return level;
}

#endif

#ifdef CTX_ARMV7L

#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <elf.h>


int ctx_arm_has_neon (int *armv)
{
  /* TODO : add or hardcode the other ways it can be on arm, where
   *        this info comes from the system and not from running cpu
   *        instructions
   */
  int has_neon = 0;
  int arm_level = 5;
  int fd = open ("/proc/self/auxv", O_RDONLY);
  Elf32_auxv_t auxv;
  if (fd >= 0)
  {
    while (read (fd, &auxv, sizeof (Elf32_auxv_t)) == sizeof (Elf32_auxv_t))
    {
      if (auxv.a_type == AT_HWCAP)
      {
        if (auxv.a_un.a_val & 4096)
          has_neon = 1;
      }
      else if (auxv.a_type == AT_PLATFORM)
      {
        if (!strncmp ((const char*)auxv.a_un.a_val, "v6l", 3))
          arm_level = 6;
        else if (!strncmp ((const char*)auxv.a_un.a_val, "v7l", 3))
          arm_level = 7;
        else if (!strncmp ((const char*)auxv.a_un.a_val, "v8l", 3))
          arm_level = 8;
      }
    }
    close (fd);
  }
  if (armv) *armv = arm_level;
  return has_neon;
}
#endif
#include <stdio.h>
#include <string.h>

#if CTX_FORMATTER

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;
}
#endif

#if CTX_PARSER
static int ctx_ydec (const char *tmp_src, char *dst, int count)
{
  const char *src = tmp_src;
#if 0
  if (tmp_src == dst)
  {
    src = ctx_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) ctx_free (src);
#endif
          return out_len;
        }
        o = (o-42-64) % 256;
        dst[out_len++] = o;
        break;
      case '\n':
      case '\033':
      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) ctx_free (src);
#endif
  return out_len;
}
#endif

#if 0
int main (){
  char *input="this is a testæøåÅØ'''\"!:_asdac\n\r";
  char  encoded[256]="";
  char  decoded[256]="";
  int   in_len = ctx_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_UTIL_H
#define __CTX_UTIL_H


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 (!ctx_strcmp (c->path, path))
    {
       if (c->free_data)
       {
         ctx_free (c->contents);
       }
       c->free_data = free_data;
       c->contents = (unsigned char*)contents;
       c->length = length;
       return;
    }
  }
  CtxFileContent *c = (CtxFileContent*)ctx_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 = ctx_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,
                          long            max_len)
{
  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 (size > max_len)
  {
     size = remaining = max_len;
  }

  if (length)
    { *length =size; }
  rewind (file);
  buffer = (char*)ctx_malloc (size + 8);
  if (!buffer)
    {
      fclose (file);
      return -1;
    }
  remaining -= fread (buffer, 1, remaining, file);
  if (remaining)
    {
      fclose (file);
      ctx_free (buffer);
      return -1;
    }
  fclose (file);
  *contents = (unsigned char*) buffer;
  buffer[size] = 0;
  return 0;
}

static int
__ctx_file_get_contents (const char     *path,
                        unsigned char **contents,
                        long           *length)
{
  return ___ctx_file_get_contents (path, contents, length, 1024*1024*1024);
}

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




#endif


#endif


static float ctx_state_get (CtxState *state, uint32_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, uint32_t key, float value)
{
  if (key != SQZ_newState)
    {
      if (ctx_state_get (state, key) == value)
        { return; }
      for (int i = state->gstate.keydb_pos-1;
           i >= 0 && state->keydb[i].key != SQZ_newState;
           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.0f)
#define CTX_KEYDB_STRING_END   (CTX_KEYDB_STRING_START + CTX_STRINGPOOL_SIZE)

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

static int ctx_float_to_string_index (float val)
{
  int idx = -1;
  if (ctx_float_is_string (val))
  {
    idx = (int)(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, uint32_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, uint32_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, uint32_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, uint32_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 && !ctx_strcmp (old_string, string))
      return;
  }

  if (ctx_str_is_number (string))
  {
    ctx_state_set (state, key, _ctx_parse_float (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, ctx_strlen(string));
}

static int ctx_state_get_color (CtxState *state, uint32_t key, CtxColor *color)
{
  CtxColor *stored = (CtxColor*)ctx_state_get_blob (state, key);
  CtxColor  copy;
  if (stored)
  {
    // we make a copy to ensure alignment
    memcpy (&copy, stored, sizeof (CtxColor));
    if (copy.magic == 127)
    {
      *color = copy;
      return 0;
    }
  }
  return -1;
}

static void ctx_state_set_color (CtxState *state, uint32_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, uint32_t hash)
{
  return ctx_state_get_string (&ctx->state, hash);
}
float ctx_get_float (Ctx *ctx, uint32_t hash)
{
  return ctx_state_get (&ctx->state, hash);
}
int ctx_get_int (Ctx *ctx, uint32_t hash)
{
  return (int)ctx_state_get (&ctx->state, hash);
}
void ctx_set_float (Ctx *ctx, uint32_t hash, float value)
{
  ctx_state_set (&ctx->state, hash, value);
}
void ctx_set_string (Ctx *ctx, uint32_t hash, const char *value)
{
  ctx_state_set_string (&ctx->state, hash, value);
}
void ctx_set_color (Ctx *ctx, uint32_t hash, CtxColor *color)
{
  ctx_state_set_color (&ctx->state, hash, color);
}
int  ctx_get_color (Ctx *ctx, uint32_t hash, CtxColor *color)
{
  return ctx_state_get_color (&ctx->state, hash, color);
}
int ctx_is_set (Ctx *ctx, uint32_t hash)
{
  return ctx_get_float (ctx, hash) != -0.0f;
}
int ctx_is_set_now (Ctx *ctx, uint32_t hash)
{
  return ctx_is_set (ctx, hash);
}
#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 2;
      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 CTX_U8_TO_FLOAT_LUT
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)
{
  ctx_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 (uint8_t)(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


#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)
{
#if CTX_PROTOCOL_U8_COLOR
  uint8_t ru, gu, bu, au;
  if (r < 0) ru = 0;
  else if ( r > 1.0f) ru = 255;
  else ru = (uint8_t)(r * 255);
  if (g < 0) gu = 0;
  else if ( g > 1.0f) gu = 255;
  else gu = (uint8_t)(g * 255);
  if (b < 0) bu = 0;
  else if ( b > 1.0f) bu = 255;
  else bu = (uint8_t)(b * 255);
  if (a < 0) au = 0;
  else if ( a > 1.0f) au = 255;
  else au = (uint8_t)(a * 255);

  CtxEntry command = ctx_u8 (CTX_SET_RGBA_U8, ru,gu,bu,au, 0, 0, 0, 0);

  uint8_t rgba[4];
  ctx_color_get_rgba8 (&ctx->state, &ctx->state.gstate.source_fill.color, rgba);
  if (rgba[0] == ru && rgba[1] == gu && rgba[2] == bu && rgba[3] == au)
     return;
  ctx_process (ctx, &command);
#else
  float components[4]={r,g,b,a};
  ctx_color_raw (ctx, CTX_RGBA, components, 0);
#endif
}

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);
}

#if 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);
}
#endif

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;

static ColorDef _ctx_colors[]={
  {SQZ_black,    0, 0, 0, 1},
  {SQZ_red,      1, 0, 0, 1},
  {SQZ_green,    0, 1, 0, 1},
  {SQZ_yellow,   1, 1, 0, 1},
  {SQZ_blue,     0, 0, 1, 1},
  {SQZ_fuchsia,  1, 0, 1, 1},
  {SQZ_cyan,     0, 1, 1, 1},
  {SQZ_white,    1, 1, 1, 1},
  {SQZ_silver,   0.75294f, 0.75294f, 0.75294f, 1},
  {SQZ_gray,     0.50196f, 0.50196f, 0.50196f, 1},
  {SQZ_magenta,  0.50196f, 0, 0.50196f, 1},
  {SQZ_maroon,   0.50196f, 0, 0, 1},
  {SQZ_purple,   0.50196f, 0, 0.50196f, 1},
  {SQZ_green,    0, 0.50196f, 0, 1},
  {SQZ_lime,     0, 1, 0, 1},
  {SQZ_olive,    0.50196f, 0.50196f, 0, 1},
  {SQZ_navy,     0, 0,      0.50196f, 1},
  {SQZ_teal,     0, 0.50196f, 0.50196f, 1},
  {SQZ_aqua,     0, 1, 1, 1},
  {SQZ_transparent, 0, 0, 0, 0},
  {SQZ_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.0f;
        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 = ctx_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;
}

int ctx_color_set_from_string (Ctx *ctx, CtxColor *color, const char *string)
{
  int i;
  uint32_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 == SQZ_currentColor)
  {
    float rgba[4];
    CtxColor ccolor;
    memset (&ccolor, 0, sizeof (CtxColor));
    ctx_get_color (ctx, SQZ_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 (!ctx_strcmp (tmp, "srgb"))            space = babl_space ("sRGB");
        else if (!ctx_strcmp (tmp, "scrgb"))      space = babl_space ("scRGB");
        else if (!ctx_strcmp (tmp, "acescg"))     space = babl_space ("ACEScg");
        else if (!ctx_strcmp (tmp, "adobe"))      space = babl_space ("Adobe");
        else if (!ctx_strcmp (tmp, "apple"))      space = babl_space ("Apple");
        else if (!ctx_strcmp (tmp, "rec2020"))    space = babl_space ("Rec2020");
        else if (!ctx_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)ctx_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 =(int)(r * 255);
  int ig =(int)(g * 255);
  int ib =(int)(b * 255);
  int ia =(int)(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 

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)
{
  ctx_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)
  {
    ctx_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_destroy (buffer->color_managed);
    }
    buffer->color_managed = NULL;
  }
}

void ctx_buffer_destroy (CtxBuffer *buffer)
{
  ctx_buffer_deinit (buffer);
  ctx_free (buffer);
}

#if 0
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  &&
        !ctx_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;
}
#endif

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 &&
          !ctx_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*)ctx_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 = ctx_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 = ctx_strdup (ascii);
  }
  return ctx->texture[id].eid;
}

void
_ctx_texture_prepare_color_management (CtxState      *state,
                                       CtxBuffer     *buffer)
{
// _ctx_texture_lock ();
   switch (buffer->format->pixel_format)
   {
#if CTX_BABL
     case CTX_FORMAT_RGBA8:
       if (buffer->space == state->gstate.device_space)
       {
         buffer->color_managed = buffer;
       }
       else
       {
          CtxBuffer *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", state->gstate.device_space)),
             buffer->data, color_managed->data,
             buffer->width * buffer->height
             );
          buffer->color_managed = color_managed;
       }
       break;
     case CTX_FORMAT_RGB8:
       if (buffer->space == state->gstate.device_space)
       {
         buffer->color_managed = buffer;
       }
       else
       {
         CtxBuffer *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", state->gstate.device_space)),
            buffer->data, color_managed->data,
            buffer->width * buffer->height
          );
         buffer->color_managed = color_managed;
       }
       break;
#endif
     default:
       buffer->color_managed = buffer;
   }
//  _ctx_texture_unlock ();
}



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_TERMINAL_EVENTS

#if !__COSMOPOLITAN__

#include <fcntl.h>
#if CTX_PTY
#include <termios.h>
#include <sys/ioctl.h>
#endif
#endif

#if 0
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, "\033[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, "\033[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;
}
#else


int ctx_terminal_width (void)
{
#if CTX_PTY
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 640;
  return ws.ws_xpixel;
#else
  return 240;
#endif
} 

int ctx_terminal_height (void)
{
#if CTX_PTY
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 450;
  return ws.ws_ypixel;
#else
  return 240;
#endif
}

#endif

int ctx_terminal_cols (void)
{
#if CTX_PTY
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 80;
  return ws.ws_col;
#else
  return 40;
#endif
} 

int ctx_terminal_rows (void)
{
#if CTX_PTY
  struct winsize ws; 
  if (ioctl(0,TIOCGWINSZ,&ws)!=0)
    return 25;
  return ws.ws_row;
#else
  return 16;
#endif
}





#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__
#if CTX_PTY
#include <termios.h>
#endif
#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 */
#if CTX_PTY
static int  ctx_term_signal_installed = 0;   /* XXX: global state */
#endif

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[10~"},
  {"F2",        "F2",  "\033[11~"},
  {"F3",        "F3",  "\033[12~"},
  {"F4",        "F4",  "\033[13~"},
  {"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, }
};
#if CTX_PTY
static struct termios orig_attr;    /* in order to restore at exit */
static int    nc_is_raw = 0;
static int    atexit_registered = 0;
#endif
static int    mouse_mode = NC_MOUSE_NONE;

static void _nc_noraw (void)
{
#if CTX_PTY
  if (nc_is_raw && tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr) != -1)
    nc_is_raw = 0;
#endif
}

void
nc_at_exit (void)
{
  printf (TERMINAL_MOUSE_OFF);
  printf (XTERM_ALTSCREEN_OFF);
  _nc_noraw();
  fprintf (stdout, "\033[?25h");
  //if (ctx_native_events)
  fprintf (stdout, "\033[?201l");
  fprintf (stdout, "\033[?1049l");
}

static const char *mouse_get_event_int (Ctx *n, int *x, int *y)
{
  static int prev_state = 0;
  const char *ret = "pm";
  float relx, rely;
  signed char buf[3];
  read (n->mouse_fd, buf, 3);
  relx = buf[1];
  rely = -buf[2];

  n->mouse_x += (int)(relx * 0.1f);
  n->mouse_y += (int)(rely * 0.1f);

  if (n->mouse_x < 1) n->mouse_x = 1;
  if (n->mouse_y < 1) n->mouse_y = 1;
  if (n->mouse_x >= n->width)  n->mouse_x = n->width;
  if (n->mouse_y >= n->height) n->mouse_y = n->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 = "pp";
    }
  else if (buf[0] & 1)
    ret = "pd";

  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, "pm")) ||
         (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;
}

#if CTX_PTY
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;
}
#endif

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)
{
  if (x) *x = -1;
  if (y) *y = -1;
#if CTX_PTY
  unsigned char buf[20];
  int length;
  if (!ctx_term_signal_installed)
    {
      _nc_raw ();
      ctx_term_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);
              if (y) *y = ((unsigned char)buf[5]-32);
              switch (buf[3])
                {
                        /* XXX : todo reduce this to less string constants */
                  case 32:  return "pp";
                  case 33:  return "mouse1-press";
                  case 34:  return "mouse2-press";
                  case 40:  return "alt-pp";
                  case 41:  return "alt-mouse1-press";
                  case 42:  return "alt-mouse2-press";
                  case 48:  return "control-pp";
                  case 49:  return "control-mouse1-press";
                  case 50:  return "control-mouse2-press";
                  case 56:  return "alt-control-pp";
                  case 57:  return "alt-control-mouse1-press";
                  case 58:  return "alt-control-mouse2-press";
                  case 64:  return "pd";
                  case 65:  return "mouse1-drag";
                  case 66:  return "mouse2-drag";
                  case 71:  return "pm"; /* shift+motion */
                  case 72:  return "alt-pd";
                  case 73:  return "alt-mouse1-drag";
                  case 74:  return "alt-mouse2-drag";
                  case 75:  return "pm"; /* alt+motion */
                  case 80:  return "control-pd";
                  case 81:  return "control-mouse1-drag";
                  case 82:  return "control-mouse2-drag";
                  case 83:  return "pm"; /* ctrl+motion */
                  case 91:  return "pm"; /* ctrl+alt+motion */
                  case 95:  return "pm"; /* 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 "pm";
                           /* have a separate pd ? */
                  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";
#else
  return "NYI.";
#endif
}

void ctx_nct_consume_events (Ctx *ctx)
{
  int ix, iy;
  CtxCtx *ctxctx = (CtxCtx*)ctx->backend;
  const char *event = NULL;

  do {
    float x, y;
    event = ctx_nct_get_event (ctx, 50, &ix, &iy);

    x = (ix - 1.0f + 0.5f) / ctxctx->cols * ctx->width;
    y = (iy - 1.0f)        / ctxctx->rows * ctx->height;

    if (!strcmp (event, "pp"))
    {
      ctx_pointer_press (ctx, x, y, 0, 0);
      ctxctx->was_down = 1;
    } else if (!strcmp (event, "pr"))
    {
      ctx_pointer_release (ctx, x, y, 0, 0);
      ctxctx->was_down = 0;
    } else if (!strcmp (event, "pm"))
    {
      //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, "pd"))
    {
      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;
      ctx_free (mrg->glyphs);
      ctx_free (mrg->styles);
      ctx_free (backend->nct_pixels);
      backend->nct_pixels = ctx_calloc (width * height * 4, 1);
      mrg->glyphs = ctx_calloc ((width/CPX) * (height/CPX) * 4, 1);
      mrg->styles = ctx_calloc ((width/CPX) * (height/CPX) * 1, 1);
      mrg_set_size (mrg, width, height);
      mrg_queue_draw (mrg, NULL);
#endif
      //if (ctx_backend_is_ctx (ctx))
#if 0
      {
        int width = ctx_terminal_width ();
        int height = ctx_terminal_height ();
        ctx_set_size (ctx, width, height);
      }
#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"))
      {
        event = NULL;
      }
      else
      ctx_key_press (ctx, 0, event, 0);
    }
  }  while (event);
}

const char *ctx_native_get_event (Ctx *n, int timeoutms)
{
#if CTX_PTY
  static unsigned char buf[256];
  int length;

  if (!ctx_term_signal_installed)
    {
      _nc_raw ();
      ctx_term_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, "\033[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);
    }
#endif
  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

#ifdef EMSCRIPTEN
#include "emscripten.h"
#endif

#define usecs(time)    ((uint64_t)(time.tv_sec - start_time.tv_sec) * 1000000 + time.     tv_usec)

#if !__COSMOPOLITAN__

#if CTX_PTY==0
#include "pico/stdlib.h"
#include "hardware/timer.h"
static uint64_t pico_get_time(void) {
    // Reading low latches the high value
    uint32_t lo = timer_hw->timelr;
    uint32_t hi = timer_hw->timehr;
    return ((uint64_t) hi << 32u) | lo;
}
static uint64_t start_time;
#else
static struct timeval start_time;
#endif
static void
_ctx_init_ticks (void)
{
  static int done = 0;
  if (done)
    return;
  done = 1;
#if CTX_PTY==0
  start_time = pico_get_time();
#else
  gettimeofday (&start_time, NULL);
#endif
}

static inline unsigned long
_ctx_ticks (void)
{
#if CTX_PTY==0
  uint64_t measure_time =  pico_get_time();
  return measure_time - start_time;
#else
  struct timeval measure_time;
  gettimeofday (&measure_time, NULL);
  return usecs (measure_time) - usecs (start_time);
#endif
}

CTX_EXPORT unsigned long
ctx_ticks (void)
{
  _ctx_init_ticks ();
  return _ctx_ticks ();
}



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;
int _ctx_max_threads = 1;
#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
  const char *backend = getenv ("CTX_BACKEND");
  if (!backend || ctx_strcmp (backend, "ctx"))
  {
    int i;
    char *new_argv[*argc+5];
    new_argv[0] = "ctx";
    new_argv[1] = "-e";
    new_argv[2] = "--";
    for (i = 0; i < *argc; i++)
    {
      new_argv[i+3] = *argv[i];
    }
    new_argv[i+3] = NULL;
    execvp (new_argv[0], new_argv);
  }
#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)
{
#if CTX_BAREMETAL==0
    fprintf (stderr, "possible values for CTX_BACKEND:\n");
    fprintf (stderr, " ctx");
#if CTX_SDL
    fprintf (stderr, " SDL");
#endif
#if CTX_KMS
    fprintf (stderr, " kms");
#endif
#if CTX_FB
    fprintf (stderr, " fb");
#endif
#if CTX_TERM
    fprintf (stderr, " term");
#endif
#if CTX_TERMIMG
    fprintf (stderr, " termimg");
#endif
    fprintf (stderr, "\n");
#endif
}

static uint32_t ctx_ms (Ctx *ctx)
{
  return _ctx_ticks () / 1000;
}

#if CTX_TERMINAL_EVENTS

static int is_in_ctx (void)
{
#if CTX_PTY
  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, "\033[?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;
  }
#endif
  return 0;
}
#endif

#if EMSCRIPTEN

CTX_EXPORT Ctx *
ctx_wasm_get_context (int flags);

static Ctx *ctx_new_ui (int width, int height, const char *backend)
{
   return ctx_wasm_get_context (CTX_FLAG_HASH_CACHE);
}
#else

static Ctx *ctx_new_ui (int width, int height, const char *backend)
{
#if CTX_TILED
  if (getenv ("CTX_DAMAGE_CONTROL"))
  {
    const char * val = getenv ("CTX_DAMAGE_CONTROL");
    if (!ctx_strcmp (val, "0") ||
        !ctx_strcmp (val, "off"))
      _ctx_damage_control = 0;
    else
      _ctx_damage_control = 1;
  }
#endif

#if CTX_BAREMETAL==0
  if (getenv ("CTX_HASH_CACHE"))
  {
    const char * val = getenv ("CTX_HASH_CACHE");
    if (!ctx_strcmp (val, "0"))
      _ctx_enable_hash_cache = 0;
    if (!ctx_strcmp (val, "off"))
      _ctx_enable_hash_cache = 0;
  }
#endif

#if CTX_THREADS
  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
  }
  
  mtx_init (&_ctx_texture_mtx, mtx_plain);

  if (_ctx_max_threads < 1) _ctx_max_threads = 1;
  if (_ctx_max_threads > CTX_MAX_THREADS) _ctx_max_threads = CTX_MAX_THREADS;
#endif

#if CTX_BAREMETAL==0
  //fprintf (stderr, "ctx using %i threads\n", _ctx_max_threads);
  if (!backend)
    backend = getenv ("CTX_BACKEND");
#endif

  if (backend && !ctx_strcmp (backend, ""))
    backend = NULL;
  if (backend && !ctx_strcmp (backend, "auto"))
    backend = NULL;
  if (backend && !ctx_strcmp (backend, "list"))
  {
    ctx_list_backends ();
    exit (-1);
  }

  Ctx *ret = NULL;
#if CTX_FORMATTER
#if CTX_TERMINAL_EVENTS
  /* we do the query on auto but not on directly set ctx
   *
   */
  if ((backend && !ctx_strcmp(backend, "ctx")) ||
      (backend == NULL && is_in_ctx ()))
  {
    if (!backend || !ctx_strcmp (backend, "ctx"))
    {
      // full blown ctx protocol - in terminal or standalone
      ret = ctx_new_ctx (width, height);
    }
  }
#endif
#endif

#if CTX_TERMINAL_EVENTS
#if CTX_HEADLESS
  if (!ret)
    {
      if (backend && !ctx_strcmp (backend, "headless"))
        ret = ctx_new_headless (width, height);
    }
#endif
#endif

#if CTX_SDL
  if (!ret && getenv ("DISPLAY"))
  {
    if ((backend==NULL) || (!ctx_strcmp (backend, "SDL")))
      ret = ctx_new_sdl (width, height);
  }
#endif

#if CTX_KMS
  if (!ret && !getenv ("DISPLAY"))
  {
    if ((backend==NULL) || (!ctx_strcmp (backend, "kms")))
      ret = ctx_new_kms (width, height);
  }
#endif


#if CTX_FB
  if (!ret && !getenv ("DISPLAY"))
    {
      if ((backend==NULL) || (!ctx_strcmp (backend, "fb")))
        ret = ctx_new_fb (width, height);
    }
#endif

#if CTX_TERMINAL_EVENTS
#if CTX_RASTERIZER
  // braille in terminal
#if CTX_TERM
  if (!ret)
  {
    if ((backend==NULL) || (!ctx_strcmp (backend, "term")))
    ret = ctx_new_term (width, height);
  }
#endif
#if CTX_TERMIMG
  if (!ret)
  {
    if ((backend==NULL) || (!ctx_strcmp (backend, "termimg")))
    ret = ctx_new_termimg (width, height);
  }
#endif
#endif
#endif
  if (!ret)
  {
#if CTX_BAREMETAL==0
    fprintf (stderr, "no interactive ctx backend\n");
#endif
    ctx_list_backends ();
    exit (2);
  }
  ctx_get_event (ret); // enables events
  return ret;
}
#endif
#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->width != width || ctx->height != height)
  {
    ctx->width = width;
    ctx->height = height;
    switch (ctx_backend_type (ctx))
    {
      case CTX_BACKEND_CTX:
      case CTX_BACKEND_TERM:
      case CTX_BACKEND_TERMIMG:
        {CtxCtx *ctxctx = (CtxCtx*)ctx->backend;
         ctxctx->width = width;
         ctxctx->height = height;
        }
        break;
      default: break;
    }
#if CTX_EVENTS
    _ctx_resized (ctx, width, height, 0);
#endif
  }
}

#if CTX_EVENTS

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;
  unsigned long ticks = ctx_ticks ();
  long tick_delta = (prev_ticks == 0) ? 0 : ticks - prev_ticks;
  prev_ticks = ticks;


  if (!ctx->events.idles && !ctx->events.idles_to_add)
  {
#ifdef EMSCRIPTEN
#ifdef ASYNCIFY
    emscripten_sleep (10);
#endif
#endif
    return;
  }

  ctx->events.in_idle_dispatch=1;

  for (l = ctx->events.idles; l; l = l->next)
  {
    CtxIdleCb *item = l->data;

    long rem = item->ticks_remaining;
    if (item->ticks_remaining >= 0)
    {
      rem -= tick_delta;

      item->ticks_remaining -= tick_delta / 100;

    if (rem < 0)
    {
      int to_be_removed = 0;
      for (CtxList *l2 = ctx->events.idles_to_remove; l2; l2=l2->next)
      {
        CtxIdleCb *item2 = l2->data;
        if (item2 == item) to_be_removed = 1;
      }
      
      if (!to_be_removed)
      {
      if (item->cb (ctx, item->idle_data) == 0)
      {
        ctx_list_prepend (&ctx->events.idles_to_remove, item);
      }
      else
        item->ticks_remaining = item->ticks_full;
      }
    }
    else
        item->ticks_remaining = rem;
    }
    else
    {
      int to_be_removed = 0;
      for (CtxList *l2 = ctx->events.idles_to_remove; l2; l2=l2->next)
      {
        CtxIdleCb *item2 = l2->data;
        if (item2 == item) to_be_removed = 1;
      }
      
      if (!to_be_removed)
      {
        if (item->cb (ctx, item->idle_data) == 0)
        {
          ctx_list_prepend (&ctx->events.idles_to_remove, item);
        }
        else
          item->ticks_remaining = item->ticks_full;
      }
    }
  }

  while (ctx->events.idles_to_add)
  {
    CtxIdleCb *item = ctx->events.idles_to_add->data;
    ctx_list_prepend (&ctx->events.idles, item);
    ctx_list_remove (&ctx->events.idles_to_add, item);
  }

  while (ctx->events.idles_to_remove)
  {
    CtxIdleCb *item = ctx->events.idles_to_remove->data;
    ctx_list_remove (&ctx->events.idles, item);
    ctx_list_remove (&ctx->events.idles_to_remove, item);
    if (item->destroy_notify)
      item->destroy_notify (item->destroy_data);
  }
  ctx->events.in_idle_dispatch=0;
#if EMSCRIPTEN
#ifdef ASYNCIFY
   emscripten_sleep(1);
#endif
#endif
}


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)
  {
#if CTX_BAREMETAL==0
    fprintf (stderr, "warning: binding overflow\n");
#endif
    return;
  }
  events->bindings[events->n_bindings].nick = ctx_strdup (key);
  strcpy (events->bindings[events->n_bindings].nick, key);

  if (action)
    events->bindings[events->n_bindings].command = action ? ctx_strdup (action) : NULL;
  if (label)
    events->bindings[events->n_bindings].label = label ? ctx_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);
    ctx_free (events->bindings[i].nick);
    if (events->bindings[i].command)
      ctx_free (events->bindings[i].command);
    if (events->bindings[i].label)
      ctx_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 (!ctx_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 (!ctx_strcmp (events->bindings[i].nick, "any"))
    {
      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 (&ctx->events.idles_to_remove, item);
    }
  }

  if (ctx->events.in_idle_dispatch)
    return;

  while (ctx->events.idles_to_remove)
  {
    CtxIdleCb *item = ctx->events.idles_to_remove->data;
    ctx_list_remove (&ctx->events.idles, item);
    ctx_list_remove (&ctx->events.idles_to_remove, item);
    if (item->destroy_notify)
      item->destroy_notify (item->destroy_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 = ctx_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;
  if (ctx->events.in_idle_dispatch)
  ctx_list_append (&ctx->events.idles_to_add, item);
  else
  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 = ctx_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)
  {
#if CTX_BAREMETAL==0
    fprintf (stderr, "EEEEK!\n");
#endif
  }
  item->ref_count++;
}


void _ctx_item_unref (CtxItem *item)
{
  if (item->ref_count <= 0)
  {
#if CTX_BAREMETAL==0
    fprintf (stderr, "EEEEK!\n");
#endif
    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)
    {
      ctx_free (item->path);
      item->path = NULL;
    }
    ctx_free (item);
  }
}


void _ctx_item_unref2 (void *data, void *data2)
{
  CtxItem *item = (CtxItem*)data;
  _ctx_item_unref (item);
}

#if 0
static int
path_equal (void *path,
            void *path2)
{
        return 0;
  CtxDrawlist *a = (CtxDrawlist*)path;
  CtxDrawlist *b = (CtxDrawlist*)path2;
  if (!a && !b) return 1;
  if (!a && b) return 0;
  if (!b && a) return 0;
  if (a->count != b->count)
    return 0;
  return memcmp (a->entries, b->entries, a->count * 9) == 0;
}
#endif

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->height * 2 ||
          tx > ctx->width * 2 ||
          tx + tw < 0 ||
          ty + th < 0)
      {
        if (finalize)
          finalize (data1, data2, finalize_data);
        return;
      }
    }

    item = ctx_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 = ctx_current_path (ctx);
    item->path_hash = ctx_path_hash (item->path);
    ctx_get_matrix (ctx, &item->inv_matrix);
    ctx_matrix_invert (&item->inv_matrix);

#if 0
    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];
          ctx_free (item);
          item2->cb_count++;
          item2->types |= types;
          return;
        }
      }
    }
#endif
    item->ref_count       = 1;
    ctx->events.last_item = item;
    ctx_list_prepend_full (&ctx->events.items, item, _ctx_item_unref2, NULL);
      return;
  }
}

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;
  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;
  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)
{
#if CTX_BAREMETAL==0
  const char *id = data;

  fprintf (stderr, "hit region %s\n", id);
#endif
  // XXX: NYI
}

void ctx_add_hit_region (Ctx *ctx, const char *id)
{
  char *id_copy = ctx_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;
  }
  
  ctx_listen_full (ctx, x, y, width, height,
                   CTX_POINTER, ctx_report_hit_region,
                   id_copy, NULL, (void*)ctx_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);
  ctx_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 = ctx_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
{
  CtxDrawlist *dl = (CtxDrawlist*)path;
  if (!dl) return;

  ctx_append_drawlist (ctx, dl->entries, dl->count*9);
}

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);
        // XXX  - is this done on wrongly transformed coordinates?
        if (ctx_in_fill (ctx, u, v))
        {
          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 CtxEvent event_copy;

static int
_ctx_emit_cb_item (Ctx *ctx, CtxItem *item, CtxEvent *event, CtxEventType type, float x, float y)
{
  CtxEvent transformed_event;
  int i;


  if (!event)
  {
    event = &event_copy;
    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

//#include <stdatomic.h>

void ctx_consume_events (Ctx *ctx)
{
  CtxBackend *backend = ctx->backend;
  if (backend && backend->consume_events)
    backend->consume_events (ctx);
}

void ctx_stdin_get_event_fds (Ctx *ctx, int *fd, int *count)
{
  fd[0] = STDIN_FILENO;
  *count = 1;
}

void ctx_get_event_fds (Ctx *ctx, int *fd, int *count)
{
  CtxBackend *backend = ctx->backend;
  if (backend && backend->get_event_fds)
    backend->get_event_fds (ctx, fd, count);
  *count = 0;
}


CtxEvent *ctx_get_event (Ctx *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;
    }

  _ctx_idle_iteration (ctx);
#if 1
  if (ctx->events.ctx_get_event_enabled==0)
  {
    ctx->events.ctx_get_event_enabled = 1;
    ctx_queue_draw (ctx);
  }
#endif

  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;
}

CTX_EXPORT 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;
}

CTX_EXPORT 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)
  {
#if CTX_BAREMETAL==0
    fprintf (stderr, "events thought device %i was already down\n", device_no);
#endif
  }
  /* 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);
  }

}

CTX_EXPORT 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.
 */
CTX_EXPORT 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_queue_draw (ctx);
    }
    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;
}

CTX_EXPORT 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;

#if CTX_BAREMETAL==0
    fprintf (stderr, "{%s|\n", message);
#endif

      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;
        }
      }
  }
}

CTX_EXPORT 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;
}

#if 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;
}
#endif


static const char *ctx_keycode_to_keyname (CtxModifierState modifier_state,
                                           int keycode)
{
   static char temp[6]=" ";
   const char *str = &temp[0];
   if (keycode >= 65 && keycode <= 90)
   {
     if (modifier_state & CTX_MODIFIER_STATE_SHIFT)
       temp[0]=keycode-65+'A';
     else
       temp[0]=keycode-65+'a';
     temp[1]=0;
   }
   else if (keycode >= 112 && keycode <= 123)
   {
     sprintf (temp, "F%i", keycode-111);
   }
   else
   switch (keycode)
   {
     case 8: str="backspace"; break;
     case 9: str="tab"; break;
     case 13: str="return"; break;
     case 16: str="shift"; break;
     case 17: str="control"; break;
     case 18: str="alt"; break;
     case 27: str="escape"; break;
     case 32: str="space"; break;
     case 33: str="page-up"; break;
     case 34: str="page-down"; break;
     case 35: str="end"; break;
     case 36: str="home"; break;
     case 37: str="left"; break;
     case 38: str="up"; break;
     case 39: str="right"; break;
     case 40: str="down"; break;
     case 45: str="insert"; break;
     case 46: str="delete"; break;
     default:
       if (modifier_state & CTX_MODIFIER_STATE_SHIFT)
       switch (keycode)
       {
         case 173: str="_"; break;
         case 186: str=":"; break;
         case 187: str="+"; break;
         case 188: str="<"; break;
         case 189: str="_"; break;
         case 190: str=">"; break;
         case 191: str="?"; break;
         case 192: str="~"; break;
         case 219: str="{"; break;
         case 221: str="}"; break;
         case 220: str="|"; break;
         case 222: str="\""; break;
         case 48: str=")"; break;
         case 49: str="!"; break;
         case 50: str="@"; break;
         case 51: str="#"; break;
         case 52: str="$"; break;
         case 53: str="%"; break;
         case 54: str="^"; break;
         case 55: str="&"; break;
         case 56: str="*"; break;
         case 57: str="("; break;
         case 59: str=":"; break;
         case 61: str="+"; break;
         default:
#if CTX_BAREMETAL==0
           fprintf (stderr, "unhandled skeycode %i\n", keycode);
#endif
           str="?";
           break;
       }
       else
       switch (keycode)
       {
         case 61: str="="; break;
         case 59: str=";"; break;
         case 173: str="-"; break;
         case 186: str=";"; break;
         case 187: str="="; break;
         case 188: str=","; break;
         case 189: str="-"; break;
         case 190: str="."; break;
         case 191: str="/"; break;
         case 192: str="`"; break;
         case 219: str="["; break;
         case 221: str="]"; break;
         case 220: str="\\"; break;
         case 222: str="'"; break;
         default:
           if (keycode >= 48 && keycode <=66)
           {
             temp[0]=keycode-48+'0';
             temp[1]=0;
           }
           else
           {
#if CTX_BAREMETAL==0
             fprintf (stderr, "unhandled keycode %i\n", keycode);
#endif
             str="?";
           }
           break;
       }
   }
   return str;
}
typedef struct CtxKeyMap {
  const char *us;
  const char *unshifted;
  const char *shifted;
} CtxKeyMap;

static CtxKeyMap intl_key_map[]=
{
   {"`","`","~"},
   {"1","1","!"},
   {"2","2","@"},
   {"3","3","#"},
   {"4","4","$"},
   {"5","5","%"},
   {"6","6","^"},
   {"7","7","&"},
   {"8","8","*"},
   {"9","9","("},
   {"0","0",")"},
   {"-","-","_"},
   {"=","=","+"},

   {"q","q","Q"},
   {"w","w","W"},
   {"e","e","E"},
   {"r","r","R"},
   {"t","t","T"},
   {"y","y","Y"},
   {"u","u","U"},
   {"i","i","I"},
   {"o","o","O"},
   {"p","p","P"},
   {"[","[","{"},
   {"]","]","}"},
   {"\\","\\","|"},

   {"a","a","A"},
   {"s","s","S"},
   {"d","d","D"},
   {"f","f","F"},
   {"g","g","G"},
   {"h","h","H"},
   {"j","j","J"},
   {"k","k","K"},
   {"l","l","L"},

   {"z","z","Z"},
   {"x","x","X"},
   {"c","c","C"},
   {"v","v","V"},
   {"b","b","B"},
   {"n","n","N"},
   {"m","m","M"},
   {";",";",":"},
   {"'","'","\""},

   {".",".",">"},
   {",",",","<"},
   {"/","/","?"}
};

static const char *keymap_get_shifted (const char *key)
{
  for (unsigned int i = 0; i < sizeof (intl_key_map)/sizeof(intl_key_map[0]);i++)
  {
     if (!strcmp (key, intl_key_map[i].us))
        return intl_key_map[i].shifted;
  }
  return key;
}

static const char *keymap_get_unshifted (const char *key)
{
  for (unsigned int i = 0; i < sizeof (intl_key_map)/sizeof(intl_key_map[0]);i++)
  {
     if (!strcmp (key, intl_key_map[i].us))
        return intl_key_map[i].unshifted;
  }
  return key;
}

CTX_EXPORT int
ctx_key_press (Ctx *ctx, unsigned int keyval,
               const char *string, uint32_t time)
{
  char temp_key[128]="";
  char event_type[128]="";
  float x, y; int b;

  if (!string)
  {
    string = ctx_keycode_to_keyname (ctx->events.modifier_state, keyval);
  }

  if (!ctx_strcmp (string, "shift") ||
      !ctx_strcmp (string, "control") ||
      !ctx_strcmp (string, "alt"))
  {
    return 0;
  }

  {
          // code duplication.. perhaps always do this?
    {
       if (ctx->events.modifier_state & CTX_MODIFIER_STATE_SHIFT)
       {
          if(
             ctx_utf8_strlen (string)>1 ||
           (ctx->events.modifier_state & CTX_MODIFIER_STATE_ALT||
            ctx->events.modifier_state & CTX_MODIFIER_STATE_CONTROL))
          {
            if (strstr (string, "shift-") == NULL ||
                strcmp (strstr (string, "shift-"), "shift-"))
            sprintf (&temp_key[ctx_strlen(temp_key)], "shift-");
          }
          else 
          {
            string = keymap_get_shifted (string);
          }
       }
       else
       {
          if (!(ctx->events.modifier_state & CTX_MODIFIER_STATE_ALT||
                ctx->events.modifier_state & CTX_MODIFIER_STATE_CONTROL))
          {
            string = keymap_get_unshifted (string);
          }
       }

       if ((ctx->events.modifier_state & CTX_MODIFIER_STATE_ALT))
       {
         if (strstr (string, "alt-") == NULL ||
             strcmp (strstr (string, "alt-"), "alt-"))
         sprintf (&temp_key[ctx_strlen(temp_key)], "alt-");
       }
       if ((ctx->events.modifier_state & CTX_MODIFIER_STATE_CONTROL))
       {
         if (strstr (string, "control-") == NULL ||
             strcmp (strstr (string, "control-"), "control-"))
           sprintf (&temp_key[ctx_strlen(temp_key)], "control-");
       }
       sprintf (&temp_key[ctx_strlen(temp_key)], "%s", string);
       string = temp_key;
    }
  }

  int i = 0;
  for (i = 0; string[i] && string[i] != ' '; i++)
  {
    event_type[i] = string[i];
  }
  event_type[i]=0;
  char *pos = (char*)&string[i] + 1;
  while (*pos==' ')pos++;
  x = _ctx_parse_float (pos, &pos);
  while (*pos==' ')pos++;
  y = _ctx_parse_float (pos, &pos);
  while (*pos==' ')pos++;
  b = atoi(pos);

  if (!ctx_strcmp (event_type, "pm") ||
      !ctx_strcmp (event_type, "pd"))
    return ctx_pointer_motion (ctx, x, y, b, 0);
  else if (!ctx_strcmp (event_type, "pp"))
    return ctx_pointer_press (ctx, x, y, b, 0);
  else if (!ctx_strcmp (event_type, "pr"))
    return ctx_pointer_release (ctx, x, y, b, 0);
  //else if (!ctx_strcmp (event_type, "keydown"))
  //  return ctx_key_down (ctx, keyval, string + 8, time);
  //else if (!ctx_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; 
#ifdef EMSCRIPTEN
    if (string)
      event.string = strdup(string);
    else
      event.string = strdup("--");
#else
    if (string)
      event.string = ctx_strdup(string);
    else
      event.string = ctx_strdup("--");
#endif
    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)
        {
#ifdef EMSCRIPTEN
          free ((void*)event.string);
#else
          ctx_free ((void*)event.string);
#endif
          return event.stop_propagate;
        }
      }
    }
#ifdef EMSCRIPTEN
    free ((void*)event.string);
#else
    ctx_free ((void*)event.string);
#endif
  }
  return 0;
}

CTX_EXPORT 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 (!string)
    string = ctx_keycode_to_keyname (0, keyval);

  if (!ctx_strcmp (string, "shift"))
  {
    ctx->events.modifier_state |= CTX_MODIFIER_STATE_SHIFT;
  }
  else if (!ctx_strcmp (string, "control"))
  {
    ctx->events.modifier_state |= CTX_MODIFIER_STATE_CONTROL;
  }
  else if (!ctx_strcmp (string, "alt"))
  {
    ctx->events.modifier_state |= CTX_MODIFIER_STATE_ALT;
  }

  if (time == 0)
    time = ctx_ms (ctx);
  if (item)
  {
    int i;
    event.ctx     = ctx;
    event.type    = CTX_KEY_DOWN;
    event.unicode = keyval; 
    event.string  = ctx_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)
        {
          ctx_free ((void*)event.string);
          return event.stop_propagate;
        }
      }
    }
    ctx_free ((void*)event.string);
  }
  return 0;
}

CTX_EXPORT 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 (!string)
    string = ctx_keycode_to_keyname (0, keyval);

  if (!ctx_strcmp (string, "shift"))
  {
    ctx->events.modifier_state &= ~(CTX_MODIFIER_STATE_SHIFT);
  }
  else if (!ctx_strcmp (string, "control"))
  {
    ctx->events.modifier_state &= ~(CTX_MODIFIER_STATE_CONTROL);
  }
  else if (!ctx_strcmp (string, "alt"))
  {
    ctx->events.modifier_state &= ~(CTX_MODIFIER_STATE_ALT);
  }

  if (time == 0)
    time = ctx_ms (ctx);
  if (item)
  {
    int i;
    event.ctx = ctx;
    event.type = CTX_KEY_UP;
    event.unicode = keyval; 
    event.string = ctx_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)
        {
          ctx_free ((void*)event.string);
          return event.stop_propagate;
        }
      }
    }
    ctx_free ((void*)event.string);
  }
  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);
}

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.8f,0.5f);
  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);
}

#if CTX_THREADS
void ctx_set_render_threads   (Ctx *ctx, int n_threads)
{
  // XXX
}
int ctx_get_render_threads   (Ctx *ctx)
{
  return _ctx_max_threads;
}
#else
void ctx_set_render_threads   (Ctx *ctx, int n_threads)
{
}
int ctx_get_render_threads   (Ctx *ctx)
{
  return 1;
}
#endif
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_need_redraw (Ctx *ctx)
{
  return (ctx->dirty != 0)
#if CTX_VT
    || ctx_clients_need_redraw (ctx)
#endif
    ;
}


/*
 * centralized global API for managing file descriptors that
 * wake us up, this to remove sleeping and polling
 */


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;
    }
  }
}
#ifdef EMSCRIPTEN
extern int em_in_len;
#endif
#if CTX_VT
extern int ctx_dummy_in_len;
#endif

int ctx_input_pending (Ctx *ctx, int timeout)
{
  int retval = 0;
#if CTX_PTY
  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;
  retval = select (_ctx_listen_max_fd + 1, &fdset, NULL, NULL, &tv);
  if (retval == -1)
  {
#if CTX_BAREMETAL==0
    perror ("select");
#endif
    return 0;
  }
#endif
#ifdef EMSCRIPTEN
  retval += em_in_len;
#endif
#if CTX_VT
  retval += ctx_dummy_in_len;
#endif
  return retval;
}

void ctx_handle_events (Ctx *ctx)
{
#if CTX_VT
  ctx_clients_handle_events (ctx);
#endif
  while (ctx_get_event (ctx)){}
}


static void ctx_events_deinit (Ctx *ctx)
{
  ctx_list_free (&ctx->events.items);
  ctx->events.last_item = NULL;

  while (ctx->events.idles)
  {
    CtxIdleCb *item = ctx->events.idles->data;
    ctx_list_remove (&ctx->events.idles, item);
    if (item->destroy_notify)
      item->destroy_notify (item->destroy_data);
  }
}

#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)

#if CTX_TERMINAL_EVENTS

#if CTX_PTY
static int mice_has_event (void);
static char *mice_get_event (void);
static void mice_destroy (void);
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;

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 (void)
{
  if (mrg_mice_this->fd != -1)
    close (mrg_mice_this->fd);
}

static int mice_has_event (void)
{
  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 (void)
{
  const char *ret = "pm";
  double relx, rely;
  signed char buf[3];
  int n_read = 0;
  CtxTiled *tiled = (void*)ctx_ev_src_mice.priv;
  n_read = read (mrg_mice_this->fd, buf, 3);
  if (n_read == 0)
     return ctx_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 = "pp";
        }
      else
        {
          ret = "pr";
        }
      button = 1;
    }
  else if (buf[0] & 1)
  {
    ret = "pd";
    button = 1;
  }

  if (!button)
  {
    if ((mrg_mice_this->prev_state & 2) != (buf[0] & 2))
    {
      if (buf[0] & 2)
        {
          ret = "pp";
        }
      else
        {
          ret = "pr";
        }
      button = 3;
    }
    else if (buf[0] & 2)
    {
      ret = "pd";
      button = 3;
    }
  }

  if (!button)
  {
    if ((mrg_mice_this->prev_state & 4) != (buf[0] & 4))
    {
      if (buf[0] & 4)
        {
          ret = "pp";
        }
      else
        {
          ret = "pr";
        }
      button = 2;
    }
    else if (buf[0] & 4)
    {
      ret = "pd";
      button = 2;
    }
  }

  mrg_mice_this->prev_state = buf[0];

  {
    char *r = ctx_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 inline 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;
}
#endif

static int evsource_kb_term_has_event (void);
static char *evsource_kb_term_get_event (void);
static void evsource_kb_term_destroy (int sign);
static int evsource_kb_term_get_fd (void);

/* kept out of struct to be reachable by atexit */
static EvSource ctx_ev_src_kb_term = {
  NULL,
  (void*)evsource_kb_term_has_event,
  (void*)evsource_kb_term_get_event,
  (void*)evsource_kb_term_destroy,
  (void*)evsource_kb_term_get_fd,
  NULL
};

#if CTX_PTY
static struct termios orig_attr;
#endif

static void real_evsource_kb_term_destroy (int sign)
{
#if CTX_PTY
  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");
#endif
}

static void evsource_kb_term_destroy (int sign)
{
  real_evsource_kb_term_destroy (-11);
}

static int evsource_kb_term_init ()
{
#if CTX_PTY
//  ioctl(STDIN_FILENO, KDSKBMODE, K_RAW);
  //atexit ((void*) real_evsource_kb_term_destroy);
  signal (SIGSEGV, (void*) real_evsource_kb_term_destroy);
  signal (SIGABRT, (void*) real_evsource_kb_term_destroy);
  signal (SIGBUS,  (void*) real_evsource_kb_term_destroy);
  signal (SIGKILL, (void*) real_evsource_kb_term_destroy);
  signal (SIGINT,  (void*) real_evsource_kb_term_destroy);
  signal (SIGTERM, (void*) real_evsource_kb_term_destroy);
  signal (SIGQUIT, (void*) real_evsource_kb_term_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?
#endif
  return 0;
}
static int evsource_kb_term_has_event (void)
{
  int retval = 0;
#if CTX_PTY
  struct timeval tv;
  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);
#endif
  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",                  "\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",         "\033[1;4A"},
  {"alt-shift-down",       "\033[1;4B"},
  {"alt-shift-right",      "\033[1;4C"},
  {"alt-shift-left",       "\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",              "\033[2~"},
  {"delete",              "\033[3~"},
  {"page-up",             "\033[5~"},
  {"page-down",           "\033[6~"},
  {"home",                "\033OH"},
  {"end",                 "\033OF"},
  {"home",                "\033[H"},
  {"end",                 "\033[F"},
 {"control-delete",       "\033[3;5~"},
  {"shift-delete",        "\033[3;2~"},
  {"control-shift-delete","\033[3;6~"},

  {"F1",         "\033[25~"},
  {"F2",         "\033[26~"},
  {"F3",         "\033[27~"},
  {"F4",         "\033[26~"},


  {"F1",         "\033[11~"},
  {"F2",         "\033[12~"},
  {"F3",         "\033[13~"},
  {"F4",         "\033[14~"},
  {"F1",         "\033OP"},
  {"F2",         "\033OQ"},
  {"F3",         "\033OR"},
  {"F4",         "\033OS"},
  {"F5",         "\033[15~"},
  {"F6",         "\033[16~"},
  {"F7",         "\033[17~"},
  {"F8",         "\033[18~"},
  {"F9",         "\033[19~"},
  {"F9",         "\033[20~"},
  {"F10",        "\033[21~"},
  {"F11",        "\033[22~"},
  {"F12",        "\033[23~"},
  {"tab",         {9, '\0'}},
  {"shift-tab",   {27, 9, '\0'}}, // also generated by alt-tab in linux console
  {"alt-space",   {27, ' ', '\0'}},
  {"shift-tab",   "\033[Z"},
  {"backspace",   {127, '\0'}},
  {"space",       " "},
  {"\033",          "\033"},
  {"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-`",       "\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"}, /* backspace? */
  {"alt-8",       "\0338"},
  {"alt-9",       "\0339"},
  {"alt-+",       "\033+"},
  {"alt--",       "\033-"},
  {"alt-/",       "\033/"},
  {"alt-a",       "\033a"},
  {"alt-b",       "\033b"},
  {"alt-c",       "\033c"},
  {"alt-d",       "\033d"},
  {"alt-e",       "\033e"},
  {"alt-f",       "\033f"},
  {"alt-g",       "\033g"},
  {"alt-h",       "\033h"}, /* backspace? */
  {"alt-i",       "\033i"},
  {"alt-j",       "\033j"},
  {"alt-k",       "\033k"},
  {"alt-l",       "\033l"},
  {"alt-n",       "\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"},
  /* Linux Console  */
  {"home",       "\033[1~"},
  {"end",        "\033[4~"},
  {"F1",         "\033[[A"},
  {"F2",         "\033[[B"},
  {"F3",         "\033[[C"},
  {"F4",         "\033[[D"},
  {"F5",         "\033[[E"},
  {"F6",         "\033[[F"},
  {"F7",         "\033[[G"},
  {"F8",         "\033[[H"},
  {"F9",         "\033[[I"},
  {"F10",        "\033[[J"},
  {"F11",        "\033[[K"},
  {"F12",        "\033[[L"},
  {NULL, }
};
static int fb_keyboard_match_keycode (const char *buf, int length, const MmmKeyCode **ret)
{
  int i;
  int matches = 0;

  if (!strncmp (buf, "\033[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)ctx_strlen (ufb_keycodes[i].sequence) == length && ret)
          {
            *ret = &ufb_keycodes[i];
            return 1;
          }
      }
  if (matches != 1 && ret)
    *ret = NULL;
  return matches==1?2:matches;
}

static char *evsource_kb_term_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 ctx_strdup ("escape");
          }

        switch (fb_keyboard_match_keycode ((void*)buf, length + 1, &match))
          {
            case 1: /* unique match */
              if (!match)
                return NULL;
              return ctx_strdup (match->nick);
              break;
            case 0: /* no matches, bail*/
             {
                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 ctx_strdup(ret); //XXX: simplify
                  }
                if (length == 0) /* ascii */
                  {
                    buf[1]=0;
                    strcpy (ret, (void*)buf);
                    return ctx_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 ctx_strdup(ret);
            }
              return NULL;
            default: /* continue */
              break;
          }
      }
    else
      return ctx_strdup("key read eek");
  return ctx_strdup("fail");
}

static int evsource_kb_term_get_fd (void)
{
  return STDIN_FILENO;
}


static inline EvSource *evsource_kb_term_new (void)
{
  if (evsource_kb_term_init() == 0)
  {
    return &ctx_ev_src_kb_term;
  }
  return NULL;
}
#endif

#if CTX_RAW_KB_EVENTS

static int evsource_kb_raw_has_event (void);
static char *evsource_kb_raw_get_event (void);
static void evsource_kb_raw_destroy (int sign);
static int evsource_kb_raw_get_fd (void);


/* kept out of struct to be reachable by atexit */
static EvSource ctx_ev_src_kb_raw = {
  NULL,
  (void*)evsource_kb_raw_has_event,
  (void*)evsource_kb_raw_get_event,
  (void*)evsource_kb_raw_destroy,
  (void*)evsource_kb_raw_get_fd,
  NULL
};

#if 0
static void real_evsource_kb_raw_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");
}
#endif

#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <errno.h>


#include <linux/input.h>


static int kb_fd = -1;
static void evsource_kb_raw_destroy (int sign)
{
#if 0
  real_evsource_kb_raw_destroy (-11);
#endif
  if (kb_fd)
    close (kb_fd);
  kb_fd = 0;
}


static int evsource_kb_raw_init ()
{
#if 0
//  ioctl(STDIN_FILENO, KDSKBMODE, K_RAW);
  //atexit ((void*) real_evsource_kb_term_destroy);
  signal (SIGSEGV, (void*) real_evsource_kb_raw_destroy);
  signal (SIGABRT, (void*) real_evsource_kb_raw_destroy);
  signal (SIGBUS,  (void*) real_evsource_kb_raw_destroy);
  signal (SIGKILL, (void*) real_evsource_kb_raw_destroy);
  signal (SIGINT,  (void*) real_evsource_kb_raw_destroy);
  signal (SIGTERM, (void*) real_evsource_kb_raw_destroy);
  signal (SIGQUIT, (void*) real_evsource_kb_raw_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?
#endif

   kb_fd = open( "/dev/input/event0", O_RDONLY | O_CLOEXEC );
        if( -1 == kb_fd )
        {
            kb_fd = 0;
            return -1;
        }
        char name[ 32 ];
        if( -1 == ioctl( kb_fd, EVIOCGNAME( sizeof( name )), name ))
        {
            kb_fd = 0;
            return -1;
        }

        // Grab input
        if( -1 == ioctl( kb_fd, EVIOCGRAB, (void*)1 ))
        {
            fprintf(stderr, "Failed to grab input %s: (%i) %m", name, errno );
            kb_fd = 0;
            return -1;
        }

  return 0;
}
static int evsource_kb_raw_has_event (void)
{
  struct timeval tv;
  int retval;

  fd_set rfds;
  FD_ZERO (&rfds);
  FD_SET(kb_fd, &rfds);
  tv.tv_sec = 0; tv.tv_usec = 0;
  retval = select (kb_fd+1, &rfds, NULL, NULL, &tv);
  return retval == 1;
}

typedef struct CtxRawKey{
  int code;
  const char *name;
  const char *shifted;
} CtxRawKey;


static CtxRawKey raw_key_map[]=
{
   {KEY_F1, "F1","F1"},
   {KEY_F2, "F2","F2"},
   {KEY_F3, "F3","F3"},
   {KEY_F4, "F4","F4"},
   {KEY_F5, "F5","F5"},
   {KEY_F6, "F6","F6"},
   {KEY_F7, "F7","F7"},
   {KEY_F8, "F8","F8"},
   {KEY_F9, "F9","F9"},
   {KEY_F10, "F10","F10"},
   {KEY_ESC, "escape","escape"},
   {KEY_SPACE, "space","space"},
   {KEY_ENTER, "return","return"},
   {KEY_LEFT, "left","left"},
   {KEY_RIGHT, "right","right"},
   {KEY_UP, "up","up"},
   {KEY_DOWN, "down","down"},
   {KEY_HOME, "home","home"},
   {KEY_END, "end","end"},
   {KEY_PAGEUP, "page-up","page-up"},
   {KEY_PAGEDOWN, "page-down","page-down"},
   {KEY_INSERT, "insert","insert"},
   {KEY_DELETE, "delete","delete"},
   {KEY_LEFTCTRL, "control","control"},
   {KEY_RIGHTCTRL, "control","control"},
   {KEY_LEFTSHIFT, "shift","shift"},
   {KEY_RIGHTSHIFT, "shift","shift"},
   {KEY_LEFTALT, "alt","alt"},
   {KEY_RIGHTALT, "alt","alt"},
   {KEY_MINUS, "-","_"},
   {KEY_EQUAL, "=","+"},
   {KEY_BACKSPACE, "backspace","backspace"},
   {KEY_TAB, "tab","tab"},
   {KEY_GRAVE, "`","~"},
   {KEY_BACKSLASH, "\\","|"},
   {KEY_SLASH, "/","?"},
   {KEY_1, "1","!"},
   {KEY_2, "2","@"},
   {KEY_3, "3","#"},
   {KEY_4, "4","$"},
   {KEY_5, "5","%"},
   {KEY_6, "6","^"},
   {KEY_7, "7","&"},
   {KEY_8, "8","*"},
   {KEY_9, "9","("},
   {KEY_0, "0",")"},

   {KEY_Q, "q","Q"},
   {KEY_W, "w","W"},
   {KEY_E, "e","E"},
   {KEY_R, "r","R"},
   {KEY_T, "t","T"},
   {KEY_Y, "y","Y"},
   {KEY_U, "u","U"},
   {KEY_I, "i","I"},
   {KEY_O, "o","O"},
   {KEY_P, "p","P"},
   {KEY_A, "a","A"},
   {KEY_S, "s","S"},
   {KEY_D, "d","D"},
   {KEY_F, "f","F"},
   {KEY_G, "g","G"},
   {KEY_H, "h","H"},
   {KEY_J, "j","J"},
   {KEY_K, "k","K"},
   {KEY_L, "l","L"},
   {KEY_Z, "z","Z"},
   {KEY_X, "x","X"},
   {KEY_C, "c","C"},
   {KEY_V, "v","V"},
   {KEY_B, "b","B"},
   {KEY_N, "n","N"},
   {KEY_M, "m","M"},
   {KEY_SEMICOLON, ";",":"},
   {KEY_APOSTROPHE, "'", "\""},
   {KEY_EQUAL, "=", "+"},
   {KEY_MINUS, "-", "_"},
   {KEY_COMMA, ",", "<"},
   {KEY_DOT, ".", ">"},
   {KEY_SLASH, "/", "?"},
   {KEY_LEFTBRACE, "[", "{"},
   {KEY_RIGHTBRACE, "]", "}"}
};

static Ctx*ctx_fb_global = NULL;

static char *evsource_kb_raw_get_event (void)
{
  struct input_event ev;
  if (!ctx_fb_global) return NULL;
  memset (&ev, 0, sizeof (ev));
  if (-1==read(kb_fd, &ev, sizeof(ev)))
  {
    return NULL;
  }
  if (ev.type == EV_KEY)
  {
     for (unsigned int i = 0; i < sizeof(raw_key_map)/sizeof(raw_key_map[0]); i++)
     {
       if (raw_key_map[i].code == ev.code)
       {
          const char *name = raw_key_map[i].name;
          switch (ev.value)
          {
            case 0: /* up */
              ctx_key_up (ctx_fb_global, 0, name, 0);
              break;
            case 1: /* down */
              ctx_key_down (ctx_fb_global, 0, name, 0);
              /*FALLTHROUGH*/
            case 2: /* repeat */
              if (strcmp(name,"shift") &&
                  strcmp(name,"control") &&
                  strcmp(name,"alt"))
              ctx_key_press (ctx_fb_global, 0, name, 0);
              break;
          }
          return NULL;
       }
     }
  }
  return NULL;
}

static int evsource_kb_raw_get_fd (void)
{
  if (kb_fd >= 0)
    return kb_fd;
  return 0;
}


static inline EvSource *evsource_kb_raw_new (void)
{
  if (evsource_kb_raw_init() == 0)
  {
    return &ctx_ev_src_kb_raw;
  }
  return NULL;
}
#endif


static inline int event_check_pending (CtxTiled *tiled)
{
  int events = 0;
  for (int i = 0; i < tiled->evsource_count; i++)
  {
    while (evsource_has_event (tiled->evsource[i]))
    {
      char *event = evsource_get_event (tiled->evsource[i]);
      if (event)
      {
        if (tiled->vt_active)
        {
          ctx_key_press (tiled->backend.ctx, 0, event, 0); // we deliver all events as key-press, the key_press handler disambiguates
          events++;
        }
        ctx_free (event);
      }
    }
  }
  return events;
}

#endif

void ctx_queue_draw (Ctx *ctx)
{
  ctx->dirty ++;
}

int ctx_in_fill (Ctx *ctx, float x, float y)
{
  float x1, y1, x2, y2;
  float width, height;
  float factor = 1.0f;
  ctx_path_extents (ctx, &x1, &y1, &x2, &y2);
  width = x2-x1;
  height = y2-y1;
  while ((width < 200 || height < 200) && factor < 16.0f)
  {
    width *=2;
    height *=2;
    factor *=2;
  }
  x1 *= factor;
  y1 *= factor;
  x2 *= factor;
  y2 *= factor;
  x *= factor;
  y *= factor;

  if (x1 <= x && x <= x2 && y1 <= y && y <= y2)
  {
#if CTX_CURRENT_PATH
     uint32_t pixels[9] = {0,};
     Ctx *tester = ctx_new_for_framebuffer (&pixels[0], 3, 3, 3*4, CTX_FORMAT_RGBA8);
     ctx_translate (tester, -(x-1), -(y-1));
     ctx_scale (tester, factor, factor);
     ctx_gray (tester, 1.0f);
     ctx_append_drawlist (tester, ctx->current_path.entries, ctx->current_path.count*9);
     ctx_fill (tester);
     ctx_destroy (tester);
     if (pixels[1+3] != 0)
       return 1;
     return 0;
#else
     return 1;
#endif
  }
  return 0;
}

int ctx_in_stroke (Ctx *ctx, float x, float y)
{
  float x1, y1, x2, y2;
  float width, height;
  float factor = 1.0f;
  ctx_path_extents (ctx, &x1, &y1, &x2, &y2);
  width = x2-x1;
  height = y2-y1;

  while ((width < 200 || height < 200) && factor < 16.0f)
  {
    width *=2;
    height *=2;
    factor *=2;
  }
  x1 *= factor;
  y1 *= factor;
  x2 *= factor;
  y2 *= factor;
  x *= factor;
  y *= factor;
  if (x1 <= x && x <= x2 && y1 <= y && y <= y2)
  {
#if CTX_CURRENT_PATH
     uint32_t pixels[9] = {0,};
     Ctx *tester = ctx_new_for_framebuffer (&pixels[0], 3, 3, 3*4, CTX_FORMAT_RGBA8);
     ctx_translate (tester, -(x-1), -(y-1));
     ctx_scale (tester, factor, factor);
     ctx_gray (tester, 1.0f);
     ctx_append_drawlist (tester, ctx->current_path.entries, ctx->current_path.count*9);
     ctx_line_width  (tester, ctx_get_line_width  (ctx) * factor);
     ctx_line_cap    (tester, ctx_get_line_cap    (ctx));
     ctx_line_join   (tester, ctx_get_line_join   (ctx));
     ctx_miter_limit (tester, ctx_get_miter_limit (ctx) * factor);
     ctx_stroke (tester);
     ctx_destroy (tester);
     if (pixels[1+3] != 0)
       return 1;
     return 0;
#else
     return 1;
#endif
  }
  return 0;
}

static void ctx_svg_arc_circle_to (Ctx *ctx,
                                   float radius,
                                   int large,
                                   int sweep,
                                   float x1, float y1)
{
  float x0, y0;
  ctx_current_point (ctx, &x0, &y0);
  int left_side = (large && !sweep) || (sweep && !large);

  float delta_x = (x1-x0) * 0.5f;
  float delta_y = (y1-y0) * 0.5f;

  float midpoint_x = x0 + delta_x;
  float midpoint_y = y0 + delta_y;

  float radius_vec_x;
  float radius_vec_y;
  float r = radius;

  if (left_side)
  {
    radius_vec_x = -delta_y;
    radius_vec_y = delta_x;
  }
  else
  {
    radius_vec_x = delta_y;
    radius_vec_y = -delta_x;
  }

  float len_squared = ctx_pow2(radius_vec_x) + ctx_pow2(radius_vec_y);
  if (len_squared - 0.03f > r * r || r < 0)
  {
    r = ctx_sqrtf (len_squared);
  }

  float center_x = midpoint_x +
           radius_vec_x * ctx_sqrtf(ctx_maxf(0, r * r / len_squared-1));
  float center_y = midpoint_y +
           radius_vec_y * ctx_sqrtf(ctx_maxf(0, r * r / len_squared-1));

  float arc = ctx_asinf(ctx_clampf(ctx_sqrtf(len_squared)/r, -1.0, 1.0))*2;
  if (large) arc = CTX_PI*2-arc;

  float start_angle = ctx_atan2f(y0 - center_y, x0 - center_x);
  float end_angle = sweep?start_angle+arc:start_angle-arc;

  ctx_arc (ctx, center_x, center_y, r, start_angle, end_angle, !sweep);
}


static inline void ctx_svg_arc_to (Ctx *ctx, float rx, float ry, 
                            float rotation,  int large, int sweep,
                            float x1, float y1)
{
  ctx_svg_arc_circle_to (ctx, rx, large, sweep, x1, y1);
  return;
   // XXX the following fails, one reason is that
   // ctx_current_point returns the point in the previous user_space
   // not the current.

  float x0, y0;
  ctx_current_point (ctx, &x0, &y0);
  float radius_min = ctx_hypotf (x1-x0,y1-y0)/2.0f;
  float radius_lim = ctx_hypotf (rx, ry);
  float up_scale = 1.0f;
  if (radius_lim < radius_min)
    up_scale = radius_min / radius_lim;
  float ratio = rx / ry;
  ctx_save (ctx);
  ctx_scale (ctx, up_scale * ratio, up_scale);

  //  the following is a hack, current_point should change instead,
  //  but that can have performance impact on adding coordinates
  ctx->state.x /= (up_scale * ratio);
  ctx->state.y /= (up_scale);


  //ctx_rotate (ctx, rotation);
  
  x1 = x1 / (up_scale * ratio);
  y1 = y1 / (up_scale);

  ctx_svg_arc_circle_to (ctx, rx, large, sweep, x1, y1);

  ctx_restore (ctx);
}

/* 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
  uint32_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, uint32_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, uint32_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, uint32_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_destroy (CtxParser *parser)
{
#if !CTX_PARSER_FIXED_TEMP
  if (parser->holding)
    ctx_free (parser->holding);
#endif
  ctx_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_START_FRAME:
      case CTX_END_FRAME:
      case CTX_RESTORE:
      case CTX_STROKE:
      case CTX_FILL:
      case CTX_PAINT:
      case CTX_DEFINE_FONT:
      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_EXTEND:
      case CTX_FONT_SIZE:
      case CTX_LINE_JOIN:
      case CTX_LINE_CAP:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
      case CTX_LINE_HEIGHT:
      case CTX_WRAP_LEFT:
      case CTX_WRAP_RIGHT:
      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_ROUND_RECTANGLE:
        return 5;
      case CTX_ARC:
      case CTX_CURVE_TO:
      case CTX_REL_CURVE_TO:
      case CTX_RADIAL_GRADIENT:
        return 6;
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
        return 7;
      case CTX_APPLY_TRANSFORM:
      case CTX_SOURCE_TRANSFORM:
        return 9;
      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)
{
  uint32_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 'e': return ctx_parser_set_command (parser, CTX_EXTEND);
      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 'H': return ctx_parser_set_command (parser, CTX_LINE_HEIGHT);
      case 'L': return ctx_parser_set_command (parser, CTX_WRAP_LEFT);
      case 'R': return ctx_parser_set_command (parser, CTX_WRAP_RIGHT);
      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])
    {
      uint32_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 SQZ_arcTo:          ret = CTX_ARC_TO; break;
          case SQZ_arc:            ret = CTX_ARC; break;
          case SQZ_curveTo:        ret = CTX_CURVE_TO; break;
          case SQZ_restore:        ret = CTX_RESTORE; break;
          case SQZ_stroke:         ret = CTX_STROKE; break;
          case SQZ_fill:           ret = CTX_FILL; break;
          case SQZ_paint:          ret = CTX_PAINT; break;
          case SQZ_endFrame:       ret = CTX_END_FRAME; break;
          case SQZ_horLineTo:      ret = CTX_HOR_LINE_TO; break;
          case SQZ_rotate:         ret = CTX_ROTATE; break;
          case SQZ_color:          ret = CTX_COLOR; break;
          case SQZ_lineTo:         ret = CTX_LINE_TO; break;
          case SQZ_moveTo:         ret = CTX_MOVE_TO; break;
          case SQZ_scale:          ret = CTX_SCALE; break;
          case SQZ_newPage:        ret = CTX_NEW_PAGE; break;
          case SQZ_quadTo:         ret = CTX_QUAD_TO; break;
          case SQZ_viewBox:        ret = CTX_VIEW_BOX; break;
          case SQZ_smoothTo:       ret = CTX_SMOOTH_TO; break;
          case SQZ_smoothQuadTo:   ret = CTX_SMOOTHQ_TO; break;
          case SQZ_clear:          ret = CTX_COMPOSITE_CLEAR; break;
          case SQZ_copy:           ret = CTX_COMPOSITE_COPY; break;
          case SQZ_destinationOver:  ret = CTX_COMPOSITE_DESTINATION_OVER; break;
          case SQZ_destinationIn:    ret = CTX_COMPOSITE_DESTINATION_IN; break;
          case SQZ_destinationOut:   ret = CTX_COMPOSITE_DESTINATION_OUT; break;
          case SQZ_sourceOver:       ret = CTX_COMPOSITE_SOURCE_OVER; break;
          case SQZ_sourceAtop:       ret = CTX_COMPOSITE_SOURCE_ATOP; break;
          case SQZ_destinationAtop:  ret = CTX_COMPOSITE_DESTINATION_ATOP; break;
          case SQZ_sourceOut:        ret = CTX_COMPOSITE_SOURCE_OUT; break;
          case SQZ_sourceIn:         ret = CTX_COMPOSITE_SOURCE_IN; break;
          case SQZ_xor:              ret = CTX_COMPOSITE_XOR; break;
          case SQZ_darken:           ret = CTX_BLEND_DARKEN; break;
          case SQZ_lighten:          ret = CTX_BLEND_LIGHTEN; break;
          //case SQZ_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 SQZ_hue:            ret = CTX_BLEND_HUE; break;
          case SQZ_multiply:       ret = CTX_BLEND_MULTIPLY; break;
          case SQZ_normal:         ret = CTX_BLEND_NORMAL;break;
          case SQZ_screen:         ret = CTX_BLEND_SCREEN;break;
          case SQZ_difference:     ret = CTX_BLEND_DIFFERENCE; break;
          case SQZ_startFrame:     ret = CTX_START_FRAME; break;
          case SQZ_verLineTo:      ret = CTX_VER_LINE_TO; break;
          case SQZ_exit:
          case SQZ_done:           ret = CTX_EXIT; break;
          case SQZ_closePath:      ret = CTX_CLOSE_PATH; break;
          case SQZ_beginPath:
          case SQZ_newPath:        ret = CTX_BEGIN_PATH; break;
          case SQZ_relArcTo:       ret = CTX_REL_ARC_TO; break;
          case SQZ_clip:           ret = CTX_CLIP; break;
          case SQZ_relCurveTo:     ret = CTX_REL_CURVE_TO; break;
          case SQZ_startGroup:     ret = CTX_START_GROUP; break;
          case SQZ_endGroup:       ret = CTX_END_GROUP; break;
          case SQZ_save:           ret = CTX_SAVE; break;
          case SQZ_translate:      ret = CTX_TRANSLATE; break;
          case SQZ_linearGradient: ret = CTX_LINEAR_GRADIENT; break;
          case SQZ_relHorLineTo:   ret = CTX_REL_HOR_LINE_TO; break;
          case SQZ_relLineTo:      ret = CTX_REL_LINE_TO; break;
          case SQZ_relMoveTo:      ret = CTX_REL_MOVE_TO; break;
          case SQZ_font:           ret = CTX_FONT; break;
          case SQZ_radialGradient:ret = CTX_RADIAL_GRADIENT; break;
          case SQZ_gradientAddStop:
          case SQZ_addStop:        ret = CTX_GRADIENT_STOP; break;
          case SQZ_relQuadTo:      ret = CTX_REL_QUAD_TO; break;
          case SQZ_rectangle:
          case SQZ_rect:           ret = CTX_RECTANGLE; break;
          case SQZ_roundRectangle: ret = CTX_ROUND_RECTANGLE; break;
          case SQZ_relSmoothTo:    ret = CTX_REL_SMOOTH_TO; break;
          case SQZ_relSmoothqTo:   ret = CTX_REL_SMOOTHQ_TO; break;
          case SQZ_strokeText:     ret = CTX_STROKE_TEXT; break;
          case SQZ_strokeRect:     ret = CTX_STROKE_RECT; break;
          case SQZ_fillRect:       ret = CTX_FILL_RECT; break;
          case SQZ_relVerLineTo:   ret = CTX_REL_VER_LINE_TO; break;
          case SQZ_text:           ret = CTX_TEXT; break;
          case SQZ_identity:       ret = CTX_IDENTITY; break;
          case SQZ_transform:      ret = CTX_APPLY_TRANSFORM; break;
          case SQZ_sourceTransform: ret = CTX_SOURCE_TRANSFORM; break;
          case SQZ_texture:        ret = CTX_TEXTURE; break;
          case SQZ_defineTexture:  ret = CTX_DEFINE_TEXTURE; break;
#if 0
          case SQZ_rgbSpace:
            return ctx_parser_set_command (parser, CTX_SET_RGB_SPACE);
          case SQZ_cmykSpace:
            return ctx_parser_set_command (parser, CTX_SET_CMYK_SPACE);
          case SQZ_drgbSpace:
            return ctx_parser_set_command (parser, CTX_SET_DRGB_SPACE);
#endif
          case SQZ_defineFont:
            return ctx_parser_set_command (parser, CTX_DEFINE_FONT);
          case SQZ_defineGlyph:
            return ctx_parser_set_command (parser, CTX_DEFINE_GLYPH);
          case SQZ_kerningPair:
            return ctx_parser_set_command (parser, CTX_KERNING_PAIR);

          case SQZ_colorSpace:
            return ctx_parser_set_command (parser, CTX_COLOR_SPACE);
          case SQZ_fillRule:
            return ctx_parser_set_command (parser, CTX_FILL_RULE);
          case SQZ_fontSize:
          case SQZ_setFontSize:
            return ctx_parser_set_command (parser, CTX_FONT_SIZE);
          case SQZ_compositingMode:
            return ctx_parser_set_command (parser, CTX_COMPOSITING_MODE);

          case SQZ_extend:
            return ctx_parser_set_command (parser, CTX_EXTEND);

          case SQZ_blend:
          case SQZ_blending:
          case SQZ_blendMode:
            return ctx_parser_set_command (parser, CTX_BLEND_MODE);

          case SQZ_miterLimit:
            return ctx_parser_set_command (parser, CTX_MITER_LIMIT);
          case SQZ_textAlign:
            return ctx_parser_set_command (parser, CTX_TEXT_ALIGN);
          case SQZ_textBaseline:
            return ctx_parser_set_command (parser, CTX_TEXT_BASELINE);
          case SQZ_textDirection:
            return ctx_parser_set_command (parser, CTX_TEXT_DIRECTION);
          case SQZ_join:
          case SQZ_lineJoin:
          case SQZ_setLineJoin:
            return ctx_parser_set_command (parser, CTX_LINE_JOIN);
          case SQZ_glyph:
            return ctx_parser_set_command (parser, CTX_GLYPH);
          case SQZ_cap:
          case SQZ_lineCap:
          case SQZ_setLineCap:
            return ctx_parser_set_command (parser, CTX_LINE_CAP);
          case SQZ_lineDash:
            return ctx_parser_set_command (parser, CTX_LINE_DASH);
          case SQZ_lineWidth:
          case SQZ_setLineWidth:
            return ctx_parser_set_command (parser, CTX_LINE_WIDTH);
          case SQZ_lineDashOffset:
            return ctx_parser_set_command (parser, CTX_LINE_DASH_OFFSET);
          case SQZ_lineHeight:
            return ctx_parser_set_command (parser, CTX_LINE_HEIGHT);
          case SQZ_wrapLeft:
            return ctx_parser_set_command (parser, CTX_WRAP_LEFT);
          case SQZ_wrapRight:
            return ctx_parser_set_command (parser, CTX_WRAP_RIGHT);
          case SQZ_imageSmoothing:
            return ctx_parser_set_command (parser, CTX_IMAGE_SMOOTHING);
          case SQZ_shadowColor:
            return ctx_parser_set_command (parser, CTX_SHADOW_COLOR);
          case SQZ_shadowBlur:
            return ctx_parser_set_command (parser, CTX_SHADOW_BLUR);
          case SQZ_shadowOffsetX:
            return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_X);
          case SQZ_shadowOffsetY:
            return ctx_parser_set_command (parser, CTX_SHADOW_OFFSET_Y);
          case SQZ_globalAlpha:
            return ctx_parser_set_command (parser, CTX_GLOBAL_ALPHA);

          case SQZ_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 SQZ_gray:
            ctx_parser_set_color_model (parser, CTX_GRAY, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_graya:
            ctx_parser_set_color_model (parser, CTX_GRAYA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_rgb:
            ctx_parser_set_color_model (parser, CTX_RGB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_drgb:
            ctx_parser_set_color_model (parser, CTX_DRGB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_rgba:
            ctx_parser_set_color_model (parser, CTX_RGBA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_drgba:
            ctx_parser_set_color_model (parser, CTX_DRGBA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_cmyk:
            ctx_parser_set_color_model (parser, CTX_CMYK, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_cmyka:
            ctx_parser_set_color_model (parser, CTX_CMYKA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_lab:
            ctx_parser_set_color_model (parser, CTX_LAB, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_laba:
            ctx_parser_set_color_model (parser, CTX_LABA, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_lch:
            ctx_parser_set_color_model (parser, CTX_LCH, 0);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_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 SQZ_grayS:
            ctx_parser_set_color_model (parser, CTX_GRAY, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_grayaS:
            ctx_parser_set_color_model (parser, CTX_GRAYA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_rgbS:
            ctx_parser_set_color_model (parser, CTX_RGB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_drgbS:
            ctx_parser_set_color_model (parser, CTX_DRGB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_rgbaS:
            ctx_parser_set_color_model (parser, CTX_RGBA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_drgbaS:
            ctx_parser_set_color_model (parser, CTX_DRGBA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_cmykS:
            ctx_parser_set_color_model (parser, CTX_CMYK, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_cmykaS:
            ctx_parser_set_color_model (parser, CTX_CMYKA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_labS:
            ctx_parser_set_color_model (parser, CTX_LAB, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_labaS:
            ctx_parser_set_color_model (parser, CTX_LABA, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_lchS:
            ctx_parser_set_color_model (parser, CTX_LCH, 1);
            return ctx_parser_set_command (parser, CTX_COLOR);
          case SQZ_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 SQZ_nonzero:     return CTX_FILL_RULE_WINDING;
          case SQZ_winding:     return CTX_FILL_RULE_WINDING;
          case SQZ_evenOdd:     return CTX_FILL_RULE_EVEN_ODD;
          case SQZ_bevel:       return CTX_JOIN_BEVEL;
          case SQZ_round:       return CTX_JOIN_ROUND;
          case SQZ_miter:       return CTX_JOIN_MITER;
          case SQZ_none:        return CTX_CAP_NONE;
          case SQZ_square:      return CTX_CAP_SQUARE;
          case SQZ_start:       return CTX_TEXT_ALIGN_START;
          case SQZ_end:         return CTX_TEXT_ALIGN_END;
          case SQZ_left:        return CTX_TEXT_ALIGN_LEFT;
          case SQZ_right:       return CTX_TEXT_ALIGN_RIGHT;
          case SQZ_center:      return CTX_TEXT_ALIGN_CENTER;
          case SQZ_top:         return CTX_TEXT_BASELINE_TOP;
          case SQZ_bottom :     return CTX_TEXT_BASELINE_BOTTOM;
          case SQZ_middle:      return CTX_TEXT_BASELINE_MIDDLE;
          case SQZ_alphabetic:  return CTX_TEXT_BASELINE_ALPHABETIC;
          case SQZ_hanging:     return CTX_TEXT_BASELINE_HANGING;
          case SQZ_ideographic: return CTX_TEXT_BASELINE_IDEOGRAPHIC;

          case SQZ_userRGB:     return CTX_COLOR_SPACE_USER_RGB;
          case SQZ_deviceRGB:   return CTX_COLOR_SPACE_DEVICE_RGB;
          case SQZ_userCMYK:    return CTX_COLOR_SPACE_USER_CMYK;
          case SQZ_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.0f;
  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.0f-parser->numbers[offset + 0]) *
               (1.0f - parser->numbers[offset + 3]);
        *green = (1.0f-parser->numbers[offset + 1]) *
                 (1.0f - parser->numbers[offset + 3]);
        *blue = (1.0f-parser->numbers[offset + 2]) *
                (1.0f - 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_PAINT:
        ctx_paint (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] = _ctx_parse_float ((char*)parser->holding, NULL);
            {
              CtxEntry e = {CTX_KERNING_PAIR, {{0},}};
              e.data.u16[0] = (uint16_t)parser->numbers[0];
              e.data.u16[1] = (uint16_t)parser->numbers[1];
              e.data.s32[1] = (int32_t)(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  = (int)arg(0);
             int height = (int)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_FONT:
        // XXX: todo
        break;

      case CTX_DEFINE_GLYPH:
        /* XXX : reuse n_args logic - to enforce order */
        if (parser->n_numbers == 1)
        {
          CtxEntry e = {CTX_DEFINE_GLYPH, {{0},}};
          e.data.u32[0] = parser->color_space_slot;
          e.data.u32[1] = (int)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_svg_arc_to (ctx, arg(0), arg(1), arg(2), (int)arg(3), (int)arg(4), arg(5), arg(6));
        break;
      case CTX_REL_ARC_TO:
        //ctx_rel_arc_to (ctx, arg(0), arg(1), arg(2), arg(3), arg(4) );
        //
        {
          float x = ctx_x (ctx);
          float y = ctx_y (ctx);
          ctx_svg_arc_to (ctx, arg(0), arg(1), arg(2), (int)arg(3), (int)arg(4), arg(5)+x, arg(6)+y);
        }
        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:
        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), arg(1) );
        break;
      case CTX_REL_SMOOTHQ_TO:
        {
          float x = ctx_x (ctx);
          float y = ctx_y (ctx);
          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) + x, arg(1) + y);
        }
        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), (int)arg(5));
        break;
      case CTX_APPLY_TRANSFORM:
        ctx_apply_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5) , arg(6), arg(7), arg(8));
        break;
      case CTX_SOURCE_TRANSFORM:
        ctx_source_transform (ctx, arg(0), arg(1), arg(2), arg(3), arg(4), arg(5), arg(6), arg(7), arg(8));
        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_NEW_PAGE:
        ctx_new_page (ctx);
        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_LINE_HEIGHT:
        ctx_line_height (ctx, arg(0));
        break;
      case CTX_WRAP_LEFT:
        ctx_wrap_left (ctx, arg(0));
        break;
      case CTX_WRAP_RIGHT:
        ctx_wrap_right (ctx, arg(0));
        break;
      case CTX_IMAGE_SMOOTHING:
        ctx_image_smoothing (ctx, (int)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 = (int)arg(0);
          if (blend_mode == CTX_COLOR) blend_mode = CTX_BLEND_COLOR;
          ctx_blend_mode (ctx, (CtxBlend)blend_mode);
        }
        break;
      case CTX_EXTEND:
        ctx_extend (ctx, (CtxExtend)arg(0));
        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) );
        ctx_parser_set_size (parser, (int)arg(2), (int)arg(3), 0, 0);
        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, (uint32_t)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_END_FRAME:
        //ctx_flush (ctx); // XXX  XXX  flush only does things inside backends
        break;
      case CTX_START_FRAME: // XXX is it right to do things here?
        ctx_start_frame (ctx);
        if (parser->translate_origin)
        {
          ctx_translate (ctx,
                         (parser->cursor_x-1) * parser->cell_width * 1.0f,
                         (parser->cursor_y-1) * parser->cell_height * 1.0f);
        }
        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*)ctx_realloc (parser->holding, parser->hold_len, 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.0f);
              break;
            case 1:
            case 4:
              *value *= (parser->height/100.0f);
              break;
            case 2:
            case 5:
              *value *= small/100.0f;
              break;
          }
        break;
      case CTX_FONT_SIZE:
      case CTX_MITER_LIMIT:
      case CTX_LINE_WIDTH:
      case CTX_LINE_DASH_OFFSET:
        {
          *value *= (small/100.0f);
        }
        break;
      case CTX_ARC_TO:
      case CTX_REL_ARC_TO:
        if (arg_no > 3)
          {
            *value *= (small/100.0f);
          }
        else
          {
            if (arg_no % 2 == 0)
              { *value  *= ( (parser->width) /100.0f); }
            else
              { *value *= ( (parser->height) /100.0f); }
          }
        break;
      case CTX_ROUND_RECTANGLE:
        if (arg_no == 4)
        {
          { *value *= ((parser->height)/100.0f); }
          return;
        }
        /* FALLTHROUGH */
      default: // even means x coord
        if (arg_no % 2 == 0)
          { *value  *= ((parser->width)/100.0f); }
        else
          { *value *= ((parser->height)/100.0f); }
        break;
    }
}

static void ctx_parser_transform_percent_height (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  *value *= (parser->height/100.0f);
}

static void ctx_parser_transform_percent_width (CtxParser *parser, CtxCode code, int arg_no, float *value)
{
  *value *= (parser->height/100.0f);
}

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

  switch (parser->state)
    {

    case 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;
    }

    case 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;
    }


      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.0f * 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;
#if 0
      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;
#endif
      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]);
}

CTX_EXPORT 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, ctx_strlen (string));
  ctx_parser_feed_bytes (parser, " ", 1);
  ctx_parser_destroy (parser);
}

CTX_EXPORT void
ctx_parse2 (Ctx *ctx, const char *string, float *scene_elapsed_time, 
            int *scene_no_p)
{
  float time = *scene_elapsed_time;
  int scene_no = *scene_no_p;
  CtxString *str = ctx_string_new ("");
  int in_var = 0;
  float scene_duration = 5.0f;

  int i;

again:
  i = 0;

  // XXX : this doesn't work when there are [ 's in the text

  int scene_pos = 0;
  int last_scene = 0;
  {
  int in_scene_marker = 0;
  float duration = -1;
  for (; string[i]; i++)
  {
    char p = string[i];
    if (in_scene_marker)
    {
       if (p == ']')
       {
          in_scene_marker = 0;
       //   printf ("scene: %i time: %f scene %i: %f\n", scene_no, time, scene_pos, duration);
          last_scene = scene_pos;
          if (scene_pos == scene_no)
          {
            scene_duration = duration;
            if (scene_duration < time)
            {
              scene_no ++;
              (*scene_no_p)++;
              *scene_elapsed_time = time = 0;
            }
            else
            {
              break;
            }
          }
          scene_pos++;
       }
       else if (p>='0' && p<='9' && duration < 0)
       {
          duration = _ctx_parse_float (&string[i], NULL);
       }
    }
    else
    {
       if (p == '[')
       {
          in_scene_marker = 1;
          duration = -1;
       }
    }
  }
  }

  if (scene_no > last_scene)
  {
     scene_no = 0;
     (*scene_no_p) = 0;
     goto again;
  }
  
  if (scene_no == 0 && last_scene==0 && string[i]==0)
    i=0;

#define MAX_KEY_FRAMES 64
  float keys[MAX_KEY_FRAMES];
  float values[MAX_KEY_FRAMES];
  int n_keys = 0;
  int smooth = 1; // default to catmull rom

  for (; string[i]; i++)
  {
    char p = string[i];
    if (in_var == 0)
    {
      if (p == '[')
        break;
      else if (p == '(')
      {
        in_var = 1;
        n_keys = 0;
      }
      else
      {
        ctx_string_append_byte (str, p);
      }
    }
    else
    {
      if (p == ')')
      {
        float resolved_val = -100000.0;
        float prev_val = 0;
        for (int i = 0; i < n_keys; i++)
        {
          float key = keys[i];
          float val = values[i];
          //printf ("%f=%f\n", key, val);
          if (key>=time && resolved_val <=-10000.0f)
          {
            if (smooth == 0) // linear interpolation
            {
              if (i == 0)
                resolved_val = val;
              else
                resolved_val = ctx_lerpf (values[i-1], val, 
                                (time-keys[i-1])/(key-keys[i-1]));
            }
            else
            {
              if (i == 0)
              {
                resolved_val = val;
              }
              else if (n_keys<=2)
              {
                resolved_val = ctx_lerpf (values[i-1], val, 
                                 (time-keys[i-1])/(key-keys[i-1]));
              } else if (i == 1)
              {
                resolved_val = ctx_catmull_rom_left (values[i-1], values[i],
                                 values[i+1],
                                 (time-keys[i-1])/(key-keys[i-1]));
              }
              else if (i > 1 && i+1 < n_keys)
              {
                resolved_val = ctx_catmull_rom (values[i-2], values[i-1],
                                 val, values[i+1],
                                 (time-keys[i-1])/(key-keys[i-1]));
              }
              else if (i >= 2 && i < n_keys)
              {
                resolved_val = ctx_catmull_rom_right (values[i-2], values[i-1],
                                 values[i],
                                 (time-keys[i-1])/(key-keys[i-1]));
              }
            }
          }
          prev_val = val;
        }
        if (resolved_val <= -100000.0f) resolved_val = prev_val;
        ctx_string_append_printf (str, "%f", (double)resolved_val);
        in_var = 0;
      }
      else if (p>='0' && p<='9')
      {
        char *sp = (char*)&string[i];
        char *ep = sp;
        float key      = _ctx_parse_float (sp, &ep);
        char *eq       = strchr (sp, '=');
        float val      = 0.0;

        if (eq)
           val = _ctx_parse_float (eq+1, &ep);

        keys[n_keys] = key;
        values[n_keys++] = val;

        i+=(ep-sp)-1;
      }
      else if (p=='s')
      {
        smooth = 1;
      } else if (p=='l')
      {
        smooth = 0;
      }
      else
      {
        /* ignore */
      }

    }
  }

  /* we've now built up the frame, and parse
   * it with the regular parser
   */
  ctx_parse (ctx, str->str);
  ctx_string_free (str, 1);
}

#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*)ctx_malloc (string->allocated_length + 1);
  string->str[0]='\0';
}

static void ctx_string_destroy (CtxString *string)
{
  if (string->str)
    {
      ctx_free (string->str);
      string->str = NULL;
    }
}

void ctx_string_clear (CtxString *string)
{
  string->length = 0;
  string->utf8_length = 0;
  string->str[string->length]=0;
}


void ctx_string_pre_alloc (CtxString *string, int size)
{
  char *old = string->str;
  int old_len = string->allocated_length;
  string->allocated_length = CTX_MAX (size + 2, string->length + 2);
  string->str = (char*)ctx_realloc (old, old_len, string->allocated_length);
}


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;
      int old_len = string->allocated_length;
      string->allocated_length = CTX_MAX (string->allocated_length * 2, string->length + 2);
      string->str = (char*)ctx_realloc (old, old_len, 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)
      { ctx_free (line->style); }
    if (line->ctx)
      { ctx_destroy (line->ctx); }
    if (line->ctx_copy)
      { ctx_destroy (line->ctx_copy); }
  }
#endif
  ctx_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);
}


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;
      ctx_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, ctx_strlen (rest) + 1);
  string->length += new_len;
  string->length -= prev_len;
  ctx_free (rest);
  //string->length = ctx_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;
      ctx_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, ctx_strlen (rest) + 1);
  ctx_free (rest);
  string->length = ctx_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;
  ctx_free (rest);
  string->length = ctx_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*)ctx_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*)ctx_malloc (needed);
  va_end (ap);
  va_start (ap, format);
  vsnprintf (buffer, needed, format, ap);
  va_end (ap);
  ctx_string_append_str (string, buffer);
  ctx_free (buffer);
}

CtxString *ctx_string_new_printf (const char *format, ...)
{
  CtxString *string = ctx_string_new ("");
  va_list ap;
  size_t needed;
  char *buffer;
  va_start (ap, format);
  needed = vsnprintf (NULL, 0, format, ap) + 1;
  buffer = (char*)ctx_malloc (needed);
  va_end (ap);
  va_start (ap, format);
  vsnprintf (buffer, needed, format, ap);
  va_end (ap);
  ctx_string_append_str (string, buffer);
  ctx_free (buffer);
  return string;
}


void
ctx_string_append_int (CtxString *string, int val)
{
  char buf[64];
  char *bp = &buf[0];
  int remainder;
  if (val < 0)
  {
    buf[0]='-';
    bp++;
    remainder = -val;
  }
  else
  remainder = val;

  int len = 0;
  do {
    int digit = remainder % 10;
    bp[len++] = digit + '0';
    remainder /= 10;
  } while (remainder);

  bp[len]=0;
  for (int i = 0; i < len/2; i++)
  {
    int tmp = bp[i];
    bp[i] = bp[len-1-i];
    bp[len-1-i] = tmp;
  }
  len += (val < 0);
  ctx_string_append_str (string, buf);
}

void
ctx_string_append_float (CtxString *string, float val)
{
  if (val < 0.0f)
  {
    ctx_string_append_byte (string, '-');
    val = -val;
  }
  int remainder = ((int)(val*10000))%10000;
  if (remainder % 10 > 5)
    remainder = remainder/10+1;
  else
    remainder /= 10;
  ctx_string_append_int (string, (int)val);
  if (remainder)
  {
    if (remainder<0)
      remainder=-remainder;
    ctx_string_append_byte (string, '.');
    if (remainder < 10)
      ctx_string_append_byte (string, '0');
    if (remainder < 100)
      ctx_string_append_byte (string, '0');
    ctx_string_append_int (string, remainder);
  }
}

void ctx_drawlist_clear (Ctx *ctx)
{
  ctx->drawlist.count = 0;
  ctx->drawlist.bitpack_pos = 0;
}

static void ctx_drawlist_backend_destroy (CtxBackend *backend)
{
  ctx_free (backend);
}

static void ctx_update_current_path (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_CURVE_TO:
      case CTX_QUAD_TO:
      case CTX_SMOOTH_TO:
      case CTX_SMOOTHQ_TO:
      case CTX_REL_LINE_TO:
      case CTX_REL_MOVE_TO:
      case CTX_REL_QUAD_TO:
      case CTX_REL_SMOOTH_TO:
      case CTX_REL_SMOOTHQ_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
}

static void
ctx_drawlist_process (Ctx *ctx, CtxEntry *entry)
{
#if CTX_CURRENT_PATH
  ctx_update_current_path (ctx, entry);
#endif
  /* these functions can 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);
}

static CtxBackend *ctx_drawlist_backend_new (void)
{
  CtxBackend *backend = (CtxBackend*)ctx_calloc (sizeof (CtxCtx), 1);
                       // the sizeof(CtxCtx) should actually be sizeof(CtxBackend)
                       // but static analysis complains about event code
                       // initializing the extra members - which might most
                       // often be a false report - we ass slack since it is
                       // "only" ~ 40 bytes per instance.
  backend->process = (void(*)(Ctx *a, CtxCommand *c))ctx_drawlist_process;
  backend->destroy = (void(*)(void *a))ctx_drawlist_backend_destroy;
  backend->type = CTX_BACKEND_DRAWLIST;
  return backend;
}

#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, uint32_t hash)
{
  CtxIntRectangle rect = {0,0, hasher->rasterizer.blit_width/hasher->cols,
                               hasher->rasterizer.blit_height/hasher->rows};
  uint32_t active = 0;
  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))
      {
        hasher->hashes[(row * hasher->cols + col)] ^= hash;
        hasher->hashes[(row * hasher->cols + col)] += 11;
        active |= (1<<hno);
      }
    }

  if (hasher->prev_command>=0)
  {
    hasher->drawlist->entries[hasher->prev_command].data.u32[1] = active;
  }

  hasher->prev_command = hasher->pos;
}

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 inline uint32_t murmur_32_scramble(uint32_t k) {
    k *= 0xcc9e2d51;
    k = (k << 15) | (k >> 17);
    k *= 0x1b873593;
    return k;
}

static inline void murmur3_32_process(CtxMurmur *murmur, const uint8_t* key, size_t len)
{
    // code direct from the wikipedia article, it appears there without
    // a license
    uint32_t h = murmur->state[0];
    uint32_t k;
    /* Read in groups of 4. */
    for (size_t i = len >> 2; i; i--) {
        // Here is a source of differing results across endiannesses.
        // A swap here has no effects on hash properties though.
        memcpy(&k, key, sizeof(uint32_t));
        key += sizeof(uint32_t);
        h ^= murmur_32_scramble(k);
        h = (h << 13) | (h >> 19);
        h = h * 5 + 0xe6546b64;
    }
    /* Read the rest. */
    k = 0;
    for (size_t i = len & 3; i; i--) {
        k <<= 8;
        k |= key[i - 1];
    }
    // A swap is *not* necessary here because the preceding loop already
    // places the low bytes in the low places according to whatever endianness
    // we use. Swaps only apply when the memory is copied in a chunk.
    h ^= murmur_32_scramble(k);
    murmur->state[0] = h;
    murmur->state[1] += len;
}

static inline void murmur3_32_init (CtxMurmur *murmur)
{
  murmur->state[0]=0;
  murmur->state[1]=0;
}
static inline void murmur3_32_free (CtxMurmur *murmur)
{
  ctx_free (murmur);
}
static inline uint32_t murmur3_32_finalize (CtxMurmur *murmur)
{
  uint32_t h = murmur->state[0];
  /* Finalize. */
  h ^= murmur->state[1];
  h ^= h >> 16;
  h *= 0x85ebca6b;
  h ^= h >> 13;
  h *= 0xc2b2ae35;
  h ^= h >> 16;
  return h;
}

static inline int murmur3_32_done (CtxMurmur *murmur, unsigned char *out)
{
  murmur3_32_finalize (murmur);
  for (int i = 0; i < 4; i++)
    out[i]=0;
  memcpy (out, &murmur->state[0], 4);
  return murmur->state[0];
}

/*
 * the hasher should store a list of
 * times when the activeness of each tile changes
 *
 * on replay path and text/glyph commands as well
 * as stroke/fill can be ignored  clips outside
 * should mean no more drawing until restore
 */

static inline void
ctx_device_corners_to_user_rect (CtxState *state,
                                 float x0, float y0, float x1, float y1,
                                 CtxIntRectangle *shape_rect)
{
  int itw, ith;
  int itx = 0, ity = 0, itx2 = 0, ity2 = 0;
  _ctx_user_to_device_prepped (state, x0, y0, &itx, &ity);
  _ctx_user_to_device_prepped (state, x1, y1, &itx2, &ity2);
  itx /= CTX_SUBDIV;
  itx2 /= CTX_SUBDIV;
  ity /= CTX_FULL_AA;
  ity2 /= CTX_FULL_AA;
  itw = itx2-itx;
  ith = ity2-ity;
  shape_rect->x=itx;
  shape_rect->y=ity;
  shape_rect->width = itw;
  shape_rect->height = ith;
}

static void
ctx_hasher_process (Ctx *ctx, CtxCommand *command)
{
  CtxEntry      *entry      = &command->entry;
  CtxRasterizer *rasterizer = (CtxRasterizer *) ctx->backend;
  CtxHasher     *hasher     = (CtxHasher*) ctx->backend;
  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:
        {
          const char *str = ctx_arg_string();
          CtxMurmur murmur;
          memcpy (&murmur, &hasher->murmur_fill[hasher->source_level], sizeof (CtxMurmur));
          float width = ctx_text_width (rasterizer->backend.ctx, str);


          float height = ctx_get_font_size (rasterizer->backend.ctx);
           CtxIntRectangle shape_rect;

           float tx = rasterizer->x;
           float ty = rasterizer->y - height * 1.2f;
           float tx2 = tx+width;
           float ty2 = ty+height * (ctx_str_count_lines (str) + 1.5f);

           ctx_device_corners_to_user_rect (rasterizer->state, tx,ty,tx2,ty2, &shape_rect);
          switch ((int)ctx_state_get (rasterizer->state, SQZ_textAlign))
          {
          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
          }

          murmur3_32_process(&murmur, (const unsigned char*)ctx_arg_string(), ctx_strlen  (ctx_arg_string()));
#if 1
        murmur3_32_process(&murmur, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
    //      murmur3_32_process(&murmur, (unsigned char*)&color, 4);
#endif
          murmur3_32_process(&murmur, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
        }
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_STROKE_TEXT:
        {
          CtxMurmur murmur;
          const char *str = ctx_arg_string();
          memcpy (&murmur, &hasher->murmur_stroke[hasher->source_level], sizeof (CtxMurmur));
          float width = ctx_text_width (rasterizer->backend.ctx, str);
          float height = ctx_get_font_size (rasterizer->backend.ctx);

           CtxIntRectangle shape_rect;

           float tx = rasterizer->x;
           float ty = rasterizer->y - height * 1.2f;
           float tx2 = tx+width;
           float ty2 = ty+height * (ctx_str_count_lines (str) + 1.5f);
           ctx_device_corners_to_user_rect (rasterizer->state, tx,ty,tx2,ty2, &shape_rect);


#if 0
          uint32_t color;
          ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color));
#endif
          murmur3_32_process(&murmur, (unsigned char*)ctx_arg_string(), ctx_strlen  (ctx_arg_string()));
#if 1
          murmur3_32_process(&murmur, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
    //    murmur3_32_process(&murmur, (unsigned char*)&color, 4);
#endif
          murmur3_32_process(&murmur, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
        }
        ctx_rasterizer_reset (rasterizer);
        break;
      case CTX_GLYPH:
         {
          CtxMurmur murmur;
          memcpy (&murmur, &hasher->murmur_fill[hasher->source_level], sizeof (CtxMurmur));

          uint8_t string[8];
          string[ctx_unichar_to_utf8 (c->u32.a0, string)]=0;
          float width = ctx_text_width (rasterizer->backend.ctx, (char*)string);
          float height = ctx_get_font_size (rasterizer->backend.ctx);

          float tx = rasterizer->x;
          float ty = rasterizer->y;
          float tx2 = rasterizer->x + width;
          float ty2 = rasterizer->y + height * 2;
          CtxIntRectangle shape_rect;
          ctx_device_corners_to_user_rect (rasterizer->state, tx,ty,tx2,ty2, &shape_rect);

          shape_rect.y-=shape_rect.height/2;


#if 0
          uint32_t color;
          ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color));
#endif
          murmur3_32_process(&murmur, string, ctx_strlen ((const char*)string));
          murmur3_32_process(&murmur, (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
#if 0
          murmur3_32_process(&murmur, (unsigned char*)&color, 4);
#endif
          murmur3_32_process(&murmur, (unsigned char*)&shape_rect, sizeof (CtxIntRectangle));
          _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));

          ctx_rasterizer_rel_move_to (rasterizer, width, 0);
          ctx_rasterizer_reset (rasterizer);
         }
        break;

      case CTX_CLIP:
      case CTX_PAINT:
        {
        CtxMurmur murmur;
        memcpy (&murmur, &hasher->murmur_fill[hasher->source_level], sizeof (CtxMurmur));
        if (rasterizer->edge_list.count)
          murmur3_32_process(&murmur,  (uint8_t*)rasterizer->edge_list.entries, sizeof(CtxSegment) * rasterizer->edge_list.count);

        {
          int is = rasterizer->state->gstate.fill_rule;
          murmur3_32_process(&murmur, (uint8_t*)&is, sizeof(int));
        }
        CtxIntRectangle shape_rect = {-100,-100,
                rasterizer->blit_width*10,
                rasterizer->blit_height*10};
        _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));
        }

        break;
      case CTX_FILL:
        {
          CtxMurmur murmur;
          memcpy (&murmur, &hasher->murmur_fill[hasher->source_level], sizeof (CtxMurmur));

          /* 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_hash2 (rasterizer); // + hasher->salt;
        CtxIntRectangle shape_rect = {
          (int)(rasterizer->col_min / CTX_SUBDIV - 3),
          (int)(rasterizer->scan_min / aa - 3),
          (int)(5+(rasterizer->col_max - rasterizer->col_min + CTX_SUBDIV-1) / CTX_SUBDIV),
          (int)(5+(rasterizer->scan_max - rasterizer->scan_min + aa-1) / aa)
        };

        if (rasterizer->edge_list.count)
          murmur3_32_process(&murmur,  (uint8_t*)rasterizer->edge_list.entries, sizeof(CtxSegment) * rasterizer->edge_list.count);

        {
          int is = rasterizer->state->gstate.fill_rule;
          murmur3_32_process(&murmur, (uint8_t*)&is, sizeof(int));
        }
        {
          int is = rasterizer->state->gstate.image_smoothing;
          murmur3_32_process(&murmur, (uint8_t*)&is, sizeof(int));
        }
        {
          int e = rasterizer->state->gstate.extend;
          murmur3_32_process(&murmur, (uint8_t*)&e, sizeof(int));
        }

          _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));

        if (c->code == CTX_CLIP)
          ctx_rasterizer_clip (rasterizer);

        if (!rasterizer->preserve)
          ctx_rasterizer_reset (rasterizer);
        rasterizer->preserve = 0;

        }
        break;
      case CTX_STROKE:
        {
          CtxMurmur murmur;
          memcpy (&murmur, &hasher->murmur_stroke[hasher->source_level], sizeof (CtxMurmur));
        if (rasterizer->edge_list.count)
        murmur3_32_process(&murmur,  (uint8_t*)rasterizer->edge_list.entries, sizeof(CtxSegment) * rasterizer->edge_list.count);
        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 += (int)(rasterizer->state->gstate.line_width * 2);
        shape_rect.height += (int)(rasterizer->state->gstate.line_width * 2);
        shape_rect.x -= (int)(rasterizer->state->gstate.line_width);
        shape_rect.y -= (int)(rasterizer->state->gstate.line_width);

        {
          float f;
          int i;
          f = rasterizer->state->gstate.line_width;
          murmur3_32_process(&murmur, (uint8_t*)&f, sizeof(float));
          i = rasterizer->state->gstate.line_cap;
          murmur3_32_process(&murmur, (uint8_t*)&i, sizeof(int));
          i = rasterizer->state->gstate.line_join;
          murmur3_32_process(&murmur, (uint8_t*)&i, sizeof(int));
          i = rasterizer->state->gstate.source_stroke.type;
          murmur3_32_process(&murmur, (uint8_t*)&i, sizeof(int));
        }

        uint32_t color;
        ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_stroke.color, (uint8_t*)(&color));

          murmur3_32_process(&murmur, (unsigned char*)&color, 4);

          _ctx_add_hash (hasher, &shape_rect, murmur3_32_finalize (&murmur));
        }
        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_line_to (rasterizer, c->c.x0, c->c.y0);
        ctx_rasterizer_line_to (rasterizer, c->c.x1, c->c.y1);
        ctx_rasterizer_line_to (rasterizer, c->c.x2, c->c.y2);
        //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_line_to (rasterizer, c->c.x2, c->c.y2);
        //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_line_to (rasterizer, c->c.x1, c->c.y1);
        //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_line_to (rasterizer, c->c.x1, c->c.y1);
        //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, (int)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_SAVE:
      case CTX_RESTORE:

        if (c->code == CTX_SAVE)
        {
           if (hasher->source_level + 1 < CTX_MAX_STATES)
           {
             hasher->source_level++;
             hasher->murmur_fill[hasher->source_level] =
               hasher->murmur_fill[hasher->source_level-1];
             hasher->murmur_stroke[hasher->source_level] =
               hasher->murmur_stroke[hasher->source_level-1];
           }
        }
        else
        {
           if (hasher->source_level - 1 >= 0)
           {
             hasher->source_level--;
             hasher->murmur_fill[hasher->source_level] =
               hasher->murmur_fill[hasher->source_level+1];
             hasher->murmur_stroke[hasher->source_level] =
               hasher->murmur_stroke[hasher->source_level+1];
           }
        }

        /* FALLTHROUGH */
      case CTX_ROTATE:
      case CTX_SCALE:
      case CTX_TRANSLATE:
      case CTX_APPLY_TRANSFORM:

        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_CLOSE_PATH:
        ctx_rasterizer_finish_shape (rasterizer);
        break;
      case CTX_DEFINE_TEXTURE:
        {
        murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
        murmur3_32_process(&hasher->murmur_fill[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
        murmur3_32_process (&hasher->murmur_fill[hasher->source_level], (uint8_t*)c->define_texture.eid, ctx_strlen (c->define_texture.eid));
        murmur3_32_process(&hasher->murmur_fill[hasher->source_level], (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));

        rasterizer->comp_op = NULL; // why?
        }
        break;
      case CTX_TEXTURE:
        murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
        murmur3_32_process(&hasher->murmur_fill[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
        murmur3_32_process (&hasher->murmur_fill[hasher->source_level], (uint8_t*)c->texture.eid, ctx_strlen (c->texture.eid));
        murmur3_32_process (&hasher->murmur_fill[hasher->source_level], (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));
            murmur3_32_init (&hasher->murmur_stroke[hasher->source_level]);
            murmur3_32_process(&hasher->murmur_stroke[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
            murmur3_32_process(&hasher->murmur_stroke[hasher->source_level], (unsigned char*)&color, 4);
          }
          else
          {
            ctx_color_get_rgba8 (rasterizer->state, &rasterizer->state->gstate.source_fill.color, (uint8_t*)(&color));
            murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
            murmur3_32_process(&hasher->murmur_fill[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
            murmur3_32_process(&hasher->murmur_fill[hasher->source_level], (unsigned char*)&color, 4);
          }
        }
        break;
      case CTX_LINEAR_GRADIENT:
          murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
          murmur3_32_process(&hasher->murmur_fill[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
          murmur3_32_process(&hasher->murmur_fill[hasher->source_level], 
                           (uint8_t*)c, sizeof (c->linear_gradient));
          murmur3_32_process (&hasher->murmur_fill[hasher->source_level], (unsigned char*)(&rasterizer->state->gstate.transform), sizeof (rasterizer->state->gstate.transform));
        break;
      case CTX_RADIAL_GRADIENT:
          murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
          murmur3_32_process(&hasher->murmur_fill[hasher->source_level], &rasterizer->state->gstate.global_alpha_u8, 1);
          murmur3_32_process(&hasher->murmur_fill[hasher->source_level], 
                           (uint8_t*)c, sizeof (c->radial_gradient));
          murmur3_32_process (&hasher->murmur_fill[hasher->source_level], (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) )
                         };
          murmur3_32_process(&hasher->murmur_fill[hasher->source_level], 
                           (uint8_t*) &rgba[0], sizeof(rgba));
        }
        break;
#endif
    }

#if 0
  if (command->code == CTX_START_FRAME)
  {
  }
#endif

    hasher->pos += ctx_conts_for_entry ((CtxEntry*)(command))+1;
  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, CtxDrawlist *drawlist)
{
  CtxHasher *hasher = (CtxHasher*)rasterizer;
  ctx_memset (rasterizer, 0, sizeof (CtxHasher) );
  CtxBackend *backend = (CtxBackend*)hasher;
  backend->type        = CTX_BACKEND_HASHER;
  backend->ctx         = ctx;
  backend->process = ctx_hasher_process;
  backend->destroy = (CtxDestroyNotify)ctx_rasterizer_destroy;
  // XXX need own destructor to not leak ->hashes
  rasterizer->edge_list.flags |= CTX_DRAWLIST_EDGE_LIST;
  rasterizer->state       = state;
  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->pos  = 0;

  hasher->drawlist = drawlist;
  hasher->prev_command = -1;

  memset(hasher->hashes,0, sizeof (hasher->hashes));
  murmur3_32_init (&hasher->murmur_fill[hasher->source_level]);
  murmur3_32_init (&hasher->murmur_stroke[hasher->source_level]);

  return rasterizer;
}

Ctx *ctx_hasher_new (int width, int height, int cols, int rows, CtxDrawlist *drawlist)
{
  Ctx *ctx           = _ctx_new_drawlist (width, height);
  CtxState    *state = &ctx->state;
  CtxRasterizer *rasterizer = (CtxRasterizer *) ctx_calloc (sizeof (CtxHasher), 1);
  ctx_hasher_init (rasterizer, ctx, state, width, height, cols, rows, drawlist);
  ctx_set_backend (ctx, (void*)rasterizer);
  return ctx;
}

uint32_t ctx_hasher_get_hash (Ctx *ctx, int col, int row)
{
  CtxHasher *hasher = (CtxHasher*)ctx->backend;
  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;

  hasher->drawlist->entries[hasher->prev_command].data.u32[1] = 0xffffffff;

  return hasher->hashes[(row*hasher->cols+col)];
}

#endif
/*
 * TODO:
 *   gradients
 *   text-layout
 *   textures
 *   links
 *
 */


#if CTX_PDF

#define CTX_PDF_MAX_OBJS 256
#define CTX_PDF_MAX_RESOURCES 256 // in one page

#define CTX_PDF_MAX_PAGES CTX_PDF_MAX_OBJS

typedef struct _CtxPDF CtxPDF;
enum { CTX_PDF_TIMES = 1,
       CTX_PDF_HELVETICA, //2
       CTX_PDF_COURIER, //3
       CTX_PDF_SYMBOL, //4
       CTX_PDF_TIMES_BOLD,
       CTX_PDF_HELVETICA_BOLD,
       CTX_PDF_COURIER_BOLD,
       CTX_PDF_ZAPF_DING_BATS, // 8
       CTX_PDF_TIMES_ITALIC, // 9
       CTX_PDF_HELVETICA_ITALIC, // 10
       CTX_PDF_COURIER_ITALIC, // 11
       CTX_PDF_TIMES_BOLD_ITALIC, // 12
       CTX_PDF_HELVETICA_BOLD_ITALIC, //13
       CTX_PDF_COURIER_BOLD_ITALIC, //14
       // courier and helvetica variants are called
       // oblique not italic in the PDF spec

};

typedef struct
_CtxPdfResource
{
  int id;
  int type; // 0 opacity, 1 linear grad, 2 radial grad
  union { 
     struct { float value;}                   opacity;
     struct { float x0, y0, x1, y1;}          linear_gradient;
     struct { float x0, y0, r0, x1, y1, r1;}  radial_gradient;
     struct { const char *eid;int width, height,stride,format;uint8_t *data;}  texture;
     struct { int   no;}    font;
     // texture
     // linear-gradient
     // radial-gradient
  };
} CtxPdfResource;


struct
  _CtxPDF
{
  CtxBackend     backend;
  int            preserve;
  const char    *path;
  CtxString     *document;
  CtxState       state;
  int            pat;
  int            xref[CTX_PDF_MAX_OBJS];
  int            objs;
  int            page_length_offset;
  int            page_height_offset;
  int            kids_offset;
  int            page_count_offset;

  int            width;
  int            height;

  char          *encoding;

  CtxPdfResource resource[CTX_PDF_MAX_RESOURCES];
  int            resource_count;

  int            page_resource[CTX_PDF_MAX_RESOURCES];
  int            page_resource_count;
  int            new_resource[CTX_PDF_MAX_RESOURCES];
  int            new_resource_count;

  int            next_obj; // pre-emptive builds
                           // during page build

  float          page_size[4];

  int            page_objs[CTX_PDF_MAX_PAGES];
  int            content_objs[CTX_PDF_MAX_PAGES];
  int            page_count;

  int            pages; // known to be 1
  int            font;
  int            font_map;


  int alphas[10];
};


#define ctx_pdf_print(str) \
        do { ctx_string_append_str (pdf->document, str);\
}while (0)
#define ctx_pdf_printf(fmt, a...) \
        do { ctx_string_append_printf (pdf->document, fmt, ##a);\
}while (0)
#define ctx_pdf_print1i(i0) \
        do { ctx_string_append_int (pdf->document, i0);\
        ctx_string_append_byte (pdf->document, ' ');\
}while (0)
#define ctx_pdf_print1f(f0) \
        do { ctx_string_append_float (pdf->document, f0);\
             ctx_string_append_byte (pdf->document, ' '); }while (0)
#define ctx_pdf_print2f(f0,f1) \
        do { ctx_pdf_print1f(f0);ctx_pdf_print1f(f1); }while (0)
#define ctx_pdf_print3f(f0,f1,f2) \
        do { ctx_pdf_print2f(f0,f1);ctx_pdf_print1f(f2); }while (0)
#define ctx_pdf_print4f(f0,f1,f2,f3) \
        do { ctx_pdf_print3f(f0,f1,f2);ctx_pdf_print1f(f3); }while (0)
#define ctx_pdf_print5f(f0,f1,f2,f3,f4) \
        do { ctx_pdf_print4f(f0,f1,f2,f3);ctx_pdf_print1f(f4); }while (0)
#define ctx_pdf_print6f(f0,f1,f2,f3,f4,f5) \
        do { ctx_pdf_print5f(f0,f1,f2,f3,f4);ctx_pdf_print1f(f5); }while (0)

/**
 * Generate a cubic Bezier representing an arc on the unit circle of total
 * angle ‘size‘ radians, beginning ‘start‘ radians above the x-axis.
 */
static void acuteArcToBezier(float start, float size,
                float *ax,
                float *ay,
                float *bx,
                float *by,
                float *cx,
                float *cy,
                float *dx,
                float *dy
                ) {
  // Evaluate constants.
  float alpha = size / 2.0,
      cos_alpha = ctx_cosf(alpha),
      sin_alpha = ctx_sinf(alpha),
      cot_alpha = 1.0 / ctx_tanf(alpha),
      phi = start + alpha, // This is how far the arc needs to be rotated.
      cos_phi = ctx_cosf(phi),
      sin_phi = ctx_sinf(phi),
      lambda = (4.0 - cos_alpha) / 3.0,
      mu = sin_alpha + (cos_alpha - lambda) * cot_alpha;
 // Return rotated waypoints.
 *ax = ctx_cosf(start),
 *ay = ctx_sinf(start),
 *bx = lambda * cos_phi + mu * sin_phi,
 *by = lambda * sin_phi - mu * cos_phi,
 *cx = lambda * cos_phi - mu * sin_phi,
 *cy = lambda * sin_phi + mu * cos_phi,
 *dx = ctx_cosf(start + size),
 *dy = ctx_sinf(start + size);
}


static char *ctx_utf8_to_mac_roman (const uint8_t *string);
static char *ctx_utf8_to_windows_1252 (const uint8_t *string);

void pdf_end_object (CtxPDF *pdf)
{
  ctx_pdf_print("\nendobj\n");
}

int pdf_add_object (CtxPDF *pdf)
{
  if (pdf->objs) pdf_end_object (pdf);
  // we use 1 indexing in this array
  pdf->xref[++pdf->objs] = pdf->document->length;
  ctx_pdf_printf("%i 0 obj\n", pdf->objs);
  return pdf->objs;
}

static void
pdf_start_page (CtxPDF *pdf)
{
  pdf->page_count++;
  pdf->content_objs[pdf->page_count]=pdf_add_object (pdf); // 2 - our actual page contents
  ctx_pdf_printf ("<</Length ");
  pdf->page_length_offset = pdf->document->length;
  ctx_pdf_printf ("XXXXXXXXXX>>\n");
  ctx_pdf_printf ("stream\nBT\n1 0 0 -1 0 ");
  pdf->page_height_offset = pdf->document->length;
  ctx_pdf_printf ("XXXXXXXXXX cm\n/F1 24 Tf\n", pdf->height);

  pdf->page_resource_count = 0;
  pdf->new_resource_count = 0;
  pdf->next_obj = pdf->content_objs[pdf->page_count]+1;
}

static void
pdf_end_page (CtxPDF *pdf)
{
  int length = (pdf->document->length - pdf->page_length_offset) - 17;
  char buf[11];
  snprintf (buf, 11, "%10u", length);
  memcpy   (&pdf->document->str[pdf->page_length_offset], buf, 10);
  snprintf (buf, 11, "% 9f", pdf->page_size[3]);
  memcpy   (&pdf->document->str[pdf->page_height_offset], buf, 10);
  ctx_pdf_printf("\nET\nendstream\n");

  for (int i = 0; i < pdf->new_resource_count; i ++)
  {
    float opacity = 1.0f;
    for (int j = 0; j < pdf->resource_count; j ++)
    {
      if (pdf->resource[j].id == pdf->new_resource[i])
        opacity = pdf->resource[j].opacity.value;
    }
    pdf->alphas[i]=pdf_add_object (pdf); // 4
    ctx_pdf_printf ("<</Type/ExtGState/ca %.2f/CA %.2f>>", opacity, opacity);
  }

   pdf->page_objs[pdf->page_count]=pdf_add_object (pdf);
   ctx_pdf_printf ("<<"
"/Contents %i 0 R/Type/Page/Resources<</ProcSet[/PDF/Text]/Font %i 0 R", pdf->content_objs[pdf->page_count], pdf->font_map);
   ctx_pdf_printf ("/ExtGState");

   ctx_pdf_printf ("<<");
   for (int i = 0; i < pdf->page_resource_count; i++)
   {
     ctx_pdf_printf ("/G%i %i 0 R", pdf->page_resource[i],
                                    pdf->page_resource[i]);
   }
   ctx_pdf_print (">>");
   ctx_pdf_print (">>/Parent ");
   ctx_pdf_print1i (pdf->pages);ctx_pdf_print ("0 R");
   ctx_pdf_print ("/MediaBox[");
   ctx_pdf_print4f (pdf->page_size[0], pdf->page_size[1],
                    pdf->page_size[2]+pdf->page_size[0], pdf->page_size[3]+pdf->page_size[1]);
   ctx_pdf_print ("]>>");

}


void ctx_pdf_set_opacity (CtxPDF *pdf, float alpha)
{
  int obj_no = 0;

  for (int i = 0; i < pdf->resource_count; i++)
  {
    if (pdf->resource[i].type == 0 &&
        pdf->resource[i].opacity.value == alpha)
    {
      obj_no = pdf->resource[i].id;
    }
  }

  if (obj_no == 0)
  {
     pdf->resource[pdf->resource_count].type = 0;
     pdf->resource[pdf->resource_count].opacity.value = alpha;
     obj_no = pdf->resource[pdf->resource_count].id = 
             pdf->next_obj++;
     pdf->resource_count++;

     pdf->new_resource[pdf->new_resource_count++] =
       obj_no;
  }

  ctx_pdf_printf("/G%i gs ", obj_no);

  for (int i = 0; i < pdf->page_resource_count; i ++)
  {
    if (pdf->page_resource[i] == obj_no)
      return;
  }
  pdf->page_resource[pdf->page_resource_count++] = obj_no;
}

static void
ctx_pdf_line_to (Ctx *ctx, float x, float y)
{
  CtxPDF *pdf = (void*)ctx->backend;
  ctx_pdf_print2f(x, y); ctx_pdf_print("l ");
}
static void
ctx_pdf_move_to (Ctx *ctx, float x, float y)
{
  CtxPDF *pdf = (void*)ctx->backend;
  ctx_pdf_print2f(x, y); ctx_pdf_print("m ");
}
static void
ctx_pdf_curve_to (Ctx *ctx, float cx0, float cy0, float cx1, float cy1, float x, float y)
{
  CtxPDF *pdf = (void*)ctx->backend;
  ctx_pdf_print6f(cx0,cy0,cx1,cy1,x,y); ctx_pdf_print("c ");
}

static void
ctx_pdf_apply_transform (Ctx *ctx, float a, float b, float c, float d, float e, float f)
{
  CtxPDF *pdf = (void*)ctx->backend;
  ctx_pdf_print6f(a,b,c,d,e,f);
  ctx_pdf_print("cm\n");
}

static void
ctx_pdf_process (Ctx *ctx, CtxCommand *c)
{
  CtxPDF *pdf = (void*)ctx->backend;
  CtxEntry *entry = (CtxEntry *) &c->entry;
  CtxState *state = &pdf->state;

  CtxDrawlist *preserved = NULL;

  ctx_interpret_style (&pdf->state, entry, NULL);

  switch (entry->code)
    {
      case CTX_NEW_PAGE:
        pdf_end_page (pdf);
        pdf_start_page (pdf);
        break;

      case CTX_LINE_TO:         ctx_pdf_line_to (ctx, c->line_to.x, c->line_to.y); break;
      case CTX_HOR_LINE_TO:     ctx_pdf_line_to (ctx, ctx_arg_float(0), state->y); break;
      case CTX_VER_LINE_TO:     ctx_pdf_line_to (ctx, state->x, ctx_arg_float(0)); break;
      case CTX_REL_LINE_TO:     ctx_pdf_line_to (ctx, c->line_to.x + state->x, c->line_to.y + state->y); break;
      case CTX_REL_HOR_LINE_TO: ctx_pdf_line_to (ctx, ctx_arg_float(0) + state->x, state->y); break;
      case CTX_REL_VER_LINE_TO: ctx_pdf_line_to (ctx, state->x, ctx_arg_float(0) + state->y); break;

      case CTX_MOVE_TO:         ctx_pdf_move_to (ctx, c->move_to.x, c->move_to.y); break;
      case CTX_REL_MOVE_TO:     ctx_pdf_move_to (ctx, c->move_to.x + state->x, c->move_to.y + state->y); break;

      case CTX_CURVE_TO:
        ctx_pdf_curve_to (ctx, c->curve_to.cx1, c->curve_to.cy1,
                               c->curve_to.cx2, c->curve_to.cy2,
                               c->curve_to.x, c->curve_to.y);
        break;


      case CTX_REL_CURVE_TO:
        ctx_pdf_curve_to (ctx, c->curve_to.cx1 + state->x, c->curve_to.cy1 + state->y,
                               c->curve_to.cx2 + state->x, c->curve_to.cy2 + state->y,
                               c->curve_to.x   + state->x, c->curve_to.y   + state->y);
        break;


      case CTX_PRESERVE:
        pdf->preserve = 1;
        break;

      case CTX_QUAD_TO:
        {
          float cx = ctx_arg_float (0);
          float cy = ctx_arg_float (1);
          float  x = ctx_arg_float (2);
          float  y = ctx_arg_float (3);
          float cx1 = (cx * 2 + state->x) / 3.0f;
          float cy1 = (cy * 2 + state->y) / 3.0f;
          float cx2 = (cx * 2 + x) / 3.0f;
          float cy2 = (cy * 2 + y) / 3.0f;
          ctx_pdf_curve_to (ctx, cx1, cy1, cx2, cy2, x, y);
        }
        break;

      case CTX_REL_QUAD_TO:
        {
          float cx = ctx_arg_float (0);
          float cy = ctx_arg_float (1);
          float  x = ctx_arg_float (2);
          float  y = ctx_arg_float (3);
          float cx1 = (cx * 2 ) / 3.0f;
          float cy1 = (cy * 2 ) / 3.0f;
          float cx2 = (cx * 2 + x) / 3.0f;
          float cy2 = (cy * 2 + y) / 3.0f;
          ctx_pdf_curve_to (ctx, cx1 + state->x, cy1 + state->y,
                                 cx2 + state->x, cy2 + state->y,
                                 x   + state->x, y   + state->y);
        }
        break;

      case CTX_LINE_WIDTH:
        ctx_pdf_printf("%f w\n", ctx_arg_float (0));
        break;

      case CTX_ARC:
        {
           float x = c->arc.x,
                 y = c->arc.y,
                 w = c->arc.radius,
                 h = c->arc.radius,
                 stop  = c->arc.angle1,
                 start = c->arc.angle2;
                 //direction = c->arc.direction;

           start = start * 0.99;

           while (start < 0) start += CTX_PI * 2;
           while (stop < 0) stop += CTX_PI * 2;

           start = ctx_fmodf (start, CTX_PI * 2);
           stop  = ctx_fmodf (stop, CTX_PI * 2);
           // Adjust angles to counter linear scaling.
           if (start <= CTX_PI/2) {
             start = ctx_atanf(w / h * ctx_tanf(start));
           } else if (start > CTX_PI/2 && start <= 3 * CTX_PI/2) {
             start = ctx_atanf(w / h * ctx_tanf(start)) + CTX_PI;
           } else {
             start = ctx_atanf(w / h * ctx_tanf(start)) + CTX_PI*2;
           }
           if (stop <= CTX_PI/2) {
             stop = ctx_atanf(w / h * ctx_tanf(stop));
           } else if (stop > CTX_PI/2 && stop <= 3 * CTX_PI/2) {
             stop = ctx_atanf (w / h * ctx_tanf(stop)) + CTX_PI;
           } else {
             stop = ctx_atanf (w / h * ctx_tanf(stop)) + CTX_PI*2;
           }
           // Exceed the interval if necessary in order to preserve the size and
           // orientation of the arc.
           if (start > stop) {
             stop += CTX_PI * 2;
           }
             // Create curves
             float epsilon = 0.00001f; // Smallest visible angle on displays up to 4K.
             float arcToDraw = 0;
             float curves[4][8]={{0.0f,}};
             int n_curves = 0;
             while(stop - start > epsilon) {
               arcToDraw = ctx_minf(stop - start, CTX_PI/2);
               {
                 //float cx0, cy0, cx1, cy1, cx2, cy2, x, y;
                 acuteArcToBezier(start, arcToDraw, 
                                 &curves[n_curves][0], &curves[n_curves][1],
                                 &curves[n_curves][2], &curves[n_curves][3],
                                 &curves[n_curves][4], &curves[n_curves][5],
                                 &curves[n_curves][6], &curves[n_curves][7]);
                 n_curves++;
               }
             start += arcToDraw;
           }

             float rx = w / 2.0f;
             float ry = h / 2.0f;
             ctx_pdf_print2f(x + rx * curves[0][0], y + ry * curves[0][1]);
             ctx_pdf_print("m\n");
             for (int i = 0; i < n_curves; i++)
             {
               ctx_pdf_curve_to (ctx, x + rx * curves[i][2], y + ry * curves[i][3],
                                  x + rx * curves[i][4], y + ry * curves[i][5],
                                  x + rx * curves[i][6], y + ry * curves[i][7]);
             }
        }
#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));
        if (ctx_arg_float (5) == 1)
          pdf_arc (cr, ctx_arg_float (0), ctx_arg_float (1),
                     ctx_arg_float (2), ctx_arg_float (3),
                     ctx_arg_float (4) );
        else
          pdf_arc_negative (cr, ctx_arg_float (0), ctx_arg_float (1),
                              ctx_arg_float (2), ctx_arg_float (3),
                              ctx_arg_float (4) );
#endif
        break;

      case CTX_COLOR:
        {
        int space =  ((int) ctx_arg_float (0)) & 511;
        switch (space) // XXX remove 511 after stroke source is complete
        {
           case CTX_RGBA:
           case CTX_DRGBA:
             ctx_pdf_set_opacity (pdf, c->rgba.a);
             /*FALLTHROUGH*/
           case CTX_RGB:
              if (space == CTX_RGB || space == CTX_DRGB)
                ctx_pdf_set_opacity (pdf, 1.0);
             ctx_pdf_print3f(c->rgba.r, c->rgba.g, c->rgba.b);
             ctx_pdf_print("rg ");
             ctx_pdf_print3f(c->rgba.r, c->rgba.g, c->rgba.b);
             ctx_pdf_print("RG\n");
             break;
           case CTX_CMYKA:
           case CTX_DCMYKA:
             ctx_pdf_set_opacity (pdf, c->cmyka.a);
               /*FALLTHROUGH*/
           case CTX_CMYK:
           case CTX_DCMYK:
              if (space == CTX_CMYK || space == CTX_DCMYK)
                ctx_pdf_set_opacity (pdf, 1.0);
              ctx_pdf_print4f(c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k);
              ctx_pdf_print("k ");
              ctx_pdf_print4f(c->cmyka.c, c->cmyka.m, c->cmyka.y, c->cmyka.k);
              ctx_pdf_print("K ");
              break;
           case CTX_GRAYA:
             ctx_pdf_set_opacity (pdf, c->graya.a);
               /*FALLTHROUGH*/
           case CTX_GRAY:
              if (space == CTX_GRAY)
                ctx_pdf_set_opacity (pdf, 1.0);
              ctx_pdf_print1f(c->graya.g);
              ctx_pdf_print("g ");
              ctx_pdf_print1f(c->graya.g);
              ctx_pdf_print("G\n");
              break;
            }
        }
        break;

      case CTX_SET_RGBA_U8:
        ctx_pdf_printf("/G%i gs\n", ctx_arg_u8(3)*10/255);
        ctx_pdf_printf("%f %f %f RG\n",
                               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_pdf_printf("%f %f %f rg\n",
                               ctx_u8_to_float (ctx_arg_u8 (0) ),
                               ctx_u8_to_float (ctx_arg_u8 (1) ),
                               ctx_u8_to_float (ctx_arg_u8 (2) ));
        break;

      case CTX_RECTANGLE:
      case CTX_ROUND_RECTANGLE:
        ctx_pdf_print4f(c->rectangle.x, c->rectangle.y,
                        c->rectangle.width, c->rectangle.height);
        ctx_pdf_print("re\n");
        break;

      case CTX_SET_PIXEL:
#if 0
        pdf_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) ) );
        pdf_rectangle (cr, ctx_arg_u16 (2), ctx_arg_u16 (3), 1, 1);
        pdf_fill (cr);
#endif
        break;

      case CTX_FILL:
        if (pdf->preserve)
        {
          preserved = ctx_current_path (ctx);
          pdf->preserve = 0;
        }
        ctx_pdf_print ("f\n");
        break;

      case CTX_TRANSLATE:
         ctx_pdf_apply_transform (ctx, 1.f, 0.f, 0.f, 1.f, c->f.a0, c->f.a1); 
         break;
      case CTX_SCALE:     
         ctx_pdf_apply_transform (ctx, c->f.a0, 0.f, 0.f, c->f.a1, 0.f, 0.f); 
         break;
      case CTX_ROTATE:
         ctx_pdf_apply_transform (ctx,
             ctx_cosf (-c->f.a0), ctx_sinf (-c->f.a0),
             -ctx_sinf (-c->f.a0), ctx_cosf (-c->f.a0),
             0.f, 0.f);
         break;

      case CTX_APPLY_TRANSFORM:
        ctx_pdf_apply_transform (ctx, c->f.a0, c->f.a1,
                                      c->f.a3, c->f.a4,
                                      c->f.a4, c->f.a7);
        break;

      case CTX_STROKE:
        if (pdf->preserve)
        {
          preserved = ctx_current_path (ctx);
          ctx_pdf_print("S\n");
          pdf->preserve = 0;
        }
        else
        {
          ctx_pdf_print("S\n");
        }
        break;

      case CTX_CLIP:
        if (pdf->preserve)
        {
          preserved = ctx_current_path (ctx);
          ctx_pdf_print("W\n");
          pdf->preserve = 0;
        }
        else
        {
          ctx_pdf_print("W\n");
        }
        break;
      case CTX_BEGIN_PATH:  ctx_pdf_print("n\n"); break;
      case CTX_CLOSE_PATH:  ctx_pdf_print("h\n"); break;
      case CTX_SAVE:        ctx_pdf_print("q\n"); break;
      case CTX_RESTORE:     ctx_pdf_print("Q\n"); break;
      case CTX_FONT_SIZE:   ctx_pdf_printf("/F%i %f Tf\n", pdf->font, state->gstate.font_size); break;
      case CTX_MITER_LIMIT: ctx_pdf_printf("%f M\n", ctx_arg_float (0)); break;
      case CTX_LINE_CAP:    ctx_pdf_printf("%i J\n", ctx_arg_u8 (0)); break;
      case CTX_LINE_JOIN:   ctx_pdf_printf("%i j\n", ctx_arg_u8 (0)); break;

      case CTX_FONT:
        {
          const char *str = ctx_arg_string ();
          if (!strcmp (str, "Helvetica"))             pdf->font = CTX_PDF_HELVETICA;
          if (!strcmp (str, "Helvetica Bold"))        pdf->font = CTX_PDF_HELVETICA_BOLD;
          if (!strcmp (str, "Helvetica Italic"))      pdf->font = CTX_PDF_HELVETICA_ITALIC;
          if (!strcmp (str, "Helvetica BoldItalic"))  pdf->font = CTX_PDF_HELVETICA_BOLD_ITALIC;
          if (!strcmp (str, "Helvetica Bold Italic")) pdf->font = CTX_PDF_HELVETICA_BOLD_ITALIC;
          if (!strcmp (str, "Symbol"))                pdf->font = CTX_PDF_SYMBOL;
          if (!strcmp (str, "Zapf Dingbats"))         pdf->font = CTX_PDF_ZAPF_DING_BATS;
          if (!strcmp (str, "ZapfDingbats"))          pdf->font = CTX_PDF_ZAPF_DING_BATS;
          if (!strcmp (str, "Times"))                 pdf->font = CTX_PDF_TIMES;
          if (!strcmp (str, "Times Italic"))          pdf->font = CTX_PDF_TIMES_ITALIC;
          if (!strcmp (str, "Times Bold"))            pdf->font = CTX_PDF_TIMES_BOLD;
          if (!strcmp (str, "Times Bold Italic"))     pdf->font = CTX_PDF_TIMES_BOLD_ITALIC;
          if (!strcmp (str, "Times BoldItalic"))      pdf->font = CTX_PDF_TIMES_BOLD_ITALIC;
          if (!strcmp (str, "Courier"))               pdf->font = CTX_PDF_COURIER;
          if (!strcmp (str, "Courier Bold"))          pdf->font = CTX_PDF_COURIER_BOLD;
          if (!strcmp (str, "Courier Italic"))        pdf->font = CTX_PDF_COURIER_ITALIC;
          if (!strcmp (str, "Courier Bold Italic"))   pdf->font = CTX_PDF_COURIER_BOLD_ITALIC;
          if (!strcmp (str, "Courier BoldItalic"))    pdf->font = CTX_PDF_COURIER_BOLD_ITALIC;
        }
        ctx_pdf_printf("/F%i %f Tf\n", pdf->font, state->gstate.font_size);
        break;


#if 0
      case CTX_BLEND_MODE:
        {
        }
        break;
      case CTX_COMPOSITING_MODE:
        {
          int pdf_val = CAIRO_OPERATOR_OVER;
          switch (ctx_arg_u8 (0) )
            {
              case CTX_COMPOSITE_SOURCE_OVER:
                pdf_val = CAIRO_OPERATOR_OVER;
                break;
              case CTX_COMPOSITE_COPY:
                pdf_val = CAIRO_OPERATOR_SOURCE;
                break;
            }
          pdf_set_operator (cr, pdf_val);
        }
        break;
#endif
      case CTX_LINEAR_GRADIENT: { ctx_pdf_print("1 0 0 rg\n"); } break;
      case CTX_RADIAL_GRADIENT: { ctx_pdf_print("0 2 0 rg\n"); } break;
      case CTX_TEXTURE:
      case CTX_DEFINE_TEXTURE: { ctx_pdf_print("0 0 1 rg\n"); } break;
      case CTX_GRADIENT_STOP:
        // we set the color so we might get a flavour of the gradient
         ctx_pdf_printf("%f %f %f rg\n", ctx_arg_u8(4)/255.0f,
                                         ctx_arg_u8(4+1)/255.0f,
                                         ctx_arg_u8(4+2)/255.0f);
        break;
      case CTX_TEXT:
        ctx_pdf_print("1 0 0 -1 ");
        ctx_pdf_print2f(state->x, state->y);
        ctx_pdf_print("Tm ");
        if (0)
        {
          char *encoded = ctx_utf8_to_mac_roman ((uint8_t*)ctx_arg_string ());
          ctx_pdf_printf ("(%s) Tj\n", encoded);
          ctx_free (encoded);
        }
        else
        {
          char *encoded = ctx_utf8_to_windows_1252 ((uint8_t*)ctx_arg_string ());
          ctx_pdf_printf ("(%s) Tj\n", encoded);
          ctx_free (encoded);
        }
        break;
      case CTX_CONT:
      case CTX_EDGE:
      case CTX_DATA:
      case CTX_DATA_REV:
      case CTX_END_FRAME:
        break;
      case CTX_VIEW_BOX:
        pdf->page_size[0] = ctx_arg_float(0);
        pdf->page_size[1] = ctx_arg_float(1);
        pdf->page_size[2] = ctx_arg_float(2);
        pdf->page_size[3] = ctx_arg_float(3);
        ctx_set_size (ctx, 
          ctx_arg_float(2),
          ctx_arg_float(3));
        break;
    }
  ctx_interpret_pos_bare (&pdf->state, entry, pdf);
#if CTX_CURRENT_PATH
  ctx_update_current_path (ctx, entry);
#endif

  if (preserved)
  {
    CtxIterator iterator;
    CtxCommand *command;
    
    ctx_iterator_init (&iterator, preserved, 0, CTX_ITERATOR_EXPAND_BITPACK);
    while ( (command = ctx_iterator_next (&iterator) ) )
      { ctx_pdf_process (ctx, command); }
    ctx_free (preserved);
  }
}

void ctx_pdf_destroy (CtxPDF *pdf)
{
  FILE *f = fopen (pdf->path, "w");
  char buf[12];

  pdf_end_page (pdf);

  int outlines=pdf_add_object (pdf);
  ctx_pdf_print("<</Type/Outlines/Count 0>>");
  int catalog=pdf_add_object (pdf);
  ctx_pdf_printf("<</Type/Catalog/Outlines %i 0 R/Pages %i 0 R>>", outlines, pdf->pages);


  // patch-back the value in pages earlier
  snprintf (buf, 11, "% 10d", pdf->page_count);
  memcpy   (&pdf->document->str[pdf->page_count_offset], buf, 10);

  // patch-back the value in pages earlier
  int kids = pdf_add_object (pdf); 
  snprintf (buf, 11, "% 10d", kids);
  memcpy   (&pdf->document->str[pdf->kids_offset], buf, 10);

  ctx_pdf_print ("[");
  for (int page_no =1; page_no <= pdf->page_count; page_no++)
    ctx_pdf_printf ("%i 0 R ", pdf->page_objs[page_no]);
  ctx_pdf_print ("]");
  pdf_end_object(pdf);

  int start_xref = pdf->document->length;
  ctx_pdf_printf ("xref\n0 %i\n", pdf->objs + 1);
  ctx_pdf_print ("0000000000 65535 f\n");
        for(int i = 1; i <= pdf->objs; i++)
        {
          ctx_pdf_printf ("%010d 65535 n\n", pdf->xref[i]);
        }
  ctx_pdf_printf ("trailer\n\n"
"<</Root %i 0 R\n/Size %i>>\n"
"startxref\n"
"%d\n"
"%%%%EOF\n", catalog, pdf->objs+1,
       start_xref);

  fwrite (pdf->document->str, pdf->document->length, 1, f);
  ctx_string_free (pdf->document, 1);
  ctx_free (pdf);
}

Ctx *
ctx_new_pdf (const char *path, float width, float height)
{
  Ctx *ctx = _ctx_new_drawlist (width, height);
  CtxPDF *pdf = ctx_calloc(sizeof(CtxPDF),1);
  CtxBackend *backend  = (CtxBackend*)pdf;
  if (width <= 0) width = 595;
  if (width <= 0) height = 842;

  pdf->width = width;
  pdf->height = height;

  backend->type = CTX_BACKEND_PDF;
  backend->destroy = (void*)ctx_pdf_destroy;
  backend->process = ctx_pdf_process;
  backend->ctx     = ctx;
  pdf->document    = ctx_string_new("");

  pdf->path = ctx_strdup (path);
  ctx_state_init (&pdf->state);
  ctx_set_backend (ctx, (void*)pdf);
  ctx_pdf_print ("%PDF-1.4\n%ÆØÅ\n");
  //ctx_pdf_printf ("%%PDF-1.4\n%%%c%c%c%c\n", 0xe2, 0xe3, 0xcf, 0xd3);
  pdf->pages=pdf_add_object (pdf); // 1
  pdf->font = CTX_PDF_HELVETICA;
  //pdf->encoding = "/MacRomanEncoding";
  pdf->encoding = "/WinAnsiEncoding";

  ctx_pdf_print ("<</Kids ");
  pdf->kids_offset = pdf->document->length;
  ctx_pdf_print ("XXXXXXXXXX 0 R/Type/Pages/Count ");
  pdf->page_count_offset = pdf->document->length;
  ctx_pdf_print ("XXXXXXXXXX");
  ctx_pdf_print (">>");

  { // shared fontmap for all pages
    // good enough without TTF fonts
    int font[16];

    char *font_names[]={"","Times","Helvetica","Courier","Symbol",
"Times-Bold", "Helvetica-Bold", "Courier-Bold",
"ZapfDingbats", "Times-Italic", "Helvetica-Oblique",
"Courier-Oblique", "Times-BoldItalic", "Helvetica-BoldItalic", "Courier-BoldItalic"
    };

    for (int font_no = 1; font_no <= 14; font_no++)
    {
      font[font_no]= pdf_add_object (pdf);
      ctx_pdf_printf ("<</Name/F%i/Subtype/Type1/Type/Font/BaseFont /%s /Encoding %s>>",
                      font_no, font_names[font_no], pdf->encoding);
    }

    pdf->font_map=pdf_add_object(pdf);
    ctx_pdf_print ("<<");
    for (int font_no = 1; font_no <= 14; font_no++)
      ctx_pdf_printf ("/F%i %i 0 R", font_no, font[font_no]);
    ctx_pdf_print (">>");
  }

  pdf->page_size[0] = 0;
  pdf->page_size[1] = 0;
  pdf->page_size[2] = pdf->width;
  pdf->page_size[3] = pdf->height;

  pdf_start_page (pdf);

  return ctx;
}

void
ctx_render_pdf (Ctx *ctx, const char *path)
{
  Ctx *pdf = ctx_new_pdf (path, 0, 0);
  CtxIterator iterator;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0, CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    { ctx_pdf_process (pdf, command); }
  ctx_destroy (pdf);
}




static char *ctx_utf8_to_mac_roman (const uint8_t *string)
{
  CtxString *ret = ctx_string_new ("");
  if (*string)
  for (const uint8_t *utf8 = (uint8_t*)string; utf8[0]; utf8 = *utf8?(uint8_t*)ctx_utf8_skip ((char*)utf8, 1):(utf8+1))
  {
    uint8_t copy[5];

    memcpy (copy, utf8, ctx_utf8_len (utf8[0]));
    copy[ctx_utf8_len (utf8[0])]=0;
    if (copy[0] <=127)
    {
      ctx_string_append_byte (ret, copy[0]);
    }
    else
    {
      int code = 128;
      /* it would be better to to this comparison on a unicode table,
       * but this was easier to create
       */
#define C(a) \
      if (!strcmp ((char*)&copy[0], a)) { ctx_string_append_byte (ret, code); continue; }; code++
      C("Ä");C("Å");C("Ç");C("É");C("Ñ");C("Ö");C("Ü");C("á");C("à");C("â");C("ä");C("ã");C("å");C("ç");C("é");C("è");
      C("ê");C("ë");C("í");C("ì");C("î");C("ï");C("ñ");C("ó");C("ò");C("ô");C("ö");C("õ");C("ú");C("ù");C("û");C("ü");
      C("†");C("°");C("¢");C("£");C("§");C("•");C("¶");C("ß");C("®");C("©");C("™");C("´");C("¨");C("≠");C("Æ");C("Ø");
      C("∞");C("±");C("≤");C("≥");C("¥");C("µ");C("∂");C("∑");C("∏");C("π");C("∫");C("ª");C("º");C("Ω");C("æ");C("ø");
      C("¿");C("¡");C("¬");C("√");C("ƒ");C("≈");C("∆");C("«");C("»");C("…");C(" ");C("À");C("Ã");C("Õ");C("Œ");C("œ");
      C("–");C("—");C("“");C("”");C("‘");C("’");C("÷");C("◊");C("ÿ");C("Ÿ");C("⁄");C("€");C("‹");C("›");C("ﬁ");C("ﬂ");
      C("‡");C("·");C("‚");C("„");C("‰");C("Â");C("Ê");C("Á");C("Ë");C("È");C("Í");C("Î");C("Ï");C("Ì");C("Ó");C("Ô");
      C("?");C("Ò");C("Ú");C("Û");C("Ù");C("ı");C("ˆ");C("˜");C("¯");C("˘");C("˙");C("˚");C("¸");C("˝");C("˛");C("ˇ");
      ctx_string_append_byte (ret, '?');
    }
  }

  return ctx_string_dissolve (ret);
}

static char *ctx_utf8_to_windows_1252 (const uint8_t *string)
{
  CtxString *ret = ctx_string_new ("");
  if (*string)
  for (const uint8_t *utf8 = (uint8_t*)string; utf8[0]; utf8 = *utf8?(uint8_t*)ctx_utf8_skip ((char*)utf8, 1):(utf8+1))
  {
    uint8_t copy[5];

    memcpy (copy, utf8, ctx_utf8_len (utf8[0]));
    copy[ctx_utf8_len (utf8[0])]=0;
    if (copy[0] == '(' || copy[0] == ')')
    {
      ctx_string_append_byte (ret, '\\');
      ctx_string_append_byte (ret, copy[0]);
    }
    else if (copy[0] <=127)
    {
      ctx_string_append_byte (ret, copy[0]);
    }
    else
    {
      int code = 128;
      /* it would be better to to this comparison on a unicode table,
       * but this was easier to create
       */
C("€");C(" ");C("‚");C("ƒ");C("„");C("…");C("†");C("‡");C("ˆ");C("‰");C("Š");C("‹");C("Œ");C(" ");C("Ž");C(" ");
C(" ");C("‘");C("’");C("“");C("”");C("•");C("–");C("—");C("˜");C("™");C("š");C("›");C("œ");C(" ");C("ž");C("Ÿ");
C(" ");C("¡");C("¢");C("£");C("¤");C("¥");C("¦");C("§");C("¨");C("©");C("ª");C("«");C("¬");C("-");C("®");C("¯");
C("°");C("±");C("²");C("³");C("´");C("µ");C("¶");C("·");C("¸");C("¹");C("º");C("»");C("¼");C("½");C("¾");C("¿");
C("À");C("Á");C("Â");C("Ã");C("Ä");C("Å");C("Æ");C("Ç");C("È");C("É");C("Ê");C("Ë");C("Ì");C("Í");C("Î");C("Ï");
C("Ð");C("Ñ");C("Ò");C("Ó");C("Ô");C("Õ");C("Ö");C("×");C("Ø");C("Ù");C("Ú");C("Û");C("Ü");C("Ý");C("Þ");C("ß");
C("à");C("á");C("â");C("ã");C("ä");C("å");C("æ");C("ç");C("è");C("é");C("ê");C("ë");C("ì");C("í");C("î");C("ï");
C("ð");C("ñ");C("ò");C("ó");C("ô");C("õ");C("ö");C("÷");C("ø");C("ù");C("ú");C("û");C("ü");C("ý");C("þ");C("ÿ");
#undef C
      ctx_string_append_byte (ret, '?');
    }
  }
  return ctx_string_dissolve (ret);
}



#endif

#if CTX_CAIRO

typedef struct _CtxCairo CtxCairo;
struct
  _CtxCairo
{
  CtxBackend        backend;
  cairo_t          *cr;
  cairo_pattern_t  *pat;
  cairo_surface_t  *image;
  int               preserve;

  // maintain separate fill and stroke state - even though the more limited use of ctx
  // then suffers?
  //
};

static void
ctx_cairo_process (Ctx *ctx, CtxCommand *c)
{
  CtxCairo *ctx_cairo = (void*)ctx->backend;
  CtxEntry *entry = (CtxEntry *) &c->entry;

#if CTX_CURRENT_PATH
  ctx_update_current_path (ctx, entry);
#endif

  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;

      case CTX_COLOR:
      {
        int space =  ((int) ctx_arg_float (0)) & 511;
        switch (space) // XXX remove 511 after stroke source is complete
        {
           case CTX_RGBA:
           case CTX_DRGBA:
             cairo_set_source_rgba (cr, c->rgba.r, c->rgba.g, c->rgba.b, c->rgba.a);
             break;
           case CTX_RGB:
             cairo_set_source_rgba (cr, c->rgba.r, c->rgba.g, c->rgba.b, 1.0f);
             break;
           case CTX_CMYKA:
           case CTX_DCMYKA:
           case CTX_CMYK:
           case CTX_DCMYK:
              break;
           case CTX_GRAYA:
             cairo_set_source_rgba (cr, c->graya.g, c->graya.g, c->graya.g, c->graya.a);
             break;
               /*FALLTHROUGH*/
           case CTX_GRAY:
             cairo_set_source_rgba (cr, c->graya.g, c->graya.g, c->graya.g, 1.0f);
             break;
            }
        }
        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;
      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);
        }
        break;
      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_END_FRAME:
        break;
    }
  ctx_process (ctx_cairo->backend.ctx, entry);
}

void ctx_cairo_destroy (CtxCairo *ctx_cairo)
{
  if (ctx_cairo->pat)
    { cairo_pattern_destroy (ctx_cairo->pat); }
  if (ctx_cairo->image)
    { cairo_surface_destroy (ctx_cairo->image); }
  ctx_free (ctx_cairo);
}

void
ctx_render_cairo (Ctx *ctx, cairo_t *cr)
{
  CtxCairo    ctx_cairo; /* on-stack backend */
  CtxBackend *backend = (CtxBackend*)&ctx_cairo;
  CtxIterator iterator;
  CtxCommand *command;
  ctx_cairo.cr = cr;
  backend->process = ctx_cairo_process;
  backend->ctx = ctx;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);
  while ( (command = ctx_iterator_next (&iterator) ) )
    { ctx_cairo_process (ctx, command); }
}

Ctx *
ctx_new_for_cairo (cairo_t *cr)
{
  Ctx *ctx = _ctx_new_drawlist (640, 480);
  CtxCairo *ctx_cairo = ctx_calloc(sizeof(CtxCairo),1);
  CtxBackend *backend  = (CtxBackend*)ctx_cairo;
  backend->type = CTX_BACKEND_CAIRO;
  backend->destroy = (void*)ctx_cairo_destroy;
  backend->process = ctx_cairo_process;
  backend->ctx = ctx;
  ctx_cairo->cr = cr;
  ctx_set_backend (ctx, (void*)ctx_cairo);
  return ctx;
}

#endif

#if CTX_TERMINAL_EVENTS
int ctx_frame_ack = -1;
#if CTX_FORMATTER

#if 0
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;
} 
#endif

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..
 */
#if 0
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 = ctx_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 = ctx_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 = ctx_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
                ctx_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);
        }
      }
    }
    ctx_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;
}
#endif

#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 " start_frame "
#define CTX_END_STRING   "\nX"  // or "\ndone"
#define CTX_END_STRING2  "\n"

static char *prev_frame_contents = NULL;
static int   prev_frame_len = 0;

static int ctx_native_events = 1;

static void ctx_ctx_end_frame (Ctx *ctx)
{
  CtxCtx *ctxctx = (CtxCtx*)ctx->backend;
#if 0
  FILE *debug = fopen ("/tmp/ctx-debug", "a");
  fprintf (debug, "------\n");
#endif

  if (ctx_native_events)
    fprintf (stdout, "\033[?201h");
  fprintf (stdout, "\033[H\033[?25l\033[?200h");
#if 1
  fprintf (stdout, CTX_START_STRING);
  ctx_render_stream (ctxctx->backend.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 = ctx_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);
    ctx_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 = ctx_strdup (cur_frame_contents);
//    encoded_len = ctx_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
      ctx_free (encoded);
    }
    else
    {
      fwrite (cur_frame_contents, cur_frame_len, 1, stdout);
    }

    if (prev_frame_contents)
      ctx_free (prev_frame_contents);
    prev_frame_contents = cur_frame_contents;
    prev_frame_len = cur_frame_len;
  }
#endif
  fprintf (stdout, CTX_END_STRING2);
#if 0
    fclose (debug);
#endif

#if CTX_SYNC_FRAMES
  fprintf (stdout, "\033[5n");
  fflush (stdout);

  ctx_frame_ack = 0;
  do {
     ctx_consume_events (ctxctx->backend.ctx);
  } while (ctx_frame_ack != 1);
#else
  fflush (stdout);
#endif
}

void ctx_ctx_destroy (CtxCtx *ctx)
{
  nc_at_exit ();
  ctx_free (ctx);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

void ctx_ctx_consume_events (Ctx *ctx)
{
  //int ix, iy;
  CtxCtx *ctxctx = (CtxCtx*)ctx->backend;
  const char *event = NULL;
#if CTX_AUDIO
  ctx_ctx_pcm (ctx);
#endif
  assert (ctx_native_events);

#if 1
    { /* XXX : this is a work-around for signals not working properly, we are polling the
         size with an ioctl per consume-events
         */
      struct winsize ws;
      ioctl(0,TIOCGWINSZ,&ws);
      ctxctx->cols = ws.ws_col;
      ctxctx->rows = ws.ws_row;
      ctx_set_size (ctx, ws.ws_xpixel, ws.ws_ypixel);
    }
#endif
    //char *cmd = ctx_strdup_printf ("touch /tmp/ctx-%ix%i", ctxctx->width, ctxctx->height);
    //system (cmd);
    //ctx_free (cmd);

  if (ctx_native_events)
    do {

      float x = 0, y = 0;
      int b = 0;
      char event_type[128]="";
      event = ctx_native_get_event (ctx, 1000/120);

      if (event)
      {
      sscanf (event, "%s %f %f %i", event_type, &x, &y, &b);
      if (!strcmp (event_type, "idle"))
      {
              event = NULL;
      }
      else if (!strcmp (event_type, "pp"))
      {
        ctx_pointer_press (ctx, x, y, b, 0);
      }
      else if (!strcmp (event_type, "pd")||
               !strcmp (event_type, "pm"))
      {
        ctx_pointer_motion (ctx, x, y, b, 0);
      }
      else if (!strcmp (event_type, "pr"))
      {
        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, "\033[H\033[2J\033[?25l");
        ctxctx->cols = ctx_terminal_cols ();
        ctxctx->rows = ctx_terminal_rows ();

        //system ("touch /tmp/ctx-abc");

        ctx_set_size (ctx, ctx_terminal_width(), ctx_terminal_height());

        if (prev_frame_contents)
          ctx_free (prev_frame_contents);
        prev_frame_contents = NULL;
        prev_frame_len = 0;
        ctx_queue_draw (ctx);

      //   ctx_key_press(ctx,0,"size-changed",0);
      }
      else if (!strcmp (event_type, "keyup"))
      {
        char buf[4]={ (int)x, 0 };
        ctx_key_up (ctx, (int)x, buf, 0);
      }
      else if (!strcmp (event_type, "keydown"))
      {
        char buf[4]={ (int)x, 0 };
        ctx_key_down (ctx, (int)x, buf, 0);
      }
      else
      {
        ctx_key_press (ctx, 0, event, 0);
      }
      }
    } while (event);
}

Ctx *ctx_new_ctx (int width, int height)
{
  float font_size = 12.0;
  Ctx *ctx = _ctx_new_drawlist (width, height);
  CtxCtx *ctxctx = (CtxCtx*)ctx_calloc (sizeof (CtxCtx), 1);
  CtxBackend *backend = (CtxBackend*)ctxctx;
  fprintf (stdout, "\033[?1049h");
  fflush (stdout);
  //fprintf (stderr, "\033[H");
  //fprintf (stderr, "\033[2J");
  ctx_native_events = 1;
  if (width <= 0 || height <= 0)
  {
    ctxctx->cols = ctx_terminal_cols ();
    ctxctx->rows = ctx_terminal_rows ();
    width  = ctx->width  = ctxctx->width = ctx_terminal_width ();
    height = ctx->height = ctxctx->width = ctx_terminal_height ();
    font_size = height / ctxctx->rows;
    ctx_font_size (ctx, font_size);
  }
  else
  {
    ctxctx->width = ctx->width  = width;
    ctxctx->height = ctx->height = height;
    ctxctx->cols   = width / 80;
    ctxctx->rows   = height / 24;
  }
  backend->ctx = ctx;
  if (!ctx_native_events)
    _ctx_mouse (ctx, NC_MOUSE_DRAG);
  backend->end_frame = ctx_ctx_end_frame;
  backend->type = CTX_BACKEND_CTX;
  backend->destroy = (void(*)(void *))ctx_ctx_destroy;
  backend->process = (void*)ctx_drawlist_process;
  backend->consume_events = ctx_ctx_consume_events;
  ctx_set_backend (ctx, ctxctx);
  ctx_set_size (ctx, width, height);
  return ctx;
}

void ctx_ctx_pcm (Ctx *ctx);


#endif
#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_destroy (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)
  {
    ctx_free (tiled->pixels);
    tiled->pixels = NULL;
    for (int i = 0 ; i < _ctx_max_threads; i++)
    {
      if (tiled->host[i])
        ctx_destroy (tiled->host[i]);
      tiled->host[i]=NULL;
    }
    ctx_destroy (tiled->ctx_copy);
  }

  // leak?
}
static unsigned char *sdl_icc = NULL;
static long sdl_icc_length = 0;

static void ctx_tiled_end_frame (Ctx *ctx)
{
  CtxTiled *tiled = (CtxTiled*)ctx->backend;
  mtx_lock (&tiled->mtx);
  if (tiled->shown_frame == tiled->render_frame)
  {
    int dirty_tiles = 0;
    ctx_set_drawlist (tiled->ctx_copy, &tiled->backend.ctx->drawlist.entries[0],
                                           tiled->backend.ctx->drawlist.count * 9);
    if (_ctx_enable_hash_cache)
    {
      Ctx *hasher = ctx_hasher_new (tiled->width, tiled->height,
                        CTX_HASH_COLS, CTX_HASH_ROWS, &tiled->ctx_copy->drawlist);
      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++)
        {
          uint32_t new_hash = ctx_hasher_get_hash (hasher, col, row);
          if (new_hash && new_hash != tiled->hashes[(row * CTX_HASH_COLS + col)])
          {
            tiled->hashes[(row * CTX_HASH_COLS +  col)] = new_hash;
            tiled->tile_affinity[row * CTX_HASH_COLS + col] = 1;
            dirty_tiles++;
          }
          else
          {
            tiled->tile_affinity[row * CTX_HASH_COLS + col] = -1;
          }
        }
      }

      ctx_destroy (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->backend;
            int swap_red_green = ((CtxRasterizer*)(host->backend))->swap_red_green;
            ctx_rasterizer_init (rasterizer,
                                 host, tiled->backend.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->backend))->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
    cnd_broadcast (&tiled->cond);
  }
  else
  {
    fprintf (stderr, "{drip}");
  }
  mtx_unlock (&tiled->mtx);
  ctx_drawlist_clear (ctx);
}

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->backend;

            int active_mask = 1 << hno;

#if CTX_TILED_MERGE_HORIZONTAL_NEIGHBORS
            while (col + 1 < CTX_HASH_COLS &&
                   tiled->tile_affinity[hno+1] == no)
            {
              width += tiled->width / CTX_HASH_COLS;
              col++;
              hno++;
              active_mask |= 1 << hno;
            }
#endif
            int swap_red_green = rasterizer->swap_red_green;
            int compressed_scanlines = 0;
#if CTX_ENABLE_CBRLE
            // compressed_scanlines = 1;
#endif
            ctx_rasterizer_init (rasterizer,
                                 host, tiled->backend.ctx, &host->state,
                                 &tiled->pixels[tiled->width * 4 * y0 + x0 * 4],
                                 0, 0, width, height,
                                 tiled->width*4, compressed_scanlines?CTX_FORMAT_CBRLE_32:CTX_FORMAT_BGRA8,
                                 tiled->antialias);
            ((CtxRasterizer*)(host->backend))->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_masked (tiled->ctx_copy, host, active_mask);

#if CTX_ENABLE_CBRLE
            if (compressed_scanlines)
            for (int i = 0; i < height; i ++)
            {
              uint8_t temp[width*4];
              _ctx_CBRLE_decompress (&tiled->pixels[tiled->width * 4 * (y0+i) + x0 * 4],
                                      temp,
                                      width,
                                      width * 4, 0, width);
              memcpy (&tiled->pixels[tiled->width * 4 * (y0+i) + x0 * 4], temp, sizeof (temp));
            }
#endif
          }
        }
      tiled->rendered_frame[no] = tiled->render_frame;
    }
  }
  tiled->thread_quit++; // need atomic?
}


static int       ctx_tiled_cursor_drawn   = 0;
static int       ctx_tiled_cursor_drawn_x = 0;
static int       ctx_tiled_cursor_drawn_y = 0;
static CtxCursor ctx_tiled_cursor_drawn_shape = 0;


#define CTX_FB_HIDE_CURSOR_FRAMES 200

static int ctx_tiled_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.0f/180 * (float)(M_PI);
        float cos_theta;
        float sin_theta;

        if ((shape == CTX_CURSOR_RESIZE_SW) ||
            (shape == CTX_CURSOR_RESIZE_NE))
        {
          theta = -theta;
          cos_theta = ctx_cosf (theta);
          sin_theta = ctx_sinf (theta);
        }
        else
        {
          cos_theta = ctx_cosf (theta);
          sin_theta = ctx_sinf (theta);
        }
        int rot_x = (int)(x * cos_theta - y * sin_theta);
        int rot_y = (int)(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_tiled_undraw_cursor (CtxTiled *tiled)
{
    int cursor_size = ctx_height (tiled->backend.ctx) / 28;

    if (ctx_tiled_cursor_drawn)
    {
      int no = 0;
      int startx = -cursor_size;
      int starty = -cursor_size;
      if (ctx_tiled_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_tiled_cursor_drawn_x < tiled->width && y + ctx_tiled_cursor_drawn_y < tiled->height)
        {
          if (ctx_is_in_cursor (x, y, cursor_size, ctx_tiled_cursor_drawn_shape))
          {
            int o = ((ctx_tiled_cursor_drawn_y + y) * tiled->width + (ctx_tiled_cursor_drawn_x + x)) * 4;
            tiled->fb[o+0]^=0x88;
            tiled->fb[o+1]^=0x88;
            tiled->fb[o+2]^=0x88;
          }
        }
      }

    ctx_tiled_cursor_drawn = 0;
    }
}

static inline void ctx_tiled_draw_cursor (CtxTiled *tiled)
{
    int cursor_x = (int)ctx_pointer_x (tiled->backend.ctx);
    int cursor_y = (int)ctx_pointer_y (tiled->backend.ctx);
    int cursor_size = ctx_height (tiled->backend.ctx) / 28;
    CtxCursor cursor_shape = tiled->backend.ctx->cursor;
    int no = 0;

    if (cursor_x == ctx_tiled_cursor_drawn_x &&
        cursor_y == ctx_tiled_cursor_drawn_y &&
        cursor_shape == ctx_tiled_cursor_drawn_shape)
      ctx_tiled_cursor_same_pos ++;
    else
      ctx_tiled_cursor_same_pos = 0;

    if (ctx_tiled_cursor_same_pos >= CTX_FB_HIDE_CURSOR_FRAMES)
    {
      if (ctx_tiled_cursor_drawn)
        ctx_tiled_undraw_cursor (tiled);
      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_tiled_cursor_same_pos && ctx_tiled_cursor_drawn)
      return;

    ctx_tiled_undraw_cursor (tiled);

    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;
            tiled->fb[o+0]^=0x88;
            tiled->fb[o+1]^=0x88;
            tiled->fb[o+2]^=0x88;
          }
        }
      }
    ctx_tiled_cursor_drawn = 1;
    ctx_tiled_cursor_drawn_x = cursor_x;
    ctx_tiled_cursor_drawn_y = cursor_y;
    ctx_tiled_cursor_drawn_shape = cursor_shape;
}

#endif





#if CTX_TERMINAL_EVENTS
#if CTX_HEADLESS

#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>

static char *ctx_fb_clipboard = NULL;
static void ctx_headless_set_clipboard (Ctx *ctx, const char *text)
{
  if (ctx_fb_clipboard)
    ctx_free (ctx_fb_clipboard);
  ctx_fb_clipboard = NULL;
  if (text)
  {
    ctx_fb_clipboard = ctx_strdup (text);
  }
}

static char *ctx_headless_get_clipboard (Ctx *ctx)
{
  if (ctx_fb_clipboard) return ctx_strdup (ctx_fb_clipboard);
  return ctx_strdup ("");
}

static inline int ctx_headless_get_mice_fd (Ctx *ctx)
{
#if CTX_PTY
  //CtxHeadless *fb = (void*)ctx->backend;
  return _ctx_mice_fd;
#endif
}

typedef struct _CtxHeadless CtxHeadless;
struct _CtxHeadless
{
   CtxTiled tiled;
   int           key_balance;
   int           key_repeat;
   int           lctrl;
   int           lalt;
   int           rctrl;


   int          fb_fd;
   char        *fb_path;
   int          fb_bits;
   int          fb_bpp;
   int          fb_mapped_size;
   int          vt;
   cnd_t        cond;
   mtx_t        mtx;
   int          tty;
};

#if UINTPTR_MAX == 0xffFFffFF
  #define fbdrmuint_t uint32_t
#elif UINTPTR_MAX == 0xffFFffFFffFFffFF
  #define fbdrmuint_t uint64_t
#endif

static void ctx_headless_show_frame (CtxHeadless *fb, int block)
{
  CtxTiled *tiled = (void*)fb;
  if (tiled->shown_frame == tiled->render_frame)
  {
    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 (tiled->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;


       if (tiled->min_row == 100){
          pre_skip = 0;
          post_skip = 0;
       }
       else
       {
         tiled->min_row = 100;
         tiled->max_row = 0;
         tiled->min_col = 100;
         tiled->max_col = 0;
         {
           uint8_t *dst = tiled->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;
           }
         }
    }
    tiled->shown_frame = tiled->render_frame;
  }
}

void ctx_headless_consume_events (Ctx *ctx)
{
  CtxHeadless *fb = (void*)ctx->backend;
  ctx_headless_show_frame (fb, 0);
  event_check_pending (&fb->tiled);
}

inline static void ctx_headless_start_frame (Ctx *ctx)
{
  ctx_headless_show_frame ((CtxHeadless*)ctx->backend, 1);
}

void ctx_headless_destroy (CtxHeadless *fb)
{
  CtxTiled *tiled=(CtxTiled*)fb;

  if (tiled->fb)
  {
  ctx_free (tiled->fb); // it is not the tiled renderers responsibilty,
                    // since it might not be allocated this way
  tiled->fb = NULL;
  }
  //munmap (tiled->fb, fb->fb_mapped_size);
  //close (fb->fb_fd);
  //if (system("stty sane")){};
  ctx_tiled_destroy ((CtxTiled*)fb);
  //ctx_free (fb);
}

//static unsigned char *fb_icc = NULL;
//static long fb_icc_length = 0;

static CtxHeadless *ctx_headless = NULL;


Ctx *ctx_new_headless (int width, int height)
{
  if (width < 0 || height < 0)
  {
    width = 1920;
    height = 780;
  }
#if CTX_RASTERIZER
  CtxHeadless *fb = ctx_calloc (sizeof (CtxHeadless), 1);
  CtxBackend *backend = (CtxBackend*)fb;
  CtxTiled *tiled     = (CtxTiled*)fb;
  ctx_headless = fb;

  tiled->width = width;
  tiled->height = height;

  fb->fb_bits        = 32;
  fb->fb_bpp         = 4;
  fb->fb_mapped_size = width * height * 4;
#endif

  tiled->fb = ctx_calloc (fb->fb_mapped_size, 1);
  if (!tiled->fb)
    return NULL;
  tiled->pixels = ctx_calloc (fb->fb_mapped_size, 1);
  tiled->show_frame = (void*)ctx_headless_show_frame;


 // ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);
 //
 // not to be done for headless, we want sRGB thumbs - at least not device specific
 // perhaps rec2020 or similar?

  backend->type = CTX_BACKEND_HEADLESS;
  backend->ctx = _ctx_new_drawlist (width, height);
  backend->end_frame = ctx_tiled_end_frame;
  backend->process = (void*)ctx_drawlist_process;
  backend->start_frame = ctx_headless_start_frame;
  backend->destroy = (void*)ctx_headless_destroy;
  backend->set_clipboard = ctx_headless_set_clipboard;
  backend->get_clipboard = ctx_headless_get_clipboard;
  backend->consume_events = ctx_headless_consume_events;

  tiled->ctx_copy = ctx_new (width, height, "drawlist");
  tiled->width    = width;
  tiled->height   = height;

  ctx_set_backend (backend->ctx, fb);
  ctx_set_backend (tiled->ctx_copy, fb);
  ctx_set_texture_cache (tiled->ctx_copy, backend->ctx);

  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]->backend))->swap_red_green = 1;
    ctx_set_texture_source (tiled->host[i], backend->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

  tiled->vt_active = 1;

  return backend->ctx;
}
#endif
#endif

#if CTX_TERMINAL_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#if CTX_PTY
#include <sys/ioctl.h>
#endif
#include <signal.h>
#endif


#if CTX_KMS || CTX_FB

static int ctx_fb_single_buffer = 0;  // used with the framebuffer this
                               // causes flicker, but brings single
                               // threaded memory use <2mb 

static int ctx_fb_get_mice_fd (Ctx *ctx)
{
#if CTX_PTY
  //CtxFb *fb = (void*)ctx->backend;
  return _ctx_mice_fd;
#endif
}

static void ctx_fb_get_event_fds (Ctx *ctx, int *fd, int *count)
{
  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;
  }
}
#endif

#if CTX_FB

#ifdef __linux__
  #include <linux/fb.h>
  #include <linux/vt.h>
  #include <linux/kd.h>
#endif

#ifdef __NetBSD__
  typedef uint8_t unchar;
  typedef uint8_t u_char;
  typedef uint16_t ushort;
  typedef uint32_t u_int;
  typedef uint64_t u_long;
  #include <sys/param.h>
  #include <dev/wscons/wsdisplay_usl_io.h>
  #include <dev/wscons/wsconsio.h>
  #include <dev/wscons/wsksymdef.h>
#endif

  #include <sys/mman.h>

typedef struct _CtxFb CtxFb;
struct _CtxFb
{
   CtxTiled tiled;
   int           key_balance;
   int           key_repeat;
   int           lctrl;
   int           lalt;
   int           rctrl;


   int          fb_fd;
   char        *fb_path;
   int          fb_bits;
   int          fb_bpp;
   int          fb_mapped_size;
   int          vt;
   int          tty;
   cnd_t        cond;
   mtx_t        mtx;
#if __linux__
   struct       fb_var_screeninfo vinfo;
   struct       fb_fix_screeninfo finfo;
#endif
};

#if UINTPTR_MAX == 0xffFFffFF
  #define fbdrmuint_t uint32_t
#elif UINTPTR_MAX == 0xffFFffFFffFFffFF
  #define fbdrmuint_t uint64_t
#endif


static void ctx_fb_flip (CtxFb *fb)
{
#ifdef __linux__
  ioctl (fb->fb_fd, FBIOPAN_DISPLAY, &fb->vinfo);
#endif
}

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_tiled_draw_cursor (tiled);
      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 (tiled->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;


       if (tiled->min_row == 100){
          pre_skip = 0;
          post_skip = 0;
#ifdef __linux__
           __u32 dummy = 0;
          ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
#endif
          ctx_tiled_undraw_cursor (tiled);
       }
       else
       {

      tiled->min_row = 100;
      tiled->max_row = 0;
      tiled->min_col = 100;
      tiled->max_col = 0;
#ifdef __linux__
    {
     __u32 dummy = 0;
     ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
    }
#endif
     ctx_tiled_undraw_cursor (tiled);
     if (!ctx_fb_single_buffer)
     switch (fb->fb_bits)
     {
       case 32:
#if 1
         {
           uint8_t *dst = tiled->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*)tiled->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 = tiled->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 = tiled->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 = tiled->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 = tiled->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_tiled_cursor_drawn = 0;
    ctx_tiled_draw_cursor (tiled);
    ctx_fb_flip (fb);
    tiled->shown_frame = tiled->render_frame;
  }
}

void ctx_fb_consume_events (Ctx *ctx)
{
#if CTX_RAW_KB_EVENTS
  ctx_fb_global = ctx;
#endif
  CtxFb *fb = (void*)ctx->backend;
  ctx_fb_show_frame (fb, 0);
  event_check_pending (&fb->tiled);
}

inline static void ctx_fb_start_frame (Ctx *ctx)
{
  // show pending frame if any
  ctx_fb_show_frame ((CtxFb*)ctx->backend, 1);
}

void ctx_fb_destroy (CtxFb *fb)
{
  CtxTiled*tiled=(CtxTiled*)fb;

//#ifdef __linux__
  ioctl (0, KDSETMODE, KD_TEXT);
//#endif
#ifdef __NetBSD__
  {
   int mode = WSDISPLAYIO_MODE_EMUL;
   ioctl (fb->fb_fd, WSDISPLAYIO_SMODE, &mode);
  }
#endif
  munmap (tiled->fb, fb->fb_mapped_size);
  if (!ctx_fb_single_buffer)
    ctx_free (tiled->pixels);
  close (fb->fb_fd);
  if (system("stty sane")){};
  ctx_tiled_destroy ((CtxTiled*)fb);
  //ctx_free (fb);
}

//static unsigned char *fb_icc = NULL;
//static long fb_icc_length = 0;

static CtxFb *ctx_fb = NULL;
#ifdef __linux__
static void fb_vt_switch_cb (int sig)
{
  CtxTiled *tiled = (void*)ctx_fb;
  CtxBackend *backend = (void*)ctx_fb;
  if (sig == SIGUSR1)
  {
    ioctl (0, VT_RELDISP, 1);
    tiled->vt_active = 0;
    ioctl (0, KDSETMODE, KD_TEXT);
  }
  else
  {
    ioctl (0, VT_RELDISP, VT_ACKACQ);
    tiled->vt_active = 1;
    // queue draw
    tiled->render_frame = ++tiled->frame;
    ioctl (0, KDSETMODE, KD_GRAPHICS);
    {
      backend->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)] += 1;
      }
    }
  }
}
#endif


Ctx *ctx_new_fb (int width, int height)
{
#if CTX_RASTERIZER
  if (getenv ("CTX_FB_SINGLE_BUFFER"))
    ctx_fb_single_buffer = atoi (getenv ("CTX_FB_SINGLE_BUFFER"));
  CtxFb *fb = ctx_calloc (sizeof (CtxFb), 1);
  CtxTiled *tiled = (void*)fb;
  CtxBackend *backend = (void*)fb;
  ctx_fb = fb;
  {
#ifdef __linux__
  const char *dev_path = "/dev/fb0";
#endif
#ifdef __NetBSD__
  const char *dev_path = "/dev/ttyE0";
#endif
#ifdef __OpenBSD__
  const char *dev_path = "/dev/ttyC0";
#endif
  fb->fb_fd = open (dev_path, O_RDWR);
  if (fb->fb_fd > 0)
    fb->fb_path = ctx_strdup (dev_path);
  else
  {
#ifdef __linux__
    fb->fb_fd = open ("/dev/graphics/fb0", O_RDWR);
    if (fb->fb_fd > 0)
    {
      fb->fb_path = ctx_strdup ("/dev/graphics/fb0");
    }
    else
#endif
    {
      ctx_free (fb);
      return NULL;
    }
  }

#ifdef __linux__
  if (ioctl(fb->fb_fd, FBIOGET_FSCREENINFO, &fb->finfo))
    {
      fprintf (stderr, "error getting fbinfo\n");
      close (fb->fb_fd);
      ctx_free (fb->fb_path);
      ctx_free (fb);
      return NULL;
    }

   if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &fb->vinfo))
     {
       fprintf (stderr, "error getting fbinfo\n");
      close (fb->fb_fd);
      ctx_free (fb->fb_path);
      ctx_free (fb);
      return NULL;
     }
  ioctl (0, KDSETMODE, KD_GRAPHICS);

//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;
#endif

#ifdef __NetBSD__
  struct wsdisplay_fbinfo finfo;

  int mode = WSDISPLAYIO_MODE_DUMBFB;
  //int mode = WSDISPLAYIO_MODE_MAPPED;
  if (ioctl (fb->fb_fd, WSDISPLAYIO_SMODE, &mode)) {
    return NULL;
  }
  if (ioctl (fb->fb_fd, WSDISPLAYIO_GINFO, &finfo)) {
    fprintf (stderr, "ioctl: WSIDSPLAYIO_GINFO failed\n");
    return NULL;
  }

  width = tiled->width = finfo.width;
  height = tiled->height = finfo.height;
  fb->fb_bits = finfo.depth;
  fb->fb_bpp = (fb->fb_bits + 1) / 8;
  fb->fb_mapped_size = width * height * fb->fb_bpp;


  if (fb->fb_bits == 8)
  {
    uint8_t red[256],  green[256],  blue[256];
    struct wsdisplay_cmap cmap;
    cmap.red = red;
    cmap.green = green;
    cmap.blue = blue;
    cmap.count = 256;
    cmap.index = 0;
    for (int i = 0; i < 256; i++)
    {
      red[i]   = ((( i >> 5) & 0x7) << 5);
      green[i] = ((( i >> 2) & 0x7) << 5);
      blue[i]  = ((( i >> 0) & 0x3) << 6);
    }

    ioctl (fb->fb_fd, WSDISPLAYIO_PUTCMAP, &cmap);
  }
#endif

                                              
  tiled->fb = mmap (NULL, fb->fb_mapped_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb->fb_fd, 0);
  }
  if (!tiled->fb)
    return NULL;
  if (ctx_fb_single_buffer)
    tiled->pixels = tiled->fb;
  else
    tiled->pixels = ctx_calloc (fb->fb_mapped_size, 1);
  tiled->show_frame = (void*)ctx_fb_show_frame;

#if CTX_BABL
  ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);
#endif

  backend->type   = CTX_BACKEND_FB;
  backend->ctx    = _ctx_new_drawlist (width, height);
  tiled->ctx_copy = _ctx_new_drawlist (width, height);
  tiled->width    = width;
  tiled->height   = height;

  ctx_set_backend (backend->ctx, fb);
  ctx_set_backend (tiled->ctx_copy, fb);
  ctx_set_texture_cache (tiled->ctx_copy, backend->ctx);


  backend->end_frame = ctx_tiled_end_frame;
  backend->process = (void*)ctx_drawlist_process;

  backend->start_frame = ctx_fb_start_frame;
  backend->destroy = (void*)ctx_fb_destroy;
  backend->set_clipboard = ctx_headless_set_clipboard;
  backend->get_clipboard = ctx_headless_get_clipboard;
  backend->consume_events = ctx_fb_consume_events;
  backend->get_event_fds = ctx_fb_get_event_fds;

  ctx_set_size (backend->ctx, width, height);
  ctx_set_size (tiled->ctx_copy, width, height);

  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]->backend))->swap_red_green = 1;
    ctx_set_texture_source (tiled->host[i], backend->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

  EvSource *kb = NULL;
 
#if CTX_RAW_KB_EVENTS
  if (!kb) kb = evsource_kb_raw_new ();
#endif
  if (!kb) kb = evsource_kb_term_new ();
  if (kb)
  {
    tiled->evsource[tiled->evsource_count++] = kb;
    kb->priv = fb;
  }
  EvSource *mice  = NULL;
#if CTX_PTY
  mice = evsource_mice_new ();
#endif
  if (mice)
  {
    tiled->evsource[tiled->evsource_count++] = mice;
    mice->priv = fb;
  }

  tiled->vt_active = 1;
#ifdef __linux__
  ioctl(0, KDSETMODE, KD_GRAPHICS);
  signal (SIGUSR1, fb_vt_switch_cb);
  signal (SIGUSR2, fb_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;
  }
#endif

  return backend->ctx;
#else
  return NULL;
#endif
}
#endif
#endif

#if CTX_TERMINAL_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#if CTX_PTY
#include <sys/ioctl.h>
#endif
#include <signal.h>
#endif



#if CTX_KMS
#ifdef __linux__
  #include <linux/kd.h>
#endif
  //#include <linux/fb.h>
  //#include <linux/vt.h>
  #include <sys/mman.h>
  //#include <threads.h>
  #include <libdrm/drm.h>
  #include <libdrm/drm_mode.h>


typedef struct _CtxKMS CtxKMS;
struct _CtxKMS
{
   CtxTiled tiled;
   int           key_balance;
   int           key_repeat;
   int           lctrl;
   int           lalt;
   int           rctrl;

   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          is_kms;
   cnd_t        cond;
   mtx_t        mtx;
   struct drm_mode_crtc crtc;
};


#if UINTPTR_MAX == 0xffFFffFF
  #define fbdrmuint_t uint32_t
#elif UINTPTR_MAX == 0xffFFffFFffFFffFF
  #define fbdrmuint_t uint64_t
#endif

void *ctx_fbkms_new (CtxKMS *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_fbkms_flip (CtxKMS *fb)
{
  if (!fb->fb_fd)
    return;
  ioctl(fb->fb_fd, DRM_IOCTL_MODE_SETCRTC, &fb->crtc);
}

void ctx_fbkms_close (CtxKMS *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_kms_flip (CtxKMS *fb)
{
  if (fb->is_kms)
    ctx_fbkms_flip (fb);
#if 0
  else
    ioctl (fb->fb_fd, FBIOPAN_DISPLAY, &fb->vinfo);
#endif
}

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 void ctx_kms_show_frame (CtxKMS *fb, int block)
{
  CtxTiled *tiled = (void*)fb;
  if (tiled->shown_frame == tiled->render_frame)
  {
    if (block == 0) // consume event call
    {
      ctx_tiled_draw_cursor ((CtxTiled*)fb);
      ctx_kms_flip (fb);
    }
    return;
  }

  if (block)
  {
    int count = 0;
    while (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
    {
      usleep (500);
      count ++;
      if (count > 500)
      {
        tiled->shown_frame = tiled->render_frame;
        return;
      }
    }
  }
  else
  {
    if (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
      return;
  }

    if (tiled->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;


       if (tiled->min_row == 100){
          pre_skip = 0;
          post_skip = 0;
          // not when kms ?
#if 0
     __u32 dummy = 0;
          ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
#endif
          ctx_tiled_undraw_cursor ((CtxTiled*)fb);
       }
       else
       {

      tiled->min_row = 100;
      tiled->max_row = 0;
      tiled->min_col = 100;
      tiled->max_col = 0;

     // not when kms ?
 #if 0
     __u32 dummy = 0;
     ioctl (fb->fb_fd, FBIO_WAITFORVSYNC, &dummy);
#endif
     ctx_tiled_undraw_cursor ((CtxTiled*)fb);
     switch (fb->fb_bits)
     {
       case 32:
#if 1
         {
           uint8_t *dst = tiled->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*)tiled->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 = tiled->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 = tiled->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 = tiled->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 = tiled->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_tiled_cursor_drawn = 0;
    ctx_tiled_draw_cursor (&fb->tiled);
    ctx_kms_flip (fb);
    tiled->shown_frame = tiled->render_frame;
  }
}

void ctx_kms_consume_events (Ctx *ctx)
{
  ctx_fb_global = ctx;
  CtxKMS *fb = (void*)ctx->backend;
  ctx_kms_show_frame (fb, 0);
  event_check_pending (&fb->tiled);
}

inline static void ctx_kms_start_frame (Ctx *ctx)
{
  ctx_kms_show_frame ((CtxKMS*)ctx->backend, 1);
}

void ctx_kms_destroy (CtxKMS *fb)
{
  if (fb->is_kms)
  {
    ctx_fbkms_close (fb);
  }
#ifdef __linux__
  ioctl (0, KDSETMODE, KD_TEXT);
#endif
  if (system("stty sane")){};
  ctx_tiled_destroy ((CtxTiled*)fb);
  //ctx_free (fb);
}

//static unsigned char *fb_icc = NULL;
//static long fb_icc_length = 0;

#if 0
static CtxKMS *ctx_fb = NULL;
static void vt_switch_cb (int sig)
{
  CtxTiled *tiled = (void*)ctx_fb;
  if (sig == SIGUSR1)
  {
    if (ctx_fb->is_kms)
      ioctl(ctx_fb->fb_fd, DRM_IOCTL_DROP_MASTER, 0);
    ioctl (0, VT_RELDISP, 1);
    ctx_fb->vt_active = 0;
#if 0
    ioctl (0, KDSETMODE, KD_TEXT);
#endif
  }
  else
  {
    ioctl (0, VT_RELDISP, VT_ACKACQ);
    ctx_fb->vt_active = 1;
    // queue draw
    tiled->render_frame = ++tiled->frame;
#if 0
    ioctl (0, KDSETMODE, KD_GRAPHICS);
#endif
    if (ctx_fb->is_kms)
    {
      ioctl(ctx_fb->fb_fd, DRM_IOCTL_SET_MASTER, 0);
      ctx_kms_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) *  4] += 1;
      }
    }
  }
}
#endif

static int ctx_kms_get_mice_fd (Ctx *ctx)
{
#if CTX_PTY
  //CtxKMS *fb = (void*)ctx->backend;
  return _ctx_mice_fd;
#endif
}

Ctx *ctx_new_kms (int width, int height)
{
#if CTX_RASTERIZER
  CtxKMS *fb = ctx_calloc (sizeof (CtxKMS), 1);
  CtxBackend *backend = (CtxBackend*)fb;

  CtxTiled *tiled = (void*)fb;
  tiled->fb = ctx_fbkms_new (fb, &tiled->width, &tiled->height);
  if (tiled->fb)
  {
    fb->is_kms         = 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;
  }
  if (!tiled->fb)
    return NULL;
  tiled->pixels = ctx_calloc (fb->fb_mapped_size, 1);

#if CTX_BABL
  ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);
#endif

  backend->type = CTX_BACKEND_KMS;
  backend->ctx = _ctx_new_drawlist (width, height);
  tiled->ctx_copy = _ctx_new_drawlist (width, height);

  tiled->width    = width;
  tiled->height   = height;
  tiled->show_frame = (void*)ctx_kms_show_frame;

  ctx_set_backend (backend->ctx, fb);
  ctx_set_backend (tiled->ctx_copy, fb);
  ctx_set_texture_cache (tiled->ctx_copy, backend->ctx);

  backend->end_frame = ctx_tiled_end_frame;
  backend->start_frame = ctx_kms_start_frame;
  backend->destroy = (void*)ctx_kms_destroy;
  backend->process = (void*)ctx_drawlist_process;
  backend->consume_events = ctx_kms_consume_events;
  backend->get_event_fds = (void*) ctx_fb_get_event_fds;
  backend->set_clipboard = ctx_headless_set_clipboard;
  backend->get_clipboard = ctx_headless_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]->backend))->swap_red_green = 1;
    ctx_set_texture_source (tiled->host[i], backend->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


  EvSource *kb = evsource_kb_raw_new ();
  if (!kb) kb = evsource_kb_term_new ();
  if (kb)
  {
    tiled->evsource[tiled->evsource_count++] = kb;
    kb->priv = fb;
  }
  EvSource *mice  = NULL;
#if CTX_PTY
  mice = evsource_mice_new ();
#endif
  if (mice)
  {
    tiled->evsource[tiled->evsource_count++] = mice;
    mice->priv = fb;
  }

  tiled->vt_active = 1;
#ifdef __linux__
  ioctl(0, KDSETMODE, KD_GRAPHICS);
#endif
  tiled->shown_frame = tiled->render_frame;
#if 0
  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;
  }
#endif

  return backend->ctx;
#else
  return NULL;
#endif
}
#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 *backend;
   SDL_Texture  *texture;

   int           fullscreen;
};



int ctx_show_fps = 1;
void ctx_sdl_set_title (void *self, const char *new_title)
{
   Ctx *ctx = (Ctx*)self;
   CtxSDL *sdl = (CtxSDL*)ctx->backend;
   if (!ctx_show_fps)
   SDL_SetWindowTitle (sdl->window, new_title);
}

static long ctx_sdl_start_time = 0;

static void ctx_sdl_show_frame (CtxSDL *sdl, int block)
{
  CtxTiled *tiled = &sdl->tiled;
  CtxBackend *backend = (CtxBackend*)tiled;
  if (tiled->shown_cursor != backend->ctx->cursor)
  {
    tiled->shown_cursor = backend->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 (500);
      count ++;
      if (count > 900)
      {
        tiled->shown_frame = tiled->render_frame;
        fprintf (stderr, "[drop]");
        return;
      }
    }
  }
  else
  {
    if (ctx_tiled_threads_done (tiled) != _ctx_max_threads)
      return;
  }


  if (tiled->min_row == 100)
  {
  }
  else
  {
    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;

    if (_ctx_damage_control)
    {
      x = 0;
      y = 0;
      x1 = tiled->width;
      y1 = tiled->height;
    }

    int width = x1 - x;
    int height = y1 - y;
    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*)(tiled->pixels + y * tiled->width * 4 + x * 4),
                      tiled->width * 4);
    SDL_RenderClear (sdl->backend);
    SDL_RenderCopy (sdl->backend, sdl->texture, NULL, NULL);
    SDL_RenderPresent (sdl->backend);


  if (ctx_show_fps)
  {
    static char tmp_title[1024];
    static uint64_t prev_time = 0;
    uint64_t time = ctx_ticks ();
    float fps = 1000000.0f/  (time - ctx_sdl_start_time);
    float fps2 = 1000000.0f/  (time - prev_time);
    prev_time = time;
    static float fps_avg = 0.0f;

    if (time - prev_time < 1000 * 1000 * 0.05f)
    fps_avg = (fps_avg * 0.9f + fps2 *  0.1f);

    sprintf (tmp_title, "FPS: %.1f %.1f %.1f", (double)(fps2*0.75f+fps_avg*0.25f), (double)fps2, (double)fps);
#if CTX_SHAPE_CACHE 
    sprintf (&tmp_title[strlen(tmp_title)], " shape hit rate: %.2f", (double)ctx_shape_cache_rate);
#endif

    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 scan_code = sym;
  const char *name = &buf[0];
   switch (sym)
   {
     case SDLK_RSHIFT: name="shift";scan_code = 16 ; break;
     case SDLK_LSHIFT: name="shift";scan_code = 16 ; break;
     case SDLK_LCTRL: name="control";scan_code = 17 ; break;
     case SDLK_RCTRL: name="control";scan_code = 17 ; break;
     case SDLK_LALT:  name="alt";scan_code = 18 ; break;
     case SDLK_RALT:  name="alt";scan_code = 18 ; break;
     case SDLK_CAPSLOCK: name = "capslock"; scan_code = 20 ; break;
     //case SDLK_NUMLOCK: name = "numlock"; scan_code = 144 ; break;
     //case SDLK_SCROLLLOCK: name = "scrollock"; scan_code = 145 ; break;

     case SDLK_F1:     name = "F1"; scan_code = 112; break;
     case SDLK_F2:     name = "F2"; scan_code = 113; break;
     case SDLK_F3:     name = "F3"; scan_code = 114; break;
     case SDLK_F4:     name = "F4"; scan_code = 115; break;
     case SDLK_F5:     name = "F5"; scan_code = 116; break;
     case SDLK_F6:     name = "F6"; scan_code = 117; break;
     case SDLK_F7:     name = "F7"; scan_code = 118; break;
     case SDLK_F8:     name = "F8"; scan_code = 119; break;
     case SDLK_F9:     name = "F9"; scan_code = 120; break;
     case SDLK_F10:    name = "F10"; scan_code = 121; break;
     case SDLK_F11:    name = "F11"; scan_code = 122; break;
     case SDLK_F12:    name = "F12"; scan_code = 123; break;
     case SDLK_ESCAPE: name = "escape"; break;
     case SDLK_DOWN:   name = "down"; scan_code = 40; break;
     case SDLK_LEFT:   name = "left"; scan_code = 37; break;
     case SDLK_UP:     name = "up"; scan_code = 38;  break;
     case SDLK_RIGHT:  name = "right"; scan_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"; scan_code = 46; break;
     case SDLK_INSERT: name = "insert"; scan_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"; scan_code = 36; break;
     case SDLK_END:      name = "end"; scan_code = 35; break;
     case SDLK_PAGEDOWN: name = "page-down"; scan_code = 34; break;
     case SDLK_PAGEUP:   name = "page-up"; scan_code = 33; break;
     case ',': scan_code = 188; break;
     case '.': scan_code = 190; break;
     case '/': scan_code = 191; break;
     case '`': scan_code = 192; break;
     case '[': scan_code = 219; break;
     case '\\': scan_code = 220; break;
     case ']':  scan_code = 221; break;
     case '\'': scan_code = 222; break;
     default:
       ;
   }
   if (sym >= 'a' && sym <='z') scan_code -= 32;
   if (r_keycode)
   {
     *r_keycode = scan_code;
   }
   return name;
}

void ctx_sdl_consume_events (Ctx *ctx)
{
  static float x = 0.0f;
  static float y = 0.0f;
  CtxBackend *backend = (void*)ctx->backend;
  CtxTiled    *tiled = (void*)backend;
  CtxSDL      *sdl = (void*)backend;
  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);
        x = event.motion.x;
        y = event.motion.y;
        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);
          }
        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 ++;
          }
          int keycode;
          name = ctx_sdl_keysym_to_name (event.key.keysym.sym, &keycode);

          ctx_key_down (ctx, keycode, name, 0);

          if (ctx_utf8_strlen (name) > 1 ||
              (ctx->events.modifier_state &
                                           (CTX_MODIFIER_STATE_CONTROL|
                                            CTX_MODIFIER_STATE_ALT))
              )
          if (strcmp(name, "space"))
            ctx_key_press (ctx, keycode, name, 0);
        }
        break;
      case SDL_KEYUP:
        {
           sdl->key_balance --;
           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_DROPFILE:
        ctx_pointer_drop (ctx, x, y, 0, 0, event.drop.file);
        break;
      case SDL_DROPTEXT:
        if (!strncmp ("file://", event.drop.file, 7))
          ctx_pointer_drop (ctx, x, y, 0, 0, event.drop.file + 7);
        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->backend, SDL_PIXELFORMAT_ABGR8888,
                          SDL_TEXTUREACCESS_STREAMING, width, height);
          ctx_free (tiled->pixels);
          tiled->pixels = ctx_calloc (4, width * height);

          tiled->width  = width;
          tiled->height = height;
          ctx_set_size (backend->ctx, width, height);
          ctx_set_size (tiled->ctx_copy, width, height);
        }
        break;
    }
  }
}

static void ctx_sdl_set_clipboard (Ctx *ctx, const char *text)
{
  if (text)
    SDL_SetClipboardText (text);
}

static char *ctx_sdl_get_clipboard (Ctx *ctx)
{
  return SDL_GetClipboardText ();
}


inline static void ctx_sdl_start_frame (Ctx *ctx)
{
  CtxSDL  *sdl = (CtxSDL*)ctx->backend;
  ctx_sdl_show_frame (sdl, 1);
  ctx_sdl_start_time = ctx_ticks ();
}

void ctx_sdl_destroy (CtxSDL *sdl)
{
  if (sdl->texture)
    SDL_DestroyTexture (sdl->texture);
  if (sdl->backend)
    SDL_DestroyRenderer (sdl->backend);
  if (sdl->window)
  {
    SDL_DestroyWindow (sdl->window);
  }
  sdl->texture = NULL;sdl->backend = NULL;sdl->window = NULL;

  ctx_tiled_destroy ((CtxTiled*)sdl);
}

void ctx_sdl_set_fullscreen (Ctx *ctx, int val)
{
  CtxSDL *sdl = (void*)ctx->backend;

  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->backend;
  return sdl->fullscreen;
}

Ctx *ctx_new_sdl (int width, int height)
{
#if CTX_RASTERIZER

  CtxSDL *sdl = (CtxSDL*)ctx_calloc (sizeof (CtxSDL), 1);
  CtxTiled *tiled = (void*)sdl;
  CtxBackend *backend = (CtxBackend*)sdl;
#if CTX_BABL
  ctx_get_contents ("file:///tmp/ctx.icc", &sdl_icc, &sdl_icc_length);
#endif
  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->backend = SDL_CreateRenderer (sdl->window, -1, SDL_RENDERER_SOFTWARE);
  sdl->backend = SDL_CreateRenderer (sdl->window, -1, 0);
  if (!sdl->backend)
  {
     ctx_destroy (backend->ctx);
     ctx_free (sdl);
     return NULL;
  }
  sdl->fullscreen = 0;


  ctx_show_fps = getenv ("CTX_SHOW_FPS")!=NULL;

  sdl->texture = SDL_CreateTexture (sdl->backend,
        SDL_PIXELFORMAT_ABGR8888,
        SDL_TEXTUREACCESS_STREAMING,
        width, height);

  SDL_StartTextInput ();
  SDL_EnableScreenSaver ();
  SDL_GL_SetSwapInterval (1);

  backend->type = CTX_BACKEND_SDL;
  backend->ctx      = _ctx_new_drawlist (width, height);
  tiled->ctx_copy = _ctx_new_drawlist (width, height);
  tiled->width    = width;
  tiled->height   = height;
  tiled->cols     = 80;
  tiled->rows     = 20;
  ctx_set_backend (backend->ctx, sdl);
  ctx_set_backend (tiled->ctx_copy, sdl);
  ctx_set_texture_cache (tiled->ctx_copy, backend->ctx);

  tiled->pixels = (uint8_t*)ctx_malloc (width * height * 4);
  tiled->show_frame = (void*)ctx_sdl_show_frame;


  backend->set_windowtitle = (void*)ctx_sdl_set_title;
  backend->end_frame = ctx_tiled_end_frame;
  backend->process = (void*)ctx_drawlist_process;
  backend->start_frame = ctx_sdl_start_frame;
  backend->destroy = (void*)ctx_sdl_destroy;
  backend->consume_events = ctx_sdl_consume_events;

  backend->set_clipboard = ctx_sdl_set_clipboard;
  backend->get_clipboard = 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], backend->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

  return backend->ctx;
#else
  return NULL;
#endif
}
#endif
#if CTX_TERM
#if CTX_TERMINAL_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
{
   CtxBackend  backender;
   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, ctx_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 = ctx_realloc (line->cells, line->size, 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)
  {
    fprintf(stderr, "\033[38;2;%i;%i;%im", red,green,blue);
  }
  else
  {
    int gray = (int)((green /255.0f) * 24 + 0.5f);
    int r    = (int)((red/255.0f)    * 6 + 0.5f);
    int g    = (int)((green/255.0f)  * 6 + 0.5f);
    int b    = (int)((blue/255.0f)   * 6 + 0.5f);
    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))))
    {
      fprintf(stderr,"\033[38;5;%im", 16 + 216 + gray);
    }
    else
      fprintf(stderr,"\033[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)
  {
    fprintf(stderr,"\033[48;2;%i;%i;%im", red,green,blue);
  }
  else
  {
    int gray = (int)((green /255.0f) * 24 + 0.5f);
    int r    = (int)((red/255.0f)    * 6 + 0.5f);
    int g    = (int)((green/255.0f)  * 6 + 0.5f);
    int b    = (int)((blue/255.0f)   * 6 + 0.5f);
    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))))
    {
      fprintf(stderr,"\033[48;5;%im", 16 + 216 + gray);
    }
    else
      fprintf(stderr,"\033[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;
  fprintf (stderr,"\033[H");
//  printf ("\033[?25l");
  fprintf (stderr, "\033[0m");

  int cur_fg[3]={-1,-1,-1};
  int cur_bg[3]={-1,-1,-1};

  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)
      {
        if (cell->fg[0] != cur_fg[0] ||
            cell->fg[1] != cur_fg[1] ||
            cell->fg[2] != cur_fg[2])
        {
          ctx_term_set_fg (cell->fg[0], cell->fg[1], cell->fg[2]);
          cur_fg[0]=cell->fg[0];
          cur_fg[1]=cell->fg[1];
          cur_fg[2]=cell->fg[2];
        }
        if (cell->bg[0] != cur_bg[0] ||
            cell->bg[1] != cur_bg[1] ||
            cell->bg[2] != cur_bg[2])
        {
          ctx_term_set_bg (cell->bg[0], cell->bg[1], cell->bg[2]);
          cur_bg[0]=cell->bg[0];
          cur_bg[1]=cell->bg[1];
          cur_bg[2]=cell->bg[2];
        }
        fprintf (stderr, "%s", cell->utf8);
      }
      else
      {
        // TODO: accumulate succesive such to be ignored items,
        // and compress them into one, making us compress largely
        // reused screens well
        fprintf (stderr, "\033[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)
      fprintf (stderr, "\n\r");
    row ++;
  }
  fprintf (stderr, "\033[0m");
  //printf ("\033[?25h");
  //
}

// xx
// xx
// xx
//

static inline int _ctx_rgba8_manhattan_diff (const uint8_t *a, const uint8_t *b)
{ // wrongly named!
  int c;
  int diff = 0;
  for (c = 0; c<3;c++)
    diff += (int)ctx_pow2(a[c]-b[c]);
  return (int)ctx_sqrtf(diff);
  return diff;
}

static inline 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 int
ctx_is_half_opaque (CtxRasterizer *rasterizer)
{
  CtxGState *gstate = &rasterizer->state->gstate;
  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] * gstate->global_alpha_f) >= 127)
      return 1;
    return 0;
  }
  return gstate->global_alpha_f > 0.5f;
}

inline static void ctx_term_process (Ctx *ctx,
                                     CtxCommand *command)
{
  CtxTerm *term = (void*)ctx->backend;

#if CTX_CURRENT_PATH
  ctx_update_current_path (ctx, &command->entry);
#endif


#if CTX_BRAILLE_TEXT
  if (command->code == CTX_FILL)
  {
     CtxRasterizer *rasterizer = (CtxRasterizer*)term->host->backend;

     if (ctx_is_half_opaque (rasterizer))
     {
        CtxIntRectangle shape_rect = {
          ((int)(rasterizer->col_min / CTX_SUBDIV - 2))/2,
          ((int)(rasterizer->scan_min / 15 - 2))/3,
          ((int)(3+((int)rasterizer->col_max - rasterizer->col_min + 1) / CTX_SUBDIV))/2,
          ((int)(3+((int)rasterizer->scan_max - rasterizer->scan_min + 1) / 15))/3
        };
#if 0
  CtxGState *gstate = &rasterizer->state->gstate;
       fprintf (stderr, "{%i,%i %ix%i %.2f %i}",
                       shape_rect.x, shape_rect.y,
                       shape_rect.width, shape_rect.height,

                       gstate->global_alpha_f,
                       ga[1]
                       
                       );
   //  sleep(1);
#endif

       if (shape_rect.y > 0) // XXX : workaround 
       for (int row = shape_rect.y;
            row < (shape_rect.y+(int)shape_rect.height);
            row++)
       for (int col = shape_rect.x;
            col < (shape_rect.x+(int)shape_rect.width);
            col++)

       if (0){ // disabled - offset is wrong (or offset of cursor in stuff is wrong
         for (CtxList *l = rasterizer->glyphs; l; l=l?l->next:NULL)
         {
           CtxTermGlyph *glyph = l->data;
           if ((glyph->row == row+1) &&
               (glyph->col == col+1))
           {
              ctx_list_remove (&rasterizer->glyphs, glyph);
              ctx_free (glyph);
              l = NULL;
           }
         }
       }
     }
  }
#endif

  /* we need to interpret state related things ourself to be able to respond to
   * queries.
   */
  ctx_interpret_style (&ctx->state, &command->entry, ctx);
  ctx_interpret_transforms (&ctx->state, &command->entry, ctx);
  ctx_interpret_pos (&ctx->state, &command->entry, ctx);

  /* directly forward */
  ctx_process (term->host, &command->entry);
}

inline static void ctx_term_end_frame (Ctx *ctx)
{
  CtxTerm *term = (CtxTerm*)ctx->backend;
  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->backend);
  // 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);
    ctx_free (glyph);
  }

#endif
  printf ("\033[H");
  printf ("\033[0m");
  ctx_term_scanout (term);
  printf ("\033[0m");
  fflush (NULL);
#if CTX_BRAILLE_TEXT
  while (rasterizer->glyphs)
    ctx_list_remove (&rasterizer->glyphs, rasterizer->glyphs->data);
#endif
}

void ctx_term_destroy (CtxTerm *term)
{
  while (term->lines)
  {
    ctx_free (term->lines->data);
    ctx_list_remove (&term->lines, term->lines->data);
  }
  printf ("\033[?25h"); // cursor on
  nc_at_exit ();
  ctx_free (term->pixels);
  ctx_destroy (term->host);
  ctx_free (term);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

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_drawlist (width, height);
#if CTX_RASTERIZER
  CtxTerm *term = (CtxTerm*)ctx_calloc (sizeof (CtxTerm), 1);
  CtxBackend *backend = (void*)term;
 
  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 (stderr, "\033[?1049h");
  fprintf (stderr, "\033[?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;
  backend->ctx = ctx;
  backend->type = CTX_BACKEND_TERM;
  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*)ctx_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->backend)->term_glyphs=1;
#endif
  _ctx_mouse (ctx, NC_MOUSE_DRAG);
  ctx_set_backend (ctx, term);
  backend->process = ctx_term_process;
  backend->end_frame = ctx_term_end_frame;
  backend->destroy = (void(*)(void*))ctx_term_destroy;
  backend->consume_events = ctx_nct_consume_events;
  backend->get_event_fds = (void*) ctx_stdin_get_event_fds;
  ctx_set_size (ctx, width, height);
  ctx_font_size (ctx, ctx_term_ch); 
  ctx_font_size (term->host, ctx_term_ch); 
#endif

  return ctx;
}

#endif
#endif
#if CTX_TERMIMG
#if CTX_TERMINAL_EVENTS

#if !__COSMOPOLITAN__
#include <fcntl.h>
#include <sys/ioctl.h>
#endif

typedef struct _CtxTermImg CtxTermImg;
struct _CtxTermImg
{
   CtxBackend backend;
   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_process (Ctx        *ctx,
                                        CtxCommand *command)
{
  CtxTermImg *termimg = (void*)ctx->backend;
#if CTX_CURRENT_PATH
  ctx_update_current_path (ctx, &command->entry);
#endif

  /* directly forward */
  ctx_process (termimg->host, &command->entry);
}

inline static void ctx_termimg_end_frame (Ctx *ctx)
{
  CtxTermImg *termimg = (CtxTermImg*)ctx->backend;
  int width =  termimg->width;
  int height = termimg->height;
  if (!termimg->pixels) return;
  char *encoded = ctx_malloc (width * height * 3 * 3);
  ctx_bin2base64 (termimg->pixels, width * height * 3,
                  encoded);
  int encoded_len = strlen (encoded);

  int i = 0;

  printf ("\033[H");
  printf ("\033_Gf=24,s=%i,v=%i,t=d,a=T,m=1;\033\\", width, height);
  while (i <  encoded_len)
  {
     if (i + 4096 <  encoded_len)
     {
       printf  ("\033_Gm=1;");
     }
     else
     {
       printf  ("\033_Gm=0;");
     }
     for (int n = 0; n < 4000 && i < encoded_len; n++)
     {
       printf ("%c", encoded[i]);
       i++;
     }
     printf ("\033\\");
  }
  ctx_free (encoded);
  
  fflush (NULL);
}

void ctx_termimg_destroy (CtxTermImg *termimg)
{
  while (termimg->lines)
  {
    ctx_free (termimg->lines->data);
    ctx_list_remove (&termimg->lines, termimg->lines->data);
  }
  printf ("\033[?25h"); // cursor on
  nc_at_exit ();
  ctx_free (termimg->pixels);
  ctx_destroy (termimg->host);
  ctx_free (termimg);
  /* we're not destoring the ctx member, this is function is called in ctx' teardown */
}

Ctx *ctx_new_termimg (int width, int height)
{
  Ctx *ctx = _ctx_new_drawlist (width, height);
#if CTX_RASTERIZER
  fprintf (stdout, "\033[?1049h");
  fprintf (stdout, "\033[?25l"); // cursor off
  CtxTermImg *termimg = (CtxTermImg*)ctx_calloc (sizeof (CtxTermImg), 1);
  CtxBackend *backend = (void*)termimg;


  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->width  = width;
  termimg->height = height;
  termimg->lines = 0;
  termimg->pixels = (uint8_t*)ctx_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_backend (ctx, termimg);

  backend->type = CTX_BACKEND_TERMIMG;
  backend->ctx = ctx;
  backend->process = ctx_termimg_process;
  backend->end_frame = ctx_termimg_end_frame;
  backend->destroy = (void(*)(void*))ctx_termimg_destroy;
  backend->consume_events = ctx_nct_consume_events;
  backend->get_event_fds = (void*) ctx_stdin_get_event_fds;
  ctx_set_size (ctx, width, height);
  ctx_font_size (ctx, 14.0f);
#endif

  return ctx;
}

#endif
#endif


typedef struct CtxCbBackend
{
  CtxBackend     backend;
  CtxPixelFormat format;
  int            flags;
  int            memory_budget;
  int            min_col; // hasher cols and rows
  int            min_row; // hasher cols and rows
  int            max_col; // hasher cols and rows
  int            max_row; // hasher cols and rows
  uint16_t      *fb;
  Ctx           *ctx;

  void (*set_pixels) (Ctx *ctx, void *user_data, 
                      int x, int y, int w, int h, void *buf, int buf_size);
  void   *set_pixels_user_data;
  int  (*update_fb) (Ctx *ctx, void *user_data);
  void   *update_fb_user_data;

  uint32_t hashes[CTX_HASH_ROWS * CTX_HASH_COLS];

  CtxHasher     rasterizer;
  uint8_t res[CTX_HASH_ROWS * CTX_HASH_COLS]; // when non-0 we have non-full res rendered
} CtxCbBackend;

void ctx_cb_set_flags (Ctx *ctx, int flags)
{
  CtxCbBackend *backend_cb = (CtxCbBackend*)ctx->backend;
  if (flags & CTX_FLAG_GRAY2)
    flags |= CTX_FLAG_LOWFI;
  if (flags & CTX_FLAG_GRAY4)
    flags |= CTX_FLAG_LOWFI;
  if (flags & CTX_FLAG_GRAY8)
    flags |= CTX_FLAG_LOWFI;
  if (flags & CTX_FLAG_RGB332)
    flags |= CTX_FLAG_LOWFI;

  if (flags & CTX_FLAG_LOWFI)
    flags |= CTX_FLAG_HASH_CACHE;
  backend_cb->flags = flags;
}

int ctx_cb_get_flags (Ctx *ctx)
{
  CtxCbBackend *backend_cb = (CtxCbBackend*)ctx->backend;
  return backend_cb->flags;
}

static inline uint16_t ctx_rgb332_to_rgb565 (uint8_t rgb, int byteswap)
{
   uint8_t red, green, blue;
   ctx_332_unpack (rgb, &red, &green, &blue);
   return ctx_565_pack (red, green, blue, byteswap);
}

void
ctx_cb_set_memory_budget (Ctx *ctx, int memory_budget)
{
  CtxCbBackend *backend_cb = (CtxCbBackend*)ctx->backend;
  backend_cb->memory_budget = memory_budget;
  if (backend_cb->fb)
  {
    ctx_free (backend_cb->fb);
    backend_cb->fb = NULL;
  }
}

#define CTX_MEMDEBUG 0  // by setting this to 1 we get reports about
                        // scratch buffer overflows into the 1kb buffer
                        // area
                        //
#if CTX_MEMDEBUG
#define CTX_SCRATCH_PAD  512

static void
ctx_memdebug (CtxCbBackend *cb_backend, int line_no)
{
  int started = 0;
  int last = 0;
  int first = 0;
  if (!cb_backend->fb)
    return;
  for (int i = cb_backend->memory_budget/2; i < cb_backend->memory_budget/2 + CTX_SCRATCH_PAD/2;i++)
  {
    if (cb_backend->fb[i] != 42)
    {
      if (!started)
      {
        first = i;
        started = 1;
      }
      last = i;
      cb_backend->fb[i] = 42;
    }
  }
  if (started)
  fprintf (stderr, "%i scratch overreach - first wrong byte at buf + %i last: %i\n",
                  line_no,
                  first - cb_backend->memory_budget/2,
                                                                          last - cb_backend->memory_budget/2);
}
                        
#define CTX_VERIFY_MEM()  do{ctx_memdebug(backend_cb, __LINE__);}while(0)
#else
#define CTX_SCRATCH_PAD   1024
#define CTX_VERIFY_MEM()  do{}while(0)
#endif

static int ctx_render_cb (CtxCbBackend *backend_cb, 
                          int x0, int y0,
                          int x1, int y1,
                          uint32_t active_mask)
{
  Ctx *ctx           = backend_cb->ctx;
  int flags          = backend_cb->flags;
  int memory_budget  = backend_cb->memory_budget;
  uint16_t *fb;
  CtxPixelFormat format = backend_cb->format;
  int bpp            = ctx_pixel_format_bits_per_pixel (format) / 8;
  int abort          = 0;
  
  int width          = x1 - x0 + 1;
  int height         = y1 - y0 + 1;
  int byteswap;
  byteswap = (format == CTX_FORMAT_RGB565_BYTESWAPPED);

  if (!backend_cb->fb)
  {
    backend_cb->fb = (uint16_t*)ctx_malloc (memory_budget + CTX_SCRATCH_PAD);
#if CTX_MEMDEBUG
    for (int i = backend_cb->memory_budget/2; i < backend_cb->memory_budget/2 + CTX_SCRATCH_PAD/2;i++)
      backend_cb->fb[i] = 42;
#endif
  }
  fb = backend_cb->fb;

#if CTX_ENABLE_CBRLE
  if (flags & CTX_FLAG_CBRLE)
  {
    int bitdepth = memory_budget * 8 / height / width;
    int zformat;
    if (bitdepth < 1)
    {
      fprintf (stderr, "not enough memory for 1bit cbrle\n");
      return 1;
    }
    switch (bitdepth)
    {
      case 1: zformat = CTX_FORMAT_CBRLE_1; break;
      case 2: zformat = CTX_FORMAT_CBRLE_2; break;
      case 3: zformat = CTX_FORMAT_CBRLE_3; break;
      case 4: zformat = CTX_FORMAT_CBRLE_4; break;
      case 5: zformat = CTX_FORMAT_CBRLE_5; break;
      case 6: zformat = CTX_FORMAT_CBRLE_6; break;
      case 7: zformat = CTX_FORMAT_CBRLE_7; break;
      case 8: zformat = CTX_FORMAT_CBRLE_8; break;
      case 9: zformat = CTX_FORMAT_CBRLE_9; break;
      case 10: zformat = CTX_FORMAT_CBRLE_10; break;
      case 11: zformat = CTX_FORMAT_CBRLE_11; break;
      case 12: zformat = CTX_FORMAT_CBRLE_12; break;
      case 13: zformat = CTX_FORMAT_CBRLE_13; break;
      case 14: zformat = CTX_FORMAT_CBRLE_14; break;
      case 15: zformat = CTX_FORMAT_CBRLE_15; break;
      case 16: zformat = CTX_FORMAT_CBRLE_16; break;
      case 17: zformat = CTX_FORMAT_CBRLE_17; break;
      case 18: zformat = CTX_FORMAT_CBRLE_18; break;
      case 19: zformat = CTX_FORMAT_CBRLE_19; break;
      case 20: zformat = CTX_FORMAT_CBRLE_20; break;
      case 21: zformat = CTX_FORMAT_CBRLE_21; break;
      case 22: zformat = CTX_FORMAT_CBRLE_22; break;
      case 23: zformat = CTX_FORMAT_CBRLE_23; break;
      default:
      case 24: zformat = CTX_FORMAT_CBRLE_24; break;
    }
    //zformat = CTX_FORMAT_GRAY4;
    //bitdepth = 4;
    int stride = (width * bitdepth + 7) / 8;

    memset(fb, 0, stride * height);
    CtxRasterizer *r = ctx_rasterizer_init ((CtxRasterizer*)&backend_cb->rasterizer,
                ctx, NULL, &ctx->state, fb, x0, y0, width, height,
                stride, zformat, CTX_ANTIALIAS_DEFAULT);
    ((CtxBackend*)r)->destroy = (CtxDestroyNotify)ctx_rasterizer_deinit;
    ctx_push_backend (ctx, r);

    //ctx_scale (ctx, 1.0f/scale_factor, 1.0f/scale_factor);
    ctx_translate (ctx, -1.0f * x0, -1.0f * y0);
    if (active_mask)
      ctx_render_ctx_masked (ctx, ctx, active_mask);
    else
      ctx_render_ctx (ctx, ctx);

    for (int y = 0; y < height;y++)
    {
      uint8_t rgba8[width*4];
      uint16_t rgb565[width];

      _ctx_CBRLE_decompress ((uint8_t*)&fb[stride * y/2], 
                              rgba8, width, stride, 0, width);

      for (int i = 0; i < width; i++)
      {
         rgb565[i] = ctx_888_to_565 ( ((uint32_t*)&rgba8[i*4])[0], 1);
      }

      backend_cb->set_pixels (ctx, backend_cb->set_pixels_user_data, 
                            //x0, y0, width, render_height, (uint16_t*)scaled,
                            //width * render_height * bpp);
                            x0,y0+y,width,1,(void*)rgb565,
                            stride);// * height);
    }

    ctx_pop_backend (ctx);    

    return abort;
  }
#endif

  if (flags & CTX_FLAG_LOWFI)
  {
    int scale_factor  = 1;
    int small_width   = width / scale_factor;
    int small_height  = height / scale_factor;

    int tbpp = bpp * 8;
    CtxPixelFormat tformat = format;
      if   (flags & CTX_FLAG_GRAY2)
      {
        tformat = CTX_FORMAT_GRAY2;
        tbpp = 2;
      }
      else if (flags & CTX_FLAG_GRAY4)
      {
        tformat = CTX_FORMAT_GRAY4;
        tbpp = 4;
      }
      else if (flags & CTX_FLAG_GRAY8)
      {
        tformat = CTX_FORMAT_GRAY8;
        tbpp = 8;
      }
      else
      if (flags & (CTX_FLAG_RGB332))
      {
        tbpp = 8;
        tformat = CTX_FORMAT_RGB332;
      }
    int small_stride = (small_width * tbpp + 7) / 8;
    int min_scanlines = 4;

    while (memory_budget - (small_height * small_stride) < width * bpp * min_scanlines)
    {
      scale_factor ++;
      small_width   = width / scale_factor;
      small_height  = height / scale_factor;
      min_scanlines = scale_factor * 2;
      small_stride  = (small_width * tbpp + 7) / 8;
    }

    int render_height = (memory_budget - (small_height * small_stride)) /
                        (width * bpp);

    const uint8_t *fb_u8 = (uint8_t*)fb;
    uint16_t *scaled = (uint16_t*)&fb_u8[small_height*small_stride];

    memset(fb, 0, small_stride * small_height);
    CtxRasterizer *r = ctx_rasterizer_init ((CtxRasterizer*)&backend_cb->rasterizer,
                ctx, NULL, &ctx->state, fb, 0, 0, small_width, small_height,
                small_stride, tformat, CTX_ANTIALIAS_DEFAULT);
    ((CtxBackend*)r)->destroy = (CtxDestroyNotify)ctx_rasterizer_deinit;
    ctx_push_backend (ctx, r);

    ctx_scale (ctx, 1.0f/scale_factor, 1.0f/scale_factor);
    ctx_translate (ctx, -1.0f * x0, -1.0f * y0);
    if (active_mask)
      ctx_render_ctx_masked (ctx, ctx, active_mask);
    else
      ctx_render_ctx (ctx, ctx);
    ctx_pop_backend (ctx);

    if (backend_cb->update_fb && (flags & CTX_FLAG_INTRA_UPDATE))
      backend_cb->update_fb (ctx, backend_cb->update_fb_user_data);
    int yo = 0;
    do
    {
      render_height = ctx_mini (render_height, y1-y0+1);
      int off = 0;
      for (int y = 0; y < render_height; y++)
      {
        int sbase = (small_stride * ((yo+y)/scale_factor));
        off = y * width;
        switch (tformat)
        {
          case CTX_FORMAT_GRAY1:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                int     soff = sbase + ((sx)/8);
                uint8_t bits = fb_u8[soff];
                uint16_t val = (bits & (1<<(sx&7)))?0xffff:0;
                sx++;

                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }
            break;
          case CTX_FORMAT_GRAY2:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                int     soff = sbase + ((sx)/4);
                uint8_t bits = fb_u8[soff];
                uint8_t g    = 85 * ((bits >> (2*(sx&3)))&3);
                uint16_t val = ctx_565_pack (g, g, g, byteswap);
                sx++;

                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }
            break;
          case CTX_FORMAT_GRAY4:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                int     soff = sbase + ((sx)/2);
                uint8_t bits = fb_u8[soff];
                uint8_t g    = 17 * ((bits >> (4*(sx&1)))&15);
                uint16_t val = ctx_565_pack (g, g, g, byteswap);
                sx++;

                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }


            break;
          case CTX_FORMAT_GRAY8:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                uint8_t g   = fb_u8[sbase + (sx++)];
                uint16_t val = ctx_565_pack (g, g, g, byteswap);
                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }
            break;
          case CTX_FORMAT_RGB332:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                uint16_t val = ctx_rgb332_to_rgb565 (
                   fb_u8[sbase + (sx++)], byteswap);
                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }
            break;
          default:
          case CTX_FORMAT_RGB565:
          case CTX_FORMAT_RGB565_BYTESWAPPED:
            {
              int sx = 0;
              for (int x = 0; x < width;)
              {
                uint16_t val = fb[sbase/2+(sx++)];
                for (int i = 0; i < scale_factor && x < width; i++, x++)
                  scaled[off++]  = val;
              }
            }
            break;
        }
        for (int ty = 1; ty < scale_factor && y + 1< render_height; ty++)
        {
           memcpy (&scaled[off], &scaled[off-width], 2 * width);
           off += width;
           y++;
        }
      }
      backend_cb->set_pixels (ctx, backend_cb->set_pixels_user_data, 
                              x0, y0, width, render_height, (uint16_t*)scaled,
                              width * render_height * bpp);
      y0 += render_height;
      yo += render_height;
    } while (y0 < y1);

    if (backend_cb->update_fb && (flags & CTX_FLAG_INTRA_UPDATE))
      backend_cb->update_fb (ctx, backend_cb->update_fb_user_data);
    // abort does not happen for low-res update
  }
  else
  {
    int render_height = height;
    if (width * render_height > memory_budget / bpp)
    {
       render_height = memory_budget / width / bpp;
    }
    CtxRasterizer *r = ctx_rasterizer_init((CtxRasterizer*)&backend_cb->rasterizer,
                         ctx, NULL, &ctx->state, fb, 0, 0, width, height,
                         width * bpp, format, CTX_ANTIALIAS_DEFAULT);
    ((CtxBackend*)r)->destroy = (CtxDestroyNotify)ctx_rasterizer_deinit;
    ctx_push_backend (ctx, r);

    do
    {
      render_height = ctx_mini (render_height, y1-y0+1);
      ctx_rasterizer_init (r, ctx, NULL, &ctx->state, fb, 0, 0, width,
                   render_height, width * bpp, format, CTX_ANTIALIAS_DEFAULT);
    ((CtxBackend*)r)->destroy = (CtxDestroyNotify)ctx_rasterizer_deinit;

      if ((flags & CTX_FLAG_KEEP_DATA) == 0)
        memset (fb, 0, width * bpp * render_height);

      ctx_translate (ctx, -1.0f * x0, -1.0f * y0);
      if (active_mask)
        ctx_render_ctx_masked (ctx, ctx, active_mask);
      else
        ctx_render_ctx (ctx, ctx);

      backend_cb->set_pixels (ctx, backend_cb->set_pixels_user_data, 
                              x0, y0, width, render_height, (uint16_t*)fb,
                              width * render_height * bpp);

      if (backend_cb->update_fb && (flags & CTX_FLAG_INTRA_UPDATE))
        abort = backend_cb->update_fb (ctx, backend_cb->update_fb_user_data);

      y0 += render_height;
    } while (y0 < y1 && !abort);
    ctx_pop_backend (ctx);    
  }
  return abort;
}


/* XXX: todo replace this with a single function that writes
 * to pointers, like path_extent
 */
static int
ctx_cb_x0 (Ctx *ctx)
{
  CtxCbBackend *cb_backend = (CtxCbBackend*)ctx->backend;
  return cb_backend->min_col * (ctx_width (ctx)/CTX_HASH_COLS);
}

static int
ctx_cb_x1 (Ctx *ctx)
{
  CtxCbBackend *cb_backend = (CtxCbBackend*)ctx->backend;
  return (cb_backend->max_col+1) * (ctx_width (ctx)/CTX_HASH_COLS)-1;
}

static int
ctx_cb_y0 (Ctx *ctx)
{
  CtxCbBackend *cb_backend = (CtxCbBackend*)ctx->backend;
  return cb_backend->min_row * (ctx_height (ctx)/CTX_HASH_ROWS);
}

static int
ctx_cb_y1 (Ctx *ctx)
{
  CtxCbBackend *cb_backend = (CtxCbBackend*)ctx->backend;
  return (cb_backend->max_row+1) * (ctx_height (ctx)/CTX_HASH_ROWS)-1;
}

void
ctx_cb_extent (Ctx *ctx, float *x0, float *y0, float *x1, float *y1)
{
  if (x0) *x0 = ctx_cb_x0 (ctx);
  if (y0) *y0 = ctx_cb_y0 (ctx);
  if (x1) *x1 = ctx_cb_x1 (ctx);
  if (y1) *y1 = ctx_cb_y1 (ctx);
}

static void
ctx_cb_end_frame (Ctx *ctx)
{
  CtxCbBackend *cb_backend = (CtxCbBackend*)ctx->backend;
  static int64_t prev_time = 0;
  int64_t cur_time = ctx_ticks () / 1000;
  int width = ctx_width (ctx);
  int height = ctx_height (ctx);

  int tile_width = width / CTX_HASH_COLS;
  int tile_height = height / CTX_HASH_ROWS;
  if (cb_backend->flags & (CTX_FLAG_GRAY2|CTX_FLAG_GRAY4|CTX_FLAG_GRAY8|CTX_FLAG_RGB332))
      cb_backend->flags|=CTX_FLAG_LOWFI;

  if (cb_backend->flags & CTX_FLAG_SHOW_FPS)
  {
   
  float em = ctx_height (ctx) * 0.08f;
  float y = em;
  ctx_font_size (ctx, em);
  ctx_rectangle (ctx, ctx_width(ctx)-(em*4), 0, em *4, em * 1.1f);
  ctx_rgba (ctx, 0, 0, 0, 0.7f);
  ctx_fill (ctx);

  ctx_rgba (ctx, 1, 1, 0, 1);

  if (prev_time)
  {
    char buf[22];
    float fps = 1.0f/((cur_time-prev_time)/1000.0f);
    ctx_move_to (ctx, width - (em * 3.8f), y);
    sprintf (buf, "%2.1f fps", (double)fps);
    ctx_text (ctx, buf);
    ctx_begin_path (ctx);
  }
  prev_time = cur_time;
  }


  if (cb_backend->flags & CTX_FLAG_HASH_CACHE)
  {
    CtxState    *state = &ctx->state;

    CtxPixelFormat format = cb_backend->format;
    int bpp               = ctx_pixel_format_bits_per_pixel (format) / 8;
    int tile_dim          = tile_width * tile_height * bpp;

    CtxRasterizer *rasterizer = (CtxRasterizer*)&cb_backend->rasterizer;
    ctx_hasher_init (rasterizer, ctx, state, width, height, CTX_HASH_COLS, CTX_HASH_ROWS, &ctx->drawlist);
    ((CtxBackend*)rasterizer)->destroy = (CtxDestroyNotify)ctx_rasterizer_deinit;

    ctx_push_backend (ctx, rasterizer);

    int dirty_tiles = 0;
    ctx_render_ctx (ctx, ctx);

    cb_backend->max_col = -100;
    cb_backend->min_col = 100;
    cb_backend->max_row = -100;
    cb_backend->min_row = 100;

    uint32_t active_mask = 0;
    uint32_t *hashes = ((CtxHasher*)(ctx->backend))->hashes;
    int tile_no =0;
    int low_res_tiles = 0;

      for (int row = 0; row < CTX_HASH_ROWS; row++)
        for (int col = 0; col < CTX_HASH_COLS; col++, tile_no++)
        {
          uint32_t new_hash = hashes[tile_no];
          if (new_hash &&
              new_hash != cb_backend->hashes[tile_no])
          {
            dirty_tiles++;
            cb_backend->max_col = ctx_maxi (cb_backend->max_col, col);
            cb_backend->max_row = ctx_maxi (cb_backend->max_row, row);
            cb_backend->min_col = ctx_mini (cb_backend->min_col, col);
            cb_backend->min_row = ctx_mini (cb_backend->min_row, row);
          }
          else
          {
            low_res_tiles += cb_backend->res[tile_no];
          }
        }


      int in_low_res = 0;
      int old_flags = cb_backend->flags;
      if (cb_backend->flags & CTX_FLAG_LOWFI)
      {
        in_low_res = 1; // default to assume we're in low res
        if (dirty_tiles == 0 && low_res_tiles !=0) // no dirty and got low_res_tiles
        {
            cb_backend->max_col = -100;
            cb_backend->min_col = 100;
            cb_backend->max_row = -100;
            cb_backend->min_row = 100;
            tile_no = 0;
            for (int row = 0; row < CTX_HASH_ROWS; row++)
              for (int col = 0; col < CTX_HASH_COLS; col++, tile_no++)
              {
                if (cb_backend->res[tile_no])
              {
                cb_backend->max_col = ctx_maxi (cb_backend->max_col, col);
                cb_backend->max_row = ctx_maxi (cb_backend->max_row, row);
                cb_backend->min_col = ctx_mini (cb_backend->min_col, col);
                cb_backend->min_row = ctx_mini (cb_backend->min_row, row);
                dirty_tiles++;
              }
              }

            active_mask = 0;
            for (int row = cb_backend->min_row; row <= cb_backend->max_row; row++)
            for (int col = cb_backend->min_col; col <= cb_backend->max_col; col++)
            {
              tile_no = row * CTX_HASH_COLS + col;
              int tile_no = 0;
              active_mask |= (1<<tile_no);
            }
            if ((cb_backend->flags & CTX_FLAG_STAY_LOW) == 0)
              cb_backend->flags &= ~CTX_FLAG_LOWFI;
            in_low_res = 0;
        }
        else if (dirty_tiles)
        {
            int memory = (cb_backend->max_col-cb_backend->min_col+1)*
                          (cb_backend->max_row-cb_backend->min_row+1)*tile_dim;
            if (memory < cb_backend->memory_budget && 0)
            {
              in_low_res = 0;
              if ((cb_backend->flags & CTX_FLAG_STAY_LOW) == 0)
                cb_backend->flags &= ~CTX_FLAG_LOWFI;
            }
        }
      }

      ctx_pop_backend (ctx); // done with hasher
      if (dirty_tiles)
      {
         int x0 = cb_backend->min_col     * tile_width;
         int y0 = cb_backend->min_row     * tile_height;
         int x1 = (cb_backend->max_col+1) * tile_width - 1;
         int y1 = (cb_backend->max_row+1) * tile_height - 1;
#if 0
         if (cb_backend->flags & CTX_FLAG_DAMAGE_CONTROL)
         {
           ctx_save (ctx);
           ctx_rectangle (ctx, x0, y0, x1-x0+1, y1-y0+1);
           ctx_rgba (ctx, 1,0,0,0.5);
           ctx_line_width (ctx, 4.0);
           ctx_stroke (ctx);
           ctx_restore (ctx);
         }
#endif

         //int width = x1 - x0 + 1;
         //int height = y1 - y0 + 1;
         int abort = 0;
         int abortable = 1;

         if (dirty_tiles <= 4 && low_res_tiles <= 4)
         {
           in_low_res = 0;
           abortable = 0;
         }

         if (in_low_res)
         {
             abort = ctx_render_cb (cb_backend, x0, y0, x1, y1, active_mask);
             for (int row = cb_backend->min_row; row <= cb_backend->max_row; row++)
               for (int col = cb_backend->min_col; col <= cb_backend->max_col; col++)
               {
                 int tile_no = row * CTX_HASH_COLS + col;
                 //if (abort)
                 //{
                   //cb_backend->res[tile_no]=0;
                   //cb_backend->hashes[tile_no]= 23;
                 //}
                 //else
                 {
                   cb_backend->hashes[tile_no]= hashes[tile_no];
                   cb_backend->res[tile_no]=in_low_res;
                 }
               }
         }
         else // full res
         {
           tile_no = 0;

           for (int row = 0; row < CTX_HASH_ROWS; row++)
           {
             for (int col = 0; col < CTX_HASH_COLS; col++)
               if (!abort)
             {
               tile_no = row * CTX_HASH_COLS + col;
               active_mask = 1<<tile_no;
               uint32_t new_hash = hashes[tile_no];
               int used_tiles = 1;

               if ((new_hash != cb_backend->hashes[tile_no]) ||
                   cb_backend->res[tile_no])
               {
                    int x0 = col * tile_width;
                    int y0 = row * tile_height;
                    int x1 = x0 +  tile_width-1;
                    int y1 = y0 +  tile_height-1;

#if 1
             int max_tiles = (cb_backend->memory_budget / tile_dim);
                    int cont = 1;
                    /* merge horizontal adjecant dirty tiles */
                    if (used_tiles < max_tiles && col + 1 < CTX_HASH_COLS) do {
                      uint32_t next_new_hash = hashes[tile_no+used_tiles];
                      if ((next_new_hash != cb_backend->hashes[tile_no+used_tiles]) ||
                        cb_backend->res[tile_no+used_tiles])
                      {
                        active_mask |= (1 << (tile_no+used_tiles));
                        used_tiles ++;
                        x1 += (ctx_width (ctx)/CTX_HASH_COLS);
                      }
                      else
                      {
                        cont = 0;
                      }
                    } while (used_tiles < max_tiles && cont && col + used_tiles < CTX_HASH_COLS);
#endif


                    abort = ctx_render_cb (cb_backend, x0, y0, x1, y1, active_mask);
                    {
                      for (int i = 0; i < used_tiles; i ++)
                      {
                        cb_backend->res[tile_no + i]=0;
                        cb_backend->hashes[tile_no + i] = hashes[tile_no+i];
                      }
                    }
                    if (!abortable)
                       abort = 0;
                    col += used_tiles - 1;
                  }
               }
           }
             }
           }
      cb_backend->flags = old_flags;
  }
  else
  {
    ctx_render_cb (cb_backend, 0, 0, ctx_width(ctx)-1, ctx_height(ctx)-1, 0);
  }
  if (cb_backend->update_fb)
    cb_backend->update_fb (ctx, cb_backend->update_fb_user_data);
}

Ctx *ctx_new_cb (int width, int height, CtxPixelFormat format,
                 void (*set_pixels) (Ctx *ctx, void *user_data, 
                                     int x, int y, int w, int h, void *buf,
                                     int buf_size),
                 void *set_pixels_user_data,
                 int (*update_fb) (Ctx *ctx, void *user_data),
                 void *update_fb_user_data,
                 int   memory_budget,
                 void *scratch_fb,
                 int   flags)
{
  Ctx *ctx                   = ctx_new_drawlist (width, height);
  CtxBackend    *backend     = (CtxBackend*)ctx_calloc (sizeof (CtxCbBackend), 1);
  CtxCbBackend  *cb_backend  = (CtxCbBackend*)backend;
  backend->end_frame         = ctx_cb_end_frame;
  cb_backend->format         = format;
  cb_backend->fb             = (uint16_t*)scratch_fb;
  cb_backend->set_pixels     = set_pixels;
  cb_backend->update_fb      = update_fb;
  cb_backend->set_pixels_user_data = set_pixels_user_data;
  cb_backend->update_fb_user_data   = update_fb_user_data;
  cb_backend->memory_budget  = memory_budget;
  ctx_set_backend (ctx, backend);
  ctx_cb_set_flags (ctx, flags);
  cb_backend->ctx = ctx;
  if (!scratch_fb)
  {
    cb_backend->memory_budget = 0;
    ctx_cb_set_memory_budget (ctx, memory_budget);
  }
#if CTX_EVENTS
  ctx_get_event (ctx);
#endif
  return ctx;
}

#if CTX_TFT_ESPI

static void ctx_tft_set_pixels (Ctx *ctx, void *user_data, int x, int y, int w, int h, void *buf, int buf_size)
{
  TFT_eSPI *tft = (TFT_eSPI*)user_data;
  tft->pushRect (x, y, w, h, (uint16_t*)buf);
}

Ctx *ctx_new_tft (TFT_eSPI *tft,
                  int memory_budget,
                  void *scratch_fb,
                  int flags)
{
  return ctx_new_cb (tft->width(), tft->height(), 
                     CTX_FORMAT_RGB565_BYTESWAPPED,
                     ctx_tft_set_pixels,
                     tft,
                     NULL,
                     NULL,
                     memory_budget,
                     scratch_fb,
                     flags);
}
#endif

#ifdef EMSCRIPTEN
#include "emscripten.h"

#include <unistd.h>

int width = 512;
int height = 384;

static uint8_t *fb = NULL;
static Ctx *em_ctx = NULL;

CTX_EXPORT unsigned char *
get_fb(int w, int h) {
  if (fb)
  {
    if (width == w && height == h) return fb;
    free (fb); // this is not using the ctx allocator
               // and will thus not be part of the micropython heap budget
    fb = NULL;
  }
  width  = w;
  height = h;
  fb = calloc (w *h, 4);
  if (em_ctx) ctx_destroy (em_ctx);
  em_ctx = NULL;
  return fb;
}

EMSCRIPTEN_KEEPALIVE
float pointer_x = 0;
EMSCRIPTEN_KEEPALIVE
float pointer_y = 0;
EMSCRIPTEN_KEEPALIVE
int32_t pointer_down = 0;
int32_t pointer_was_down = 0;


static uint32_t key_queue[32];
static int key_queue_head = 0; // read head
static int key_queued = 0;

EMSCRIPTEN_KEEPALIVE
void ctx_wasm_queue_key_event (int type, int keycode)
{
  if (key_queued >= 31) return;
  int pos = (key_queue_head + key_queued) % 32;
  key_queue[pos * 2 + 0] = type;
  key_queue[pos * 2 + 1] = keycode;
  key_queued ++;
}

int ctx_wasm_get_key_event (int *type, int *keycode)
{
  if (!key_queued)
    return -1;

  *type = key_queue[key_queue_head * 2 + 0];
  *keycode = key_queue[key_queue_head * 2 + 1];

  key_queued--;
  key_queue_head++;
  key_queue_head = key_queue_head % 16;

  return 0;
}

int update_fb (Ctx *ctx, void *user_data)
{
  EM_ASM(
    var canvas = document.getElementById('c');
    var context = canvas.getContext('2d');

     if (!canvas.regevents)
     {
       canvas.onmousedown = function (e){
          var loc = windowToCanvas (canvas, e.clientX, e.clientY);
          setValue(_pointer_x, loc.x, "float");
          setValue(_pointer_y, loc.y, "float");
          setValue(_pointer_down, 1, "i32");
          e.stopPropagate=1;
                       };
       canvas.onmouseup = function (e){
          var loc = windowToCanvas (canvas, e.clientX, e.clientY);
          setValue(_pointer_x, loc.x, "float");
          setValue(_pointer_y, loc.y, "float");
          setValue(_pointer_down, 0, "i32");
          e.stopPropagate=1;
                       };
       canvas.onmousemove = function (e){
          var loc = windowToCanvas (canvas, e.clientX, e.clientY);
          setValue(_pointer_x, loc.x, "float");
          setValue(_pointer_y, loc.y, "float");
          e.stopPropagate=1;
                       };
       canvas.onkeydown = function (e){
          _ctx_wasm_queue_key_event (1, e.keyCode);
                       e.preventDefault();
                       e.stopPropagate = 1;
                       };

       canvas.onkeyup = function (e){
          _ctx_wasm_queue_key_event (2, e.keyCode);
                       e.preventDefault();
                       e.stopPropagate = 1;
                       };
       canvas.regevents = true;
     }
  );

#ifdef EMSCRIPTEN
#ifdef ASYNCIFY
   emscripten_sleep(0);
#endif
#endif

   int ret = 0;

   if (key_queued)
     while (key_queued)
   {
     int type = 0 , keycode = 0;

     ctx_wasm_get_key_event (&type, &keycode);
     switch (type)
     {
       case 1:
         ctx_key_down(ctx,keycode,NULL,0);
         ctx_key_press(ctx,keycode,NULL,0);
         ret = 1;
         break;
       case 2:
         ctx_key_up(ctx,keycode,NULL,0);
         ret = 1;
         break;
     }
   }

   if (pointer_down && !pointer_was_down)
   {
      ctx_pointer_press (ctx, pointer_x, pointer_y, 0, 0);
      ret = 1;
   } else if (!pointer_down && pointer_was_down)
   {
      ctx_pointer_release (ctx, pointer_x, pointer_y, 0, 0);
      ret = 1;
   } else if (pointer_down)
   {
      ctx_pointer_motion (ctx, pointer_x, pointer_y, 0, 0);
      ret = 1;
   }

   pointer_was_down = pointer_down;

   return ret;
}

EMSCRIPTEN_KEEPALIVE
uint8_t wasm_scratch[1024*1024*4];

static void set_pixels (Ctx *ctx, void *user_data, int x0, int y0, int w, int h, void *buf, int buf_size)
{
  uint8_t *src = (uint8_t*)buf;
  int in_w = w;
  if (x0 < 0) x0 = 0;
  if (y0 < 0) y0 = 0;
  if (x0 + w > ctx_width (ctx))
  {
     fprintf (stderr, "adjusting xbounds from %i %i\n", x0, w);
     w = ctx_width (ctx) - x0;
  }
  if (y0 + h > ctx_height (ctx))
  {
     h = ctx_height (ctx) - y0;
     fprintf (stderr, "adjusting ybounds\n");
  }
  for (int i = 0; i < h; i++)
  {
    ctx_RGB565_BS_to_RGBA8 (NULL, x0, src + i * in_w * 2,
                    wasm_scratch + i * w * 4, w);
  }

  EM_ASM(
    var x0 = $0;
    var y0 = $1;
    var w = $2;
    var h = $3;
    var canvas = document.getElementById('c');
    var context = canvas.getContext('2d');
    var _ctx = _ctx_wasm_get_context(0); // we presume an earlier
                                         // call to have passed
                                         // the memory budget
    const offset = _get_fb(canvas.width, canvas.height);
    const imgData = context.createImageData(w,h);

    const linearMem = new Uint8Array(wasmMemory.buffer, _wasm_scratch,
                                     w*h*4);

    for (let i = 0; i < w * h;i++)
    {
      //var a = linearMem[i*4+3];
      //var r = 1.0;
      //if (a!=0) r = 255.0/a;
      imgData.data[i*4+0] = linearMem[i*4+0];// * r;
      imgData.data[i*4+1] = linearMem[i*4+1];// * r;
      imgData.data[i*4+2] = linearMem[i*4+2];// * r;
      imgData.data[i*4+3] = 255;
    }
    context.putImageData(imgData,x0,y0);
  , x0,y0, w, h);

}

#if 0
int wasm_damage_control = 0;

CTX_EXPORT
void wasm_set_damage_control(int val)
{
  wasm_damage_control = val;
}
#endif

void ctx_wasm_reset (void)
{
  if (fb) free (fb); fb = NULL;
  em_ctx = NULL;
}

Ctx *ctx_wasm_get_context (int memory_budget)
{


EM_ASM(
    {var canvas = document.getElementById('c');
     const offset = _get_fb(canvas.width, canvas.height);

     //var dc = document.getElementById('damagecontrol');
     //if (dc)
     //{
     //  _wasm_set_damage_control(dc.checked?1:0);
     //}
   }
);

   if (em_ctx && memory_budget)
   {
      CtxCbBackend *cb_backend = (CtxCbBackend*)em_ctx->backend;
      if (memory_budget != cb_backend->memory_budget)
      {
         ctx_cb_set_memory_budget (em_ctx, memory_budget);
         ctx_cb_set_flags (em_ctx, 0);
      }
   }



   if (!em_ctx){
      em_ctx = ctx_new_cb (width, height, CTX_FORMAT_RGB565_BYTESWAPPED,
                           set_pixels, 
                           NULL,
                           update_fb,
                           NULL,
                           memory_budget, NULL, 
                           0);
   }

#if 0
   if (wasm_damage_control)
   {
     int flags = ctx_cb_get_flags (em_ctx);
     flags |= CTX_FLAG_DAMAGE_CONTROL;
     ctx_cb_set_flags (em_ctx, flags);
   }
   else
   {
     int flags = ctx_cb_get_flags (em_ctx);
     flags &= ~CTX_FLAG_DAMAGE_CONTROL;
     ctx_cb_set_flags (em_ctx, flags);
   }
#endif
   return em_ctx;
}

#endif

static CtxFont ctx_fonts[CTX_MAX_FONTS];// = NULL;
static int     ctx_font_count = 0;
typedef struct CtxResolvedFont {uint32_t sqstr; int font_no;} CtxResolvedFont;

#if CTX_RESOLVED_FONTS!=0
static CtxResolvedFont ctx_resolved_fonts[CTX_RESOLVED_FONTS];
#endif

static void ctx_font_setup (Ctx *ctx);

static inline int ctx_font_is_monospaced (CtxFont *font)
{
#if CTX_ONE_FONT_ENGINE
  return 0; // XXX
#else
  return font->monospaced;
#endif
}

#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)
{
  char buf[256];
  ctx_font_setup (NULL);
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }

  if (!stbtt_InitFont (&ctx_fonts[ctx_font_count].stb.ttf_info, ttf_contents, 0) )
    {
      ctx_log ( "Font init failed\n");
      return -1;
    }

  if (name == NULL || !strcmp (name, "import")){
  int length = 0;
  const char *val = stbtt_GetFontNameDefault (&ctx_fonts[ctx_font_count].stb.ttf_info,
                          &length);
  if (val)
  {
    memset(buf,0,sizeof(buf));
    memcpy(buf,val, length);
    name = buf;
  }
  else
    name = "import";
  }

  ctx_fonts[ctx_font_count].type = 1;
  ctx_fonts[ctx_font_count].stb.name = (char *) ctx_malloc (ctx_strlen (name) + 1);
  ctx_strcpy ( (char *) ctx_fonts[ctx_font_count].stb.name, name);

  ctx_fonts[ctx_font_count].engine = &ctx_font_engine_stb;

  CtxFont *font = &ctx_fonts[ctx_font_count];
  if (font->engine->glyph_width (font, NULL, 'O') ==
      font->engine->glyph_width (font, NULL, 'I'))
  {
    font->monospaced = 1;
  }
  else
    font->monospaced = 0;

  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)
{
  ctx_font_setup (NULL);
  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 (Ctx *ctx, CtxFont *font, uint32_t unichar)
{
  stbtt_fontinfo *ttf_info = &font->stb.ttf_info;

#if CTX_GLYPH_CACHE
  uint32_t hash = ((((size_t)(font) * 23) ^ unichar) * 17) %
            (CTX_GLYPH_CACHE_SIZE);
  if (ctx)
  {
    if (ctx->glyph_index_cache[hash].font == font &&
        ctx->glyph_index_cache[hash].unichar == unichar)
          return ctx->glyph_index_cache[hash].offset;
  }
#endif

  int index = stbtt_FindGlyphIndex (ttf_info, unichar);

#if CTX_GLYPH_CACHE
  if (ctx)
  {
    ctx->glyph_index_cache[hash].font    = font;
    ctx->glyph_index_cache[hash].unichar = unichar;
    ctx->glyph_index_cache[hash].offset  = index;
  }
#endif

  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          = 1.0f;
  if (ctx)
      font_size = ctx->state.gstate.font_size;
  float scale              = stbtt_ScaleForPixelHeight (ttf_info, font_size);
  int advance, lsb;
  int glyph = ctx_glyph_stb_find (ctx, font, unichar);

#if CTX_EVENTS
  if (ctx && ctx_backend_type (ctx) == CTX_BACKEND_TERM && ctx_fabsf(3.0f - font_size) < 0.03f)
    return 2;
#endif

  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 (ctx, font, unicharA);
  int glyphB = ctx_glyph_stb_find (ctx, 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 (ctx, 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

static inline int ctx_font_get_length (CtxFont *font)
{
   return font->ctx.data->data.u32[1];
}

#if CTX_FONT_ENGINE_CTX

static inline uint32_t
ctx_glyph_find_next (CtxFont *font, Ctx *ctx, int offset)
{
  int length = ctx_font_get_length (font);
  for (int i = offset; i < length; i++)
  {
    CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
    if (entry->code == CTX_DEFINE_GLYPH)
    {
      return entry->data.u32[0];
    }
  }
  return 0;
}

static int ctx_glyph_find_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
#if CTX_GLYPH_CACHE
  uint32_t hash = ((((size_t)(font) * 23) ^ unichar) * 17) %
            (CTX_GLYPH_CACHE_SIZE);
  if (ctx)
  {
    if (ctx->glyph_index_cache[hash].font == font &&
        ctx->glyph_index_cache[hash].unichar == unichar)
          return ctx->glyph_index_cache[hash].offset;
  }
#endif
#if 1
  int length = ctx_font_get_length (font);
  for (int i = 0; i < length; i++)
  {
    CtxEntry *entry = (CtxEntry *) &font->ctx.data[i];
    if (entry->code == CTX_DEFINE_GLYPH &&
        entry->data.u32[0] == unichar)
    {
#if CTX_GLYPH_CACHE
       if (ctx)
       {
         ctx->glyph_index_cache[hash].font    = font;
         ctx->glyph_index_cache[hash].unichar = unichar;
         ctx->glyph_index_cache[hash].offset  = i;
       }
#endif
       return i;
       // XXX this could be prone to insertion of valid header
       // data in included bitmaps.. is that an issue?
       //   
    }
  }
#else
  int start = -1;
  int end = ctx_font_get_length (font);
  int max_iter = 10;
    uint32_t start_glyph = ctx_glyph_find_next (font, ctx, start);
    if (unichar  == start_glyph)
    {
#if CTX_GLYPH_CACHE
       if (ctx)
       {
         ctx->glyph_index_cache[hash].font    = font;
         ctx->glyph_index_cache[hash].unichar = unichar;
         ctx->glyph_index_cache[hash].offset  = start;
       }
#endif
       return start;
    }

  do {
    int middle = (start + end) / 2;

    uint32_t middle_glyph = ctx_glyph_find_next (font, ctx, middle);
    if (unichar  == middle_glyph)
    {
#if CTX_GLYPH_CACHE
       if (ctx)
       {
         ctx->glyph_index_cache[hash].font    = font;
         ctx->glyph_index_cache[hash].unichar = unichar;
         ctx->glyph_index_cache[hash].offset  = middle;
       }
#endif
       return middle;
    }
    else if (unichar < middle_glyph)
    {
       end = middle;
    } else
    {
       start = middle;
    }

    if (start == end)
      return -1;
  } while (max_iter -- > 0);
#endif
  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;

#if CTX_EVENTS
  if (ctx_backend_type (ctx) == CTX_BACKEND_TERM && ctx_fabsf(3.0f - font_size) < 0.03f)
    return 0.0f;
#endif

  int length = ctx_font_get_length (font);
  for (int i = first_kern + 1; i < 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.0f * font_size / CTX_BAKE_FONT_SIZE; }
        }
      if (entry->code == CTX_DEFINE_GLYPH)
        return 0.0;
    }
  return 0.0;
}

static float
ctx_glyph_width_ctx (CtxFont *font, Ctx *ctx, uint32_t unichar)
{
  float font_size = 1.0f;
  if (ctx)
  {
    CtxState *state = &ctx->state;
    font_size = state->gstate.font_size;
  }
  int   start     = ctx_glyph_find_ctx (font, ctx, unichar);
  if (start < 0)
    { return 0.0; }  // XXX : fallback

#if CTX_EVENTS
  if (ctx && ctx_backend_type (ctx) == CTX_BACKEND_TERM && 
                  ctx_fabsf(3.0f - font_size) < 0.03f 
                  )
    return 2.0f;
#endif

  int length = ctx_font_get_length (font);
  for (int i = start; i < 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.0f * 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 CTX_ONE_FONT_ENGINE==0
  if (font->type == 0)
#endif
  {
  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->backend && ((CtxRasterizer*)(ctx->backend))->in_shadow)
      {
        ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->backend);
        ((CtxRasterizer*)(ctx->backend))->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->backend && ((CtxRasterizer*)(ctx->backend))->in_shadow)
      {
        ctx_rasterizer_shadow_fill ((CtxRasterizer*)ctx->backend);
        ((CtxRasterizer*)(ctx->backend))->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;
  drawlist.entries = font->ctx.data;
  int length = ctx_font_get_length (font);
  drawlist.count = length;
  drawlist.size  = length;
  drawlist.flags = CTX_DRAWLIST_DOESNT_OWN_ENTRIES;
  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]; // needs index
}
#endif

static void ctx_font_init_ctx (CtxFont *font)
{
}

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

#if CTX_ONE_FONT_ENGINE==0
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,
};
#endif

int
ctx_load_font_ctx (const char *name, const void *data, int length)
{
  ctx_font_setup (NULL);
  if (length % sizeof (CtxEntry) )
    { return -1; }
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }

#if CTX_ONE_FONT_ENGINE==0
  ctx_fonts[ctx_font_count].type = 0;
  ctx_fonts[ctx_font_count].engine = &ctx_font_engine_ctx;
#endif
  //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_font_count++;

#if CTX_ONE_FONT_ENGINE==0
  CtxFont *font = &ctx_fonts[ctx_font_count-1];
  if (font->engine->glyph_width (font, NULL, 'O') ==
      font->engine->glyph_width (font, NULL, 'I'))
  {
    font->monospaced = 1;
  }
  else
    font->monospaced = 0;
#endif

  return ctx_font_count-1;
}

#if CTX_FONTS_FROM_FILE
int
ctx_load_font_ctx_file (const char *name, const char *path)
{
  ctx_font_setup (NULL);
  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.0f * 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/%010x", font->ctx_fs.path, (uint32_t)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_drawlist (100, 100);
    ctx_parse (glyph_ctx, (char*)data);
    for (uint32_t 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.0f * font_size / CTX_BAKE_FONT_SIZE;
    }
    ctx_free (data);
    ctx_destroy (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/%010x", 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_drawlist (100, 100);
    ctx_parse (glyph_ctx, (char*)data);
    int ret = ctx_glyph_drawlist (font, ctx, &(glyph_ctx->drawlist),
                                  unichar, stroke);
    ctx_free (data);
    ctx_destroy (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
{
  ctx_font_setup (NULL);
  if (ctx_font_count >= CTX_MAX_FONTS)
    { return -1; }

  ctx_fonts[ctx_font_count].type = 3;
  ctx_fonts[ctx_font_count].ctx_fs.name = strdup (name);
  ctx_fonts[ctx_font_count].ctx_fs.path = ctx_strdup (path);
  int path_len = ctx_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
#if CTX_ONE_FONT_ENGINE
  return ctx_glyph_ctx (font, ctx, unichar, stroke);
#else
  return font->engine->glyph (font, ctx, unichar, stroke);
#endif

}

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) );
  if (stroke)
    unichar = unichar | (1<<31);
  commands[0] = ctx_u32 (CTX_GLYPH, unichar, 0);
  //commands[1].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];
#if CTX_ONE_FONT_ENGINE
  return ctx_glyph_width_ctx (font, ctx, unichar);
#else
  return font->engine->glyph_width (font, ctx, unichar);
#endif
}

static float
ctx_glyph_kern (Ctx *ctx, int unicharA, int unicharB)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];
#if CTX_ONE_FONT_ENGINE
  return ctx_glyph_kern_ctx (font, ctx, unicharA, unicharB);
#else
  return font->engine->glyph_kern (font, ctx, unicharA, unicharB);
#endif
}

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);
      }
    }
}


#define CTX_MAX_WORD_LEN 128

#if 1
static int ctx_glyph_find (Ctx *ctx, CtxFont *font, uint32_t unichar)
{
  int length = ctx_font_get_length (font);
  for (int i = 0; i < 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 inline int
_ctx_text_substitute_ligatures (Ctx *ctx, CtxFont *font,
                                uint32_t *unichar, uint32_t next_unichar)
{
  if (ctx_font_is_monospaced (font))
    return 0;
  if (*unichar == 'f')
    switch (next_unichar)
    {
      case 'f': if (ctx_glyph_find (ctx, font, 0xfb00))
        {
          *unichar = 0xfb00;
          return 1;
        }
        break;
      case 'i':
        if (ctx_glyph_find (ctx, font, 0xfb01))
        {
          *unichar = 0xfb01;
          return 1;
        }
        break;
      case 'l': 
        if (ctx_glyph_find (ctx, font, 0xfb02))
        {
          *unichar = 0xfb02;
          return 1;
        }
        break;
      case 't': 
        if (ctx_glyph_find (ctx, font, 0xfb05))
        {
          *unichar = 0xfb05;
          return 1;
        }
        break;
    }
  return 0;
}

static void
_ctx_text (Ctx        *ctx,
           const char *string,
           int         stroke,
           int         visible)
{
  char word[CTX_MAX_WORD_LEN];
  int word_len = 0;
  CtxState *state = &ctx->state;
  CtxFont *font = &ctx_fonts[state->gstate.font];
  float x = ctx->state.x;
  word[word_len]=0;
  switch ( (int) ctx_state_get (state, SQZ_textAlign) )
    //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, SQZ_textBaseline) )
    {
      case CTX_TEXT_BASELINE_HANGING:
        /* XXX : crude */
        baseline_offset = ctx->state.gstate.font_size  * 0.55f;
        break;
      case CTX_TEXT_BASELINE_TOP:
        /* XXX : crude */
        baseline_offset = ctx->state.gstate.font_size  * 0.7f;
        break;
      case CTX_TEXT_BASELINE_BOTTOM:
        baseline_offset = -ctx->state.gstate.font_size * 0.1f;
        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.25f;
        break;
    }
  float x0 = x;
  float x1 = x + 10000.0f;
  
  float wrap_left = ctx_get_wrap_left (ctx);
  float wrap_right = ctx_get_wrap_right (ctx);
  if (wrap_left != wrap_right)
  {
    x0 = wrap_left;
  }

  if (*string)
  for (const char *utf8 = string; utf8 && ( (utf8==string ) || utf8[-1]); utf8 = *utf8?ctx_utf8_skip (utf8, 1):NULL)
    {
      if (*utf8 == '\n' ||
          *utf8 == ' ' ||
          *utf8 == '\0')
        {
          float word_width = 0.0;
          word[word_len]=0;

          for (const char *bp = &word[0]; *bp; bp = ctx_utf8_skip (bp, 1))
          {
            uint32_t unichar      = ctx_utf8_to_unichar (bp);
            const char *next_utf8 = ctx_utf8_skip (bp, 1);
            uint32_t next_unichar = *next_utf8?ctx_utf8_to_unichar (next_utf8):0;

            if (_ctx_text_substitute_ligatures (ctx, font, &unichar, next_unichar))
              bp++;

            float glyph_width     = ctx_glyph_width (ctx, unichar);
            word_width += glyph_width;
            if (next_unichar)
              word_width += ctx_glyph_kern (ctx, unichar, next_unichar);
          }

          if (wrap_left != wrap_right &&
              x + word_width >= wrap_right)
          {
            y += ctx->state.gstate.font_size * ctx_get_line_height (ctx);
            x = x0;
          }

          for (const char *bp = &word[0]; *bp; bp = ctx_utf8_skip (bp, 1))
          {
            uint32_t unichar      = ctx_utf8_to_unichar (bp);
            const char *next_utf8 = ctx_utf8_skip (bp, 1);
            uint32_t next_unichar = *next_utf8?ctx_utf8_to_unichar (next_utf8):0;

            if (_ctx_text_substitute_ligatures (ctx, font, &unichar, next_unichar))
              bp++;

            float glyph_width = ctx_glyph_width (ctx, unichar);
            if (x + glyph_width >= x1)
            {
              y += ctx->state.gstate.font_size * ctx_get_line_height (ctx);
              x = x0;
            }
            if (visible)
            {
              ctx_move_to (ctx, x, y + baseline_offset);
              _ctx_glyph (ctx, unichar, stroke);
            }
            x += glyph_width;
            if (next_unichar)
              x += ctx_glyph_kern (ctx, unichar, next_unichar );
          }

          if (*utf8 == '\n')
          {
            y += ctx->state.gstate.font_size * ctx_get_line_height (ctx);
            x = x0;
          }
          else if (*utf8 == ' ')
          {
            x += ctx_glyph_width (ctx, ' ');
          }
          word_len=0;
          word[word_len]=0;
        }
      else
      {
        int len = ctx_utf8_len (*utf8);
        for (int i = 0; i < len; i++)
        {
          if (word_len + 1 < CTX_MAX_WORD_LEN-1)
            word[word_len++]=utf8[i];
        }
      }

    }
  if (!visible)
    { ctx->state.x =x; ctx->state.y=y; }
  else
    { ctx_move_to (ctx, x, y); }
}


CtxGlyph *
ctx_glyph_allocate (int n_glyphs)
{
  return (CtxGlyph *) ctx_malloc (sizeof (CtxGlyph) * n_glyphs);
}
void
ctx_glyph_free     (CtxGlyph *glyphs)
{
  ctx_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);
}


int
ctx_font_get_vmetrics (Ctx *ctx,
                       CtxFont *font,
                       float   *ascent,
                       float   *descent,
                       float   *linegap);

int
ctx_font_get_vmetrics (Ctx *ctx,
                       CtxFont *font,
                       float   *ascent,
                       float   *descent,
                       float   *linegap)
{
#if CTX_ONE_FONT_ENGINE
      if (ascent) *ascent=0.8f;
      if (descent)*descent=0.2f;
      if (linegap)*linegap=1.2f;
#else
  //float font_size          = 1.0f;
  //if (ctx)
  //    font_size = ctx->state.gstate.font_size;
  switch (font->type)
  {
#if CTX_FONT_ENGINE_CTX_FS
    case 3:
#endif
    case 0:  
      if (ascent) *ascent=0.8f;
      if (descent)*descent=0.2f;
      if (linegap)*linegap=1.2f;
      return 0;
#if CTX_FONT_ENGINE_STB
    case 1:  
    case 2:  
             {
               int aval,dval,lgval;
               float font_size = 16.0f;
  if (ctx)
      font_size = ctx->state.gstate.font_size;
  stbtt_GetFontVMetrics(&font->stb.ttf_info, &aval, &dval, &lgval);
  float scale = stbtt_ScaleForPixelHeight (&font->stb.ttf_info, font_size);
               if (ascent) *ascent= (aval * scale) / font_size;
               if (descent)*descent= (dval * scale) / font_size;
               if (linegap)*linegap= (lgval * scale) / font_size;
             }
             
             return 0;
#endif
  }
#endif
  return 0;
}

int
ctx_font_extents (Ctx *ctx,
                  float *ascent,
                  float *descent,
                  float *line_gap)
{
  CtxFont *font = &ctx_fonts[ctx->state.gstate.font];
  return ctx_font_get_vmetrics (ctx,
                         font,
                         ascent,
                         descent,
                         line_gap);
}

static const char *ctx_font_get_name (CtxFont *font)
{
#if CTX_ONE_FONT_ENGINE
    return ((char*)(font->ctx.data+2))+1;
#else
  switch (font->type)
  {
    case 0:  return ((char*)(font->ctx.data+2))+1;
#if CTX_FONT_ENGINE_STB
    case 1:  return font->stb.name;
    case 2:  return font->stb.name;
#endif
#if CTX_FONT_ENGINE_CTX_FS
    case 3:  return font->ctx_fs.name;
#endif
  }
  return "-";
#endif
}

static int _ctx_resolve_font (const char *name)
{
  int ret = -1;
#if CTX_RESOLVED_FONTS!=0
  uint32_t sqstr = ctx_strhash (name);
  int pos = sqstr % CTX_RESOLVED_FONTS;
  int tries = 0;
  while (ctx_resolved_fonts[pos].sqstr && tries < CTX_RESOLVED_FONTS)
  {
    if (ctx_resolved_fonts[pos].sqstr == sqstr)
      return ctx_resolved_fonts[pos].font_no;
    pos++;
    pos %= CTX_RESOLVED_FONTS;
    tries++;
  }
#endif

  char temp[ctx_strlen (name)+1];
  /* first we look for exact */
  for (int i = 0; ret < 0 && i < ctx_font_count; i ++)
    {
      if (!ctx_strcmp (ctx_font_get_name (&ctx_fonts[i]), name) )
        { ret = i; }
    }
  /* ... and substring matches for passed in string */
  for (int i = 0; ret < 0 && i < ctx_font_count; i ++)
    {
      if (ctx_strstr (ctx_font_get_name (&ctx_fonts[i]), name) )
        { ret = i; }
    }

  if (ret < 0)
  {
  /* then we normalize some names */
  if (!strncmp (name, "Helvetica", 9))
  {
     memset(temp,0,sizeof(temp));
     strncpy (temp, name + 4, sizeof(temp)-1);
     memcpy (temp, "Arrrr", 5);  // we should match Arial and Arimo
     name = temp;
  }
  else if (!strncmp (name, "Monospace", 9))
  {
     memset(temp,0,sizeof(temp));
     strncpy (temp, name + 2, sizeof(temp)-1);
     memcpy (temp, "Courier", 7); 
     name = temp;
  }
  else if (!strncmp (name, "Mono ", 5))
  {
    memset(temp,0,sizeof(temp));
    strncpy (temp + 3, name, sizeof(temp)-1-3);
    memcpy (temp, "Courier ", 8); 
    name = temp;
  }
  else if (!strcmp (name, "Mono"))
  {
    name = "Courier";
  }
  }

  /* and attempt substring matching with mangled named
   * permitting matches with length and two first chars
   * to be valid
   */
  if (ret < 0 ) {
    char *subname = (char*)name;
    int namelen = 0; 
    if (strchr (subname, ' '))
    {
      subname = strchr (subname, ' ');
      namelen = subname - name;
      subname++;
    }
    for (int i = 0; ret < 0 && i < ctx_font_count; i ++)
    {
      const char *font_name = ctx_font_get_name (&ctx_fonts[i]);
      if (font_name[0]==name[0] &&
          font_name[1]==name[1] &&
          font_name[namelen] == name[namelen] &&
          (namelen == 0 || ctx_strstr (font_name, subname) ))
        ret = i;
    }
  }

  /* then we look for a match of the substring after the first
   * space
   */
  if (ret < 0 && strchr (name, ' '))
  {
     char *subname = strchr (name, ' ');
     for (int i = 0; ret < 0 && i < ctx_font_count; i ++)
     {
       const char *font_name = ctx_font_get_name (&ctx_fonts[i]);
       if (ctx_strstr (font_name, subname) )
         { ret = i; }
     }
  }
#if CTX_RESOLVED_FONTS!=0
  if (ret >=0 && ctx_resolved_fonts[pos].sqstr == 0)
  {
    ctx_resolved_fonts[pos].sqstr = sqstr;
    ctx_resolved_fonts[pos].font_no = ret;
  }
#endif
  return ret;
}

const char *ctx_get_font_name (Ctx *ctx, int no)
{
  if (no >= 0 && no < ctx_font_count)
    return ctx_font_get_name (&ctx_fonts[no]);
  return NULL;
}

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;
}


#if !( defined(CTX_FONT_0) ||\
       defined(CTX_FONT_1) ||\
       defined(CTX_FONT_2) ||\
       defined(CTX_FONT_3) ||\
       defined(CTX_FONT_4) ||\
       defined(CTX_FONT_5) ||\
       defined(CTX_FONT_6) ||\
       defined(CTX_FONT_7) ||\
       defined(CTX_FONT_8) ||\
       defined(CTX_FONT_9) ||\
       defined(CTX_FONT_10) ||\
       defined(CTX_FONT_11) ||\
       defined(CTX_FONT_12) ||\
       defined(CTX_FONT_13) ||\
       defined(CTX_FONT_14) ||\
       defined(CTX_FONT_15) ||\
       defined(CTX_FONT_16) ||\
       defined(CTX_FONT_17) ||\
       defined(CTX_FONT_18) ||\
       defined(CTX_FONT_19) ||\
       defined(CTX_FONT_20) ||\
       defined(CTX_FONT_21))
#define static_FONT(font_string, font_data) \
  ctx_load_font_ctx(font_string, ctx_font_##font_data, sizeof (ctx_font_##font_data))
#define CTX_FONT_0 static_FONT("sans-ctx", ascii)
#endif



static void ctx_font_setup (Ctx *ctx)
{
  static int initialized = 0;
  if (initialized) { 
    if (ctx)
      ctx->fonts = ctx_fonts;
    return;
  }
  initialized = 1;

  //if (!ctx_fonts)
#ifdef EMSCRIPTEN
    //ctx_fonts = calloc (CTX_MAX_FONTS, sizeof (CtxFont));
#else
    //ctx_fonts = ctx_calloc (CTX_MAX_FONTS, sizeof (CtxFont));
#endif
  if (ctx)
    ctx->fonts = &ctx_fonts[0];

  ctx_font_count = 0; 

#if CTX_FONT_ENGINE_CTX_FS
  if (getenv ("CTX_FONT_LIVE_PATH"))
  {
    if (getenv ("CTX_FONT_LIVE_NAME"))
      ctx_load_font_ctx_fs (getenv ("CTX_FONT_LIVE_NAME"), getenv ("CTX_FONT_LIVE_PATH"), 0);
    else
      ctx_load_font_ctx_fs ("Arrrr Regular", getenv ("CTX_FONT_LIVE_PATH"),0);
  }
#endif

#if CTX_FONT_ENGINE_CTX

#ifdef CTX_FONT_0
  CTX_FONT_0;
#endif
#ifdef CTX_FONT_1
  CTX_FONT_1;
#endif
#ifdef CTX_FONT_2
  CTX_FONT_2;
#endif
#ifdef CTX_FONT_3
  CTX_FONT_3;
#endif
#ifdef CTX_FONT_4
  CTX_FONT_4;
#endif
#ifdef CTX_FONT_5
  CTX_FONT_5;
#endif
#ifdef CTX_FONT_6
  CTX_FONT_6;
#endif
#ifdef CTX_FONT_7
  CTX_FONT_7;
#endif
#ifdef CTX_FONT_8
  CTX_FONT_8;
#endif
#ifdef CTX_FONT_9
  CTX_FONT_9;
#endif
#ifdef CTX_FONT_10
  CTX_FONT_10;
#endif
#ifdef CTX_FONT_11
  CTX_FONT_11;
#endif
#ifdef CTX_FONT_12
  CTX_FONT_12;
#endif
#ifdef CTX_FONT_13
  CTX_FONT_13;
#endif
#ifdef CTX_FONT_14
  CTX_FONT_14;
#endif
#ifdef CTX_FONT_15
  CTX_FONT_15;
#endif
#ifdef CTX_FONT_16
  CTX_FONT_16;
#endif
#ifdef CTX_FONT_17
  CTX_FONT_17;
#endif
#ifdef CTX_FONT_18
  CTX_FONT_18;
#endif
#ifdef CTX_FONT_19
  CTX_FONT_19;
#endif
#ifdef CTX_FONT_20
  CTX_FONT_20;
#endif
#ifdef CTX_FONT_21
  CTX_FONT_21;
#endif
#ifdef CTX_FONT_22
  CTX_FONT_22;
#endif
#ifdef CTX_FONT_23
  CTX_FONT_23;
#endif
#ifdef CTX_FONT_24
  CTX_FONT_24;
#endif
#ifdef CTX_FONT_25
  CTX_FONT_25;
#endif
#ifdef CTX_FONT_26
  CTX_FONT_26;
#endif
#ifdef ctx_font_27
  ctx_font_27;
#endif
#ifdef ctx_font_28
  ctx_font_28;
#endif
#ifdef CTX_FONT_29
  CTX_FONT_29;
#endif
#ifdef CTX_FONT_30
  CTX_FONT_30;
#endif
#ifdef CTX_FONT_31
  CTX_FONT_31;
#endif
#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);
}

#if 0
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*) ctx_malloc (needed);
   va_end (ap);
   va_start (ap, format);
   vsnprintf (buffer, needed, format, ap);
   va_end (ap);
   ctx_formatter_addstr (formatter, buffer, -1);
   ctx_free (buffer);
}
#endif


static void
ctx_print_int (CtxFormatter *formatter, int val)
{
  char buf[64];
  char *bp = &buf[0];
  int remainder;
  if (val < 0)
  {
    buf[0]='-';
    bp++;
    remainder = -val;
  }
  else
  remainder = val;

  int len = 0;
  do {
    int digit = remainder % 10;
    bp[len++] = digit + '0';
    remainder /= 10;
  } while (remainder);

  bp[len]=0;
  for (int i = 0; i < len/2; i++)
  {
    int tmp = bp[i];
    bp[i] = bp[len-1-i];
    bp[len-1-i] = tmp;
  }
  len += (val < 0);
  ctx_formatter_addstr (formatter, buf, len);
}

static void
ctx_print_float (CtxFormatter *formatter, float val)
{
  if (val < 0.0f)
  {
    ctx_formatter_addstr (formatter, "-", 1);
    val = -val;
  }
  int remainder = ((int)(val*10000))%10000;
  if (remainder % 10 > 5)
    remainder = remainder/10+1;
  else
    remainder /= 10;

  ctx_print_int (formatter, (int)val);
  if (remainder)
  {
    ctx_formatter_addstr (formatter, ".", 1);
    if (remainder < 10)
      ctx_formatter_addstr (formatter, "0", 1);
    if (remainder < 100)
      ctx_formatter_addstr (formatter, "0", 1);
    ctx_print_int (formatter, remainder);
  }
}

static void _ctx_stream_addstr (CtxFormatter *formatter, const char *str, int len)
{
  if (!str || len == 0)
  {
    return;
  }
  if (len < 0) len = ctx_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 = ctx_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_DEFINE_FONT:          return "defineFont"; 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_PAINT:                return "paint"; 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_START_FRAME:          return "start_frame"; break;
          case CTX_END_FRAME:            return "end_frame"; 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_EXTEND:               return "extend"; 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_LINE_HEIGHT:          return "lineHeight";break;
          case CTX_WRAP_LEFT:            return "wrapLeft"; break;
          case CTX_WRAP_RIGHT:           return "wrapRight"; 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_EXTEND:            name[1]='e'; 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_LINE_HEIGHT:       name[1]='H'; break;
        case CTX_WRAP_LEFT:         name[1]='L'; break;
        case CTX_WRAP_RIGHT:        name[1]='R'; 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_EXTEND:
                switch (val)
                  {
                    case CTX_EXTEND_NONE:    str = "none"; break;
                    case CTX_EXTEND_PAD:     str = "pad"; break;
                    case CTX_EXTEND_REPEAT:  str = "repeat"; break;
                    case CTX_EXTEND_REFLECT: str = "reflect"; 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_print_int (formatter, val);
            }
        }
      else
#endif
        {
          ctx_print_int (formatter, val);
        }
    }
  _ctx_print_endcmd (formatter);
}

#if 0
static void
ctx_print_a85 (CtxFormatter *formatter, uint8_t *data, int length)
{
  char *tmp = (char*)ctx_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);
  ctx_free (tmp);
}
#endif

static void
ctx_print_yenc (CtxFormatter *formatter, uint8_t *data, int length)
{
  char *tmp = (char*)ctx_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);
  ctx_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_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
            {
              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_print_int (formatter, 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_addstr (formatter, "\"", -1);
        ctx_print_escaped_string (formatter, c->texture.eid);
        ctx_formatter_addstr (formatter, "\", ", 2);
        ctx_print_float (formatter, c->texture.x);
        ctx_formatter_addstr (formatter, ", ", 2);
        ctx_print_float (formatter, c->texture.y);
        ctx_formatter_addstr (formatter, " ", 1);
        _ctx_print_endcmd (formatter);
        break;

      case CTX_DEFINE_TEXTURE:
        {
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstr (formatter, "\"", 1);
        ctx_print_escaped_string (formatter, c->define_texture.eid);
        ctx_formatter_addstr (formatter, "\", ", 2);
        ctx_print_int (formatter, c->define_texture.width);
        ctx_formatter_addstr (formatter, ", ", 2);
        ctx_print_int (formatter, c->define_texture.height);
        ctx_formatter_addstr (formatter, ", ", 2);
        ctx_print_int (formatter, c->define_texture.format);
        ctx_formatter_addstr (formatter, ", ", 2);

        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, "\"", 1);
        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:
        ctx_print_entry (formatter, entry, 7);
        break;
      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:
        ctx_print_entry (formatter, entry, 6);
        break;
      case CTX_APPLY_TRANSFORM:
      case CTX_SOURCE_TRANSFORM:
        ctx_print_entry (formatter, entry, 9);
        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_LINE_HEIGHT:
      case CTX_WRAP_LEFT:
      case CTX_WRAP_RIGHT:
      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 SQZ_x: ctx_formatter_addstrf (formatter, " 'x' "); break;
           case SQZ_y: ctx_formatter_addstrf (formatter, " 'y' "); break;
           case SQZ_width: ctx_formatter_addstrf (formatter, " width "); break;
           case SQZ_height: ctx_formatter_addstrf (formatter, " height "); break;
           default:
             ctx_formatter_addstrf (formatter, " %d ", c->set.key_hash);
        }
        ctx_formatter_addstrf (formatter, "\"", 1);
        ctx_print_escaped_string (formatter, (char*)c->set.utf8);
        ctx_formatter_addstrf (formatter, "\"", 1);
        _ctx_print_endcmd (formatter);
        break;
#endif
      case CTX_COLOR:
        if (1)//formatter->longform)
          {
            _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_addstr (formatter, "gray", 4);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);

                  ctx_print_float (formatter, c->graya.g);
                  break;
                case CTX_GRAYA:
                  ctx_formatter_addstr (formatter, "graya", 5);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);

                  ctx_print_float (formatter, c->graya.g);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->graya.a);
                  break;
                case CTX_RGBA:
                  if (c->rgba.a != 1.0f)
                  {
                    ctx_formatter_addstr (formatter, "rgba", 4);
                    ctx_formatter_addstr (formatter, suffix, -1);
                    ctx_formatter_addstr (formatter, " ", 1);

                    ctx_print_float (formatter, c->rgba.r);
                    ctx_formatter_addstr (formatter, " ", 1);
                    ctx_print_float (formatter, c->rgba.g);
                    ctx_formatter_addstr (formatter, " ", 1);
                    ctx_print_float (formatter, c->rgba.b);
                    ctx_formatter_addstr (formatter, " ", 1);
                    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_addstr (formatter, "gray", 4);
                    ctx_formatter_addstr (formatter, suffix, -1);
                    ctx_formatter_addstr (formatter, " ", 1);

                    ctx_print_float (formatter, c->rgba.r);
                    ctx_formatter_addstr (formatter, " ", 1);
                    break;
                  }
                  ctx_formatter_addstr (formatter, "rgb", 3);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.b);
                  break;
                case CTX_DRGB:
                  ctx_formatter_addstr (formatter, "drgb", 4);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);

                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.b);
                  break;
                case CTX_DRGBA:
                  ctx_formatter_addstr (formatter, "drgba", 5);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);

                  ctx_print_float (formatter, c->rgba.r);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.g);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.b);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->rgba.a);
                  break;
                case CTX_CMYK:
                  ctx_formatter_addstr (formatter, "cmyk", 4);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);

                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.k);
                  break;
                case CTX_CMYKA:
                  ctx_formatter_addstr (formatter, "cmyk", 5);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.k);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.a);
                  break;
                case CTX_DCMYK:
                  ctx_formatter_addstr (formatter, "dcmyk", 6);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.k);
                  break;
                case CTX_DCMYKA:
                  ctx_formatter_addstr (formatter, "dcmyka", 7);
                  ctx_formatter_addstr (formatter, suffix, -1);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.c);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.m);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.y);
                  ctx_formatter_addstr (formatter, " ", 1);
                  ctx_print_float (formatter, c->cmyka.k);
                  ctx_formatter_addstr (formatter, " ", 1);
                  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_addstr (formatter, "rgba (", -1);
          }
        else
          {
            ctx_formatter_addstr (formatter, "rgba (", -1);
          }
        for (int c = 0; c < 4; c++)
          {
            if (c)
              {
                if (formatter->longform)
                  ctx_formatter_addstr (formatter, ", ", 2);
                else
                  ctx_formatter_addstr (formatter, " ", 1);
              }
            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_PAINT:
      case CTX_START_FRAME:
      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_EXTEND:
      case CTX_IMAGE_SMOOTHING:
        ctx_print_entry_enum (formatter, entry, 1);
        break;
      case CTX_GRADIENT_STOP:
        _ctx_print_name (formatter, entry->code);
        ctx_print_float (formatter, ctx_arg_float (0));
        for (int c = 0; c < 4; c++)
          {
            ctx_formatter_addstr (formatter, " ", 1);
            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_addstr (formatter, "\"", 1);
        ctx_print_escaped_string (formatter, ctx_arg_string() );
        ctx_formatter_addstr (formatter, "\"", 1);
        _ctx_print_endcmd (formatter);
        break;
      case CTX_CONT:
      case CTX_EDGE:
      case CTX_DATA:
      case CTX_DATA_REV:
      case CTX_END_FRAME:
        break;

      case CTX_DEFINE_FONT:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstr (formatter, "\"", 1);
        ctx_print_escaped_string (formatter, ctx_arg_string());
        ctx_formatter_addstr (formatter, "\"", 1);
        _ctx_print_endcmd (formatter);
        // XXX: todo, also print license if present
        break;

      case CTX_KERNING_PAIR:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstr (formatter, "\"", 1);
        {
           uint8_t utf8[16];
           utf8[ctx_unichar_to_utf8 (c->kern.glyph_before, utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstr (formatter, "\", \"", -1);
           utf8[ctx_unichar_to_utf8 (c->kern.glyph_after, utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstr (formatter, "\", ", 3);
           ctx_print_float (formatter, c->kern.amount/256.0f);
           ctx_print_escaped_string (formatter, (char*)utf8);
        }
        _ctx_print_endcmd (formatter);
        break;

      case CTX_DEFINE_GLYPH:
        _ctx_print_name (formatter, entry->code);
        ctx_formatter_addstr (formatter, "\"", 1);
        {
           uint8_t utf8[16];
           utf8[ctx_unichar_to_utf8 (entry->data.u32[0], utf8)]=0;
           ctx_print_escaped_string (formatter, (char*)utf8);
           ctx_formatter_addstr (formatter, "\", ", 3);
           ctx_print_float (formatter, entry->data.u32[1]/256.0f);
        }
        _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); }
  fwrite ("\n", 1, 1, stream);
}

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
#include <ctype.h>
#include <sys/stat.h>

CTX_EXPORT int
ctx_width (Ctx *ctx)
{
  return ctx->width;
}
CTX_EXPORT int
ctx_height (Ctx *ctx)
{
  return ctx->height;
}

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.ink_min_x > ctx->state.ink_max_x) ||
       (ctx->state.ink_min_y > ctx->state.ink_max_y) )
    {
      if (x) { *x = 0; }
      if (y) { *y = 0; }
      if (width) { *width = 0; }
      if (height) { *height = 0; }
      return;
    }
  if (ctx->state.ink_min_x < 0)
    { ctx->state.ink_min_x = 0; }
  if (ctx->state.ink_min_y < 0)
    { ctx->state.ink_min_y = 0; }
  if (x) { *x = ctx->state.ink_min_x; }
  if (y) { *y = ctx->state.ink_min_y; }
  if (width) { *width = ctx->state.ink_max_x - ctx->state.ink_min_x + 1; }
  if (height) { *height = ctx->state.ink_max_y - ctx->state.ink_min_y + 1; }
}

#if CTX_CURRENT_PATH
static CtxIterator *
ctx_current_path_iterator (Ctx *ctx)
{
  CtxIterator *iterator = &ctx->current_path_iterator;
  ctx_iterator_init (iterator, &ctx->current_path, 0, CTX_ITERATOR_EXPAND_BITPACK);
  return iterator;
}

CtxDrawlist *
ctx_current_path (Ctx *ctx)
{
  CtxDrawlist *drawlist = ctx_calloc (sizeof (CtxDrawlist) + 
                              ctx->current_path.count * 9, 1);
  drawlist->entries = (CtxEntry*)(&drawlist[1]);
  drawlist->size = drawlist->count = ctx->current_path.count;
  drawlist->flags = CTX_DRAWLIST_DOESNT_OWN_ENTRIES;
  if (drawlist->count)
    memcpy (drawlist->entries, ctx->current_path.entries,
            drawlist->count * 9);
  return drawlist;
}

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_iterator (ctx);
  CtxCommand *command;

  while ((command = ctx_iterator_next (iterator)))
  {
     int got_coord = 0;
     switch (command->code)
     {
        // XXX missing some segment 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->line_to.x;
          y += command->line_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->rel_curve_to.x;
          y += command->rel_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;
        default:
          break;
     }
          //fprintf(stderr, "[%c]", command->code);
    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, SQZ_newState, 0.0f);
  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);
}


CTX_EXPORT 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_backend_type (ctx) == CTX_BACKEND_RASTERIZER)
   {
     CtxRasterizer *rasterizer = (CtxRasterizer*)ctx->backend;
     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
   else if ((format == CTX_FORMAT_RGBA8 ||
             format == CTX_FORMAT_BGRA8)
                   && ctx_backend_is_tiled (ctx))
   {
     /* synchronize */
     CtxTiled *tiled = (CtxTiled*)ctx->backend;
     {
       if (dst_stride <= 0) dst_stride = ctx_pixel_format_get_stride (format, sw);
       int bytes_per_pix = 4;
       int y = 0;
       int count = 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);
            count++;
         }
       }
       if (format == CTX_FORMAT_RGBA8) // XXX does this vary between tiled
                                       // backends?
       {
         for (int i = 0; i < count; i++)
         {
           uint32_t tmp = dst_data[i*4+0];
           dst_data[i*4+0] = dst_data[i*4+2];
           dst_data[i*4+2] = tmp;
         }
       }
       return;
     }
   }
#if CTX_RASTERIZER
   else
   {
     Ctx *rasterizer = ctx_new_for_framebuffer (dst_data, sw, sh, dst_stride, format);
     ctx_translate (rasterizer, sx, sy);
     ctx_render_ctx (ctx, rasterizer);
     ctx_destroy (rasterizer);
   }
#endif
}

void ctx_screenshot (Ctx *ctx, const char *output_path)
{
#if CTX_IMAGE_WRITE
  uint32_t width = ctx_width (ctx);
  uint32_t height = ctx_height (ctx);
  uint8_t *buf = ctx_malloc (width * height * 4);
  ctx_get_image_data (ctx, 0, 0, width, height,
                      CTX_FORMAT_RGBA8, width *4,
                      buf);
  _ctx_write_png (output_path, width, height, 4, buf);
  ctx_free (buf);
#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;
  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 (!ctx_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);
    ctx_list_remove (&ctx->eid_db, eid_info);
    ctx_list_remove (&to_remove, eid_info);
    ctx_free (eid_info->eid);
    ctx_free (eid_info);
  }
  return ret;
}

void ctx_drop_eid (Ctx *ctx, const char *eid)
{
  ctx = ctx->texture_cache;
  CtxList *to_remove = NULL;
  for (CtxList *l = ctx->eid_db; l; l = l->next)
  {
    CtxEidInfo *eid_info = (CtxEidInfo*)l->data;
    if (!ctx_strcmp (eid_info->eid, eid))
    {
      ctx_list_prepend (&to_remove, eid_info);
    }
  }
  while (to_remove)
  {
    CtxEidInfo *eid_info = (CtxEidInfo*)to_remove->data;
    ctx_list_remove (&ctx->eid_db, eid_info);
    ctx_list_remove (&to_remove, eid_info);
    ctx_free (eid_info->eid);
    ctx_free (eid_info);
  }

  for (int i = 0; i <  CTX_MAX_TEXTURES; i++)
  {
    if (ctx->texture[i].data &&
        ctx->texture[i].eid  &&
        !ctx_strcmp (ctx->texture[i].eid, eid))
    {
      ctx->texture[i].eid[0]++;
    }
  }
}


void ctx_texture (Ctx *ctx, const char *eid, float x, float y)
{
  int eid_len = ctx_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 = ctx_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.0f, 0.0f);
  }
  else

  {
    CtxEntry *commands;
    int command_size = 1 + (data_len+1+1)/9 + 1 + (eid_len+1+1)/9 + 1 +   8;
    if (ctx->backend && (void*)ctx->backend->process != (void*)ctx_drawlist_process)
    {
       commands = (CtxEntry*)ctx_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->backend && (void*)ctx->backend->process != (void*)ctx_drawlist_process)
    {
      ctx_process (ctx, commands);
      ctx_free (commands);
    }
    else
    {
       ctx->drawlist.count += ctx_conts_for_entry (commands) + 1;
    }

    CtxEidInfo *eid_info = (CtxEidInfo*)ctx_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        = ctx_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;
  if (strstr (path, "svg"))return;
  char ascii[41]="";
  int eid_len = ctx_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;
  }

#if CTX_STB_IMAGE
  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);
       ctx_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;
      for (int i = 0; i < w * h; i++)
      {
        ctx_RGBA8_associate_alpha (&pixels[i * 4]);
      }
        break;
    }
    if (tw) *tw = w;
    if (th) *th = h;
    ctx_define_texture (ctx, eid, w, h, w * components, pixel_format, pixels, reid);
    ctx_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.0f && height > 0.0f)
    {
#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_paint (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_PAINT);
}

void ctx_fill (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_FILL);
}

void ctx_stroke (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_STROKE);
}


#if 0
static void ctx_empty (Ctx *ctx)
{
#if CTX_RASTERIZER
  if (ctx->backend == NULL)
#endif
    ctx_drawlist_clear (ctx);
}
#endif

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)
{
  ctx_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 && !ctx_strcmp (event->string, "idle"))
    return;
  copy = (CtxEvent*)ctx_malloc (sizeof (CtxEvent));
  *copy = *event;
  if (copy->string)
    copy->string = ctx_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

CTX_EXPORT void
ctx_start_frame (Ctx *ctx)
{
  ctx_drawlist_clear (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->backend && ctx->backend->start_frame)
    ctx->backend->start_frame (ctx);

  //CTX_PROCESS_VOID (CTX_START_FRAME);
  //if (ctx->transformation & CTX_TRANSFORMATION_STORE_CLEAR)
  //  { return; }
  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->width, ctx->height,
                     (CtxEventType)(CTX_PRESS|CTX_RELEASE|CTX_MOTION),
                     ctx_collect_events, ctx, ctx,
                     NULL, NULL);
  }
  ctx->dirty = 0;
#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, uint32_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_new_page (Ctx *ctx)
{
  CTX_PROCESS_VOID (CTX_NEW_PAGE);
}

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);
}

void ctx_line_height (Ctx *ctx, float x)
{
  CTX_PROCESS_F1 (CTX_LINE_HEIGHT, x);
}

void ctx_wrap_left (Ctx *ctx, float x)
{
  CTX_PROCESS_F1 (CTX_WRAP_LEFT , x);
}

void ctx_wrap_right (Ctx *ctx, float x)
{
  CTX_PROCESS_F1 (CTX_WRAP_RIGHT, 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, uint32_t key_hash, const char *string, int len)
{
  if (len <= 0) len = ctx_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);
#endif
  _ctx_font (ctx, name);
}

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_get_font_name (ctx, ctx->state.gstate.font);
}

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;
}

CtxExtend ctx_get_extend (Ctx *ctx)
{
  return ctx->state.gstate.extend;
}

CtxTextAlign ctx_get_text_align  (Ctx *ctx)
{
  return (CtxTextAlign)ctx_state_get (&ctx->state, SQZ_textAlign);
}

float ctx_get_wrap_left        (Ctx *ctx)
{
  return ctx_state_get (&ctx->state, SQZ_wrapLeft);
}
float ctx_get_wrap_right       (Ctx *ctx)
{
  return ctx_state_get (&ctx->state, SQZ_wrapRight);
}

float ctx_get_line_height      (Ctx *ctx)
{
  return ctx_state_get (&ctx->state, SQZ_lineHeight);
}

CtxTextBaseline ctx_get_text_baseline (Ctx *ctx)
{
  return (CtxTextBaseline)ctx_state_get (&ctx->state, SQZ_textBaseline);
}

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_extend (Ctx *ctx, CtxExtend extend)
{
  if (ctx->state.gstate.extend != extend)
    CTX_PROCESS_U32 (CTX_EXTEND, extend, 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.0001f)
    { 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.5f ||
          radius < 0.5f)
        {
          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);
}

CTX_EXPORT void
ctx_end_frame (Ctx *ctx)
{
  if (ctx->backend && ctx->backend->end_frame)
    ctx->backend->end_frame (ctx);
  ctx->frame++;
  if (ctx->texture_cache != ctx)
    ctx->texture_cache->frame++;
  ctx_drawlist_clear (ctx);
  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_HEIGHT:
        ctx_state_set (state, SQZ_lineHeight, ctx_arg_float (0) );
        break;
      case CTX_WRAP_LEFT:
        ctx_state_set (state, SQZ_wrapLeft, ctx_arg_float (0) );
        break;
      case CTX_WRAP_RIGHT:
        ctx_state_set (state, SQZ_wrapRight, ctx_arg_float (0) );
        break;
      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_EXTEND:
        state->gstate.extend = (CtxExtend) ctx_arg_u32 (0);
        break;
      case CTX_TEXT_ALIGN:
        ctx_state_set (state, SQZ_textAlign, ctx_arg_u8 (0) );
        break;
      case CTX_TEXT_BASELINE:
        ctx_state_set (state, SQZ_textBaseline, ctx_arg_u8 (0) );
        break;
      case CTX_TEXT_DIRECTION:
        ctx_state_set (state, SQZ_textDirection, 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_FONT:
        state->gstate.font = ctx_resolve_font (ctx_arg_string());
        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.0f?1.0f/(end - start):1.0f;
          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.0f ? 1.0f/(r1-r0):0.0f;
          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);
        //state->gstate.transform_type = 0;
        _ctx_transform_prime (state);
        break;
      case CTX_TRANSLATE:
        ctx_matrix_translate (&state->gstate.transform,
                              ctx_arg_float (0), ctx_arg_float (1) );
        //state->gstate.transform_type = 0;
        _ctx_transform_prime (state);
        break;
      case CTX_SCALE:
        ctx_matrix_scale (&state->gstate.transform,
                          ctx_arg_float (0), ctx_arg_float (1) );
        //state->gstate.transform_type = 0;
        _ctx_transform_prime (state);
        break;
      case CTX_ROTATE:
        ctx_matrix_rotate (&state->gstate.transform, ctx_arg_float (0) );
        //state->gstate.transform_type = 0;
        _ctx_transform_prime (state);
        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_arg_float (6), ctx_arg_float (7),
                          ctx_arg_float (8));
          _ctx_matrix_multiply (&state->gstate.transform,
                                &state->gstate.transform, &m); // XXX verify order
          // state->gstate.transform_type = 0;
          _ctx_transform_prime (state);
        }
#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_START_FRAME:
        ctx_state_init (state);
        state->has_moved = 0;
        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);
        state->has_moved = 1;
        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

#ifndef CTX_TEXT_WRAP
#define CTX_TEXT_WRAP 1
#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       = 32; // 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_color_set_graya (state, &state->gstate.source_fill.color, 1.0f, 1.0f);
  ctx_state_set (state, SQZ_lineHeight, 1.0f);
#if CTX_TEXT_WRAP
  ctx_state_set (state, SQZ_wrapLeft, 0.0f);
  ctx_state_set (state, SQZ_wrapRight, 0.0f);
#endif

  state->ink_min_x              = 8192;
  state->ink_min_y              = 8192;
  state->ink_max_x              = -8192;
  state->ink_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_setup (Ctx *ctx);

#if CTX_SIMD
void ctx_simd_setup (void);
#endif
static void
_ctx_init (Ctx *ctx)
{
  static int done_first_run = 0;
  ctx_setup (ctx);

  if (!done_first_run)
  {
#if CTX_BABL
    babl_init ();
#endif
    done_first_run = 1;
#if CTX_SIMD
    ctx_simd_setup ();
#endif
#if CTX_U8_TO_FLOAT_LUT
    for (int i = 0; i <256;i++)
      ctx_u8_float[i] = i/255.0f;
#endif
#if CTX_SHAPE_CACHE
    if (getenv ("CTX_SHAPE_CACHE"))
    {
      const char * val = getenv ("CTX_SHAPE_CACHE");
      if (!ctx_strcmp (val, "0") ||
          !ctx_strcmp (val, "off"))
        _ctx_shape_cache_enabled = 0;
      else
        _ctx_shape_cache_enabled = 1;
    }
#endif
  }

  ctx_state_init (&ctx->state);

#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;

  ctx->fonts = ctx_fonts;
}


#if CTX_DRAWLIST_STATIC
static Ctx ctx_state;
#endif

void ctx_push_backend (Ctx *ctx,
                       void *backend)
{
  if (ctx->backend_pushed)
    fprintf (stderr, "double push\n");
  ctx->backend_pushed = ctx->backend;
  ctx->backend = (CtxBackend*)backend;
  if (ctx->backend->process == NULL)
    ctx->backend->process = (void(*)(Ctx*,CtxCommand*))ctx_drawlist_process;
}


void ctx_pop_backend (Ctx *ctx)
{
  if (!ctx->backend_pushed)
    fprintf (stderr, "backend pop without push\n");
  if (ctx->backend && ctx->backend->destroy)
    ctx->backend->destroy (ctx->backend);
  ctx->backend = ctx->backend_pushed;
  ctx->backend_pushed = NULL;
}

void ctx_set_backend (Ctx  *ctx,
                       void *backend)
{
  if (ctx->backend && ctx->backend->destroy)
    ctx->backend->destroy (ctx->backend);
  ctx->backend = (CtxBackend*)backend;
  if (ctx->backend->process == NULL)
    ctx->backend->process = (void(*)(Ctx*,CtxCommand*))ctx_drawlist_process;
}

void *ctx_get_backend (Ctx *ctx)
{
  return ctx->backend;
}


static Ctx *
_ctx_new_drawlist (int width, int height)
{
#if CTX_DRAWLIST_STATIC
  Ctx *ctx = &ctx_state;
#else
  Ctx *ctx = (Ctx *) ctx_malloc (sizeof (Ctx) );
#endif
  ctx_memset (ctx, 0, sizeof (Ctx) );
  _ctx_init (ctx);

  ctx_set_backend (ctx, ctx_drawlist_backend_new ());
  ctx_set_size (ctx, width, height);
  return ctx;
}

Ctx *
ctx_new_drawlist (int width, int height)
{
  return _ctx_new_drawlist (width, height);
}

#if CTX_EVENTS
static Ctx *ctx_new_ui (int width, int height, const char *backend);
#endif

#if CTX_PTY==0
Ctx *ctx_pico_init (void);
#endif

CTX_EXPORT Ctx *
ctx_new (int width, int height, const char *backend)
{
#if CTX_PTY==0
  return ctx_pico_init ();
#endif

#if CTX_EVENTS
  if (backend && !ctx_strcmp (backend, "drawlist"))
#endif
  {
    return _ctx_new_drawlist (width, height);
  }
#if CTX_EVENTS
  else
    return ctx_new_ui (width, height, backend);
#endif
}

static inline void
ctx_drawlist_deinit (CtxDrawlist *drawlist)
{
#if !CTX_DRAWLIST_STATIC
  if (drawlist->entries && ! (drawlist->flags & CTX_DRAWLIST_DOESNT_OWN_ENTRIES) )
    {
      ctx_free (drawlist->entries); 
    }
#endif
  drawlist->entries = NULL;
  drawlist->size = 0;
}


static void ctx_deinit (Ctx *ctx)
{
#if CTX_EVENTS
  ctx_events_deinit (ctx);
#endif

  if (ctx->backend)
    {
      if (ctx->backend->destroy)
        ctx->backend->destroy (ctx->backend);
      ctx->backend    = NULL;
    }
  ctx_drawlist_deinit (&ctx->drawlist);
#if CTX_CURRENT_PATH
  ctx_drawlist_deinit (&ctx->current_path);
#endif

  for (int no = 0; no < CTX_MAX_TEXTURES; no++)
    ctx_buffer_deinit (&ctx->texture[no]);
}

CTX_EXPORT void
ctx_destroy (Ctx *ctx)
{
  if (!ctx)
    { return; }

#if CTX_VT
  while (ctx_clients (ctx))
    ctx_client_remove (ctx, ctx_clients(ctx)->data);
#endif

#if CTX_EVENTS
  ctx_clear_bindings (ctx);
#endif
  ctx_deinit (ctx);
#if !CTX_DRAWLIST_STATIC
  ctx_free (ctx);
#endif
}


Ctx *
ctx_new_for_drawlist (int width, int height, void *data, size_t length)
{
  Ctx *ctx = _ctx_new_drawlist (width, height);
  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 (Ctx *ctx)
{
  ctx_font_setup (ctx);
}

void
ctx_render_ctx (Ctx *ctx, Ctx *d_ctx)
{
  CtxIterator iterator;
  CtxCommand *command;
  ctx->bail = 0;
  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_masked (Ctx *ctx, Ctx *d_ctx, uint32_t mask)
{
  CtxIterator iterator;
  CtxCommand *command;
  ctx_iterator_init (&iterator, &ctx->drawlist, 0,
                     CTX_ITERATOR_EXPAND_BITPACK);

  uint32_t active_mask = 0xffffffff;

  while ( (command = ctx_iterator_next (&iterator) ) )
    {
       d_ctx->bail = ((active_mask & mask) == 0);
       ctx_process (d_ctx, &command->entry);

       switch (command->code)
       {
         case CTX_FILL:
         case CTX_STROKE:
         case CTX_CLIP:
         case CTX_TEXT:
         case CTX_GLYPH:
           active_mask = command->entry.data.u32[1];
       }
    }
}

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_VT
  while (ctx_clients (ctx))
    ctx_client_remove (ctx, ctx_clients(ctx)->data);
#endif

#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_queue_draw (ctx);
    ctx->cursor = cursor;
  }
}
CtxCursor    ctx_get_cursor (Ctx *ctx)
{
  return ctx->cursor;
}

void ctx_set_clipboard (Ctx *ctx, const char *text)
{
  if (ctx->backend && ctx->backend->set_clipboard)
  {
    ctx->backend->set_clipboard (ctx, text);
    return;
  }
}

void ctx_windowtitle (Ctx *ctx, const char *text)
{
  if (ctx->backend && ctx->backend->set_windowtitle)
  {
    ctx->backend->set_windowtitle (ctx, text);
    return;
  }
}

char *ctx_get_clipboard (Ctx *ctx)
{
  if (ctx->backend && ctx->backend->get_clipboard)
  {
    return ctx->backend->get_clipboard (ctx);
  }
  return ctx_strdup ("");
}

void ctx_set_texture_source (Ctx *ctx, Ctx *texture_source)
{
  ((CtxRasterizer*)ctx->backend)->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, float g, float h, float i)
{
  ctx_identity (ctx);
  ctx_apply_transform (ctx, a, b, c, d, e, f, g, h, i);
}

#endif

#if CTX_GET_CONTENTS

#if CTX_CURL
#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

#ifdef ITK_HAVE_FS
int itk_static_get_contents (const char *path, char **contents, long *length);
#endif

int
ctx_get_contents2 (const char     *uri,
                   unsigned char **contents,
                   long           *length,
                   long            max_len)
{
  char *temp_uri = NULL; // XXX XXX breaks with data uri's
  int   success  = -1;

  if (uri[0] == '/')
  {
    temp_uri = (char*) ctx_malloc (ctx_strlen (uri) + 8);
    sprintf (temp_uri, "file://%s", uri);
    uri = temp_uri;
  }

  if (strchr (uri, '#'))
  {
    if (temp_uri == NULL)
      uri = temp_uri = strdup (uri);
   strchr (uri, '#')[0]=0;
  }

  for (CtxList *l = registered_contents; l; l = l->next)
  {
    CtxFileContent *c = (CtxFileContent*)l->data;
    if (!ctx_strcmp (c->path, uri))
    {
      contents = ctx_malloc (c->length+1);
      contents[c->length]=0;
      if (length) *length = c->length;
      ctx_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, max_len);
#ifdef ITK_HAVE_FS
  else if (!strncmp (uri, "itk:", 4))
  {
    success = itk_static_get_contents (uri, (char**)contents, length);
  }
#endif
  else
  {
#if CTX_CURL
  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, max_len);
#endif
  }
  ctx_free (temp_uri);
  return success;
}


int
ctx_get_contents (const char     *uri,
                  unsigned char **contents,
                  long           *length)
{
  return ctx_get_contents2 (uri, contents, length, 1024*1024*1024);
}



typedef struct CtxMagicEntry {
  int is_text;
  const char *mime_type;
  const char *ext1;
  int len;
  uint8_t magic[16];
} CtxMagicEntry;

static CtxMagicEntry ctx_magics[]={
  {0, "image/bmp",  ".bmp", 0, {0}},
  {0, "image/png",  ".png", 8, {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}},
  {0, "image/jpeg", ".jpg", 8,  {0xff, 0xd8, 0xff, 0xdb, 0xff, 0xd8, 0xff, 0xe0}},
  {0, "image/jpeg", ".jpg", 4,  {0xff, 0xd8, 0xff, 0xe0}},
  {0, "image/jpeg", ".jpg", 4,  {0xff, 0xd8, 0xff, 0xee}},
  {0, "image/jpeg", ".jpg", 4,  {0xff, 0xd8, 0xff, 0xe1}},
  {0, "image/jpeg", ".jpeg", 8, {0xff, 0xd8, 0xff, 0xdb, 0xff, 0xd8, 0xff, 0xe0}},

  {0, "image/psd", ".psd", 4,  {0x38, 0x42, 0x50, 0x53}},
  {0, "image/tinyvg", ".tvg", 3, {0x72, 0x56, 1}}, 
  {0, "image/gif",  ".gif", 6, {0x47, 0x49, 0x46, 0x38, 0x37, 0x61}},
  {0, "image/gif",  ".gif", 6, {0x47, 0x49, 0x46, 0x38, 0x39, 0x61}},
  {0, "image/exr",  ".exr", 4, {0x76, 0x2f, 0x31, 0x01}},
  {0, "video/mpeg", ".mpg", 4, {0x00, 0x00, 0x01, 0xba}},
  {0, "application/blender", ".blend", 8, {0x42, 0x4c,0x45,0x4e,0x44,0x45,0x52}},
  {0, "image/xcf",  ".xcf", 8, {0x67, 0x69,0x6d,0x70,0x20,0x78,0x63,0x66}},
  {0, "application/bzip2", ".bz2", 3, {0x42, 0x5a, 0x68}},
  {0, "application/gzip", ".gz", 2, {0x1f, 0x8b}},
  {0, "application/zip", ".zip", 4, {0x50, 0x4b, 0x03, 0x04}},
  {0, "application/zip", ".zip", 4, {0x50, 0x4b, 0x05, 0x06}},
  {0, "application/rar", ".rar", 6, {0x52, 0x61, 0x72, 0x1a, 0x07, 0x00}},
  {0, "application/rar", ".rar", 7, {0x52, 0x61, 0x72, 0x1a, 0x07, 0x01, 0x00}},
  {1, "text/x-csrc", ".c", 0, {0,}},
  {1, "text/x-chdr", ".h", 0, {0,}},
  {1, "text/css", ".css", 0, {0x0}},

  {0, "application/gzip", ".z", 2, {0x1f, 0x9d}},

  {0, "application/dos-mz", ".exe", 2, {0x4d, 0x5a}},

  {1, "text/csv", ".csv", 0, {0x0}},
  {1, "text/html", ".htm", 0, {0x0}},
  {1, "text/html", ".html", 0, {0x0}},
  {1, "text/x-makefile", "makefile", 0, {0x0}},
  {1, "application/atom+xml", ".atom", 0, {0x0}},
  {1, "application/rdf+xml", ".rdf", 0, {0x0}},
  {1, "application/javascript", ".js", 0, {0x0}},
  {1, "application/json", ".json", 0, {0x0}},
  {0, "application/octet-stream", ".bin", 0, {0x0}},
  {0, "application/x-object", ".o", 0, {0x0}},
  {1, "text/utf-8", ".txt", 0, {0xef, 0xbb, 0xbf}}, // utf8 bom
  {1, "text/x-sh", ".sh", 0, {0x0}},
  {1, "text/x-python", ".py", 0, {0x0}},
  {1, "text/x-perl", ".pl", 0, {0x0}},
  {1, "text/x-perl", ".pm", 0, {0x0}},
  {1, "application/x-shellscript", ".sh", 2, {0x23, 0x21}}, // #!
  {0, "application/pdf", ".pdf", 0, {0x0}},
  {0, "application/ctx", ".ctx", 0, {0x0}},
  {0, "application/wasm", ".wasm", 0, {0x00, 0x61, 0x73, 0x6d}},
  {1, "text/xml", ".xml",     0, {0x0}},
  {0, "video/mp4", ".mp4",    7, {0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f}},
  {0, "video/matroska", ".mkv", 4, {0x1a, 0x45, 0xdf, 0xa3}},
  {0, "video/ogg", ".ogv",    0, {0x0}},
  {0, "audio/flac", ".flac",  0, {0x66, 0x4c, 0x61, 0x43}},
  {0, "audio/sp-midi", ".mid",  4, {0x4d, 0x54, 0x68, 0x64}},
  {0, "audio/x-wav", ".wav",  4, {0x52, 0x49, 0x46, 0x46}},
  {0, "audio/ogg", ".ogg",    4, {0x4f, 0x67, 0x67, 0x53}},
  {0, "audio/ogg", ".opus",   0, {0x0}},
  {0, "audio/ogg", ".oga",    0, {0x0}},
  {0, "audio/mpeg", ".mp1",   0, {0x0}},
  {0, "audio/m3u", ".m3u",    0, {0x0}},
  {0, "audio/mpeg", ".mp2",   0, {0x0}},
  {0, "audio/mpeg", ".mp3",   0, {0x0}},
  {0, "audio/mpeg", ".m4a",   0, {0x0}},
  {0, "audio/mpeg", ".mpga",  0, {0x0}},
  {0, "audio/mpeg", ".mpega", 0, {0x0}},
  {0, "font/otf", ".otf", 0,{0x0}},
  {0, "font/ttf", ".ttf", 5,{0x0, 0x01, 0x00, 0x00, 0x00}},
  // inode-directory
};

int ctx_path_is_dir (const char *path)
{
  struct stat stat_buf;
  if (!path || path[0]==0) return 0;
  lstat (path, &stat_buf);
  return S_ISDIR (stat_buf.st_mode);
}

static int ctx_path_is_exec (const char *path)
{
  struct stat stat_buf;
  if (!path || path[0]==0) return 0;
  lstat (path, &stat_buf);
  return stat_buf.st_mode & 0x1;
}

int ctx_media_matched_content = 0;
const char *ctx_guess_media_type (const char *path, const char *content, int len)
{
  const char *extension_match = NULL;
  ctx_media_matched_content = 0;
  if (path && strrchr (path, '.'))
  {
    char *pathdup = ctx_strdup (strrchr(path, '.'));
    for (int i = 0; pathdup[i]; i++) pathdup[i]=tolower(pathdup[i]);
    for (unsigned int i = 0; i < sizeof (ctx_magics)/sizeof(ctx_magics[0]);i++)
    {
      if (ctx_magics[i].ext1 && !ctx_strcmp (ctx_magics[i].ext1, pathdup))
      {
        extension_match = ctx_magics[i].mime_type;
      }
    }
    ctx_free (pathdup);
  }

  if (len > 16)
  {
    for (unsigned int i = 0; i < sizeof (ctx_magics)/sizeof(ctx_magics[0]);i++)
    {
       if (ctx_magics[i].len) // skip extension only matches
       if (!memcmp (content, ctx_magics[i].magic, ctx_magics[i].len))
       {
         ctx_media_matched_content = 1;
         return ctx_magics[i].mime_type;
       }
    }
  }

  if (extension_match && !ctx_strcmp (extension_match, "application/ctx"))
  {
    //if (!ctx_path_is_exec (path))
    //  extension_match = NULL;
  }

  if (extension_match) return extension_match;


  int non_ascii=0;
  for (int i = 0; i < len; i++)
  {
    int p = content[i];
    if (p > 127) non_ascii = 1;
    if (p < ' ' && (p!='\n')) non_ascii = 1;
    if (p == 0) non_ascii = 1;
  }
  if (non_ascii)
    return "application/octet-stream";
  return "text/plain";
}


const char *ctx_path_get_media_type (const char *path)
{
  char *content = NULL;
  long length = 0;

  if (strchr(path, ':'))
  {
    path = strchr (path, ':') + 1;
    if (path[0]=='/')path++;
    if (path[0]=='/')path++;
  }

#if 0
  /* XXX : code duplication, factor out in separate fun */
  if (path && strrchr (path, '.'))
  {
    char *pathdup = ctx_strdup (strrchr(path, '.'));
    for (int i = 0; pathdup[i]; i++) pathdup[i]=tolower(pathdup[i]);
    for (unsigned int i = 0; i < sizeof (ctx_magics)/sizeof(ctx_magics[0]);i++)
    {
      if (ctx_magics[i].ext1 && !ctx_strcmp (ctx_magics[i].ext1, pathdup))
      {
        ctx_free (pathdup);
        return ctx_magics[i].mime_type;
      }
    }
    ctx_free (pathdup);
  }
#endif
  if (ctx_path_is_dir (path))
    return "inode/directory";

  ctx_get_contents2 (path, (uint8_t**)&content, &length, 128);
  if (content)
  {
  const char *guess = ctx_guess_media_type (path, content, length);
  ctx_free (content);
  return guess;
  }
  return "application/none";
}

int ctx_media_type_is_text (const char *media_type)
{
  for (unsigned int i = 0; i < sizeof (ctx_magics)/sizeof(ctx_magics[0]);i++)
    if (media_type == ctx_magics[i].mime_type)
       return ctx_magics[i].is_text;
  for (unsigned int i = 0; i < sizeof (ctx_magics)/sizeof(ctx_magics[0]);i++)
    if (!strcmp (media_type,  ctx_magics[i].mime_type))
       return ctx_magics[i].is_text;
  if (!strcmp (media_type, "text/plain"))
    return 1;
  return 0;
}

CtxMediaTypeClass ctx_media_type_class (const char *media_type)
{
  CtxMediaTypeClass ret = CTX_MEDIA_TYPE_NONE;
  if (!media_type) return ret;
  if (!ret){
    ret = CTX_MEDIA_TYPE_IMAGE;
    if (media_type[0]!='i')ret = 0;
    if (media_type[1]!='m')ret = 0;
    /*
    if (media_type[2]!='a')ret = 0;
    if (media_type[3]!='g')ret = 0;
    if (media_type[4]!='e')ret = 0;*/
  }
  if (!ret){
    ret = CTX_MEDIA_TYPE_VIDEO;
    if (media_type[0]!='v')ret = 0;
    if (media_type[1]!='i')ret = 0;
    /*
    if (media_type[2]!='d')ret = 0;
    if (media_type[3]!='e')ret = 0;
    if (media_type[4]!='o')ret = 0;*/
  }
  if (!ret){
    ret = CTX_MEDIA_TYPE_AUDIO;
    if (media_type[0]!='a')ret = 0;
    if (media_type[1]!='u')ret = 0;
    /*
    if (media_type[2]!='d')ret = 0;
    if (media_type[3]!='i')ret = 0;
    if (media_type[4]!='o')ret = 0;*/
  }
  if (!ret){
    ret = CTX_MEDIA_TYPE_TEXT;
    if (media_type[0]!='t')ret = 0;
    if (media_type[1]!='e')ret = 0;
    /*
    if (media_type[2]!='x')ret = 0;
    if (media_type[3]!='t')ret = 0;*/
  }
  if (!ret){
    ret = CTX_MEDIA_TYPE_APPLICATION;
    if (media_type[0]!='a')ret = 0;
    if (media_type[1]!='p')ret = 0;
    /*
    if (media_type[2]!='p')ret = 0;
    if (media_type[3]!='l')ret = 0;*/
  }
  if (!ret){
    ret = CTX_MEDIA_TYPE_INODE;
    if (media_type[0]!='i')ret = 0;
    if (media_type[1]!='n')ret = 0;
    /*
    if (media_type[2]!='o')ret = 0;
    if (media_type[3]!='d')ret = 0;
    if (media_type[4]!='e')ret = 0;*/
  }
  return ret;
}

#else
int
ctx_get_contents (const char     *uri,
                  unsigned char **contents,
                  long           *length)
{
  *contents = NULL;
  *length = -1;
  return -1;
//ctx_get_contents2 (uri, contents, length, 1024*1024*1024);
}

#endif


void
ctx_current_point (Ctx *ctx, float *x, float *y)
{
  float user_x = 0.0f, user_y = 0.0f;
  if (!ctx)
    { 
      if (x) { *x = 0.0f; }
      if (y) { *y = 0.0f; }
    }
#if CTX_RASTERIZER_X
  if (ctx->backend && ctx->backend->process == ctx_rasterizer_process)
    {
      user_x = ((CtxRasterizer *) (ctx->backend) )->x;
      user_y = ((CtxRasterizer *) (ctx->backend) )->y;
    }
  else
#endif
    {
      user_x = ctx->state.x;
      user_y = ctx->state.y;
    }


  if (x) *x = user_x;
  if (y) *y = user_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 CtxBackendType __ctx_backend_type (Ctx *ctx)
{
  if (!ctx)
    return CTX_BACKEND_NONE;
  CtxBackend *backend = ctx->backend;
  if (backend == NULL)
    return CTX_BACKEND_NONE;
#if CTX_FORMATTER
  else if (backend->destroy == (void*) ctx_ctx_destroy) return CTX_BACKEND_CTX;
#if CTX_HEADLESS
  else if (backend->destroy == (void*) ctx_headless_destroy) return CTX_BACKEND_HEADLESS;
#endif
#endif
#if CTX_TERMINAL_EVENTS
#if CTX_TERM
  else if (backend->destroy == (void*) ctx_term_destroy) return CTX_BACKEND_TERM;
#endif
#endif
#if CTX_RASTERIZER
  else if (backend->process == (void*) ctx_hasher_process) return CTX_BACKEND_HASHER;
#endif
#if CTX_RASTERIZER
  else if (backend->destroy == (void*) ctx_rasterizer_destroy) return CTX_BACKEND_RASTERIZER;
#endif
#if CTX_KMS
  else if (backend->destroy == (void*) ctx_kms_destroy) return CTX_BACKEND_KMS;
#endif
#if CTX_FB
  else if (backend->destroy == (void*) ctx_fb_destroy) return CTX_BACKEND_FB;
#endif
#if CTX_SDL
  else if (backend->destroy == (void*) ctx_sdl_destroy) return CTX_BACKEND_SDL;
#endif
#if CTX_CAIRO
  else if (backend->destroy == (void*) ctx_cairo_destroy) return CTX_BACKEND_CAIRO;
#endif
#if CTX_TERMIMG
  else if (backend->destroy == (void*) ctx_termimg_destroy) return CTX_BACKEND_TERMIMG;
#endif
  return CTX_BACKEND_NONE;
}

CtxBackendType ctx_backend_type (Ctx *ctx)
{
  CtxBackend *backend = ctx->backend;
  CtxBackendType internal = backend->type;

  if (!internal)
  {
    CtxBackendType computed = __ctx_backend_type (ctx);
    backend->type = computed;
    //fprintf (stderr, "did a caching set of %i\n", computed);
    return computed;
  }

  return internal;
}


void ctx_set_fullscreen (Ctx *ctx, int val)
{
#if CTX_SDL
    if (ctx_backend_type (ctx) == CTX_BACKEND_SDL)
      ctx_sdl_set_fullscreen (ctx, val);
#endif
}

int ctx_get_fullscreen (Ctx *ctx)
{
#if CTX_SDL
    if (ctx_backend_type (ctx) == CTX_BACKEND_SDL)
      return ctx_sdl_get_fullscreen (ctx);
#endif
    return 0;
}

CtxPixelFormatInfo *ctx_pixel_formats =
#if CTX_COMPOSITE
ctx_pixel_formats_generic;
#else
NULL;
#endif

CtxPixelFormatInfo *
ctx_pixel_format_info (CtxPixelFormat format)
{
  if (!ctx_pixel_formats)
  {
    assert (0);
    return NULL;
  }
  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];
        }
    }
  assert (0);
  return NULL;
}


#if CTX_RASTERIZER


void (*ctx_rasterizer_rasterize_edges) (CtxRasterizer *rasterizer, const int fill_rule
#if CTX_SHAPE_CACHE
                ,CtxShapeEntry *shape
#endif
                ) =
      ctx_rasterizer_rasterize_edges_generic;

void (*ctx_composite_setup) (CtxRasterizer *rasterizer) =
      ctx_composite_setup_generic;
#if CTX_FAST_FILL_RECT
void (*ctx_composite_stroke_rect) (CtxRasterizer *rasterizer,
                           float          x0,
                           float          y0,
                           float          x1,
                           float          y1,
                           float          line_width) =
      ctx_composite_stroke_rect_generic;

void (*ctx_composite_fill_rect) (CtxRasterizer *rasterizer,
                           float        x0,
                           float        y0,
                           float        x1,
                           float        y1,
                           uint8_t      cov) =
      ctx_composite_fill_rect_generic;
#endif

#endif


CTX_EXPORT  void
ctx_logo (Ctx *ctx, float x, float y, float dim)
{
     //float width = ctx_width (ctx);
     //float height = ctx_height (ctx);
     ctx_save (ctx);
     ctx_translate (ctx, x, y);//
                               //width/2, height/2);

     //if (width < height) height = width;
     
     ctx_scale (ctx, dim, dim);
     ctx_translate (ctx, -0.5f, -0.5f);
     ctx_begin_path (ctx);
     ctx_rgba(ctx,1,1,1,0.4f);
     ctx_move_to(ctx,0.43956786f,0.90788066f);
     ctx_rel_curve_to(ctx,0.0195929f,0.0102943f,0.0716181f,0.0218038f,0.10361884f,-0.0167646f);
     ctx_line_to (ctx,0.93768705f,0.37887837f);
     ctx_rel_curve_to (ctx,  0.019925f,-0.0342044f,-0.00963f,-0.0544608f,-0.0308834f,-0.0508084f);
     ctx_rel_curve_to (ctx,-0.17965502f,0.0285588f,-0.35466092f,-0.055125f,-0.45096394f,-0.21253089f);
     ctx_rel_curve_to (ctx, -0.0176003f,-0.02988716f, -0.0594422f,-0.01560777f,-0.0594422f,0.0139473f);
     ctx_rel_curve_to (ctx, 0, 0.0591101f,0.003321f,0.49845135f,0.001991f, 0.70699722f);
     ctx_rel_curve_to (ctx, 0.00039042f, 0.0283487f,0.0157362f,0.0529866f,0.0408456f,0.070733f);
     ctx_fill (ctx);

     ctx_move_to (ctx, 0.39772584f,0.91850721f);
     ctx_rel_line_to (ctx, -0.0664159f, 0);
     ctx_rel_curve_to (ctx, -0.15408489f,0, -0.27894675f,-0.12486192f, -0.27894675f,-0.2789468f);
     ctx_rel_curve_to (ctx, 0,-0.15408489f, 0.12486186f,-0.27861466f, 0.27894675f,-0.27894675f);
     ctx_rel_line_to (ctx, 0.18585599f,0.0000662f);
     ctx_rel_curve_to (ctx, 0.0111839f,0.00017138f, 0.0158287f,0.001542f, 0.0263337f,0.0134822f);
     ctx_rel_curve_to (ctx, 0.11733258f,0.14373102f, 0.3018009f,0.36870115f, 0.3942639f,0.49195316f);
     ctx_rel_curve_to (ctx, 0.0185394f,0.0332794f, -0.0106225f,0.0505515f, -0.0228143f,0.0505207f);

     ctx_linear_gradient (ctx, 0.0525f, 0, 0.9905f, 0);
     ctx_gradient_add_stop (ctx, 0.0f, 1.0f, 1.0f, 0.66f, 1.0f);
     ctx_gradient_add_stop (ctx, 0.2f, 1.0f, 0.66f, 0.0f, 1.0f);
     ctx_gradient_add_stop (ctx, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f);
     ctx_gradient_add_stop (ctx, 1.0f, 0.4f, 0.0f, 0.53f, 1.0f);
     ctx_fill (ctx);
     


     ctx_linear_gradient(ctx, 0.697f, 0.17f, 0.4318f, 0.884f);
     ctx_gradient_add_stop (ctx, 0, 0.26f, 0.26f, 1, 1.0f);
     ctx_gradient_add_stop (ctx, 0.3f, 0, 1, 1, 0.4f);
     ctx_gradient_add_stop (ctx, 1.0f, 0, 1, 0.26f,1.0f);
     
     ctx_move_to(ctx,0.43956786f,0.90788066f);
     ctx_rel_curve_to(ctx,0.0195929f,0.0102943f,0.0716181f,0.0218038f,0.10361884f,-0.0167646f);
     ctx_line_to (ctx,0.93768705f,0.37887837f);
     ctx_rel_curve_to (ctx,  0.019925f,-0.0342044f,-0.00963f,-0.0544608f,-0.0308834f,-0.0508084f);
     ctx_rel_curve_to (ctx,-0.17965502f,0.0285588f,-0.35466092f,-0.055125f,-0.45096394f,-0.21253089f);
     ctx_rel_curve_to (ctx, -0.0176003f,-0.02988716f, -0.0594422f,-0.01560777f,-0.0594422f,0.0139473f);
     ctx_rel_curve_to (ctx, 0, 0.0591101f,0.003321f,0.49845135f,0.001991f, 0.70699722f);
     ctx_rel_curve_to (ctx, 0.00039042f, 0.0283487f,0.0157362f,0.0529866f,0.0408456f,0.070733f);
     ctx_fill (ctx);
     
     ctx_restore (ctx);
}

void
ctx_CBRLE_decompress (const uint8_t *cbrle, uint8_t *rgba8, int width, int size);
void
ctx_CBRLE_decompress (const uint8_t *cbrle, uint8_t *rgba8, int width, int size)
{
#if CTX_ENABLE_CBRLE
  _ctx_CBRLE_decompress (cbrle, rgba8, width, size, 0, width);
#endif
}

void
ctx_clip_extents (Ctx *ctx, float *x0, float *y0,
                           float *x1, float *y1)
{
   CtxGState *gstate = &ctx->state.gstate;
   if(x0)*x0 = gstate->clip_min_x;
   if(y0)*y0 = gstate->clip_min_y;
   if(x1)*x1 = gstate->clip_max_x;
   if(y1)*y1 = gstate->clip_max_y;
}

typedef struct CtxDeferredCommand {
  uint32_t name;
  int offset;
  int is_rect;
} CtxDeferredCommand;

static CtxDeferredCommand *deferred_new (Ctx *ctx, const char *name)
{
   CtxDeferredCommand *deferred = calloc (sizeof (CtxDeferredCommand), 1);
   if (name)
     deferred->name = ctx_strhash (name);
   deferred->offset = ctx->drawlist.count;
   ctx_list_prepend (&ctx->deferred, deferred);
   return deferred;
}

void ctx_deferred_move_to (Ctx *ctx, const char *name, float x, float y)
{
   deferred_new (ctx, name);
   ctx_move_to (ctx, x, y);
}

void ctx_deferred_rel_move_to (Ctx *ctx, const char *name, float x, float y)
{
   deferred_new (ctx, name);
   ctx_rel_move_to (ctx, x, y);
}

void ctx_deferred_rel_line_to (Ctx *ctx, const char *name, float x, float y)
{
   deferred_new (ctx, name);
   ctx_rel_line_to (ctx, x, y);
}

void ctx_deferred_scale (Ctx *ctx, const char *name, float x, float y)
{
   deferred_new (ctx, name);
   ctx_scale (ctx, x, y);
}

void ctx_deferred_translate (Ctx *ctx, const char *name, float x, float y)
{
   deferred_new (ctx, name);
   ctx_translate (ctx, x, y);
}

void ctx_deferred_rectangle   (Ctx *ctx, const char *name,
                               float x, float y,
                               float width, float height)
{
   CtxDeferredCommand *deferred = deferred_new (ctx, name);
   deferred->is_rect = 1;
   ctx_rectangle (ctx, x, y, width, height);
}

static CtxList *ctx_deferred_commands (Ctx *ctx, const char *name, int *ret_count)
{
  CtxList *matching = NULL;
  uint32_t name_id = ctx_strhash (name);
  int count = 0;
  for (CtxList *l = ctx->deferred; l; l = l->next)
  {
    CtxDeferredCommand *command = l->data;
    if (name)
    {
       if (command->name == name_id)
       {
         ctx_list_prepend (&matching, command);
         count ++;
       }
    }
    else
    {
       if (command->name == 0)
       {
         ctx_list_prepend (&matching, command);
         count ++;
       }
    }
  }
  if (ret_count)
    *ret_count = count;
  return matching;
}

#if 0
void ctx_resolve_rel_line_to  (Ctx *ctx, const char *name,
                               void (*set_dim) (void *userdata,
                                                const char *name,
                                                int         count,
                                                float *x,
                                                float *y),
                               void *userdata)
{
  int count = 0;
  CtxList *matching = ctx_deferred_commands (ctx, name, &count);
  while (matching)
  {
    CtxDeferredCommand *command = matching->data;

    float x = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.x;
    float y = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.y;

    set_dim (userdata, name, count, &x, &y);

    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.x = x;
    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.y = y;

    ctx_list_remove (&ctx->deferred, command);
    ctx_list_remove (&matching, command);
    free (command);
  }
}

void ctx_resolve_rectangle    (Ctx *ctx, const char *name,
                               void (*set_dim) (void *userdata,
                                                const char *name,
                                                int         count,
                                                float *x,
                                                float *y,
                                                float *width,
                                                float *height),
                               void *userdata)
{
  int count = 0;
  CtxList *matching = ctx_deferred_commands (ctx, name, &count);
  while (matching)
  {
    CtxDeferredCommand *command = matching->data;

    float x = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.x;
    float y = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.y;
    float w = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.width;
    float h = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.height;

    set_dim (userdata, name, count, &x, &y, &w, &h);

    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.x = x;
    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.y = y;
    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.width = w;
    ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.height = h;
    ctx_list_remove (&ctx->deferred, command);
    ctx_list_remove (&matching, command);
    free (command);
  }
}
#endif

void ctx_resolve (Ctx *ctx, const char *name,
                            void (*resolve) (Ctx        *ctx,
                                             void       *userdata,
                                             const char *name,
                                             int         count,
                                             float      *x,
                                             float      *y,
                                             float      *width,  // ignored
                                             float      *height),// for non-rect
                             void *userdata)
{
  int count = 0;
  CtxList *matching = ctx_deferred_commands (ctx, name, &count);
  while (matching)
  {
    CtxDeferredCommand *command = matching->data;

    float x, y, w = 0, h = 0;
    if (command->is_rect)
    {
      x = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.x;
      y = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.y;
      w = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.width;
      h = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.height;
    }
    else
    {
      x = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.x;
      y = ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.y;
    }

    resolve (ctx, userdata, name, count, &x, &y, &w, &h);

    if (command->is_rect)
    {
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.x = x;
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.y = y;
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.width = w;
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rectangle.height = h;
    }
    else
    {
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.x = x;
      ((CtxCommand*)&ctx->drawlist.entries[command->offset])->rel_line_to.y = y;
    }
    ctx_list_remove (&ctx->deferred, command);
    ctx_list_remove (&matching, command);
    free (command);
  }
}

void _ctx_write_png (const char *dst_path, int w, int h, int num_chans, void *data)
{
#if CTX_IMAGE_WRITE
  size_t len = 0;
  char *buf = tdefl_write_image_to_png_file_in_memory (data, w, h, num_chans, &len);
  if (buf)
  {
    FILE *f = fopen (dst_path, "w");
    fwrite (buf, len, 1, f);
    fclose (f);
    mz_free (buf);
  }
#endif
}

const char *
ctx_str_decode (uint32_t number)
{
  static char temp[16];
  return squoze32_utf8_decode (number, temp);
}

uint32_t ctx_strhash(const char *str)
{
  return squoze32_utf8 (str, strlen (str));
}


#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;

  void     *ctx; // each line can have an attached ctx context;
  char     *prev;
  int       style_size;
  int       prev_length;
  CtxString *frame;


  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;
  int       wrapped;

  /*  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 = ctx_realloc (string->style, string->style_size, 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)
      { ctx_free (line->style); }
    if (line->ctx)
      { ctx_destroy (line->ctx); }
    if (line->ctx_copy)
      { ctx_destroy (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*)ctx_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 = ctx_calloc (sizeof (VtLine), 1);
  CtxString *string = (CtxString*)line;
  ctx_string_init (string, initial_size);
  if (initial)
    { ctx_string_append_str (string, initial); }
  line->style = ctx_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; //    0 if thread
  pid_t      pid; //    0 if thread
  int        done;

  void      *userdata;

  uint8_t   *shm;
  int        shm_size;
} 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);
#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        empty_count;
  int       id;
  unsigned char buf[BUFSIZ]; // need one per vt
  int keyrepeat;
  int       lastx;
  int       lasty;
  int        result;
  //SDL_Rect   dirty;
  float  dirtpad;
  float  dirtpad1;
  float  dirtpad2;
  float  dirtpad3;

  CtxClient *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       had_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];
};


// add vt_new_cb - suitable for hooking up to generic stdout/stdin callbacks
VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch);
VT *vt_new_argv (char **argv, int width, int height, float font_size, float line_spacing, int id, int can_launch);
VT *vt_new_thread (void (*start_routine)(void *userdata), void *userdata,
                   int width, int height, float font_size, float line_spacing, int id, int can_launch);


void vt_open_log (VT *vt, const char *path);

int         ctx_vt_had_alt_screen (VT *vt);
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 *ctx_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)

#if CTX_PTY
#define DEFAULT_SCROLLBACK   (1<<13)
#else
#define DEFAULT_SCROLLBACK   (2)
#endif
#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);
#if 0
void        vt_register_events      (VT *vt, Ctx *ctx, double x0, double y0);
#endif

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 EMSCRIPTEN
//#undef uncompress
//#include <zlib.h>
//#endif

#if CTX_AUDIO
#if CTX_SDL
#include <SDL.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;
}

#if CTX_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')
  {
    unsigned long len = bytes * 1.2;//compressBound(bytes);
    data = ctx_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 = ctx_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));
  ctx_free (encoded);

  if (data != samples)
    ctx_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)
  {
#if CTX_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;
#if CTX_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 = ctx_malloc (audio->data_size + 1);
     }
     else
     {
       audio->data_size += chunk_size;
       audio->data = ctx_realloc (audio->data, audio->data_size+1 - chunk_size, 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 = ctx_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 = ctx_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;
        ctx_free (data2);
      }
      break;
    }

    if (audio->frames)
    switch (audio->compression)
    {
      case 'z':
    {
      unsigned long int
              actual_uncompressed_size = audio->frames * audio->bits/8 * audio->channels + 512;
      unsigned char *data2 = ctx_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
      ctx_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;
      ctx_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);
             }
           }
         }
       }
       ctx_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)
      ctx_free (audio->data);
    audio->data = NULL;
    audio->data_size=0;
}
#endif
#endif
#if CTX_VT

/* 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
 *
 */

int ctx_dummy_in_len = 0;
#if CTX_TERMINAL_EVENTS

#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>
#if CTX_PTY
#include <sys/ioctl.h>
#include <termios.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);
#if CTX_PARSER
static void vt_state_ctx          (VT *vt, int byte);
#endif
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_=ctx_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_= ctx_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;
      ctx_free (*list);
      *list = prev;
      return;
    }
  for (iter = *list; iter; iter = iter->next)
    if (iter->data == data)
      {
        prev->next = iter->next;
        ctx_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_=ctx_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 CtxList *ctx_vts;
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)
    { ctx_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)
{
#if CTX_PTY
  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);
#endif
}

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_drawlist (vt->width, vt->height);
      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_destroy (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)

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);

static void vt_set_title (VT *vt, const char *new_title)
{
  if (vt->inert) return;
  if (vt->title)
    { ctx_free (vt->title); }
  vt->title = ctx_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);
}


static void ctx_clients_signal_child (int signum)
{
#if CTX_PTY
  pid_t pid;
  int   status;
  if ( (pid = waitpid (-1, &status, WNOHANG) ) != -1)
    {
      if (pid)
        {
          for (CtxList *l = ctx_vts; l; l=l->next)
            {
              VtPty *vt = l->data;
              if (vt->pid == pid)
                {
                  vt->done = 1;
                  //vt->result = status;
                }
            }
        }
    }
#endif
}

static void vt_init (VT *vt, int width, int height, float font_size, float line_spacing, int id, int can_launch)
{
  static int signal_installed = 0;
  if (!signal_installed)
  {
    signal (SIGCHLD,ctx_clients_signal_child);
    signal_installed = 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       = ctx_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->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;
}

#if CTX_PTY
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;
}
#endif

static void
ctx_child_prepare_env (int was_pidone, const char *term)
{
  if (was_pidone)
  {
    if (setuid(1000)) fprintf (stderr, "setuid failed\n");
  }
  else
  {
    for (int 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
}

void _ctx_add_listen_fd (int fd);
void _ctx_remove_listen_fd (int fd);

#ifdef EMSCRIPTEN

#define EM_BUFSIZE 81920

char em_inbuf[EM_BUFSIZE]="";
char em_outbuf[EM_BUFSIZE]="";
int em_in_len = 0;
int em_in_pos = 0;
int em_in_read_pos = 0;
EMSCRIPTEN_KEEPALIVE int em_out_len = 0;
int em_out_pos = 0;

ssize_t em_write (void *s, const void *buf, size_t count)
{
  const char *src = (const char*)buf;
  int i;
  for (i = 0; i < count && em_out_len < EM_BUFSIZE; i ++)
  {
    em_outbuf[em_out_pos++] = src[i];
    em_out_len++;
    if (em_out_pos >= EM_BUFSIZE)em_out_pos = 0;
  }
  if (em_out_len >= EM_BUFSIZE)
    printf ("em_outbuf overflow\n");
  else
  EM_ASM({
    console.log('a a ' + UTF8ToString($1));
    ws.send(new Blob([UTF8ToString($0)]));
    }, src
       );

  return i;
}

EMSCRIPTEN_KEEPALIVE
ssize_t em_buffer (void *s, const void *buf, size_t count)
{
  const char *src = (const char*)buf;
  int i;
  for (i = 0; i < count && em_in_len < EM_BUFSIZE; i ++)
  {
    em_inbuf[em_in_pos++] = src[i];
    em_in_len++;
    if (em_in_pos >= EM_BUFSIZE)em_in_pos = 0;
    if (src[i]=='\n')
    {
      em_inbuf[em_in_pos++] = '\r';
      em_in_len++;
      if (em_in_pos >= EM_BUFSIZE)em_in_pos = 0;
    }
  }
  if (em_in_len >= EM_BUFSIZE)
    printf ("em_inbuf overflow\n");
  return i;
}

ssize_t em_read    (void *serial_obj, void *buf, size_t count)
{
  char *dst = (char*)buf;
  if (em_in_len)
  {
    *dst = em_inbuf[em_in_read_pos++];
    --em_in_len;
    if (em_in_read_pos>=EM_BUFSIZE)em_in_read_pos = 0;
    return 1;
  }
  return 0;
}

int     em_waitdata (void *serial_obj, int timeout)
{
  return em_in_len;
}

#endif

#define CTX_VT_INBUFSIZE  128
#define CTX_VT_OUTBUFSIZE 128

static char ctx_dummy_inbuf[CTX_VT_INBUFSIZE]="";
static char ctx_dummy_outbuf[CTX_VT_OUTBUFSIZE]="";
static int ctx_dummy_in_pos = 0;
static int ctx_dummy_in_read_pos = 0;
static int ctx_dummy_out_len = 0;
static int ctx_dummy_out_pos = 0;
static int ctx_dummy_out_read_pos = 0;

void ctx_vt_write (Ctx *ctx, uint8_t byte)
{
  if (ctx_dummy_in_len < CTX_VT_INBUFSIZE)
  {
    ctx_dummy_inbuf[ctx_dummy_in_pos++] = byte;
    ctx_dummy_in_len++;
    if (ctx_dummy_in_pos >= CTX_VT_INBUFSIZE)ctx_dummy_in_pos = 0;
  }
  else
  {
    fprintf (stderr, "ctx uart overflow\n");
  }
}

int ctx_vt_has_data (Ctx *ctx)
{
  return ctx_dummy_out_len;
}

int ctx_vt_read (Ctx *ctx)
{
  int ret = -1;
  if (ctx_dummy_out_len)
  {
    ret = ctx_dummy_outbuf[ctx_dummy_out_read_pos++];
    --ctx_dummy_out_len;
    if (ctx_dummy_out_read_pos>=CTX_VT_OUTBUFSIZE)ctx_dummy_out_read_pos = 0;
  }
  return ret;
}


static ssize_t ctx_dummy_write (void *s, const void *buf, size_t count)
{
  const char *src = (const char*)buf;
  unsigned int i;
  for (i = 0; i < count && ctx_dummy_out_len < CTX_VT_OUTBUFSIZE; i ++)
  {
    ctx_dummy_outbuf[ctx_dummy_out_pos++] = src[i];
    ctx_dummy_out_len++;
    if (ctx_dummy_out_pos >= CTX_VT_OUTBUFSIZE)ctx_dummy_out_pos = 0;
  }
  if (ctx_dummy_out_len >= CTX_VT_OUTBUFSIZE)
    printf ("ctx_dummy_outbuf overflow\n");

  return i;
}

static ssize_t ctx_dummy_read    (void *serial_obj, void *buf, size_t count)
{
  char *dst = (char*)buf;
  if (ctx_dummy_in_len)
  {
    *dst = ctx_dummy_inbuf[ctx_dummy_in_read_pos++];
    --ctx_dummy_in_len;
    if (ctx_dummy_in_read_pos>=CTX_VT_INBUFSIZE)ctx_dummy_in_read_pos = 0;
    return 1;
  }
  return 0;
}

static int ctx_dummy_waitdata (void *serial_obj, int timeout)
{
  return ctx_dummy_in_len;
}

void ctx_dummy_resize  (void *serial_obj, int cols, int rows, int px_width, int px_height)
{
}

static void vt_run_argv (VT *vt, char **argv, const char *term)
{
#ifdef EMSCRIPTEN
        vt->read = em_read;
        vt->write = em_write;
        vt->waitdata = em_waitdata;
        vt->resize = dummy_resize;
#else

#if 0
  int was_pidone = (getpid () == 1);
#else
  int was_pidone = 0; // do no special treatment, all child processes belong
                      // to root
#endif

#if CTX_PTY==1
  if (!argv)
#endif
  {
    vt->read = ctx_dummy_read;
    vt->write = ctx_dummy_write;
    vt->waitdata = ctx_dummy_waitdata;
    vt->resize = ctx_dummy_resize;
    return;
  }


#if CTX_PTY

  struct winsize ws;
  //signal (SIGCHLD,signal_child);
  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);
#endif
  if (vt->vtpty.pid == 0)
    {
      ctx_child_prepare_env (was_pidone, term);

      execvp (argv[0], (char**)argv);
      exit (0);
    }
  else if (vt->vtpty.pid < 0)
    {
      VT_error ("forkpty failed (%s)", argv[0]);
      return;
    }
  fcntl(vt->vtpty.pty, F_SETFL, O_NONBLOCK|O_NOCTTY);
  _ctx_add_listen_fd (vt->vtpty.pty);
#endif
}


VT *vt_new_argv (char **argv, int width, int height, float font_size, float line_spacing, int id, int can_launch)
{
  VT *vt                 = ctx_calloc (sizeof (VT), 1);
  vt_init (vt, width, height, font_size, line_spacing, id, can_launch);
  vt_set_font_size (vt, font_size);
  vt_set_line_spacing (vt, line_spacing);
  //if (argv)
    {
      vt_run_argv (vt, argv, NULL);
    }
  if (width <= 0) width = 640;
  if (height <= 0) width = 480;
  vt_set_px_size (vt, width, height);

  vtcmd_reset_to_initial_state (vt, NULL);
  //vt->ctx = ctx_new ();
  ctx_list_prepend (&ctx_vts, vt);
  return vt;
}

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;
}


VT *vt_new (const char *command, int width, int height, float font_size, float line_spacing, int id, int can_launch)
{
  if (!command)
    return vt_new_argv (NULL, width, height, font_size, line_spacing, id, can_launch);
  char *cargv[32];
  int   cargc;
  char *rest, *copy;
  copy = ctx_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;
  return vt_new_argv ((char**)cargv, width, height, font_size, line_spacing, id, can_launch);
}


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)
    {
      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 CTX_PARSER
  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;
  }
#endif

  if(1)vt_rewrap (vt, icols);

  while (irows > vt->rows)
    {
      if (vt->scrollback_count && vt->scrollback)
        {
          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);
  ctx_client_rev_inc (vt->client);
  VT_info ("resize %i %i", irows, icols);
#if CTX_PARSER
  if (vt->ctxp)
    ctx_parser_destroy (vt->ctxp);
#endif
  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 = ctx_realloc (vt->argument_buf, vt->argument_buf_cap/2, 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);
  ctx_client_rev_inc (vt->client);
}

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;
  ctx_client_rev_inc (vt->client);
}

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");
    }
  ctx_client_rev_inc (vt->client);
}

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 (!((arg[1]>='0' && arg[1]<='9')) || 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.0f * 5;
  g = g / 255.0f * 5;
  b = b / 255.0f * 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;
  ctx_client_rev_inc (vt->client);
  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_destroy (vt->ctxp);
  //vt->ctxp = NULL;
}

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 SQZ_title:
      sprintf (str, "setkey %s %s\n", key, client->title);
      break;
    case SQZ_x:      
      sprintf (str, "setkey %s %i\n", key, client->x);
      break;
    case SQZ_y:    
      sprintf (str, "setkey %s %i\n", key, client->y);
      break;
    case SQZ_width:
      sprintf (str, "setkey %s %i\n", key, client->width);
      break;
    case SQZ_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->had_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;
         
#if CTX_PARSER 
          case 200:/*MODE;;ctx vector graphics mode;on;;*/
            if (set)
              {
                if (!vt->current_line->ctx)
                  {
                    vt->current_line->ctx = ctx_new (vt->width, vt->height, "drawlist");
                    vt->current_line->ctx_copy = ctx_new (vt->width, vt->height, "drawlist");
                    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)
                {

                if (vt->ctxp)
                  ctx_parser_destroy (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;
#endif
          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;
#if CTX_PARSER
          case 200:/*ctx protocol;On;;*/
            is_set = (vt->state == vt_state_ctx);
            break;
#endif
          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)
{
  /* \033[21y is request title - allows inserting keychars */
  if      (!strcmp (sequence,  "[1t")) { ctx_client_unshade (vt->root_ctx, vt->id); }
  else if (!strcmp (sequence,  "[2t")) { ctx_client_shade (vt->root_ctx, vt->id); } 
  else if (!strncmp (sequence, "[3;", 3)) {
    int x=0,y=0;
    sscanf (sequence, "[3;%i;%ir", &y, &x);
    ctx_client_move (vt->root_ctx, 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->root_ctx, vt->id, width, height);
  }
  else if (!strcmp (sequence, "[5t") ) { ctx_client_raise_top (vt->root_ctx, vt->id); } 
  else if (!strcmp (sequence, "[6t") ) { ctx_client_lower_bottom (vt->root_ctx, vt->id); } 
  else if (!strcmp (sequence, "[7t") ) { 
          ctx_client_rev_inc (vt->client);
          /* 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->root_ctx, vt->id, cols * vt->cw, rows * vt->ch);
  }
  else if (!strcmp (sequence, "[9;0t") ) { ctx_client_unmaximize (vt->root_ctx, vt->id); } 
  else if (!strcmp (sequence, "[9;1t") ) { ctx_client_maximize (vt->root_ctx, vt->id);} 

  /* should actually be full-screen */
  else if (!strcmp (sequence, "[10;0t") ) { ctx_client_unmaximize (vt->root_ctx, vt->id); } 
  else if (!strcmp (sequence, "[10;1t") ) { ctx_client_maximize (vt->root_ctx, vt->id);} 
  else if (!strcmp (sequence, "[10;2t") ) { ctx_client_toggle_maximized (vt->root_ctx, vt->id);} 

  else if (!strcmp (sequence, "[11t") )  /* report window state  */
    {
      char buf[128];
      if (ctx_client_is_iconified (vt->root_ctx, 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->root_ctx, vt->id), ctx_client_x (vt->root_ctx, 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->cursor_x > 1 && ! vt->tabs[ (int) 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?
}
void vt_audio_task (VT *vt, int click);

#if CTX_TILED
static void ctx_show_frame (Ctx *ctx, int block)
{
  CtxTiled *tiled = (CtxTiled*)(ctx->backend);
  tiled->show_frame (tiled, block);
}
#endif

static void ctx_wait_frame (Ctx *ctx, VT *vt)
{
#if CTX_TILED
  if (ctx_backend_is_tiled (ctx))
  {
    CtxTiled *tiled = (CtxTiled*)(ctx->backend);
    int max_wait    = 500;
    //int wait_frame  = tiled->frame;  // tiled->frame and tiled->render_frame are expected
                                       // to be equal, unless something else has timed out
    int wait_frame  = tiled->render_frame;
    ctx_show_frame (ctx, 0);
    while (wait_frame > tiled->shown_frame &&
           max_wait-- > 0)
    {
#if CTX_AUDIO
      usleep (10);
      vt_audio_task (vt, 0);
#else
      usleep (10);
#endif
      ctx_show_frame (ctx, 0);
    }
#if 1
    if (max_wait > 0)
    {}//fprintf (stderr, "[%i]", max_wait);
    else
      fprintf (stderr, "[wait-drop]");
#endif
  }
#endif
}

static void vtcmd_report (VT *vt, const char *sequence)
{
  char buf[64]="";
  if (!strcmp (sequence, "[5n") ) // DSR device status report
    {
      ctx_wait_frame (vt->root_ctx, vt);
      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>\033[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>\033[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;
  ctx_client_rev_inc (vt->client);
  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"

#if CTX_SDL
static void vt_state_apc_audio (VT *vt, int byte)
{
  if ( (byte < 32) && ( (byte < 8) || (byte > 13) ) )
    {
#if CTX_AUDIO
      vt_audio (vt, vt->argument_buf);
#endif
      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 = ctx_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 */
#if CTX_AUDIO
        vt_bell (vt);
#endif
        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 = ctx_malloc (vt->gfx.data_size + 1);
      }
    else
      {
        vt->gfx.data_size += chunk_size;
        vt->gfx.data = ctx_realloc (vt->gfx.data, vt->gfx.data_size-chunk_size,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 = ctx_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 = ctx_malloc (vt->gfx.buf_size + 1);
          /* if a buf size is set (rather compression, but
           * this works first..) then */
      unsigned long int
          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 CTX_STB_IMAGE
      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;
        }
#endif
      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 = ctx_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);
    }
  ctx_client_rev_inc (vt->client);
}

#if CTX_PARSER
static void vt_state_ctx (VT *vt, int byte)
{
  ctx_parser_feed_byte (vt->ctxp, 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\033\",  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
#if CTX_STB_IMAGE
          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 = ctx_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 = ctx_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 = ctx_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;
#endif
          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..\033\", 0, vtcmd_delete_n_chars, VT102}, /* ref:none id: <a href='https://sw.kovidgoyal.net/kitty/graphics-protocol.html'>kitty graphics</a> */ "
    {"_A..\033\", 0, vtcmd_delete_n_chars, VT102}, /* id:  <a href='https://github.com/hodefoting/atty/'>atty</a> audio input/output */ "
    {"_C..\033\", 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\033\",  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;
  int first = 1;
  while (remaining_chars > 0 &&
         vt_waitdata (vt, first?0:1000*5) &&
         ( ticks - start_ticks < timeout
#if CTX_PARSER
	   ||  vt->state == vt_state_ctx
#endif
	   
	   ))
    {
      first = 0;
#if CTX_AUDIO
      vt_audio_task (vt, 0);
#endif
  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 CTX_PARSER
      if (vt->state == vt_state_ctx) {
         if (remaining_chars < read_size)
         {
           remaining_chars = read_size * 2;
         }
      }
#endif
      ticks = ctx_ticks ();
    }
  if (got_data < 0)
    {
      vt->empty_count ++;
      if (vt->empty_count > 256)
      {
        if (kill (vt->vtpty.pid, 0) != 0)
        {
          vt->vtpty.done = 1;
        }
        vt->empty_count = 1;
      }
    }
  else
     vt->empty_count = 0;
  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 (!ctx_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);
      ctx_client_rev_inc (vt->client);
      return;
    }
  else if (!strcmp (str, "shift-control-end"))
    {
      int new_scroll = 0;
      vt_set_scroll (vt, new_scroll);
      ctx_client_rev_inc (vt->client);
      return;
    }
  else if (!strcmp (str, "shift-control-down"))
    {
      int new_scroll = vt_get_scroll (vt) - 1;
      vt_set_scroll (vt, new_scroll);
      ctx_client_rev_inc (vt->client);
      return;
    }
  else if (!strcmp (str, "shift-control-up"))
    {
      int new_scroll = vt_get_scroll (vt) + 1;
      vt_set_scroll (vt, new_scroll);
      ctx_client_rev_inc (vt->client);
      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);
      ctx_client_rev_inc (vt->client);
      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);
      ctx_client_rev_inc (vt->client);
      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 (str[0]=='p' && str[1] != 0 && str[2] == ' ')
    {
      int cw = vt_cw (vt);
      int ch = vt_ch (vt);
      if (!strncmp (str, "pm", 2))
        {
          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, "pp", 2))
        {
          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, "pd", 2))
        {
          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, "pr", 2))
        {
          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 *ctx_find_shell_command (void)
{
#if CTX_PTY
#ifdef EMSCRIPTEN
  return NULL;  
#else
  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;
#endif
#else
  return NULL;
#endif
}




static void vt_run_command (VT *vt, const char *command, const char *term)
{
#if CTX_PTY
#ifdef EMSCRIPTEN
        printf ("run command %s\n", command);
#else
  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)
    {
      ctx_child_prepare_env (was_pidone, term);
      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);
#endif
#endif
}


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 CTX_PARSER
  if (vt->ctxp)
    ctx_parser_destroy (vt->ctxp);
#endif
  //if (vt->ctx)
  //  { ctx_destroy (vt->ctx); }
  free (vt->argument_buf);
  ctx_list_remove (&ctx_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)
{
  int max_pop = 0;
  int no = 0;
  for (CtxList *l = vt->lines; l; l = l->next, no++)
  {
    CtxString *str = l->data;
    if (str->str[0]) max_pop = no;
  }
  return max_pop + 1;
}

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;

  CtxBackendType backend_type = ctx_backend_type (ctx);

  if (backend_type != CTX_BACKEND_TERM)
  {
    // 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;
  ctx_move_to (ctx, x, y);
  if (bold)
  {
    if (!did_save)
    {
      ctx_save (ctx);
      did_save = 1;
    }
    // TODO : check if proportional and use other font for that
    ctx_font (ctx, "Mono Bold");
  }
  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) );
          ctx_font (ctx, "Mono Italic");
        }
      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 ctx_client_mouse_event (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  if (!client)
  {
    event->stop_propagate = 1;
    return;
  }
  VT *vt = client->vt;

  float  x = event->x;
  float  y = event->y;
  int device_no = event->device_no;
  char buf[128]="";

  if (vt)
  {
  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))
  {
    scrollbar_drag (event, vt, data2);
    return;
  }
  switch (event->type)
  {
    case CTX_MOTION:
    case CTX_DRAG_MOTION:
      //if (event->device_no==1)
      {
        sprintf (buf, "pm %.0f %.0f %i", x, y, device_no);
//      ctx_queue_draw (event->ctx);
        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, "pp %.0f %.0f %i", x, y, device_no);
        ctx_client_lock (client);
        vt_feed_keystring (vt, event, buf);
        ctx_client_unlock (client);
//      ctx_queue_draw (event->ctx);
//      vt->rev++;
      }
      break;
    case CTX_DRAG_RELEASE:
      if (event->device_no==3 && !vt->in_alt_screen)
      {
        vt->popped = 0;
      }
        ctx_queue_draw (event->ctx);
        sprintf (buf, "pr %.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;
  }
  }
  else
  {
     CtxEvent *copy = ctx_event_copy (event);
     ctx_list_append (&client->ctx_events, copy);
  }
  event->stop_propagate = 1;
//vt->rev++;
}

void vt_mouse_event (CtxEvent *event, void *data, void *data2)
{
  VT   *vt = data;
  CtxClient *client = vt_get_client (vt);
  if (!client)
  {
    event->stop_propagate = 1;
    return;
  }
  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))
   {
    scrollbar_drag (event, vt, data2);return;
   }
  switch (event->type)
  {
    case CTX_MOTION:
    case CTX_DRAG_MOTION:
      //if (event->device_no==1)
      {
        sprintf (buf, "pm %.0f %.0f %i", x, y, device_no);
//      ctx_queue_draw (event->ctx);
        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, "pp %.0f %.0f %i", x, y, device_no);
        ctx_client_lock (client);
        vt_feed_keystring (vt, event, buf);
        ctx_client_unlock (client);
//      ctx_queue_draw (event->ctx);
//      vt->rev++;
      }
      break;
    case CTX_DRAG_RELEASE:
      if (event->device_no==3 && !vt->in_alt_screen)
      {
        vt->popped = 0;
      }
        ctx_queue_draw (event->ctx);
        sprintf (buf, "pr %.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

int ctx_vt_had_alt_screen (VT *vt)
{
  return vt?vt->had_alt_screen:0;
}

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->root_ctx, 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; }
  ctx_client_rev_inc (vt->client);
  ctx_queue_draw (event->ctx);
  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->root_ctx, vt->id);
  float y = ctx_client_y (vt->root_ctx, 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 ctx_client_register_events (CtxClient *client, Ctx *ctx, double x0, double y0)
{
  ctx_begin_path (ctx);
  ctx_save (ctx);
  ctx_translate (ctx, x0, y0);
  ctx_rectangle (ctx, 0, 0, client->width, client->height);
  ctx_listen (ctx, CTX_DRAG,   ctx_client_mouse_event, client, NULL);
  ctx_listen (ctx, CTX_MOTION, ctx_client_mouse_event, client, NULL);
  ctx_begin_path (ctx);
  ctx_restore (ctx);
}

#if 0
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);
}
#endif

void vt_draw (VT *vt, Ctx *ctx, double x0, double y0)
{
  ctx_begin_path (ctx);
  ctx_save (ctx);
  ctx_translate (ctx, x0, y0);
  if (getenv ("CTX_STAR_WARS"))
  ctx_apply_transform (ctx, 0.3120, -0.666, 700.,
                            0.0000, 0.015,  200.0,
                            0.00, -0.0007, 1.0);
  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 CTX_PTY
      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_rgba (ctx,0,0,0,1.0f);
          ctx_fill  (ctx);
        }
#else
          //ctx_rgba (ctx,0,0,0,1.0f);
          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;
             ctx_client_rev_inc (vt->client);
           }
       }
     else
       {
         vt->scroll_offset -= SCROLL_SPEED;
         if (vt->scroll_offset <= 0.0)
           {
             vt->scroll_offset = 0;
             vt->in_smooth_scroll = 0;
             ctx_client_rev_inc (vt->client);
           }
       }
   }

    /* 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);
    }

  if (getenv ("CTX_STAR_WARS"))
  ctx_apply_transform (ctx, 0.3120, -0.666, 700.,
                            0.0000, 0.015,  200.0,
                            0.00, -0.0007, 1.0);
    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;
}

#if CTX_PTY
static unsigned long prev_press_time = 0;
static int short_count = 0;
#endif

void terminal_set_primary (const char *text)
{
  if (primary) ctx_free (primary);
  primary = NULL;
  if (text) primary = ctx_strdup (text);
}

void terminal_long_tap (Ctx *ctx, VT *vt);
#if CTX_PTY
static int long_tap_cb_id = 0;
#endif
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)
{
#if CTX_PTY
 char buf[64]="";
 int button_state = 0;
 ctx_client_rev_inc (vt->client);
 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;
                 ctx_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;
                 ctx_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);
                    ctx_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);
                    ctx_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 ();
         ctx_client_rev_inc (vt->client);
       }
     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);
             ctx_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;
         }

         ctx_client_rev_inc (vt->client);
       }
     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) );
     fsync (vt->vtpty.pty);
   }
#endif
}

pid_t vt_get_pid (VT *vt)
{
  return vt->vtpty.pid;
}

void vt_set_ctx (VT *vt, Ctx *ctx)
{
  vt->root_ctx = ctx;
}

#endif
#endif
#endif

float ctx_target_fps = 100.0; /* this might end up being the resolution of our
                                 idle callback firing
                               */

#if CTX_VT


#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 <math.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));


void terminal_update_title    (const char *title);
void ctx_sdl_set_fullscreen   (Ctx *ctx, int val);
int  ctx_sdl_get_fullscreen   (Ctx *ctx);
static int ctx_fetched_bytes = 1;

CtxClient *vt_get_client (VT *vt);

void ctx_client_set_title        (Ctx *ctx, int id, const char *title)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client)
     return;
   if (client->title)
     ctx_free (client->title);
   client->title = NULL;
   if (title)
     client->title = ctx_strdup (title);
}
const char *ctx_client_get_title (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client)
     return NULL;
   return client->title;
}

int vt_set_prop (VT *vt, uint32_t key_hash, const char *val)
{
#if CTX_VT
#if 1
  switch (key_hash)
  {
    case SQZ_title:  
     {
       CtxClient *client = vt_get_client (vt);
       if (client)
       {
         ctx_client_set_title (vt->root_ctx, client->id, val);
         //if (client->title) ctx_free (client->title);
         //client->title = ctx_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 SQZ_title:  ctx_client_set_title (ct->id, val); break;
    case SQZ_x:      client->x = fval; break;
    case SQZ_y:      client->y = fval; break;
    case SQZ_width:  ctx_client_resize (ct->id, fval, client->height); break;
    case SQZ_height: ctx_client_resize (ct->id, client->width, fval); break;
    case SQZ_action:
      switch (val_hash)
      {
        case SQZ_maximize:     ctx_client_maximize (client); break;
        case SQZ_unmaximize:   ctx_client_unmaximize (client); break;
        case SQZ_lower:        ctx_client_lower (client); break;
        case SQZ_lowerBottom:  ctx_client_lower_bottom (client);  break;
        case SQZ_raise:        ctx_client_raise (client); break;
        case SQZ_raiseTop:     ctx_client_raise_top (client); break;
      }
      break;
  }
  ct->rev++;
#endif
#endif
  return 0;
}

static float _ctx_font_size = 10.0;


int ctx_client_resize (Ctx *ctx, int id, int width, int height);
void ctx_client_maximize (Ctx *ctx, int id);

CtxClient *vt_get_client (VT *vt)
{
#if CTX_VT
  for (CtxList *l = ctx_clients (vt->root_ctx); l; l =l->next)
  {
    CtxClient *client = l->data;
    if (client->vt == vt)
            return client;
  }
#endif
  return NULL;
}

static void ctx_client_init (Ctx *ctx, CtxClient *client, int x, int y, int width, int height, float font_size,
                             CtxClientFlags flags, void *user_data, CtxClientFinalize finalize)
{
  static int global_id = 0;


  if (font_size <= 0.0) font_size = ctx_get_font_size (ctx);
  if (ctx_backend_type (ctx) == CTX_BACKEND_TERM)
  {
    font_size = 3;
  }
  client->id = ++global_id; // starting at 1 is nicer, then we can use 0 for none
  client->x = x;
  client->y = y;
  client->flags = flags;
  client->ctx = ctx;
  client->width = width;
  client->height = height;
  client->user_data = user_data;
  client->finalize = finalize;
  client->opacity = 1.0f;

      //fprintf (stderr, "client new:%f\n", font_size);
#if CTX_THREADS
  mtx_init (&client->mtx, mtx_plain);
#endif
}

CtxClient *ctx_client_new (Ctx *ctx,
                           const char *commandline,
                           int x, int y, int width, int height,
                           float font_size,
                           CtxClientFlags flags,
                           void *user_data,
                           CtxClientFinalize finalize)
{
  CtxClient *client = ctx_calloc (sizeof (CtxClient), 1);
  ctx_list_append (&ctx->events.clients, client);
  ctx_client_init (ctx, client, x, y, width, height, font_size, flags, user_data, finalize);
  float line_spacing = 2.0f;
  client->vt = vt_new (commandline, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0);
  client->vt->client = client;
  vt_set_ctx (client->vt, ctx);
  return client;
}

CtxClient *ctx_client_new_argv (Ctx *ctx, char **argv, int x, int y, int width, int height, float font_size, CtxClientFlags flags, void *user_data, CtxClientFinalize finalize)
{

  CtxClient *client = ctx_calloc (sizeof (CtxClient), 1);
  ctx_client_init (ctx, client, x, y, width, height, font_size, flags, user_data, finalize);
  ctx_list_append (&ctx->events.clients, client);

  float line_spacing = 2.0f;
  client->vt = vt_new_argv (argv, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0);
  client->vt->client = client;
  vt_set_ctx (client->vt, ctx);
  return client;
}

#ifndef EMSCRIPTEN
#if 0
static void *launch_client_thread (void *data)
{
  CtxClient *client = data;

  client->sub_ctx = ctx_new (client->width, client->height,
                                "headless");

  client->start_routine (client->sub_ctx, client->user_data);

  fprintf (stderr, "%s: cleanup\n", __FUNCTION__);
  ctx_destroy (client->sub_ctx);
  return NULL;
}

CtxClient *ctx_client_new_thread (Ctx *ctx, void (*start_routine)(Ctx *ctx, void *user_data),
                                  int x, int y, int width, int height, float font_size, CtxClientFlags flags, void *user_data, CtxClientFinalize finalize)
{
  CtxClient *client = ctx_calloc (sizeof (CtxClient), 1);
  ctx_client_init (ctx, client, x, y, width, height, font_size, flags, user_data, finalize);

  ctx_list_append (&ctx->events.clients, client);


  client->start_routine = start_routine;
  thrd_create (&client->tid, launch_client_thread, client);
  //float line_spacing = 2.0f;
  //client->vt = vt_new_thread (start_routine, userdata, width, height, font_size,line_spacing, client->id, (flags & ITK_CLIENT_CAN_LAUNCH)!=0);
  //vt_set_ctx (client->vt, ctx);
  if (client->vt)
    client->vt->client = client;
  return client;
}
#endif
#endif

#if CTX_SHAPE_CACHE
extern float ctx_shape_cache_rate;
#endif
#if CTX_THREADS
extern int _ctx_max_threads;
#endif

static int focus_follows_mouse = 0;

static CtxClient *find_active (Ctx *ctx, int x, int y)
{
  CtxClient *ret = NULL;
  float titlebar_height = _ctx_font_size;
  int resize_border = titlebar_height/2;

  for (CtxList *l = ctx_clients (ctx); l; l = l->next)
  {
     CtxClient *c = l->data;
     if ((c->flags & ITK_CLIENT_MAXIMIZED) && c == ctx->events.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 = ctx_clients (ctx); 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 (Ctx *ctx, int id)
{
  CtxList *l;
  int no = 0;

  for (l = ctx_clients (ctx); l; l = l->next)
  {
    CtxClient *client = l->data;
    if (client->id == id)
      return no;
    no++;
  }
  return -1;
}

void ctx_client_move (Ctx *ctx, int id, int x, int y);
int ctx_client_resize (Ctx *ctx, int id, int w, int h);
void ctx_client_shade_toggle (Ctx *ctx, int id);
float ctx_client_min_y_pos (Ctx *ctx);
float ctx_client_max_y_pos (Ctx *ctx);

static void ctx_clients_ensure_layout (Ctx *ctx)
{
  CtxList *clients = ctx_clients (ctx);
  int n_clients = ctx_list_length (clients);
  if (n_clients == 1)
  {
    CtxClient *client = clients->data;
    if (client->flags & ITK_CLIENT_MAXIMIZED)
    {
      ctx_client_move (ctx, client->id, 0, 0);
      ctx_client_resize (ctx, client->id, ctx_width (ctx), ctx_height(ctx));
      if (ctx->events.active_tab == NULL)
        ctx->events.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 (ctx, client->id, 0, ctx_client_min_y_pos (ctx));
      ctx_client_resize (ctx, 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 (ctx->events.active_tab == NULL)
        ctx->events.active_tab = client;
    }
  }
}

CtxClient *ctx_client_by_id (Ctx *ctx, int id)
{
  for (CtxList *l = ctx_clients (ctx); 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)
    ctx_free (client->title);

#if VT_RECORD
  if (client->recording)
    ctx_destroy (client->recording);
#endif
  if (client->finalize)
     client->finalize (client, client->user_data);

  ctx_list_remove (&ctx->events.clients, client);

  if (client == ctx->events.active_tab)
  {
    ctx->events.active_tab = NULL;
  }

  if (ctx)
  if (client == ctx->events.active)
  {
    ctx->events.active = find_active (ctx, ctx_pointer_x (ctx), ctx_pointer_y (ctx));
    if (!ctx->events.active)
    {
      if (ctx->events.clients)
        ctx->events.active = ctx->events.clients->data;
    }
  }

  ctx_client_unlock (client);
  ctx_free (client);
}

void ctx_client_remove_by_id (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (client)
    ctx_client_remove (ctx, client);
}

int ctx_client_height (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (!client) return 0;
  return client->height;
}

int ctx_client_x (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (!client) return 0;
  return client->x;
}

int ctx_client_y (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (!client) return 0;
  return client->y;
}

void ctx_client_raise_top (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (!client) return;
  ctx_list_remove (&ctx->events.clients, client);
  ctx_list_append (&ctx->events.clients, client);
  ctx_queue_draw (ctx);
}

void ctx_client_lower_bottom (Ctx *ctx, int id)
{
  CtxClient *client = ctx_client_by_id (ctx, id);
  if (!client) return;
  ctx_list_remove (&ctx->events.clients, client);
  ctx_list_prepend (&ctx->events.clients, client);
  ctx_queue_draw (ctx);
}


void ctx_client_iconify (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   client->flags |= ITK_CLIENT_ICONIFIED;
   ctx_queue_draw (ctx);
}

int ctx_client_is_iconified (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_ICONIFIED) != 0;
}

void ctx_client_uniconify (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   client->flags &= ~ITK_CLIENT_ICONIFIED;
   ctx_queue_draw (ctx);
}

void ctx_client_maximize (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   if (!(client->flags &  ITK_CLIENT_MAXIMIZED))
   {
     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;
     ctx_client_move (ctx, id, 0, ctx_client_min_y_pos (client->ctx));
   }

   // enforce_layout does the size
   //client_resize (ctx, id, ctx_width (ctx), ctx_height(ctx) - ctx_client_min_y_pos (ctx));
   
   ctx->events.active = ctx->events.active_tab = client;
   ctx_queue_draw (ctx);
}

int ctx_client_is_maximized (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_MAXIMIZED) != 0;
}

void ctx_client_unmaximize (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   if ((client->flags & ITK_CLIENT_MAXIMIZED) == 0)
     return;
   client->flags &= ~ITK_CLIENT_MAXIMIZED;
   ctx_client_resize (ctx, id, client->unmaximized_width, client->unmaximized_height);
   ctx_client_move (ctx, id, client->unmaximized_x, client->unmaximized_y);
   ctx->events.active_tab = NULL;
   ctx_queue_draw (ctx);
}

void ctx_client_maximized_toggle (Ctx *ctx, int id)
{
  if (ctx_client_is_maximized (ctx, id))
    ctx_client_unmaximize (ctx, id);
  else
    ctx_client_maximize (ctx, id);
}


void ctx_client_shade (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   client->flags |= ITK_CLIENT_SHADED;
   ctx_queue_draw (ctx);
}

int ctx_client_is_shaded (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return -1;
   return (client->flags & ITK_CLIENT_SHADED) != 0;
}

void ctx_client_unshade (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   client->flags &= ~ITK_CLIENT_SHADED;
   ctx_queue_draw (ctx);
}

void ctx_client_toggle_maximized (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   if (ctx_client_is_maximized (ctx, id))
     ctx_client_unmaximize (ctx, id);
   else
     ctx_client_maximize (ctx, id);
}

void ctx_client_shade_toggle (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client) return;
   if (ctx_client_is_shaded (ctx, id))
    ctx_client_shade (ctx, id);
   else
    ctx_client_unshade (ctx, id);
}


void ctx_client_paste (Ctx *ctx, int id, const char *str)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (client && client->vt)
     vt_paste (client->vt, str);
}

char  *ctx_client_get_selection (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (client && client->vt)
     return vt_get_selection (client->vt);
   return ctx_strdup ("");
}

void ctx_client_move (Ctx *ctx, int id, int x, int y)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (client && (client->x != x || client->y != y))
   {
     client->x = x;
     client->y = y;
     ctx_client_rev_inc (client);
     ctx_queue_draw (ctx);
   }
}

void ctx_client_set_font_size (Ctx *ctx, int id, float font_size)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (client->vt)
   {
     if (vt_get_font_size (client->vt) != font_size)
       vt_set_font_size (client->vt, font_size);
     ctx_queue_draw (ctx);
   }
}

float ctx_client_get_font_size (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (client->vt)
     return vt_get_font_size (client->vt);
   return 14.0;
}

void ctx_client_set_opacity (Ctx *ctx, int id, float opacity)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client)
     return;
   if (opacity > 0.98) opacity = 1.0f;
   client->opacity = opacity;
   ctx_queue_draw (ctx);
}

float ctx_client_get_opacity (Ctx *ctx, int id)
{
   CtxClient *client = ctx_client_by_id (ctx, id);
   if (!client)
     return 1.0f;
   return client->opacity;
}

int ctx_client_resize (Ctx *ctx, int id, int width, int height)
{
   CtxClient *client = ctx_client_by_id (ctx, 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);
     ctx_queue_draw (ctx);
     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 (ctx, client->id);
      ctx_client_maximized_toggle (event->ctx, 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_backend_type (event->ctx) == CTX_BACKEND_TERM)
     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 (event->ctx, client->id, new_x, new_y);

  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 (event->ctx, client->id, new_w, new_h);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, new_w, client->height);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, client->width, new_h);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, client->width, new_h);
  ctx_client_move (event->ctx, client->id, client->x, new_y);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, new_w, new_h);
  ctx_client_move (event->ctx, client->id, client->x, new_y);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, new_w, new_h);
  ctx_client_move (event->ctx, client->id, new_x, client->y);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, new_w, new_h);
  ctx_client_move (event->ctx, client->id, new_x, new_y);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);
  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 (event->ctx, client->id, new_w, client->height);
  ctx_client_move (event->ctx, client->id, new_x, client->y);
  ctx_client_rev_inc (client);
  ctx_queue_draw (event->ctx);

  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_queue_draw (event->ctx);
  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 0
    if (client->tid)
    {
      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 (ctx_find_shell_command(), 1);
      }
#endif
      //itk_sameline (itk);
      on_screen_keyboard = itk_toggle (itk, "on screen keyboard", on_screen_keyboard);
      focus_follow_mouse = 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);
    }
    else
#endif
    {
       ctx_client_lock (client);

       int found = 0;
       for (CtxList *l2 = ctx_clients (ctx); l2; l2 = l2->next)
         if (l2->data == client) found = 1;
       if (found)
       {

      int rev = ctx_client_rev (client);
#if VT_RECORD
      if (client->drawn_rev != rev)
      {
        if (!client->recording)
          client->recording = _ctx_new_drawlist (client->width, client->height);
        else
          ctx_start_frame (client->recording);
        vt_draw (client->vt, client->recording, 0.0, 0.0);
      }

      if (client->recording)
      {
        ctx_save (ctx);
        ctx_translate (ctx, x, y);
        if (client->opacity != 1.0f)
        {
          ctx_global_alpha (ctx, client->opacity);
        }
        ctx_render_ctx (client->recording, ctx);
        vt_register_events (client->vt, ctx, 0.0, 0.0);
        ctx_restore (ctx);
      }
#else
      if (client->opacity != 1.0)
      {
        ctx_save (ctx);
        ctx_global_alpha (ctx, client->opacity);
      }
      vt_draw (client->vt, ctx, x, y);
      if (client->opacity != 1.0)
      {
        ctx_restore (ctx);
      }
      ctx_client_register_events (client, 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 = ctx_client_rev (client);
#if VT_RECORD
      if (client->drawn_rev != rev)
      {
        if (!client->recording)
          client->recording = _ctx_new_drawlist (client->width, client->height);
        else
          ctx_start_frame (client->recording);
        vt_draw (client->vt, client->recording, 0.0, 0.0);
      }

      if (client->recording)
      {
        ctx_save (ctx);
        if (client->opacity != 1.0f)
        {
          ctx_global_alpha (ctx, client->opacity);
        }
        ctx_render_ctx_textures (client->recording, ctx);
        ctx_restore (ctx);
      }
#else
    if (client->vt)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
}


CtxEvent *ctx_event_copy (CtxEvent *event)
{
  CtxEvent *copy = ctx_calloc (1, sizeof (CtxEvent));
  *copy = *event;
  if (copy->string) {
    copy->string = ctx_strdup (copy->string);
    copy->owns_string = 1;
  }
  return copy;
}

#if 0
void ctx_client_handle_event (Ctx *ctx, CtxEvent *ctx_event, const char *event)
{
  if (!ctx->events.active)
    return;
  if (ctx->events.active->internal)
    return;
  VT *vt = ctx->events.active->vt;
  CtxClient *client = vt_get_client (vt);

  ctx_client_lock (client);

  if (!strcmp (event, "F11"))
  {
#if CTX_SDL
    if (ctx_backend_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);
          ctx_free (text);
        }
    }
  else if (!strcmp (event, "shift-control-c") && vt)
    {
      char *text = vt_get_selection (vt);
      if (text)
        {
          ctx_set_clipboard (ctx, text);
          ctx_free (text);
        }
    }
  else if (!strcmp (event, "shift-control-t") ||
           ((ctx_backend_is_fb (ctx) || ctx_backend_is_term (ctx) || ctx_backend_is_kms (ctx))
           &&   !strcmp (event, "control-t") ))
  {
    //XXX add_tab (ctx_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") )
    {
      ctx->events.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);
          ctx_free (sel);
        }
      }
    }
  else
    {
      if (vt)
        vt_feed_keystring (vt, ctx_event, event);
    }
  ctx_client_unlock (client);
}
#endif

static int ctx_clients_dirty_count (Ctx *ctx)
{
  int changes = 0;
  for (CtxList *l = ctx_clients (ctx); l; l = l->next)
  {
    CtxClient *client = l->data;
    if ((client->drawn_rev != ctx_client_rev (client) ) ||
        vt_has_blink (client->vt))
      changes++;
  }
  return changes;
}

static void ctx_client_titlebar_drag_maximized (CtxEvent *event, void *data, void *data2)
{
  CtxClient *client = data;
  Ctx *ctx = event->ctx;
  ctx->events.active = ctx->events.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 (ctx, client->id);
      ctx_client_unmaximize (ctx, client->id);
      ctx_client_raise_top (ctx, client->id);
      ctx->events.active_tab = NULL;
    }
    prev_drag_end_time = event->time;
  }
  ctx_queue_draw (event->ctx);
  ctx_client_rev_inc (client);
  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 CTX_PTY==0
  ctx_move_to (ctx, x, y + titlebar_height * 0.8);
  if (client == ctx->events.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 == ctx->events.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 == ctx->events.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 == ctx->events.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 == ctx->events.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, int layer2)
{
  CtxList *clients = ctx_clients (ctx);
  _ctx_font_size = ctx_get_font_size (ctx);
  float titlebar_height = _ctx_font_size;
  int n_clients         = ctx_list_length (clients);

  if (ctx->events.active && flag_is_set(ctx->events.active->flags, ITK_CLIENT_MAXIMIZED) && n_clients == 1)
  {
    ctx_client_draw (ctx, ctx->events.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 == ctx->events.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 (layer2)
    {
      if (!flag_is_set (client->flags, ITK_CLIENT_LAYER2))
        continue;
    }
    else
    {
      if (flag_is_set (client->flags, ITK_CLIENT_LAYER2))
        continue;
    }

    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 == ctx->events.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))
      {
#if CTX_PTY
        itk_style_color (ctx, "titlebar-focused-bg");
#else
        ctx_rgb(ctx,0.1,0.2,0.3);
#endif

        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_active (Ctx *ctx)
{
  if (ctx->events.active) return ctx->events.active->id;
  return -1;
}

int ctx_clients_need_redraw (Ctx *ctx)
{
  int changes = 0;
  int follow_mouse = focus_follows_mouse;
      CtxList *to_remove = NULL;
  ctx_clients_ensure_layout (ctx);

//  if (print_shape_cache_rate)
//    fprintf (stderr, "\r%f ", ctx_shape_cache_rate);

   CtxClient *client = find_active (ctx, ctx_pointer_x (ctx),
                                    ctx_pointer_y (ctx));

   if (follow_mouse || ctx_pointer_is_down (ctx, 0) ||
       ctx_pointer_is_down (ctx, 1) || (ctx->events.active==NULL))
   {
        if (client)
        {
          if (ctx->events.active != client)
          {
            ctx->events.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 (&ctx->events.clients, client);
                ctx_list_append (&ctx->events.clients, client);
              }
#endif
            }
            changes ++;
          }
        }
   }

   for (CtxList *l = ctx_clients (ctx); l; l = l->next)
   {
     CtxClient *client = l->data;
     if (client->vt)
       {
         if (vt_is_done (client->vt))
         {
           if ((client->flags & ITK_CLIENT_KEEP_ALIVE))
           {
             client->flags |= ITK_CLIENT_FINISHED;
           }
           else
           {
             ctx_list_prepend (&to_remove, client);
           }
         }
       }
   }
   while (to_remove)
   {
     changes++;
     ctx_client_remove (ctx, to_remove->data);
     ctx_list_remove (&to_remove, to_remove->data);
   }

   changes += ctx_clients_dirty_count (ctx);
   return changes != 0;
}
float ctx_avg_bytespeed = 0.0;

int ctx_clients_tab_to_id (Ctx *ctx, int tab_no)
{
  CtxList *clients = ctx_clients (ctx);
  int no = 0;
  for (CtxList *l = clients; l; l = l->next)
  {
    CtxClient *client = l->data;
    if (flag_is_set(client->flags, ITK_CLIENT_MAXIMIZED))
    {
      if (no == tab_no)
        return client->id;
      no++;
    }
  }
  return -1;
}

CtxList *ctx_clients (Ctx *ctx)
{
  return ctx?ctx->events.clients:NULL;
}

#endif /* CTX_VT */

int ctx_clients_handle_events (Ctx *ctx)
{
  //int n_clients = ctx_list_length (clients);
#if CTX_VT
  int pending_data = 0;
  long time_start = ctx_ticks ();
  int sleep_time = 1000000/ctx_target_fps;
  pending_data += ctx_input_pending (ctx, sleep_time);

  CtxList *clients = ctx_clients (ctx);
  if (!clients)
    return pending_data != 0;
  ctx_fetched_bytes = 0;
  if (pending_data)
  {
    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;
    long int fractional_sleep = sleep_time * 0.75;
    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:
    if(0){
    }
  }
  else
  {
#if CTX_AUDIO
    for (CtxList *l = clients; l; l = l->next)
    {
      CtxClient *client = l->data;
      vt_audio_task (client->vt, 0);
    }
#endif
  }

  //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 = 100.0; // need to be higher than vsync rate to hit vsync
#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

#endif
  return 0;
}

void ctx_client_rev_inc (CtxClient *client)
{
  if (client) client->rev++;
}
long ctx_client_rev (CtxClient *client)
{
  return client?client->rev:0;
}

void
ctx_client_feed_keystring (CtxClient *client, CtxEvent *event, const char *str)
{
#if CTX_VT
  if (!client || !client->vt) return;
  vt_feed_keystring (client->vt, event, str);
#endif
}

#if CTX_VT
int ctx_client_id (CtxClient *client)
{
  return client?client->id:-1;
}

VT *ctx_client_vt (CtxClient *client)
{
  return client?client->vt:NULL;
}

void ctx_client_add_event (CtxClient *client, CtxEvent *event)
{
  ctx_list_append (&client->ctx_events, ctx_event_copy (event));
}

void ctx_client_quit (CtxClient *client)
{
   if (!client) return;
  client->do_quit = 1;
}

int ctx_client_flags (CtxClient *client)
{
  return client?client->flags:0;
}

void *ctx_client_userdata (CtxClient *client)
{
  return client?client->user_data:NULL;
}

const char *ctx_client_title (CtxClient *client)
{
  return client?client->title:NULL;
}

CtxClient *ctx_client_find (Ctx *ctx, const char *label)
{
  for (CtxList *l = ctx_clients (ctx); l; l = l->next)
  {
    CtxClient *client = l->data;
    if (client->user_data && !strcmp (client->user_data, label))
    {
      return client;
    }
  }
  return NULL;
}

#endif
/* a C tinyvg parser with minimal RAM requirement and geometry culling
 * ability, (c) 2022 Øyvind Kolås, pippin@gimp.org
 */


#if CTX_TINYVG

#ifndef CTX_TVG_STDIO
#define CTX_TVG_STDIO     1
#endif

#if CTX_TVG_STDIO
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif


typedef struct CtxTinyVGStyle
{
   uint8_t  type;
   uint32_t idx0;
   uint32_t idx1;
   float    x0;
   float    y0;
   float    x1;
   float    y1;
} CtxTinyVGStyle;


#define CTX_TVG_CACHE_SIZE  256

typedef struct CtxTinyVG {
   Ctx     *ctx;
   uint32_t length;
   uint32_t pos;

#if CTX_TVG_STDIO
   int      fd;
   uint32_t fd_pos;
#endif
   uint8_t  _cache[CTX_TVG_CACHE_SIZE];
   uint8_t  *cache;
   uint32_t cache_length;
   uint32_t cache_offset;

   int      pal_pos; // position of palette in TVG
   int      flags;
   uint32_t width;
   uint32_t height;
   uint8_t  scale;
   float    scale_factor;
   uint8_t  val_type:2;
   uint8_t  color_bytes:5;
   uint16_t color_count;
   uint8_t  error;
   uint8_t *pal;

   float    clipx0;
   float    clipy0;
   float    clipx1;
   float    clipy1;

   CtxTinyVGStyle fill;
   CtxTinyVGStyle stroke;
} CtxTinyVG;

#define CTX_TVG_MAGIC0    0x72
#define CTX_TVG_MAGIC1    0x56
#define CTX_TVG_VERSION      1

enum {
  CTX_TVG_VALTYPE_U16=0,
  CTX_TVG_VALTYPE_U8,
  CTX_TVG_VALTYPE_U32
};

enum {
  CTX_TVG_FLAT=0,
  CTX_TVG_LGRAD,
  CTX_TVG_RGRAD
};

enum {
  CTX_TVG_END_DRAWING=0,
  CTX_TVG_FILL_POLYGON,
  CTX_TVG_FILL_RECTANGLES,
  CTX_TVG_FILL_PATH,
  CTX_TVG_DRAW_LINES,
  CTX_TVG_DRAW_LINE_LOOP,
  CTX_TVG_DRAW_LINE_STRIP,
  CTX_TVG_DRAW_LINE_PATH,
  CTX_TVG_OUTLINE_FILL_POLYGON,
  CTX_TVG_OUTLINE_FILL_RECTANGLES,
  CTX_TVG_OUTLINE_FILL_PATH
};

enum {
  CTX_TVG_LINE_TO = 0,
  CTX_TVG_HOR_TO,
  CTX_TVG_VER_TO,
  CTX_TVG_CURVE_TO,
  CTX_TVG_ARC_CIRCLE,
  CTX_TVG_ARC_ELLIPSE,
  CTX_TVG_CLOSE_PATH,
  CTX_TVG_QUAD_TO
};

/* bounds protected accesors */
static inline void ctx_tvg_seek (CtxTinyVG *tvg, uint32_t pos)
{
  if (pos < tvg->length)
  {
    tvg->pos = pos;
  }
}

#if CTX_TVG_STDIO
static void ctx_tvg_init_fd (CtxTinyVG *tvg, Ctx *ctx, int fd, int flags)
{
  memset (tvg, 0, sizeof (CtxTinyVG));
  tvg->fd = fd;
  tvg->ctx = ctx;
  tvg->length = lseek (fd, 0, SEEK_END);
  lseek (fd, 0, SEEK_SET);
  tvg->cache_offset = 900000000;
  tvg->flags = flags;
  tvg->cache = &tvg->_cache[0];
  tvg->cache_length = CTX_TVG_CACHE_SIZE;
}
#endif

static void ctx_tvg_init_data (CtxTinyVG *tvg, Ctx *ctx, void *data, int len, int flags)
{
  memset (tvg, 0, sizeof (CtxTinyVG));
  tvg->ctx = ctx;
  tvg->cache = data;
  tvg->cache_length = len;
  tvg->length = len;
  tvg->cache_offset = 0;
  tvg->flags = flags;
}

static inline int ctx_tvg_prime_cache (CtxTinyVG *tvg, uint32_t pos, int len)
{
#if CTX_TVG_STDIO
  if (!tvg->fd)
#endif
  {
    if (pos + len < tvg->length)
      return 1;
    return 0;
  }
#if CTX_TVG_STDIO
  if (tvg->cache_offset < pos && tvg->cache_offset + CTX_TVG_CACHE_SIZE - 1 > pos+len)
  {
    return 1;
  }
  tvg->cache_offset = pos;

  if (tvg->fd_pos != pos)
  {
    lseek (tvg->fd, pos, SEEK_SET);
    tvg->fd_pos = pos;
  }
  read (tvg->fd, tvg->cache, CTX_TVG_CACHE_SIZE);
  tvg->fd_pos += CTX_TVG_CACHE_SIZE;
#endif
  return 1;
}

static inline void ctx_tvg_memcpy (CtxTinyVG *tvg, void *dst, int pos, int len)
{
  if (ctx_tvg_prime_cache (tvg, pos, len))
    memcpy (dst, &tvg->cache[pos-tvg->cache_offset], len);
  else
    memset (dst, 0, len);
}

#define CTX_TVG_DEFINE_ACCESOR(nick, type) \
static inline type ctx_tvg_##nick (CtxTinyVG *tvg)\
{ type ret;\
  ctx_tvg_memcpy (tvg, &ret, tvg->pos, sizeof (type));\
  tvg->pos += sizeof(type);\
  return ret;\
}

CTX_TVG_DEFINE_ACCESOR(u8, uint8_t)
CTX_TVG_DEFINE_ACCESOR(u16, uint16_t)
CTX_TVG_DEFINE_ACCESOR(u32, uint32_t)
CTX_TVG_DEFINE_ACCESOR(float, float)

#undef CTX_TVG_DEFINE_ACCESSOR

static inline uint8_t ctx_tvg_u6_u2 (CtxTinyVG *tvg, uint8_t *u2_ret)
{
  uint8_t ret = ctx_tvg_u8 (tvg);
  *u2_ret = (ret >> 6) & 3;
  return (ret & 63);
}

// XXX if this is inline the ESP32 fails with a compiler error
//
static int32_t ctx_tvg_val (CtxTinyVG *tvg)
{
  switch (tvg->val_type)
  {
    case CTX_TVG_VALTYPE_U16: return ctx_tvg_u16(tvg);
    case CTX_TVG_VALTYPE_U8:  return ctx_tvg_u8(tvg);
    case CTX_TVG_VALTYPE_U32: return ctx_tvg_u32(tvg);
  }
  return 0;
}

static inline float ctx_tvg_unit (CtxTinyVG *tvg)
{
  uint32_t rv = ctx_tvg_val(tvg);
  return rv * tvg->scale_factor;
}

static inline uint32_t ctx_tvg_var (CtxTinyVG *tvg)
{
  uint32_t pos = 0, last, res = 0;
  do {
    last = ctx_tvg_u8(tvg);
    res += ((last & 0x7f) << (7*pos));
    pos++;
  } while (last & 0x80);
  return res;
}

#define CTX_TVG_MIN(a,b)  (((a)<(b))?(a):(b))
#define CTX_TVG_MAX(a,b)  (((a)>(b))?(a):(b))

static void
ctx_tvg_segment (CtxTinyVG *tvg, int n_commands)
{
  Ctx *ctx = tvg->ctx;

  if (tvg->flags & CTX_TVG_FLAG_BBOX_CHECK)
  {
      float minx = 1000000.0f;
      float miny = 1000000.0f;
      float maxx = -1000000.0f;
      float maxy = -1000000.0f;
      int start_pos = tvg->pos;
  
      float x = ctx_tvg_unit(tvg);
      float y = ctx_tvg_unit(tvg);
  
  #define ADD_COORD(x,y)\
    minx = CTX_TVG_MIN (minx, x);\
    miny = CTX_TVG_MIN (miny, y);\
    maxx = CTX_TVG_MAX (maxx, x);\
    maxy = CTX_TVG_MAX (maxy, y);\
  
    ADD_COORD(x,y);
  
    for (int i = 0; i < n_commands; i++)
    {
       int kind = ctx_tvg_u8(tvg);
       int has_line_width = (kind & ~0x7) !=0;
       kind = kind & 0x7;
  
       if (has_line_width)
       {
          float new_line_width = ctx_tvg_unit (tvg);
          //printf ("with new line width! %f\n", new_line_width);
          ctx_line_width (ctx, new_line_width);
       }
       switch (kind)
       {
         case CTX_TVG_LINE_TO:
           x = ctx_tvg_unit(tvg);
           y = ctx_tvg_unit(tvg);
           ADD_COORD(x,y);
           break;
         case CTX_TVG_HOR_TO:
           x = ctx_tvg_unit(tvg);
           ADD_COORD(x,y);
           break;
         case CTX_TVG_VER_TO:
           y = ctx_tvg_unit(tvg);
           ADD_COORD(x,y);
           break;
         case CTX_TVG_CURVE_TO:
           {
             float cx0 = ctx_tvg_unit(tvg);
             float cy0 = ctx_tvg_unit(tvg);
             float cx1 = ctx_tvg_unit(tvg);
             float cy1 = ctx_tvg_unit(tvg);
                   x   = ctx_tvg_unit(tvg);
                   y   = ctx_tvg_unit(tvg);
             if (cx0 + cy0 + cx1 + cy1 != 0.f){}
             ADD_COORD(x,y);
           }
           break;
         case CTX_TVG_ARC_CIRCLE:
           { // XXX NYI XXX
             uint8_t large_and_sweep = ctx_tvg_u8 (tvg);
             float   radius = ctx_tvg_unit (tvg);
                     x = ctx_tvg_unit (tvg);
                     y = ctx_tvg_unit (tvg);
             if (radius != 0.0f && large_and_sweep != 0){};
             ADD_COORD(x,y);
           }
           break;
         case CTX_TVG_ARC_ELLIPSE:
           { // XXX NYI XXX
             uint8_t large_and_sweep = ctx_tvg_u8 (tvg);
             float rx       = ctx_tvg_unit (tvg);
             float ry       = ctx_tvg_unit (tvg);
             float rotation = ctx_tvg_unit (tvg);
                   x        = ctx_tvg_unit (tvg);
                   y        = ctx_tvg_unit (tvg);
             if (rotation !=0.0f && rx != 0.0f && ry !=0.0f && large_and_sweep) {};
             ADD_COORD(x,y);
           }
           break;
         case CTX_TVG_CLOSE_PATH:
  //       ctx_close_path (ctx);
           break;
         case CTX_TVG_QUAD_TO:
           {
             float cx0 = ctx_tvg_unit(tvg);
             float cy0 = ctx_tvg_unit(tvg);
             if (cx0 + cy0 != 0.0f){}
             x = ctx_tvg_unit(tvg);
             y = ctx_tvg_unit(tvg);
             ADD_COORD(x,y);
           }
           break;
         default:
           tvg->error = 1;
       }
    }
  
      if (minx > tvg->clipx1) return;
      if (miny > tvg->clipy1) return;
      if (maxx < tvg->clipx0) return;
      if (maxy < tvg->clipy0) return;
  
      ctx_tvg_seek (tvg,start_pos);
  }

  {
  float x = ctx_tvg_unit(tvg);
  float y = ctx_tvg_unit(tvg);
  ctx_move_to (ctx, x, y);

  for (int i = 0; i < n_commands; i++)
  {
     int kind = ctx_tvg_u8(tvg);
     int has_line_width = (kind & ~0x7) !=0;
     kind = kind & 0x7;

     if (has_line_width)
     {
       float new_line_width = ctx_tvg_unit (tvg);
       //printf ("with new line width! %f\n", new_line_width);
       ctx_line_width (ctx, new_line_width);
     }
     switch (kind)
     {
       case CTX_TVG_LINE_TO:
         x = ctx_tvg_unit(tvg);
         y = ctx_tvg_unit(tvg);
         ctx_line_to (ctx, x, y);
         break;
       case CTX_TVG_HOR_TO:
         x = ctx_tvg_unit(tvg);
         ctx_line_to (ctx, x, y);
         break;
       case CTX_TVG_VER_TO:
         y = ctx_tvg_unit(tvg);
         ctx_line_to (ctx, x, y);
         break;
       case CTX_TVG_CURVE_TO:
         {
           float cx0 = ctx_tvg_unit(tvg);
           float cy0 = ctx_tvg_unit(tvg);
           float cx1 = ctx_tvg_unit(tvg);
           float cy1 = ctx_tvg_unit(tvg);
                 x   = ctx_tvg_unit(tvg);
                 y   = ctx_tvg_unit(tvg);
           ctx_curve_to (ctx, cx0, cy0, cx1, cy1, x, y);
         }
         break;
       case CTX_TVG_ARC_CIRCLE:
         { 
           uint8_t large_and_sweep = ctx_tvg_u8 (tvg);
           uint8_t large = ((large_and_sweep & 1) == 1);
           uint8_t sweep = ((large_and_sweep & 2) == 2);
           float   radius = ctx_tvg_unit (tvg);
                   x = ctx_tvg_unit (tvg);
                   y = ctx_tvg_unit (tvg);
           ctx_svg_arc_to (ctx, radius, radius, 0.0f, large, !sweep, x, y);
         }
         break;
       case CTX_TVG_ARC_ELLIPSE:
         {
           uint8_t large_and_sweep = ctx_tvg_u8 (tvg);
           uint8_t large = ((large_and_sweep & 1) == 1);
           uint8_t sweep = ((large_and_sweep & 2) == 2);
           float rx       = ctx_tvg_unit (tvg);
           float ry       = ctx_tvg_unit (tvg);
           float rotation = ctx_tvg_unit (tvg);
                 x        = ctx_tvg_unit (tvg);
                 y        = ctx_tvg_unit (tvg);
           ctx_svg_arc_to (ctx, rx, ry, rotation, large, !sweep, x, y);
         }
         break;
       case CTX_TVG_CLOSE_PATH:
         ctx_close_path (ctx);
         break;
       case CTX_TVG_QUAD_TO:
         {
           float cx0 = ctx_tvg_unit(tvg);
           float cy0 = ctx_tvg_unit(tvg);
           x = ctx_tvg_unit(tvg);
           y = ctx_tvg_unit(tvg);
           ctx_quad_to (ctx, cx0, cy0, x, y);
         }
         break;
       default:
         tvg->error = 1;
     }
  }
  }
}

static void ctx_tvg_style (CtxTinyVG *tvg, int type,
                           CtxTinyVGStyle *style)
{
  style->type = type;
  switch (type)
  {
    case CTX_TVG_FLAT:
      style->idx0 = ctx_tvg_var(tvg);
      if (style->idx0 >= tvg->color_count) style->idx0 = 0;
      break;
    case CTX_TVG_LGRAD:
    case CTX_TVG_RGRAD:
      style->x0   = ctx_tvg_unit (tvg);
      style->y0   = ctx_tvg_unit (tvg);
      style->x1   = ctx_tvg_unit (tvg);
      style->y1   = ctx_tvg_unit (tvg);
      style->idx0 = ctx_tvg_var (tvg);
      style->idx1 = ctx_tvg_var (tvg);
      /*printf ("style:%f %f-%f %f   %i %i\n", 
      style->x0   ,
      style->y0   ,
      style->x1   ,
      style->y1   ,
      style->idx0 ,
      style->idx1 );
      */
      if (style->idx0 >= tvg->color_count) style->idx0 = 0;
      if (style->idx1 >= tvg->color_count) style->idx1 = 0;
      break;
  }
}

static inline void
ctx_tvg_get_color (CtxTinyVG *tvg, uint32_t idx,
                   float *red, float *green, float *blue, float *alpha)
{
#if CTX_TVG_STDIO
   if (tvg->fd && ((tvg->flags & CTX_TVG_FLAG_LOAD_PAL)==0))
   {
     int old_pos = tvg->pos;
     switch (tvg->color_bytes)
     {
       default:
       case 4: 
         ctx_tvg_seek (tvg, tvg->pal_pos + 4 * idx);
         *red   = ctx_tvg_u8 (tvg)/255.0f;
         *green = ctx_tvg_u8 (tvg)/255.0f;
         *blue  = ctx_tvg_u8 (tvg)/255.0f;
         *alpha = ctx_tvg_u8 (tvg)/255.0f;
         break;
       case 2: 
         {
           ctx_tvg_seek (tvg, tvg->pal_pos + 2 * idx);
           uint16_t val = ctx_tvg_u16 (tvg);
           *red     = ((val >> 0) & 31) / 31.0f;
           *green   = ((val >> 5) & 63) / 63.0f;
           *blue    = ((val >> 11)& 31) / 31.0f;
           *alpha   = 1.0f;
         }
         break;
       case 16: 
         ctx_tvg_seek (tvg, tvg->pal_pos + 16 * idx);
         *red   = ctx_tvg_float (tvg);
         *green = ctx_tvg_float (tvg);
         *blue  = ctx_tvg_float (tvg);
         *alpha = ctx_tvg_float (tvg);
         break;
      }
      ctx_tvg_seek (tvg, old_pos);
      return;
   }
#endif
   switch (tvg->color_bytes)
   {
     default:
     case 4: 
         *red   = tvg->pal[4*idx+0]/255.0f;
         *green = tvg->pal[4*idx+1]/255.0f;
         *blue  = tvg->pal[4*idx+2]/255.0f;
         *alpha = tvg->pal[4*idx+3]/255.0f;
         break;
     case 2: 
         {
           uint16_t val = ((uint16_t*)tvg->pal)[idx];
           *red     = ((val >> 0) & 31) / 31.0f;
           *green   = ((val >> 5) & 63) / 63.0f;
           *blue    = ((val >> 11)& 31) / 31.0f;
           *alpha   = 1.0f;
         }
         break;
     case 16: 
         *red   = ((float*)(tvg->pal))[4*idx+0];
         *green = ((float*)(tvg->pal))[4*idx+1];
         *blue  = ((float*)(tvg->pal))[4*idx+2];
         *alpha = ((float*)(tvg->pal))[4*idx+3];
         break;
   }
}

static void ctx_tvg_set_style (CtxTinyVG *tvg, CtxTinyVGStyle *style)
{
  Ctx *ctx = tvg->ctx;
  float x0 = style->x0, y0 = style->y0, x1 = style->x1, y1 = style->y1;
  uint32_t idx0 = style->idx0, idx1 = style->idx1;
  float red, green, blue, alpha;
  switch (style->type)
  {
    case CTX_TVG_FLAT:
      ctx_tvg_get_color (tvg, idx0, &red, &green, &blue, &alpha);
      ctx_rgba (ctx, red, green, blue, alpha);
      break;
    case CTX_TVG_LGRAD:
      ctx_linear_gradient (ctx, x0, y0, x1, y1);
      ctx_tvg_get_color (tvg, idx0, &red, &green, &blue, &alpha);
      ctx_gradient_add_stop (ctx, 0.0f, red, green, blue, alpha);
      ctx_tvg_get_color (tvg, idx1, &red, &green, &blue, &alpha);
      ctx_gradient_add_stop (ctx, 1.0f, red, green, blue, alpha);
      break;
    case CTX_TVG_RGRAD:
      {
        float radius = ctx_sqrtf ((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0));
        ctx_radial_gradient (ctx, x0, y0, 0.0f, x1, y1, radius);
      }
      ctx_tvg_get_color (tvg, idx0, &red, &green, &blue, &alpha);
      ctx_gradient_add_stop (ctx, 0.0f, red, green, blue, alpha);
      ctx_tvg_get_color (tvg, idx1, &red, &green, &blue, &alpha);
      ctx_gradient_add_stop (ctx, 1.0f, red, green, blue, alpha);
      break;
  }
}

static void ctx_tvg_path (CtxTinyVG *tvg, int item_count)
{
   int segment_length[item_count];
   for (int i = 0; i < item_count; i ++)
     segment_length[i] = ctx_tvg_var(tvg) + 1;
   for (int i = 0; i < item_count; i ++)
     ctx_tvg_segment (tvg, segment_length[i]);
}

static void ctx_tvg_poly (CtxTinyVG *tvg, int item_count)
{
   Ctx *ctx = tvg->ctx;
   for (int i = 0; i < item_count; i ++)
   {
     float x = ctx_tvg_unit (tvg);
     float y = ctx_tvg_unit (tvg);
     if (i)
       ctx_line_to (ctx, x, y);
     else
       ctx_move_to (ctx, x, y);
   }
}

static void ctx_tvg_rectangles (CtxTinyVG *tvg, int item_count,
                                int fill, int stroke)
{
   Ctx *ctx = tvg->ctx;
   for (int i = 0; i < item_count; i ++)
   {
     float x = ctx_tvg_unit (tvg);
     float y = ctx_tvg_unit (tvg);
     float w = ctx_tvg_unit (tvg);
     float h = ctx_tvg_unit (tvg);
   //  printf ("%f %f %f %f\n", x, y, w, h);
     if (fill)
     {
       ctx_begin_path (ctx);
       ctx_rectangle (ctx, x, y, w, h);
       ctx_tvg_set_style (tvg, &tvg->fill);
       ctx_fill (ctx);
     }
     if (stroke)
     {
       ctx_begin_path (ctx);
       ctx_rectangle (ctx, x, y, w, h);
       ctx_tvg_set_style (tvg, &tvg->stroke);
       ctx_stroke (ctx);
     }
   }
}

static void ctx_tvg_lines (CtxTinyVG *tvg, int item_count)
{
   Ctx *ctx = tvg->ctx;
   for (int i = 0; i < item_count; i ++)
   {
     float x0 = ctx_tvg_unit (tvg);
     float y0 = ctx_tvg_unit (tvg);
     float x1 = ctx_tvg_unit (tvg);
     float y1 = ctx_tvg_unit (tvg);
     ctx_move_to (ctx, x0, y0);
     ctx_line_to (ctx, x1, y1);
   }
}

static int ctx_tvg_command (CtxTinyVG *tvg)
{
  Ctx *ctx = tvg->ctx;
  uint8_t primary_style_type;
  float factor = ctx_matrix_get_scale (&ctx->state.gstate.transform);
  int command = ctx_tvg_u6_u2(tvg, &primary_style_type);
  int item_count;
  float line_width = 0.0f;

  int save_offset = 0;

  switch (command)
  {
    case CTX_TVG_FILL_POLYGON:
    case CTX_TVG_FILL_RECTANGLES:
    case CTX_TVG_FILL_PATH:
      item_count = ctx_tvg_var (tvg) + 1;
      ctx_tvg_style (tvg, primary_style_type, &tvg->fill);
      switch (command)
      {
        case CTX_TVG_FILL_POLYGON:
           ctx_tvg_poly (tvg, item_count);
           ctx_tvg_set_style (tvg, &tvg->fill);
           ctx_fill (ctx);
           break;
        case CTX_TVG_FILL_RECTANGLES:
           ctx_tvg_rectangles (tvg, item_count, 1, 0);
           break;
        case CTX_TVG_FILL_PATH:
           ctx_tvg_path (tvg, item_count); 
           ctx_tvg_set_style (tvg, &tvg->fill);
           ctx_fill (ctx);
           break;
      }
      break;

    case CTX_TVG_DRAW_LINES:
    case CTX_TVG_DRAW_LINE_LOOP:
    case CTX_TVG_DRAW_LINE_STRIP:
    case CTX_TVG_DRAW_LINE_PATH:
      item_count = ctx_tvg_var (tvg) + 1;
      ctx_tvg_style (tvg, primary_style_type, &tvg->stroke);
      line_width = ctx_tvg_unit (tvg);

      if (line_width * factor < 1.0f) line_width = 1.0f / factor;

      if (command == CTX_TVG_DRAW_LINE_PATH)
        ctx_tvg_path (tvg, item_count);
      else if (command == CTX_TVG_DRAW_LINES)
        ctx_tvg_lines (tvg, item_count);
      else
        ctx_tvg_poly (tvg, item_count);

      if (command == CTX_TVG_DRAW_LINE_LOOP)
        ctx_close_path (ctx);
      ctx_tvg_set_style (tvg, &tvg->stroke);

      ctx_line_width (ctx, line_width);
      ctx_stroke (ctx);
      break;

    case CTX_TVG_OUTLINE_FILL_RECTANGLES:
      item_count = ctx_tvg_u6_u2 (tvg, &tvg->stroke.type) + 1;
      ctx_tvg_style (tvg, primary_style_type, &tvg->fill);
      ctx_tvg_style (tvg, tvg->stroke.type, &tvg->stroke);
      line_width = ctx_tvg_unit (tvg);

      if (line_width * factor < 1.0f) line_width = 1.0f / factor;

      ctx_line_width (ctx, line_width);
      ctx_tvg_rectangles (tvg, item_count, 1, 1);
      break;

    case CTX_TVG_OUTLINE_FILL_POLYGON:
    case CTX_TVG_OUTLINE_FILL_PATH:
      item_count = ctx_tvg_u6_u2 (tvg, &tvg->stroke.type) + 1;
      ctx_tvg_style (tvg, tvg->fill.type, &tvg->fill);
      ctx_tvg_style (tvg, tvg->stroke.type, &tvg->stroke);
      line_width = ctx_tvg_unit (tvg);

      if (line_width * factor < 1.0f) line_width = 1.0f / factor;

      save_offset = tvg->pos;
      if (command == CTX_TVG_OUTLINE_FILL_POLYGON)
        ctx_tvg_poly (tvg, item_count);
      else
        ctx_tvg_path (tvg, item_count);
      ctx_tvg_set_style (tvg, &tvg->fill);
      ctx_fill (ctx);

      ctx_tvg_seek (tvg, save_offset);
      if (command == CTX_TVG_OUTLINE_FILL_POLYGON)
      {
        ctx_tvg_poly (tvg, item_count);
        ctx_close_path (ctx);
      }
      else
        ctx_tvg_path (tvg, item_count);

      if (line_width > 4) line_width =4;
      ctx_line_width (ctx, line_width);
      ctx_tvg_set_style (tvg, &tvg->stroke);
      ctx_stroke (ctx);
      break;
    case CTX_TVG_END_DRAWING:
      break;
  }
  return command;
}

int ctx_tvg_read_header (CtxTinyVG *tvg)
{
   ctx_tvg_seek (tvg, 0);
   if (ctx_tvg_u8 (tvg) != CTX_TVG_MAGIC0) return -1;
   if (ctx_tvg_u8 (tvg) != CTX_TVG_MAGIC1) return -1;
   if (ctx_tvg_u8 (tvg) != CTX_TVG_VERSION) return -1;
   {
     uint8_t val         = ctx_tvg_u8(tvg);
     tvg->scale          = (val>> 0) & 0xf;
     tvg->color_bytes    = (val>> 4) & 0x3;
     tvg->val_type       = (val>> 6) & 0x3;
     tvg->scale_factor   = 1.0f/(1<<(tvg->scale));

     switch (tvg->color_bytes)
     {
       case 0: tvg->color_bytes = 4; break;
       case 1: tvg->color_bytes = 2; break;
       case 2: tvg->color_bytes = 16; break;
     }
   }

   tvg->width = ctx_tvg_val(tvg);
   tvg->height = ctx_tvg_val(tvg);
   return 0;
}


static int
ctx_tvg_draw (CtxTinyVG *tvg)
{
   Ctx *ctx = tvg->ctx;

   tvg->color_count = ctx_tvg_var (tvg);

   tvg->pal_pos = tvg->pos;
   if (tvg->flags & CTX_TVG_FLAG_LOAD_PAL)
   {
     int count = tvg->color_bytes * tvg->color_count;
     tvg->pal = ctx_malloc (count);
     for (int i = 0; i < count; i++)
       tvg->pal[i] = ctx_tvg_u8 (tvg);
   }
   else 
#if CTX_TVG_STDIO
           if (!tvg->fd)
#endif
   {
     tvg->pal = &tvg->cache[tvg->pos];
     ctx_tvg_seek (tvg, tvg->pos + tvg->color_bytes * tvg->color_count);
   }

   ctx_save (ctx);
   ctx_fill_rule (ctx, CTX_FILL_RULE_EVEN_ODD);
   ctx_line_cap  (ctx, CTX_CAP_ROUND);

   if (tvg->flags & CTX_TVG_FLAG_BBOX_CHECK)
   {
      float minx = 1000000.0f;
      float miny = 1000000.0f;
      float maxx = -1000000.0f;
      float maxy = -1000000.0f;
      float x, y;
      x = 0; y =0;
      ctx_device_to_user (ctx, &x, &y);
      ADD_COORD(x,y);
      x = ctx_width (ctx); y =0;
      ctx_device_to_user (ctx, &x, &y);
      ADD_COORD(x,y);
      x = ctx_width (ctx); y = ctx_height (ctx);
      ctx_device_to_user (ctx, &x, &y);
      ADD_COORD(x,y);
      x = 0; y = ctx_height (ctx);
      ctx_device_to_user (ctx, &x, &y);
      ADD_COORD(x,y);

      //fprintf (stderr, "%f %f %f %f\n", minx, miny, maxx, maxy);
      tvg->clipx0 = minx;
      tvg->clipy0 = miny;
      tvg->clipx1 = maxx;
      tvg->clipy1 = maxy;
   }

   while (ctx_tvg_command (tvg));

   ctx_restore (ctx);
   if (tvg->flags & CTX_TVG_FLAG_LOAD_PAL)
     ctx_free (tvg->pal);
   return tvg->error;
}

#if 0
static int
ctx_tvg_draw2 (CtxTinyVG *tvg,
               float x, float y,
               float target_width,
               float target_height)
{
   Ctx*ctx = tvg->ctx;
   float scale_x = 1.0f;
   float scale_y = 1.0f;
   { /* handle aspect ratio, add translate ? */
   if (target_width<=0 && target_height <= 0)
   {
     target_width  = tvg->width;
     target_height = tvg->height;
   }
   else if (target_width<=0 && target_height > 0)
   {
     target_width = target_height / tvg->height * tvg->width;
   }
   else if (target_width<=0 && target_height > 0)
   {
     target_height = target_width / tvg->width * tvg->height;
   }
   scale_x = target_width / tvg->width;
   scale_y = target_height / tvg->height;
   if (scale_x > scale_y)
     scale_x = scale_y;
   else
     scale_y = scale_x;
   }

   ctx_save (ctx);
   ctx_translate (ctx, x, y);
   ctx_scale (ctx, scale_x, scale_y);

   ctx_tvg_draw (tvg);
   ctx_restore (ctx);
   return tvg->error;
}
#endif

int
ctx_tinyvg_get_size (uint8_t *data, int length, int *width, int *height)
{
   CtxTinyVG tvg;
   ctx_tvg_init_data (&tvg, NULL, data, length, 0);
   if (ctx_tvg_read_header (&tvg))
     return -1;
   if (width)*width = tvg.width;
   if (height)*height = tvg.height;
   return 0;
}

int
ctx_tinyvg_fd_get_size (int fd, int *width, int *height)
{
#if CTX_TVG_STDIO
   CtxTinyVG tvg;
   ctx_tvg_init_fd (&tvg, NULL, fd, 0);
   if (ctx_tvg_read_header (&tvg))
     return -1;
   if (width)*width = tvg.width;
   if (height)*height = tvg.height;
   return 0;
#else
   return -1;
#endif
}

int ctx_tinyvg_draw (Ctx     *ctx,
                     uint8_t *data, int length,
                     int      flags)
{
   CtxTinyVG tvg;
   ctx_tvg_init_data (&tvg, ctx, data, length, flags);
   if (ctx_tvg_read_header (&tvg))
     return -1;
   return ctx_tvg_draw (&tvg);
}

int ctx_tinyvg_fd_draw (Ctx *ctx, int fd, int flags)
{
#if CTX_TVG_STDIO
   CtxTinyVG tvg;
   ctx_tvg_init_fd (&tvg, ctx, fd, flags);
   if (ctx_tvg_read_header (&tvg))
     return -1;
   return ctx_tvg_draw (&tvg);
#else
   return -1;
#endif
}

#endif

#endif // CTX_IMPLEMENTATION
#endif //  __CTX_H__
